Refactorisation to writers in generator package
This commit is contained in:
110
generator/writer.go
Normal file
110
generator/writer.go
Normal file
@@ -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()
|
||||||
|
}
|
17
generator/writers/configmap.go
Normal file
17
generator/writers/configmap.go
Normal file
@@ -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()
|
||||||
|
}
|
40
generator/writers/deployment.go
Normal file
40
generator/writers/deployment.go
Normal file
@@ -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()
|
||||||
|
|
||||||
|
}
|
40
generator/writers/ingress.go
Normal file
40
generator/writers/ingress.go
Normal file
@@ -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()
|
||||||
|
}
|
23
generator/writers/service.go
Normal file
23
generator/writers/service.go
Normal file
@@ -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()
|
||||||
|
}
|
21
generator/writers/storage.go
Normal file
21
generator/writers/storage.go
Normal file
@@ -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 -}}")
|
||||||
|
}
|
171
main.go
171
main.go
@@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"katenary/compose"
|
"katenary/compose"
|
||||||
@@ -9,21 +8,15 @@ import (
|
|||||||
"katenary/helm"
|
"katenary/helm"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ComposeFile = "docker-compose.yaml"
|
var ComposeFile = "docker-compose.yaml"
|
||||||
var AppName = "MyApp"
|
var AppName = "MyApp"
|
||||||
var AppVersion = "0.0.1"
|
var AppVersion = "0.0.1"
|
||||||
var Version = "master"
|
var Version = "master" // set at build time to the git version/tag
|
||||||
var ChartsDir = "chart"
|
var ChartsDir = "chart"
|
||||||
|
|
||||||
var PrefixRE = regexp.MustCompile(`\{\{.*\}\}-?`)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.StringVar(&ChartsDir, "chart-dir", ChartsDir, "set the chart directory")
|
flag.StringVar(&ChartsDir, "chart-dir", ChartsDir, "set the chart directory")
|
||||||
flag.StringVar(&ComposeFile, "compose", ComposeFile, "set the compose file to parse")
|
flag.StringVar(&ComposeFile, "compose", ComposeFile, "set the compose file to parse")
|
||||||
@@ -39,10 +32,6 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make the appname global (yes... ugly but easy)
|
|
||||||
helm.Appname = AppName
|
|
||||||
helm.Version = Version
|
|
||||||
|
|
||||||
dirname := filepath.Join(ChartsDir, AppName)
|
dirname := filepath.Join(ChartsDir, AppName)
|
||||||
if _, err := os.Stat(dirname); err == nil && !*force {
|
if _, err := os.Stat(dirname); err == nil && !*force {
|
||||||
response := ""
|
response := ""
|
||||||
@@ -50,7 +39,7 @@ func main() {
|
|||||||
response = "n"
|
response = "n"
|
||||||
fmt.Printf(""+
|
fmt.Printf(""+
|
||||||
"The %s directory already exists, it will be \x1b[31;1mremoved\x1b[0m!\n"+
|
"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)
|
fmt.Scanf("%s", &response)
|
||||||
response = strings.ToLower(response)
|
response = strings.ToLower(response)
|
||||||
}
|
}
|
||||||
@@ -68,160 +57,6 @@ func main() {
|
|||||||
// Parse the compose file now
|
// Parse the compose file now
|
||||||
p := compose.NewParser(ComposeFile)
|
p := compose.NewParser(ComposeFile)
|
||||||
p.Parse(AppName)
|
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()
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user