Fix problems and adding functionnalities

Many fixes and enhancements:

- Add icon option
- Add env file managment
- Ordering compose parsing options
- Fix path with underscores
- Fix image and tag discovery
- Better documentation for labels
This commit is contained in:
2024-10-17 17:08:42 +02:00
parent 78dfb15cf5
commit 918f1b845b
22 changed files with 991 additions and 893 deletions

View File

@@ -4,6 +4,10 @@ import (
"bytes"
"errors"
"fmt"
"katenary/generator/extrafiles"
"katenary/generator/labelStructs"
"katenary/parser"
"katenary/utils"
"log"
"os"
"os/exec"
@@ -13,13 +17,22 @@ import (
"time"
"github.com/compose-spec/compose-go/types"
"katenary/generator/extrafiles"
"katenary/generator/labelStructs"
"katenary/parser"
"katenary/utils"
)
const ingressClassHelp = `# Default value for ingress.class annotation
# class: "-"
# If the value is "-", controller will not set ingressClassName
# If the value is "", Ingress will be set to an empty string, so
# controller will use the default value for ingressClass
# If the value is specified, controller will set the named class e.g. "nginx"
`
const storageClassHelp = `# Storage class to use for PVCs
# storageClass: "-" means use default
# storageClass: "" means do not specify
# storageClass: "foo" means use that storageClass
`
const headerHelp = `# This file is autogenerated by katenary
#
# DO NOT EDIT IT BY HAND UNLESS YOU KNOW WHAT YOU ARE DOING
@@ -32,6 +45,47 @@ const headerHelp = `# This file is autogenerated by katenary
`
const imagePullSecretHelp = `
# imagePullSecrets allows you to specify a name of an image pull secret.
# You must provide a list of object with the name field set to the name of the
# e.g.
# pullSecrets:
# - name: regcred
# You are, for now, repsonsible for creating the secret.
`
const imagePullPolicyHelp = `# imagePullPolicy allows you to specify a policy to cache or always pull an image.
# You must provide a string value with one of the following values:
# - Always -> will always pull the image
# - Never -> will never pull the image, the image should be present on the node
# - IfNotPresent -> will pull the image only if it is not present on the node
`
const resourceHelp = `# Resources allows you to specify the resource requests and limits for a service.
# Resources are used to specify the amount of CPU and memory that
# a container needs.
#
# e.g.
# resources:
# requests:
# memory: "64Mi"
# cpu: "250m"
# limits:
# memory: "128Mi"
# cpu: "500m"
`
const mainTagAppDoc = `This is the version of the main application.
Leave it to blank to use the Chart "AppVersion" value.`
var unwantedLines = []string{
"creationTimestamp:",
"status:",
}
// keyRegExp checks if the line starts by a #
var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
// Convert a compose (docker, podman...) project to a helm chart.
// It calls Generate() to generate the chart and then write it to the disk.
func Convert(config ConvertOptions, dockerComposeFile ...string) {
@@ -59,7 +113,7 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
}
// parse the compose files
project, err := parser.Parse(config.Profiles, dockerComposeFile...)
project, err := parser.Parse(config.Profiles, config.EnvFiles, dockerComposeFile...)
if err != nil {
fmt.Println(err)
os.Exit(1)
@@ -109,6 +163,11 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
os.Exit(1)
}
// add icon from the command line
if config.Icon != "" {
chart.Icon = config.Icon
}
// write the templates to the disk
chart.SaveTemplates(templateDir)
@@ -132,141 +191,6 @@ func Convert(config ConvertOptions, dockerComposeFile ...string) {
callHelmUpdate(config)
}
const ingressClassHelp = `# Default value for ingress.class annotation
# class: "-"
# If the value is "-", controller will not set ingressClassName
# If the value is "", Ingress will be set to an empty string, so
# controller will use the default value for ingressClass
# If the value is specified, controller will set the named class e.g. "nginx"
`
func addCommentsToValues(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "ingress:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent ingressClassHelper comment
ingressClassHelp := strings.ReplaceAll(ingressClassHelp, "\n", "\n"+spacesString)
ingressClassHelp = strings.TrimRight(ingressClassHelp, " ")
ingressClassHelp = spacesString + ingressClassHelp
lines[i] = ingressClassHelp + line
}
}
return []byte(strings.Join(lines, "\n"))
}
const storageClassHelp = `# Storage class to use for PVCs
# storageClass: "-" means use default
# storageClass: "" means do not specify
# storageClass: "foo" means use that storageClass
`
// addStorageClassHelp adds a comment to the values.yaml file to explain how to
// use the storageClass option.
func addStorageClassHelp(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "storageClass:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent ingressClassHelper comment
storageClassHelp := strings.ReplaceAll(storageClassHelp, "\n", "\n"+spacesString)
storageClassHelp = strings.TrimRight(storageClassHelp, " ")
storageClassHelp = spacesString + storageClassHelp
lines[i] = storageClassHelp + line
}
}
return []byte(strings.Join(lines, "\n"))
}
// addModeline adds a modeline to the values.yaml file to make sure that vim
// will use the correct syntax highlighting.
func addModeline(values []byte) []byte {
modeline := "# vi" + "m: ft=helm.gotmpl.yaml"
// if the values ends by `{{- end }}` we need to add the modeline before
lines := strings.Split(string(values), "\n")
if lines[len(lines)-1] == "{{- end }}" || lines[len(lines)-1] == "{{- end -}}" {
lines = lines[:len(lines)-1]
lines = append(lines, modeline, "{{- end }}")
return []byte(strings.Join(lines, "\n"))
}
return append(values, []byte(modeline)...)
}
// addDescriptions adds the description from the label to the values.yaml file on top
// of the service definition.
func addDescriptions(values []byte, project types.Project) []byte {
for _, service := range project.Services {
if description, ok := service.Labels[LabelDescription]; ok {
// set it as comment
description = "\n# " + strings.ReplaceAll(description, "\n", "\n# ")
values = regexp.MustCompile(
`(?m)^`+service.Name+`:$`,
).ReplaceAll(values, []byte(description+"\n"+service.Name+":"))
} else {
// set it as comment
description = "\n# " + service.Name + " configuration"
values = regexp.MustCompile(
`(?m)^`+service.Name+`:$`,
).ReplaceAll(
values,
[]byte(description+"\n"+service.Name+":"),
)
}
}
return values
}
func addDependencyDescription(values []byte, dependencies []labelStructs.Dependency) []byte {
for _, d := range dependencies {
name := d.Name
if d.Alias != "" {
name = d.Alias
}
values = regexp.MustCompile(
`(?m)^`+name+`:$`,
).ReplaceAll(
values,
[]byte("\n# "+d.Name+" helm dependency configuration\n"+name+":"),
)
}
return values
}
const imagePullSecretHelp = `
# imagePullSecrets allows you to specify a name of an image pull secret.
# You must provide a list of object with the name field set to the name of the
# e.g.
# pullSecrets:
# - name: regcred
# You are, for now, repsonsible for creating the secret.
`
func addImagePullSecretsHelp(values []byte) []byte {
// add imagePullSecrets help
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "pullSecrets:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent imagePullSecretHelp comment
imagePullSecretHelp := strings.ReplaceAll(imagePullSecretHelp, "\n", "\n"+spacesString)
imagePullSecretHelp = strings.TrimRight(imagePullSecretHelp, " ")
imagePullSecretHelp = spacesString + imagePullSecretHelp
lines[i] = imagePullSecretHelp + line
}
}
return []byte(strings.Join(lines, "\n"))
}
func addChartDoc(values []byte, project *types.Project) []byte {
chartDoc := fmt.Sprintf(`# This is the main values.yaml file for the %s chart.
# More information can be found in the chart's README.md file.
@@ -303,67 +227,63 @@ func addChartDoc(values []byte, project *types.Project) []byte {
return []byte(chartDoc + strings.Join(lines, "\n"))
}
const imagePullPolicyHelp = `# imagePullPolicy allows you to specify a policy to cache or always pull an image.
# You must provide a string value with one of the following values:
# - Always -> will always pull the image
# - Never -> will never pull the image, the image should be present on the node
# - IfNotPresent -> will pull the image only if it is not present on the node
`
func addImagePullPolicyHelp(values []byte) []byte {
// add imagePullPolicy help
func addCommentsToValues(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "imagePullPolicy:") {
if strings.Contains(line, "ingress:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent imagePullPolicyHelp comment
imagePullPolicyHelp := strings.ReplaceAll(imagePullPolicyHelp, "\n", "\n"+spacesString)
imagePullPolicyHelp = strings.TrimRight(imagePullPolicyHelp, " ")
imagePullPolicyHelp = spacesString + imagePullPolicyHelp
lines[i] = imagePullPolicyHelp + line
// indent ingressClassHelper comment
ingressClassHelp := strings.ReplaceAll(ingressClassHelp, "\n", "\n"+spacesString)
ingressClassHelp = strings.TrimRight(ingressClassHelp, " ")
ingressClassHelp = spacesString + ingressClassHelp
lines[i] = ingressClassHelp + line
}
}
return []byte(strings.Join(lines, "\n"))
}
const resourceHelp = `# Resources allows you to specify the resource requests and limits for a service.
# Resources are used to specify the amount of CPU and memory that
# a container needs.
#
# e.g.
# resources:
# requests:
# memory: "64Mi"
# cpu: "250m"
# limits:
# memory: "128Mi"
# cpu: "500m"
`
func addResourceHelp(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "resources:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent resourceHelp comment
resourceHelp := strings.ReplaceAll(resourceHelp, "\n", "\n"+spacesString)
resourceHelp = strings.TrimRight(resourceHelp, " ")
resourceHelp = spacesString + resourceHelp
lines[i] = resourceHelp + line
func addDependencyDescription(values []byte, dependencies []labelStructs.Dependency) []byte {
for _, d := range dependencies {
name := d.Name
if d.Alias != "" {
name = d.Alias
}
values = regexp.MustCompile(
`(?m)^`+name+`:$`,
).ReplaceAll(
values,
[]byte("\n# "+d.Name+" helm dependency configuration\n"+name+":"),
)
}
return []byte(strings.Join(lines, "\n"))
return values
}
func addVariablesDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n")
// addDescriptions adds the description from the label to the values.yaml file on top
// of the service definition.
func addDescriptions(values []byte, project types.Project) []byte {
for _, service := range project.Services {
lines = addDocToVariable(service, lines)
if description, ok := service.Labels[LabelDescription]; ok {
// set it as comment
description = "\n# " + strings.ReplaceAll(description, "\n", "\n# ")
values = regexp.MustCompile(
`(?m)^`+service.Name+`:$`,
).ReplaceAll(values, []byte(description+"\n"+service.Name+":"))
} else {
// set it as comment
description = "\n# " + service.Name + " configuration"
values = regexp.MustCompile(
`(?m)^`+service.Name+`:$`,
).ReplaceAll(
values,
[]byte(description+"\n"+service.Name+":"),
)
}
}
return []byte(strings.Join(lines, "\n"))
return values
}
func addDocToVariable(service types.ServiceConfig, lines []string) []string {
@@ -394,25 +314,38 @@ func addDocToVariable(service types.ServiceConfig, lines []string) []string {
return lines
}
const mainTagAppDoc = `This is the version of the main application.
Leave it to blank to use the Chart "AppVersion" value.`
func addImagePullPolicyHelp(values []byte) []byte {
// add imagePullPolicy help
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "imagePullPolicy:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent imagePullPolicyHelp comment
imagePullPolicyHelp := strings.ReplaceAll(imagePullPolicyHelp, "\n", "\n"+spacesString)
imagePullPolicyHelp = strings.TrimRight(imagePullPolicyHelp, " ")
imagePullPolicyHelp = spacesString + imagePullPolicyHelp
lines[i] = imagePullPolicyHelp + line
}
}
return []byte(strings.Join(lines, "\n"))
}
func addMainTagAppDoc(values []byte, project *types.Project) []byte {
func addImagePullSecretsHelp(values []byte) []byte {
// add imagePullSecrets help
lines := strings.Split(string(values), "\n")
for _, service := range project.Services {
// read the label LabelMainApp
if v, ok := service.Labels[LabelMainApp]; !ok {
continue
} else if v == "false" || v == "no" || v == "0" {
continue
} else {
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
for i, line := range lines {
if strings.Contains(line, "pullSecrets:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent imagePullSecretHelp comment
imagePullSecretHelp := strings.ReplaceAll(imagePullSecretHelp, "\n", "\n"+spacesString)
imagePullSecretHelp = strings.TrimRight(imagePullSecretHelp, " ")
imagePullSecretHelp = spacesString + imagePullSecretHelp
lines[i] = imagePullSecretHelp + line
}
lines = addMainAppDoc(lines, service)
}
return []byte(strings.Join(lines, "\n"))
}
@@ -440,107 +373,84 @@ func addMainAppDoc(lines []string, service types.ServiceConfig) []string {
return lines
}
func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil {
log.Fatal(err)
}
return re.ReplaceAllFunc(values, func(b []byte) []byte {
// get the first match
matches := re.FindSubmatch(b)
replacement := bytes.ReplaceAll(matches[1], []byte("\n"), []byte(" "))
// remove repeated spaces
replacement = regexp.MustCompile(`\s+`).ReplaceAll(replacement, []byte(" "))
// remove newlines inside brackets
return bytes.ReplaceAll(b, matches[1], replacement)
})
}
var unwantedLines = []string{
"creationTimestamp:",
"status:",
}
func removeUnwantedLines(values []byte) []byte {
func addMainTagAppDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n")
output := []string{}
for _, line := range lines {
next := false
for _, unwanted := range unwantedLines {
if strings.Contains(line, unwanted) {
next = true
}
}
if !next {
output = append(output, line)
}
}
return []byte(strings.Join(output, "\n"))
}
// check if the project makes use of older labels (kanetary.[^v3])
func checkOldLabels(project *types.Project) error {
badServices := make([]string, 0)
for _, service := range project.Services {
for label := range service.Labels {
if strings.Contains(label, "katenary.") && !strings.Contains(label, katenaryLabelPrefix) {
badServices = append(badServices, fmt.Sprintf("- %s: %s", service.Name, label))
}
// read the label LabelMainApp
if v, ok := service.Labels[LabelMainApp]; !ok {
continue
} else if v == "false" || v == "no" || v == "0" {
continue
} else {
fmt.Printf("%s Adding main tag app doc %s\n", utils.IconConfig, service.Name)
}
lines = addMainAppDoc(lines, service)
}
return []byte(strings.Join(lines, "\n"))
}
// addModeline adds a modeline to the values.yaml file to make sure that vim
// will use the correct syntax highlighting.
func addModeline(values []byte) []byte {
modeline := "# vi" + "m: ft=helm.gotmpl.yaml"
// if the values ends by `{{- end }}` we need to add the modeline before
lines := strings.Split(string(values), "\n")
if lines[len(lines)-1] == "{{- end }}" || lines[len(lines)-1] == "{{- end -}}" {
lines = lines[:len(lines)-1]
lines = append(lines, modeline, "{{- end }}")
return []byte(strings.Join(lines, "\n"))
}
return append(values, []byte(modeline)...)
}
func addResourceHelp(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "resources:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent resourceHelp comment
resourceHelp := strings.ReplaceAll(resourceHelp, "\n", "\n"+spacesString)
resourceHelp = strings.TrimRight(resourceHelp, " ")
resourceHelp = spacesString + resourceHelp
lines[i] = resourceHelp + line
}
}
if len(badServices) > 0 {
message := fmt.Sprintf(` Old labels detected in project "%s".
The current version of katenary uses labels with the prefix "%s" which are not compatible with previous versions.
Your project is not compatible with this version.
Please upgrade your labels to follow the current version
Services to upgrade:
%s`,
project.Name,
katenaryLabelPrefix[0:len(katenaryLabelPrefix)-1],
strings.Join(badServices, "\n"),
)
return errors.New(utils.WordWrap(message, 80))
}
return nil
return []byte(strings.Join(lines, "\n"))
}
// helmUpdate runs "helm dependency update" on the output directory.
func helmUpdate(config ConvertOptions) error {
// lookup for "helm" binary
fmt.Println(utils.IconInfo, "Updating helm dependencies...")
helm, err := exec.LookPath("helm")
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
// addStorageClassHelp adds a comment to the values.yaml file to explain how to
// use the storageClass option.
func addStorageClassHelp(values []byte) []byte {
lines := strings.Split(string(values), "\n")
for i, line := range lines {
if strings.Contains(line, "storageClass:") {
spaces := utils.CountStartingSpaces(line)
spacesString := strings.Repeat(" ", spaces)
// indent ingressClassHelper comment
storageClassHelp := strings.ReplaceAll(storageClassHelp, "\n", "\n"+spacesString)
storageClassHelp = strings.TrimRight(storageClassHelp, " ")
storageClassHelp = spacesString + storageClassHelp
lines[i] = storageClassHelp + line
}
}
// run "helm dependency update"
cmd := exec.Command(helm, "dependency", "update", config.OutputDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
return []byte(strings.Join(lines, "\n"))
}
// helmLint runs "helm lint" on the output directory.
func helmLint(config ConvertOptions) error {
fmt.Println(utils.IconInfo, "Linting...")
helm, err := exec.LookPath("helm")
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
}
cmd := exec.Command(helm, "lint", config.OutputDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
func addVariablesDoc(values []byte, project *types.Project) []byte {
lines := strings.Split(string(values), "\n")
// keyRegExp checks if the line starts by a #
var keyRegExp = regexp.MustCompile(`^\s*[^#]+:.*`)
for _, service := range project.Services {
lines = addDocToVariable(service, lines)
}
return []byte(strings.Join(lines, "\n"))
}
// addYAMLSelectorPath adds a selector path to the yaml file for each key
// as comment. E.g. foo.ingress.host
@@ -587,14 +497,40 @@ func addYAMLSelectorPath(values []byte) []byte {
return []byte(strings.Join(toReturn, "\n"))
}
func writeContent(path string, content []byte) {
f, err := os.Create(path)
func buildCharYamlFile(chart *HelmChart, project *types.Project, chartPath string) {
// calculate the sha1 hash of the services
yamlChart, err := utils.EncodeBasicYaml(chart)
if err != nil {
fmt.Println(utils.IconFailure, err)
fmt.Println(err)
os.Exit(1)
}
defer f.Close()
f.Write(content)
// concat chart adding a comment with hash of services on top
yamlChart = append([]byte(fmt.Sprintf("# compose hash (sha1): %s\n", *chart.composeHash)), yamlChart...)
// add the list of compose files
files := []string{}
for _, file := range project.ComposeFiles {
base := filepath.Base(file)
files = append(files, base)
}
yamlChart = append([]byte(fmt.Sprintf("# compose files: %s\n", strings.Join(files, ", "))), yamlChart...)
// add generated date
yamlChart = append([]byte(fmt.Sprintf("# generated at: %s\n", time.Now().Format(time.RFC3339))), yamlChart...)
// document Chart.yaml file
yamlChart = addChartDoc(yamlChart, project)
writeContent(chartPath, yamlChart)
}
func buildNotesFile(project *types.Project, notesPath string) {
// get the list of services to write in the notes
services := make([]string, 0)
for _, service := range project.Services {
services = append(services, service.Name)
}
// write the notes to the disk
notes := extrafiles.NotesFile(services)
writeContent(notesPath, []byte(notes))
}
func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
@@ -622,42 +558,6 @@ func buildValues(chart *HelmChart, project *types.Project, valuesPath string) {
writeContent(valuesPath, values)
}
func buildNotesFile(project *types.Project, notesPath string) {
// get the list of services to write in the notes
services := make([]string, 0)
for _, service := range project.Services {
services = append(services, service.Name)
}
// write the notes to the disk
notes := extrafiles.NotesFile(services)
writeContent(notesPath, []byte(notes))
}
func buildCharYamlFile(chart *HelmChart, project *types.Project, chartPath string) {
// calculate the sha1 hash of the services
yamlChart, err := utils.EncodeBasicYaml(chart)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
// concat chart adding a comment with hash of services on top
yamlChart = append([]byte(fmt.Sprintf("# compose hash (sha1): %s\n", *chart.composeHash)), yamlChart...)
// add the list of compose files
files := []string{}
for _, file := range project.ComposeFiles {
base := filepath.Base(file)
files = append(files, base)
}
yamlChart = append([]byte(fmt.Sprintf("# compose files: %s\n", strings.Join(files, ", "))), yamlChart...)
// add generated date
yamlChart = append([]byte(fmt.Sprintf("# generated at: %s\n", time.Now().Format(time.RFC3339))), yamlChart...)
// document Chart.yaml file
yamlChart = addChartDoc(yamlChart, project)
writeContent(chartPath, yamlChart)
}
func callHelmUpdate(config ConvertOptions) {
executeAndHandleError := func(fn func(ConvertOptions) error, config ConvertOptions, message string) {
if err := fn(config); err != nil {
@@ -672,3 +572,107 @@ func callHelmUpdate(config ConvertOptions) {
fmt.Println(utils.IconSuccess, "Helm chart created successfully")
}
}
func removeNewlinesInsideBrackets(values []byte) []byte {
re, err := regexp.Compile(`(?s)\{\{(.*?)\}\}`)
if err != nil {
log.Fatal(err)
}
return re.ReplaceAllFunc(values, func(b []byte) []byte {
// get the first match
matches := re.FindSubmatch(b)
replacement := bytes.ReplaceAll(matches[1], []byte("\n"), []byte(" "))
// remove repeated spaces
replacement = regexp.MustCompile(`\s+`).ReplaceAll(replacement, []byte(" "))
// remove newlines inside brackets
return bytes.ReplaceAll(b, matches[1], replacement)
})
}
func removeUnwantedLines(values []byte) []byte {
lines := strings.Split(string(values), "\n")
output := []string{}
for _, line := range lines {
next := false
for _, unwanted := range unwantedLines {
if strings.Contains(line, unwanted) {
next = true
}
}
if !next {
output = append(output, line)
}
}
return []byte(strings.Join(output, "\n"))
}
func writeContent(path string, content []byte) {
f, err := os.Create(path)
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
}
defer f.Close()
f.Write(content)
}
// helmLint runs "helm lint" on the output directory.
func helmLint(config ConvertOptions) error {
fmt.Println(utils.IconInfo, "Linting...")
helm, err := exec.LookPath("helm")
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
}
cmd := exec.Command(helm, "lint", config.OutputDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// helmUpdate runs "helm dependency update" on the output directory.
func helmUpdate(config ConvertOptions) error {
// lookup for "helm" binary
fmt.Println(utils.IconInfo, "Updating helm dependencies...")
helm, err := exec.LookPath("helm")
if err != nil {
fmt.Println(utils.IconFailure, err)
os.Exit(1)
}
// run "helm dependency update"
cmd := exec.Command(helm, "dependency", "update", config.OutputDir)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// check if the project makes use of older labels (kanetary.[^v3])
func checkOldLabels(project *types.Project) error {
badServices := make([]string, 0)
for _, service := range project.Services {
for label := range service.Labels {
if strings.Contains(label, "katenary.") && !strings.Contains(label, katenaryLabelPrefix) {
badServices = append(badServices, fmt.Sprintf("- %s: %s", service.Name, label))
}
}
}
if len(badServices) > 0 {
message := fmt.Sprintf(` Old labels detected in project "%s".
The current version of katenary uses labels with the prefix "%s" which are not compatible with previous versions.
Your project is not compatible with this version.
Please upgrade your labels to follow the current version
Services to upgrade:
%s`,
project.Name,
katenaryLabelPrefix[0:len(katenaryLabelPrefix)-1],
strings.Join(badServices, "\n"),
)
return errors.New(utils.WordWrap(message, 80))
}
return nil
}