2021-12-05 10:13:11 +01:00
|
|
|
package generator
|
|
|
|
|
|
|
|
import (
|
|
|
|
"katenary/compose"
|
|
|
|
"katenary/generator/writers"
|
|
|
|
"katenary/helm"
|
2022-06-10 16:15:18 +02:00
|
|
|
"katenary/tools"
|
2022-05-08 09:55:25 +02:00
|
|
|
"log"
|
2021-12-05 10:13:11 +01:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"regexp"
|
2022-05-08 09:55:25 +02:00
|
|
|
"strconv"
|
2021-12-05 10:13:11 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
"github.com/compose-spec/compose-go/types"
|
2021-12-05 10:13:11 +01:00
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
// HelmFile represents a helm file from helm package that has got some necessary methods
|
|
|
|
// to generate a helm file.
|
|
|
|
type HelmFile interface {
|
|
|
|
GetType() string
|
|
|
|
GetPathRessource() string
|
|
|
|
}
|
|
|
|
|
|
|
|
// HelmFileGenerator is a chanel of HelmFile.
|
|
|
|
type HelmFileGenerator chan HelmFile
|
|
|
|
|
2021-12-05 10:13:11 +01:00
|
|
|
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
func portExists(port int, ports []types.ServicePortConfig) bool {
|
|
|
|
for _, p := range ports {
|
|
|
|
if p.Target == uint32(port) {
|
|
|
|
log.Println("portExists:", port, p.Target)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate get a parsed compose file, and generate the helm files.
|
|
|
|
func Generate(p *compose.Parser, katernayVersion, appName, appVersion, chartVersion, composeFile, dirName string) {
|
2021-12-05 10:13:11 +01:00
|
|
|
|
|
|
|
// make the appname global (yes... ugly but easy)
|
|
|
|
helm.Appname = appName
|
|
|
|
helm.Version = katernayVersion
|
|
|
|
templatesDir := filepath.Join(dirName, "templates")
|
2022-03-31 14:12:20 +02:00
|
|
|
|
|
|
|
// try to create the directory
|
|
|
|
err := os.MkdirAll(templatesDir, 0755)
|
|
|
|
if err != nil {
|
2022-05-08 09:55:25 +02:00
|
|
|
log.Fatal(err)
|
2022-03-31 14:12:20 +02:00
|
|
|
}
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
generators := make(map[string]HelmFileGenerator)
|
|
|
|
|
|
|
|
// remove skipped services from the parsed data
|
|
|
|
for i, service := range p.Data.Services {
|
|
|
|
if v, ok := service.Labels[helm.LABEL_IGNORE]; !ok || v != "true" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
p.Data.Services = append(p.Data.Services[:i], p.Data.Services[i+1:]...)
|
|
|
|
i--
|
2021-12-05 10:13:11 +01:00
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
// find this service in others as "depends_on" and remove it
|
|
|
|
for _, service2 := range p.Data.Services {
|
|
|
|
delete(service2.DependsOn, service.Name)
|
2022-02-16 17:40:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
for i, service := range p.Data.Services {
|
|
|
|
n := service.Name
|
|
|
|
|
|
|
|
// if the service port is declared in labels, add it to the service.
|
|
|
|
if ports, ok := service.Labels[helm.LABEL_PORT]; ok {
|
|
|
|
if service.Ports == nil {
|
|
|
|
service.Ports = make([]types.ServicePortConfig, 0)
|
|
|
|
}
|
|
|
|
for _, port := range strings.Split(ports, ",") {
|
2022-05-22 22:53:05 +02:00
|
|
|
port = strings.TrimSpace(port)
|
2022-05-08 09:55:25 +02:00
|
|
|
target, err := strconv.Atoi(port)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
if portExists(target, service.Ports) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
service.Ports = append(service.Ports, types.ServicePortConfig{
|
|
|
|
Target: uint32(target),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// find port and store it in servicesMap
|
|
|
|
for _, port := range service.Ports {
|
|
|
|
target := int(port.Target)
|
|
|
|
if target != 0 {
|
|
|
|
servicesMap[n] = target
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-02-16 17:40:11 +01:00
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
// manage emptyDir volumes
|
|
|
|
if empty, ok := service.Labels[helm.LABEL_EMPTYDIRS]; ok {
|
2022-02-16 17:40:11 +01:00
|
|
|
//split empty list by coma
|
|
|
|
emptyDirs := strings.Split(empty, ",")
|
2022-05-08 09:55:25 +02:00
|
|
|
for i, emptyDir := range emptyDirs {
|
|
|
|
emptyDirs[i] = strings.TrimSpace(emptyDir)
|
|
|
|
}
|
2022-02-16 17:40:11 +01:00
|
|
|
//append them in EmptyDirs
|
|
|
|
EmptyDirs = append(EmptyDirs, emptyDirs...)
|
|
|
|
}
|
2022-05-08 09:55:25 +02:00
|
|
|
p.Data.Services[i] = service
|
2022-02-16 17:40:11 +01:00
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
}
|
2022-02-16 17:40:11 +01:00
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
// for all services in linked map, and not in samePods map, generate the service
|
|
|
|
for _, s := range p.Data.Services {
|
|
|
|
name := s.Name
|
|
|
|
|
|
|
|
// do not make a deployment for services declared to be in the same pod than another
|
|
|
|
if _, ok := s.Labels[helm.LABEL_SAMEPOD]; ok {
|
2022-02-16 17:40:11 +01:00
|
|
|
continue
|
|
|
|
}
|
2022-05-08 09:55:25 +02:00
|
|
|
|
|
|
|
// find services that is in the same pod
|
|
|
|
linked := make(map[string]types.ServiceConfig, 0)
|
|
|
|
for _, service := range p.Data.Services {
|
|
|
|
n := service.Name
|
|
|
|
if linkname, ok := service.Labels[helm.LABEL_SAMEPOD]; ok && linkname == name {
|
|
|
|
linked[n] = service
|
2022-06-10 16:15:18 +02:00
|
|
|
delete(s.DependsOn, n)
|
2022-05-08 09:55:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
generators[name] = CreateReplicaObject(name, s, linked)
|
2021-12-05 10:13:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// to generate notes, we need to keep an Ingresses list
|
|
|
|
ingresses := make(map[string]*helm.Ingress)
|
|
|
|
|
2022-05-08 09:55:25 +02:00
|
|
|
for n, generator := range generators { // generators is a map : name -> generator
|
|
|
|
for helmFile := range generator { // generator is a chan
|
|
|
|
if helmFile == nil { // generator finished
|
2021-12-05 10:13:11 +01:00
|
|
|
break
|
|
|
|
}
|
2022-05-08 09:55:25 +02:00
|
|
|
kind := helmFile.(helm.Kinded).Get()
|
2021-12-05 10:13:11 +01:00
|
|
|
kind = strings.ToLower(kind)
|
|
|
|
|
|
|
|
// Add a SHA inside the generated file, it's only
|
|
|
|
// to make it easy to check it the compose file corresponds to the
|
|
|
|
// generated helm chart
|
2022-05-08 09:55:25 +02:00
|
|
|
helmFile.(helm.Signable).BuildSHA(composeFile)
|
2021-12-05 10:13:11 +01:00
|
|
|
|
|
|
|
// Some types need special fixes in yaml generation
|
2022-05-08 09:55:25 +02:00
|
|
|
switch c := helmFile.(type) {
|
2021-12-05 10:13:11 +01:00
|
|
|
case *helm.Storage:
|
|
|
|
// For storage, we need to add a "condition" to activate it
|
|
|
|
writers.BuildStorage(c, n, templatesDir)
|
|
|
|
|
|
|
|
case *helm.Deployment:
|
|
|
|
// for the deployment, we need to fix persitence volumes
|
|
|
|
// to be activated only when the storage is "enabled",
|
|
|
|
// either we use an "emptyDir"
|
|
|
|
writers.BuildDeployment(c, n, templatesDir)
|
|
|
|
|
|
|
|
case *helm.Service:
|
|
|
|
// Change the type for service if it's an "exposed" port
|
|
|
|
writers.BuildService(c, n, templatesDir)
|
|
|
|
|
|
|
|
case *helm.Ingress:
|
|
|
|
// we need to make ingresses "activable" from values
|
|
|
|
ingresses[n] = c // keep it to generate notes
|
|
|
|
writers.BuildIngress(c, n, templatesDir)
|
|
|
|
|
|
|
|
case *helm.ConfigMap, *helm.Secret:
|
|
|
|
// there could be several files, so let's force the filename
|
2022-06-10 16:15:18 +02:00
|
|
|
name := c.(helm.Named).Name() + "." + c.GetType()
|
2022-05-08 09:55:25 +02:00
|
|
|
suffix := c.GetPathRessource()
|
2022-06-10 16:15:18 +02:00
|
|
|
suffix = tools.PathToName(suffix)
|
2022-05-08 09:55:25 +02:00
|
|
|
name += suffix
|
2021-12-05 10:13:11 +01:00
|
|
|
name = PrefixRE.ReplaceAllString(name, "")
|
|
|
|
writers.BuildConfigMap(c, kind, n, name, templatesDir)
|
|
|
|
|
|
|
|
default:
|
2022-06-10 16:15:18 +02:00
|
|
|
name := c.(helm.Named).Name() + "." + c.GetType()
|
|
|
|
name = PrefixRE.ReplaceAllString(name, "")
|
|
|
|
fname := filepath.Join(templatesDir, name+".yaml")
|
2022-05-08 09:55:25 +02:00
|
|
|
fp, err := os.Create(fname)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer fp.Close()
|
2021-12-05 10:13:11 +01:00
|
|
|
enc := yaml.NewEncoder(fp)
|
2022-05-08 09:55:25 +02:00
|
|
|
enc.SetIndent(writers.IndentSize)
|
2021-12-05 10:13:11 +01:00
|
|
|
enc.Encode(c)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Create the values.yaml file
|
2022-05-08 09:55:25 +02:00
|
|
|
valueFile, err := os.Create(filepath.Join(dirName, "values.yaml"))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer valueFile.Close()
|
|
|
|
enc := yaml.NewEncoder(valueFile)
|
|
|
|
enc.SetIndent(writers.IndentSize)
|
2021-12-05 10:13:11 +01:00
|
|
|
enc.Encode(Values)
|
|
|
|
|
|
|
|
// Create tht Chart.yaml file
|
2022-05-08 09:55:25 +02:00
|
|
|
chartFile, err := os.Create(filepath.Join(dirName, "Chart.yaml"))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer chartFile.Close()
|
|
|
|
chartFile.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n")
|
|
|
|
chartFile.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n")
|
|
|
|
enc = yaml.NewEncoder(chartFile)
|
2022-03-31 14:12:20 +02:00
|
|
|
enc.SetIndent(writers.IndentSize)
|
2021-12-05 10:13:11 +01:00
|
|
|
enc.Encode(map[string]interface{}{
|
|
|
|
"apiVersion": "v2",
|
|
|
|
"name": appName,
|
|
|
|
"description": "A helm chart for " + appName,
|
|
|
|
"type": "application",
|
2022-05-08 09:55:25 +02:00
|
|
|
"version": chartVersion,
|
2021-12-05 10:13:11 +01:00
|
|
|
"appVersion": appVersion,
|
|
|
|
})
|
|
|
|
|
|
|
|
// And finally, create a NOTE.txt file
|
2022-05-08 09:55:25 +02:00
|
|
|
noteFile, err := os.Create(filepath.Join(templatesDir, "NOTES.txt"))
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal(err)
|
|
|
|
}
|
|
|
|
defer noteFile.Close()
|
|
|
|
noteFile.WriteString(helm.GenerateNotesFile(ingresses))
|
2021-12-05 10:13:11 +01:00
|
|
|
}
|