From df60c2c866536f4d41975c68524615f8d0297d4f Mon Sep 17 00:00:00 2001 From: Patrice Ferlet Date: Sun, 5 Dec 2021 10:13:11 +0100 Subject: [PATCH] Refactorisation to writers in generator package --- generator/writer.go | 110 ++++++++++++++++++++ generator/writers/configmap.go | 17 ++++ generator/writers/deployment.go | 40 ++++++++ generator/writers/ingress.go | 40 ++++++++ generator/writers/service.go | 23 +++++ generator/writers/storage.go | 21 ++++ main.go | 171 +------------------------------- 7 files changed, 254 insertions(+), 168 deletions(-) create mode 100644 generator/writer.go create mode 100644 generator/writers/configmap.go create mode 100644 generator/writers/deployment.go create mode 100644 generator/writers/ingress.go create mode 100644 generator/writers/service.go create mode 100644 generator/writers/storage.go diff --git a/generator/writer.go b/generator/writer.go new file mode 100644 index 0000000..f241dea --- /dev/null +++ b/generator/writer.go @@ -0,0 +1,110 @@ +package generator + +import ( + "katenary/compose" + "katenary/generator/writers" + "katenary/helm" + "os" + "path/filepath" + "regexp" + "strings" + "time" + + "gopkg.in/yaml.v3" +) + +var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`) + +func Generate(p *compose.Parser, katernayVersion, appName, appVersion, composeFile, dirName string) { + + // make the appname global (yes... ugly but easy) + helm.Appname = appName + helm.Version = katernayVersion + templatesDir := filepath.Join(dirName, "templates") + files := make(map[string]chan interface{}) + + for name, s := range p.Data.Services { + files[name] = CreateReplicaObject(name, s) + } + + // to generate notes, we need to keep an Ingresses list + ingresses := make(map[string]*helm.Ingress) + + for n, f := range files { + for c := range f { + if c == nil { + break + } + kind := c.(helm.Kinded).Get() + 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 + c.(helm.Signable).BuildSHA(composeFile) + + // Some types need special fixes in yaml generation + switch c := c.(type) { + 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 + name := c.(helm.Named).Name() + name = PrefixRE.ReplaceAllString(name, "") + writers.BuildConfigMap(c, kind, n, name, templatesDir) + + default: + fname := filepath.Join(templatesDir, n+"."+kind+".yaml") + fp, _ := os.Create(fname) + enc := yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(c) + fp.Close() + } + } + } + // Create the values.yaml file + fp, _ := os.Create(filepath.Join(dirName, "values.yaml")) + enc := yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(Values) + fp.Close() + + // Create tht Chart.yaml file + fp, _ = os.Create(filepath.Join(dirName, "Chart.yaml")) + fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n") + fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n") + enc = yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(map[string]interface{}{ + "apiVersion": "v2", + "name": appName, + "description": "A helm chart for " + appName, + "type": "application", + "version": "0.1.0", + "appVersion": appVersion, + }) + fp.Close() + + // And finally, create a NOTE.txt file + fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt")) + fp.WriteString(helm.GenerateNotesFile(ingresses)) + fp.Close() +} diff --git a/generator/writers/configmap.go b/generator/writers/configmap.go new file mode 100644 index 0000000..d3ec879 --- /dev/null +++ b/generator/writers/configmap.go @@ -0,0 +1,17 @@ +package writers + +import ( + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +func BuildConfigMap(c interface{}, kind, servicename, name, templatesDir string) { + fname := filepath.Join(templatesDir, servicename+"."+name+"."+kind+".yaml") + fp, _ := os.Create(fname) + enc := yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(c) + fp.Close() +} diff --git a/generator/writers/deployment.go b/generator/writers/deployment.go new file mode 100644 index 0000000..1cbe810 --- /dev/null +++ b/generator/writers/deployment.go @@ -0,0 +1,40 @@ +package writers + +import ( + "bytes" + "katenary/helm" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +func BuildDeployment(deployment *helm.Deployment, name, templatesDir string) { + kind := "deployment" + fname := filepath.Join(templatesDir, name+"."+kind+".yaml") + fp, _ := os.Create(fname) + buffer := bytes.NewBuffer(nil) + enc := yaml.NewEncoder(buffer) + enc.SetIndent(2) + enc.Encode(deployment) + _content := string(buffer.Bytes()) + content := strings.Split(string(_content), "\n") + dataname := "" + component := deployment.Spec.Selector["matchLabels"].(map[string]string)[helm.K+"/component"] + for _, line := range content { + if strings.Contains(line, "name:") { + dataname = strings.Split(line, ":")[1] + dataname = strings.TrimSpace(dataname) + } else if strings.Contains(line, "persistentVolumeClaim") { + line = " {{- if .Values." + component + ".persistence." + dataname + ".enabled }}\n" + line + } else if strings.Contains(line, "claimName") { + line += "\n {{ else }}" + line += "\n emptyDir: {}" + line += "\n {{- end }}" + } + fp.WriteString(line + "\n") + } + fp.Close() + +} diff --git a/generator/writers/ingress.go b/generator/writers/ingress.go new file mode 100644 index 0000000..c15f3c8 --- /dev/null +++ b/generator/writers/ingress.go @@ -0,0 +1,40 @@ +package writers + +import ( + "bytes" + "katenary/helm" + "os" + "path/filepath" + "strings" + + "gopkg.in/yaml.v3" +) + +func BuildIngress(ingress *helm.Ingress, name, templatesDir string) { + kind := "ingress" + buffer := bytes.NewBuffer(nil) + fname := filepath.Join(templatesDir, name+"."+kind+".yaml") + enc := yaml.NewEncoder(buffer) + enc.SetIndent(2) + buffer.WriteString("{{- if .Values." + name + ".ingress.enabled -}}\n") + enc.Encode(ingress) + buffer.WriteString("{{- end -}}") + + fp, _ := os.Create(fname) + content := string(buffer.Bytes()) + lines := strings.Split(content, "\n") + for _, l := range lines { + if strings.Contains(l, "ingressClassName") { + p := strings.Split(l, ":") + condition := p[1] + condition = strings.ReplaceAll(condition, "'", "") + condition = strings.ReplaceAll(condition, "{{", "") + condition = strings.ReplaceAll(condition, "}}", "") + condition = strings.TrimSpace(condition) + condition = "{{- if " + condition + " }}" + l = " " + condition + "\n" + l + "\n {{- end }}" + } + fp.WriteString(l + "\n") + } + fp.Close() +} diff --git a/generator/writers/service.go b/generator/writers/service.go new file mode 100644 index 0000000..2dae7d8 --- /dev/null +++ b/generator/writers/service.go @@ -0,0 +1,23 @@ +package writers + +import ( + "katenary/helm" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +func BuildService(service *helm.Service, name, templatesDir string) { + kind := "service" + suffix := "" + if service.Spec.Type == "NodePort" { + suffix = "-external" + } + fname := filepath.Join(templatesDir, name+suffix+"."+kind+".yaml") + fp, _ := os.Create(fname) + enc := yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(service) + fp.Close() +} diff --git a/generator/writers/storage.go b/generator/writers/storage.go new file mode 100644 index 0000000..69343ab --- /dev/null +++ b/generator/writers/storage.go @@ -0,0 +1,21 @@ +package writers + +import ( + "katenary/helm" + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +func BuildStorage(storage *helm.Storage, name, templatesDir string) { + kind := "pvc" + fname := filepath.Join(templatesDir, name+"."+kind+".yaml") + fp, _ := os.Create(fname) + volname := storage.K8sBase.Metadata.Labels[helm.K+"/pvc-name"] + fp.WriteString("{{ if .Values." + name + ".persistence." + volname + ".enabled }}\n") + enc := yaml.NewEncoder(fp) + enc.SetIndent(2) + enc.Encode(storage) + fp.WriteString("{{- end -}}") +} diff --git a/main.go b/main.go index 237bce5..c159a92 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "flag" "fmt" "katenary/compose" @@ -9,21 +8,15 @@ import ( "katenary/helm" "os" "path/filepath" - "regexp" "strings" - "time" - - "gopkg.in/yaml.v3" ) var ComposeFile = "docker-compose.yaml" var AppName = "MyApp" var AppVersion = "0.0.1" -var Version = "master" +var Version = "master" // set at build time to the git version/tag var ChartsDir = "chart" -var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`) - func main() { flag.StringVar(&ChartsDir, "chart-dir", ChartsDir, "set the chart directory") flag.StringVar(&ComposeFile, "compose", ComposeFile, "set the compose file to parse") @@ -39,10 +32,6 @@ func main() { os.Exit(0) } - // make the appname global (yes... ugly but easy) - helm.Appname = AppName - helm.Version = Version - dirname := filepath.Join(ChartsDir, AppName) if _, err := os.Stat(dirname); err == nil && !*force { response := "" @@ -50,7 +39,7 @@ func main() { response = "n" fmt.Printf(""+ "The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+ - "Do you really want to continue ? [y/N]: ", dirname) + "Do you really want to continue? [y/N]: ", dirname) fmt.Scanf("%s", &response) response = strings.ToLower(response) } @@ -68,160 +57,6 @@ func main() { // Parse the compose file now p := compose.NewParser(ComposeFile) p.Parse(AppName) + generator.Generate(p, Version, AppName, AppVersion, ComposeFile, dirname) - files := make(map[string]chan interface{}) - - for name, s := range p.Data.Services { - o := generator.CreateReplicaObject(name, s) - files[name] = o - //}(name, s) - } - - // to generate notes, we need to keep an Ingresses list - ingresses := make(map[string]*helm.Ingress) - - for n, f := range files { - for c := range f { - if c == nil { - break - } - kind := c.(helm.Kinded).Get() - 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 - c.(helm.Signable).BuildSHA(ComposeFile) - - // Some types need special fixes in yaml generation - switch c := c.(type) { - case *helm.Storage: - // For storage, we need to add a "condition" to activate it - fname := filepath.Join(templatesDir, n+"."+kind+".yaml") - fp, _ := os.Create(fname) - volname := c.K8sBase.Metadata.Labels[helm.K+"/pvc-name"] - fp.WriteString("{{ if .Values." + n + ".persistence." + volname + ".enabled }}\n") - enc := yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(c) - fp.WriteString("{{- end -}}") - 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" - fname := filepath.Join(templatesDir, n+"."+kind+".yaml") - fp, _ := os.Create(fname) - buffer := bytes.NewBuffer(nil) - enc := yaml.NewEncoder(buffer) - enc.SetIndent(2) - enc.Encode(c) - _content := string(buffer.Bytes()) - content := strings.Split(string(_content), "\n") - dataname := "" - component := c.Spec.Selector["matchLabels"].(map[string]string)[helm.K+"/component"] - for _, line := range content { - if strings.Contains(line, "name:") { - dataname = strings.Split(line, ":")[1] - dataname = strings.TrimSpace(dataname) - } else if strings.Contains(line, "persistentVolumeClaim") { - line = " {{- if .Values." + component + ".persistence." + dataname + ".enabled }}\n" + line - } else if strings.Contains(line, "claimName") { - line += "\n {{ else }}" - line += "\n emptyDir: {}" - line += "\n {{- end }}" - } - fp.WriteString(line + "\n") - } - fp.Close() - - case *helm.Service: - // Change the type for service if it's an "exposed" port - suffix := "" - if c.Spec.Type == "NodePort" { - suffix = "-external" - } - fname := filepath.Join(templatesDir, n+suffix+"."+kind+".yaml") - fp, _ := os.Create(fname) - enc := yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(c) - fp.Close() - - case *helm.Ingress: - // we need to make ingresses "activable" from values - buffer := bytes.NewBuffer(nil) - fname := filepath.Join(templatesDir, n+"."+kind+".yaml") - ingresses[n] = c // keep it to generate notes - enc := yaml.NewEncoder(buffer) - enc.SetIndent(2) - buffer.WriteString("{{- if .Values." + n + ".ingress.enabled -}}\n") - enc.Encode(c) - buffer.WriteString("{{- end -}}") - - fp, _ := os.Create(fname) - content := string(buffer.Bytes()) - lines := strings.Split(content, "\n") - for _, l := range lines { - if strings.Contains(l, "ingressClassName") { - p := strings.Split(l, ":") - condition := p[1] - condition = strings.ReplaceAll(condition, "'", "") - condition = strings.ReplaceAll(condition, "{{", "") - condition = strings.ReplaceAll(condition, "}}", "") - condition = strings.TrimSpace(condition) - condition = "{{- if " + condition + " }}" - l = " " + condition + "\n" + l + "\n {{- end }}" - } - fp.WriteString(l + "\n") - } - fp.Close() - - case *helm.ConfigMap, *helm.Secret: - // there could be several files, so let's force the filename - name := c.(helm.Named).Name() - name = PrefixRE.ReplaceAllString(name, "") - fname := filepath.Join(templatesDir, n+"."+name+"."+kind+".yaml") - fp, _ := os.Create(fname) - enc := yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(c) - fp.Close() - - default: - fname := filepath.Join(templatesDir, n+"."+kind+".yaml") - fp, _ := os.Create(fname) - enc := yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(c) - fp.Close() - } - } - } - - // Create the values.yaml file - fp, _ := os.Create(filepath.Join(dirname, "values.yaml")) - enc := yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(generator.Values) - fp.Close() - - // Create tht Chart.yaml file - fp, _ = os.Create(filepath.Join(dirname, "Chart.yaml")) - fp.WriteString(`# Create on ` + time.Now().Format(time.RFC3339) + "\n") - fp.WriteString(`# Katenary command line: ` + strings.Join(os.Args, " ") + "\n") - enc = yaml.NewEncoder(fp) - enc.SetIndent(2) - enc.Encode(map[string]interface{}{ - "apiVersion": "v2", - "name": AppName, - "description": "A helm chart for " + AppName, - "type": "application", - "version": "0.1.0", - "appVersion": AppVersion, - }) - fp.Close() - - // And finally, create a NOTE.txt file - fp, _ = os.Create(filepath.Join(templatesDir, "NOTES.txt")) - fp.WriteString(helm.GenerateNotesFile(ingresses)) - fp.Close() }