From 95e3252e2d097ea0faa7c05e23d59a709ac7046e Mon Sep 17 00:00:00 2001 From: pamiel <14542297+pamiel@users.noreply.github.com> Date: Thu, 4 Apr 2019 09:46:11 +0200 Subject: [PATCH 1/3] Adding capability to include values files into the default value file of the umbrella chart --- main.go | 154 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 128 insertions(+), 26 deletions(-) diff --git a/main.go b/main.go index b945114..e0f5b86 100644 --- a/main.go +++ b/main.go @@ -17,14 +17,18 @@ import ( "fmt" "os" "bufio" + "io/ioutil" "strconv" "strings" + "regexp" "time" "text/tabwriter" "github.com/gemalto/helm-spray/pkg/helm" "github.com/gemalto/helm-spray/pkg/kubectl" + chartutil "k8s.io/helm/pkg/chartutil" + chartHapi "k8s.io/helm/pkg/proto/hapi/chart" "github.com/spf13/cobra" ) @@ -52,6 +56,7 @@ type Dependency struct { Name string Alias string UsedName string + AppVersion string Targeted bool Weight int CorrespondingReleaseName string @@ -115,24 +120,20 @@ func newSprayCmd(args []string) *cobra.Command { if p.chartVersion != "" { if strings.HasSuffix(p.chartName, "tgz") { - os.Stderr.WriteString("You cannot use --version together with chart archive\n") - os.Exit(1) + logErrorAndExit("You cannot use --version together with chart archive") } if _, err := os.Stat(p.chartName); err == nil { - os.Stderr.WriteString("You cannot use --version together with chart directory\n") - os.Exit(1) + logErrorAndExit("You cannot use --version together with chart directory") } if (strings.HasPrefix(p.chartName, "http://") || strings.HasPrefix(p.chartName, "https://")) { - os.Stderr.WriteString("You cannot use --version together with chart URL\n") - os.Exit(1) + logErrorAndExit("You cannot use --version together with chart URL") } } if p.prefixReleasesWithNamespace == true && p.prefixReleases != "" { - os.Stderr.WriteString("You cannot use both --prefix-releases and --prefix-releases-with-namespace together\n") - os.Exit(1) + logErrorAndExit("You cannot use both --prefix-releases and --prefix-releases-with-namespace together") } @@ -189,26 +190,50 @@ func newSprayCmd(args []string) *cobra.Command { } +// Running Spray command func (p *sprayCmd) spray() error { // Load and valide the umbrella chart... chart, err := chartutil.Load(p.chartName) if err != nil { - panic(fmt.Errorf("%s", err)) + logErrorAndExit("Error loading chart \"%s\": %s", p.chartName, err) } // Load and valid the requirements file... reqs, err := chartutil.LoadRequirements(chart) if err != nil { - panic(fmt.Errorf("%s", err)) + logErrorAndExit("Error reading \"requirements.yaml\" file: %s", err) } + // Get the default values file of the umbrella chart and process the '#!include' directives that might be specified in it + updatedDefaultValues := processIncludeInValuesFile(chart) + // Load default values... - values, err := chartutil.CoalesceValues(chart, chart.GetValues()) + values, err := chartutil.CoalesceValues(chart, &chartHapi.Config{Raw: string(updatedDefaultValues)}) if err != nil { - panic(fmt.Errorf("%s", err)) + logErrorAndExit("Error processing default values for umbrella chart: %s", err) } + // Write default values to a temporary file and add it to the list of values files, + // for later usage during the calls to helm + tempDir, err := ioutil.TempDir("", "spray-") + if err != nil { + logErrorAndExit("Error creating temporary directory to write updated default values file for umbrella chart: %s", err) + } + defer os.RemoveAll(tempDir) + + tempFile, err := ioutil.TempFile(tempDir, "updatedDefaultValues-*.yaml") + if err != nil { + logErrorAndExit("Error creating temporary file to write updated default values file for umbrella chart: %s", err) + } + defer os.Remove(tempFile.Name()) + + if _, err = tempFile.Write([]byte(updatedDefaultValues)); err != nil { + logErrorAndExit("Error writing updated default values file for umbrella chart into temporary file: %s", err) + } + p.valueFiles = append([]string{tempFile.Name()}, p.valueFiles...) + + // Build the list of all rependencies, and their key attributes dependencies := make([]Dependency, len(reqs.Dependencies)) for i, req := range reqs.Dependencies { @@ -241,7 +266,7 @@ func (p *sprayCmd) spray() error { w64 := depi["weight"].(float64) w, err := strconv.Atoi(strconv.FormatFloat(w64, 'f', 0, 64)) if err != nil { - panic(fmt.Errorf("%s", err)) + logErrorAndExit("Error computing weight value for sub-chart \"%s\": %s", dependencies[i].UsedName, err) } dependencies[i].Weight = w } @@ -254,6 +279,14 @@ func (p *sprayCmd) spray() error { } else { dependencies[i].CorrespondingReleaseName = dependencies[i].UsedName } + + // Get the AppVersion that is contained in the Chart.yaml file of the dependency sub-chart + for _, subChart := range chart.GetDependencies() { + if subChart.GetMetadata().GetName() == dependencies[i].Name { + dependencies[i].AppVersion = subChart.GetMetadata().GetAppVersion() + break + } + } } @@ -307,10 +340,10 @@ w.Flush() } if release, ok := helmReleases[dependency.CorrespondingReleaseName]; ok { - log(2, "upgrading release \"%s\": going from revision %d (status %s) to %d...", dependency.CorrespondingReleaseName, release.Revision, release.Status, release.Revision+1) + log(2, "upgrading release \"%s\": going from revision %d (status %s) to %d (target App Version: \"%s\")...", dependency.CorrespondingReleaseName, release.Revision, release.Status, release.Revision+1, dependency.AppVersion) } else { - log(2, "upgrading release \"%s\": deploying first revision...", dependency.CorrespondingReleaseName) + log(2, "upgrading release \"%s\": deploying first revision (target App Version: \"%s\")...", dependency.CorrespondingReleaseName, dependency.AppVersion) } shouldWait = true @@ -348,8 +381,7 @@ w.Flush() if helmstatus.Status == "" { log(2, "Warning: no status returned by helm.") } else if helmstatus.Status != "DEPLOYED" { - log(2, "Error: status returned by helm differs from \"DEPLOYED\". Cannot continue spray processing.") - os.Exit(1) + logErrorAndExit("Error: status returned by helm differs from \"DEPLOYED\". Cannot continue spray processing.") } } } @@ -380,9 +412,7 @@ w.Flush() } if !done { - os.Stderr.WriteString("Error: UPGRADE FAILED: timed out waiting for the condition\n") - os.Stderr.WriteString("==> Error: exit status 1\n") - os.Exit(1) + logErrorAndExit("Error: UPGRADE FAILED: timed out waiting for the condition\n==> Error: exit status 1") } } @@ -403,9 +433,7 @@ w.Flush() } if !done { - os.Stderr.WriteString("Error: UPGRADE FAILED: timed out waiting for the condition\n") - os.Stderr.WriteString("==> Error: exit status 1\n") - os.Exit(1) + logErrorAndExit("Error: UPGRADE FAILED: timed out waiting for the condition\n==> Error: exit status 1") } } @@ -426,9 +454,7 @@ w.Flush() } if !done { - os.Stderr.WriteString("Error: UPGRADE FAILED: timed out waiting for the condition\n") - os.Stderr.WriteString("==> Error: exit status 1\n") - os.Exit(1) + logErrorAndExit("Error: UPGRADE FAILED: timed out waiting for the condition\n==> Error: exit status 1") } } } @@ -454,6 +480,75 @@ func getMaxWeight(v []Dependency) (m int) { return m } +// Search the "#!include" clauses in the default value file of the chart and replace them by the content +// of the corresponding file. +// Possible formats are: +// #!include myfile.yaml +// #!include myfile.yaml | indent 2 +// #!include myfile.yaml | .Values.tag +// #!include myfile.yaml | .Values.tag.subTag | indent 4 +// +func processIncludeInValuesFile(chart *chartHapi.Chart) string { + defaultValues := string(chart.GetValues().GetRaw()) + + // Process includes with "| indent" + includeFileNameExp := regexp.MustCompile(`#!include\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.Values|\.Values\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\n`) + match := includeFileNameExp.FindStringSubmatch(defaultValues) + + for ; len(match) != 0; { + fullMatch := match[0] + includeFileName := match[1] + subValuePath := match[4] + indent := match[6] + + replaced := false + + for _, f := range chart.GetFiles() { + if f.GetTypeUrl() == strings.Trim(strings.TrimSpace(includeFileName), "\"") { + dataToAdd := string(f.GetValue()) + if subValuePath != "" { + data, err := chartutil.ReadValues(f.GetValue()) + if err != nil { + logErrorAndExit("Unable to read values from file \"%s\": %s", includeFileName, err) + } + + subData, err := data.Table(subValuePath) + if err != nil { + logErrorAndExit("Unable to find path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) + } + + dataToAdd, err = subData.YAML() + if err != nil { + logErrorAndExit("Unable to generate a value YAML file from values at path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) + } + } + + if indent == "" { + defaultValues = strings.Replace(defaultValues, fullMatch, dataToAdd + "\n", -1) + } else { + nbrOfSpaces, err := strconv.Atoi(indent) + if err != nil { + logErrorAndExit("Error computing indentation value in \"#!include\" clause: %s", err) + } + + toAdd := strings.Replace(dataToAdd, "\n", "\n" + strings.Repeat (" ", nbrOfSpaces), -1) + defaultValues = strings.Replace(defaultValues, fullMatch, strings.Repeat (" ", nbrOfSpaces) + toAdd + "\n", -1) + } + + replaced = true + } + } + + if !replaced { + logErrorAndExit("Unable to find file \"%s\" referenced in the \"%s\" clause of the default values file of the umbrella chart", match[1], strings.TrimRight(match[0], "\n")) + } + + match = includeFileNameExp.FindStringSubmatch(defaultValues) + } + + return defaultValues +} + // Log spray messages func log(level int, str string, params ...interface{}) { var logStr = "[spray] " @@ -471,6 +566,13 @@ func log(level int, str string, params ...interface{}) { fmt.Println(logStr + fmt.Sprintf(str, params...)) } +// Log error and exit +func logErrorAndExit(str string, params ...interface{}) { + os.Stderr.WriteString(fmt.Sprintf(str + "\n", params...)) + os.Exit(1) +} + + func main() { cmd := newSprayCmd(os.Args[1:]) if err := cmd.Execute(); err != nil { From 816bb275c60ae3530596a89899987302e40549e9 Mon Sep 17 00:00:00 2001 From: pamiel <14542297+pamiel@users.noreply.github.com> Date: Thu, 18 Apr 2019 14:39:42 +0200 Subject: [PATCH 2/3] Support of an include mechanism in the values.yaml file of the umbrella chart --- README.md | 39 +++++++++++++++++++++++++++++++- main.go | 67 ++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a812b72..fcfdd5b 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ dependencies: condition: ms3.enabled ``` -A "values" file shall also be set with the weight ito be applied to each individual sub-chart. This weight shall be set in the `.weight` element. A good practice is that thei weigths are statically set in the default `values.yaml` file of the umbrella chart (and not in a yaml file provided using the `-f` option), as sub-chart's weight is not likely to change over time. +A "values" file shall also be set with the weight it be applied to each individual sub-chart. This weight shall be set in the `.weight` element. A good practice is that thei weigths are statically set in the default `values.yaml` file of the umbrella chart (and not in a yaml file provided using the `-f` option), as sub-chart's weight is not likely to change over time. As an example corresponding to the above `requirement.yaml` file, the `values.yaml` file of the umbrella chart might be: ``` micro-service-1: @@ -73,6 +73,43 @@ ms3 7 Wed Jan 30 17:18:45 2019 DEPLOYED Note: if an alias is set for a sub-chart, then this is this alias that should be used with the `--target` optioni, not the sub-chart name. +### Values: + +The umbrella chart gathers several components or micro-services into a single solution. Values can then be set at many different places: +- At micro-service level, inside the `values.yaml` file of each micro-service chart: these are common defaults values set by the micro-service developer, independently from the deployment context and location of the micro-service +- At the solution level, inside the `values.yaml` file of the umbrella chart: these are values complementing or overwriting default values of the micro-services sub-charts, usually formalizing the deployment topology of the solution and giving the standard configuration of the underlying micro-services for any deployments of cwthis specific solution +- At deployment time, using the `--values/-f` or `--set` flags: this is the placeholder for giving the deployment-dependent values, specifying for example the exact database url for this deployment, the exact password value for this deployment, the targeted remote server url for this deployment, etc. These values usually change from one deployment of the solution to another. + +Within the micro-services paradigm, decoupling between micro-services is one of the most important criteria to respect. While values con be provided in a per-micro-service basis for the first and last places mentioned above, Helm only allows one single `values.yaml` file in the umbrella chart. All solution-level values should then be gathered into a single file, while it would have been better to provide values in several files, on a one-file-per-micro-service basis (to ensure decoupling of the micro-services configuration, even at solution level). +Helm Spray is consequently adding this capability to have several values file in the umbrella chart and to include them into the single `values.yaml` file using the `#! {{ .File.Get }}` directive. +- The file to be included shall be a valid yaml file. +- It is possible to only include a sub-part of the yaml content by piping the `File.Get` directive with the path to be extracted and included: `#! {{ .File.Get | .for.bar }}`. `#! {{ .File.Get | . }}` is equivalent to `#! {{ .File.Get }}`. +- It is possible to indent the included content using the `indent` directive: `#! {{ .File.Get | indent 2 }}`, `#! {{ .File.Get | .for.bar | indent 4 }} + +Note: The `{{ .File.Get ... }}` directive shall be prefixed by `#!` as the `values.yaml` file is parsed both with and without the included content. When parsed without the included content, it shall still be a valid yaml file, thus mandating the usage of a comment to specify the `{{ .File.Get ... }}` clause that is supported by default neither by yaml nor by Helm in default values files of charts. Usage of `#!` (with a bang '!') allows differentiating the include clauses from regular comments. +Note also that when Helm is parsing the `values.yaml` file without the included content, some warning may be raised by helm if yaml elements are nil or empty (while they are not with the included content). A typical warning could be: 'Warning: Merging destination map for chart 'my-solution'. The destination item 'bar' is a table and ignoring the source 'bar' as it has a non-table value of: ' + +Example of `values.yaml`: +``` +micro-service-1: + weight: 0 +#! {{ .File.Get ms1.yaml }} + +micro-service-2: + weight: 1 +#! {{ .File.Get ms2.yaml | .foo | indent 2 }} + +ms3: + weight: 2 + bar: +#! {{ .File.Get ms3.yaml | .bar.baz | indent 4 }} +# To prevent from having a warning when the file is processed by Helm, a fake content may be set here. +# Format of the added dummy elements fully depends on the application's values structure + dummy: + dummy: "just here to prevent from a warning" + +``` + ### Flags: ``` diff --git a/main.go b/main.go index e0f5b86..3bddabd 100644 --- a/main.go +++ b/main.go @@ -206,33 +206,42 @@ func (p *sprayCmd) spray() error { } // Get the default values file of the umbrella chart and process the '#!include' directives that might be specified in it - updatedDefaultValues := processIncludeInValuesFile(chart) + // Only in case '--reuseValues' has not been set + var values chartutil.Values + if p.reuseValues == false { + updatedDefaultValues := processIncludeInValuesFile(chart) + + // Load default values... + values, err = chartutil.CoalesceValues(chart, &chartHapi.Config{Raw: string(updatedDefaultValues)}) + if err != nil { + logErrorAndExit("Error processing default values for umbrella chart: %s", err) + } - // Load default values... - values, err := chartutil.CoalesceValues(chart, &chartHapi.Config{Raw: string(updatedDefaultValues)}) - if err != nil { - logErrorAndExit("Error processing default values for umbrella chart: %s", err) - } + // Write default values to a temporary file and add it to the list of values files, + // for later usage during the calls to helm + tempDir, err := ioutil.TempDir("", "spray-") + if err != nil { + logErrorAndExit("Error creating temporary directory to write updated default values file for umbrella chart: %s", err) + } + defer os.RemoveAll(tempDir) - // Write default values to a temporary file and add it to the list of values files, - // for later usage during the calls to helm - tempDir, err := ioutil.TempDir("", "spray-") - if err != nil { - logErrorAndExit("Error creating temporary directory to write updated default values file for umbrella chart: %s", err) - } - defer os.RemoveAll(tempDir) + tempFile, err := ioutil.TempFile(tempDir, "updatedDefaultValues-*.yaml") + if err != nil { + logErrorAndExit("Error creating temporary file to write updated default values file for umbrella chart: %s", err) + } + defer os.Remove(tempFile.Name()) - tempFile, err := ioutil.TempFile(tempDir, "updatedDefaultValues-*.yaml") - if err != nil { - logErrorAndExit("Error creating temporary file to write updated default values file for umbrella chart: %s", err) - } - defer os.Remove(tempFile.Name()) + if _, err = tempFile.Write([]byte(updatedDefaultValues)); err != nil { + logErrorAndExit("Error writing updated default values file for umbrella chart into temporary file: %s", err) + } + p.valueFiles = append([]string{tempFile.Name()}, p.valueFiles...) - if _, err = tempFile.Write([]byte(updatedDefaultValues)); err != nil { - logErrorAndExit("Error writing updated default values file for umbrella chart into temporary file: %s", err) + } else { + values, err = chartutil.CoalesceValues(chart, chart.GetValues()) + if err != nil { + logErrorAndExit("Error processing default values for umbrella chart: %s", err) + } } - p.valueFiles = append([]string{tempFile.Name()}, p.valueFiles...) - // Build the list of all rependencies, and their key attributes dependencies := make([]Dependency, len(reqs.Dependencies)) @@ -340,10 +349,10 @@ w.Flush() } if release, ok := helmReleases[dependency.CorrespondingReleaseName]; ok { - log(2, "upgrading release \"%s\": going from revision %d (status %s) to %d (target App Version: \"%s\")...", dependency.CorrespondingReleaseName, release.Revision, release.Status, release.Revision+1, dependency.AppVersion) + log(2, "upgrading release \"%s\": going from revision %d (status %s) to %d (appVersion %s)...", dependency.CorrespondingReleaseName, release.Revision, release.Status, release.Revision+1, dependency.AppVersion) } else { - log(2, "upgrading release \"%s\": deploying first revision (target App Version: \"%s\")...", dependency.CorrespondingReleaseName, dependency.AppVersion) + log(2, "upgrading release \"%s\": deploying first revision (appVersion %s)...", dependency.CorrespondingReleaseName, dependency.AppVersion) } shouldWait = true @@ -483,6 +492,13 @@ func getMaxWeight(v []Dependency) (m int) { // Search the "#!include" clauses in the default value file of the chart and replace them by the content // of the corresponding file. // Possible formats are: +// #! {{ .File.Get myfile.yaml }} +// #! {{ .File.Get myfile.yaml | . }} +// #! {{ .File.Get myfile.yaml | .tag }} +// #! {{ .File.Get myfile.yaml | indent 2 }} +// #! {{ .File.Get myfile.yaml | .tag.subTag | indent 4 }} + +// TODO TO DELETE // #!include myfile.yaml // #!include myfile.yaml | indent 2 // #!include myfile.yaml | .Values.tag @@ -492,7 +508,8 @@ func processIncludeInValuesFile(chart *chartHapi.Chart) string { defaultValues := string(chart.GetValues().GetRaw()) // Process includes with "| indent" - includeFileNameExp := regexp.MustCompile(`#!include\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.Values|\.Values\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\n`) +// includeFileNameExp := regexp.MustCompile(`#!include\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.Values|\.Values\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\n`) + includeFileNameExp := regexp.MustCompile(`#!\s*\{\{\s*\.File\.Get\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.|\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\}\}\s*\n`) match := includeFileNameExp.FindStringSubmatch(defaultValues) for ; len(match) != 0; { From 681d96a76df56dcd46571f64fd84140d67b1ad02 Mon Sep 17 00:00:00 2001 From: pamiel <14542297+pamiel@users.noreply.github.com> Date: Fri, 19 Apr 2019 20:11:56 +0200 Subject: [PATCH 3/3] Using the pick function instead of a pipe --- README.md | 10 +++---- main.go | 82 ++++++++++++++++++++++++++++++++++++------------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fcfdd5b..9a3c5f8 100644 --- a/README.md +++ b/README.md @@ -83,10 +83,10 @@ The umbrella chart gathers several components or micro-services into a single so Within the micro-services paradigm, decoupling between micro-services is one of the most important criteria to respect. While values con be provided in a per-micro-service basis for the first and last places mentioned above, Helm only allows one single `values.yaml` file in the umbrella chart. All solution-level values should then be gathered into a single file, while it would have been better to provide values in several files, on a one-file-per-micro-service basis (to ensure decoupling of the micro-services configuration, even at solution level). Helm Spray is consequently adding this capability to have several values file in the umbrella chart and to include them into the single `values.yaml` file using the `#! {{ .File.Get }}` directive. - The file to be included shall be a valid yaml file. -- It is possible to only include a sub-part of the yaml content by piping the `File.Get` directive with the path to be extracted and included: `#! {{ .File.Get | .for.bar }}`. `#! {{ .File.Get | . }}` is equivalent to `#! {{ .File.Get }}`. -- It is possible to indent the included content using the `indent` directive: `#! {{ .File.Get | indent 2 }}`, `#! {{ .File.Get | .for.bar | indent 4 }} +- It is possible to only include a sub-part of the yaml content by picking an element of the `File.Get`, specifying the path to be extracted and included: `#! {{ pick (.File.Get ) for.bar }}`. Only paths targeting a Yaml element or a leaf value can be provided. Paths targeted lists are not supported. +- It is possible to indent the included content using the `indent` directive: `#! {{ .File.Get | indent 2 }}`, `#! {{ pick (.File.Get ) for.bar | indent 4 }} -Note: The `{{ .File.Get ... }}` directive shall be prefixed by `#!` as the `values.yaml` file is parsed both with and without the included content. When parsed without the included content, it shall still be a valid yaml file, thus mandating the usage of a comment to specify the `{{ .File.Get ... }}` clause that is supported by default neither by yaml nor by Helm in default values files of charts. Usage of `#!` (with a bang '!') allows differentiating the include clauses from regular comments. +Note: The `{{ .File.Get ... }}` directive shall be prefixed by `#!` as the `values.yaml` file is parsed both with and without the included content. When parsed without the included content, it shall still be a valid yaml file, thus mandating the usage of a comment to specify the `{{ .File.Get ... }}` clause that is by default supported by neither yaml nor Helm in default values files of charts. Usage of `#!` (with a bang '!') allows differentiating the include clauses from regular comments. Note also that when Helm is parsing the `values.yaml` file without the included content, some warning may be raised by helm if yaml elements are nil or empty (while they are not with the included content). A typical warning could be: 'Warning: Merging destination map for chart 'my-solution'. The destination item 'bar' is a table and ignoring the source 'bar' as it has a non-table value of: ' Example of `values.yaml`: @@ -97,12 +97,12 @@ micro-service-1: micro-service-2: weight: 1 -#! {{ .File.Get ms2.yaml | .foo | indent 2 }} +#! {{ pick (.File.Get ms2.yaml) foo | indent 2 }} ms3: weight: 2 bar: -#! {{ .File.Get ms3.yaml | .bar.baz | indent 4 }} +#! {{ pick (.File.Get ms3.yaml) bar.baz | indent 4 }} # To prevent from having a warning when the file is processed by Helm, a fake content may be set here. # Format of the added dummy elements fully depends on the application's values structure dummy: diff --git a/main.go b/main.go index 3bddabd..960c021 100644 --- a/main.go +++ b/main.go @@ -220,7 +220,7 @@ func (p *sprayCmd) spray() error { // Write default values to a temporary file and add it to the list of values files, // for later usage during the calls to helm tempDir, err := ioutil.TempDir("", "spray-") - if err != nil { + if err != nil { logErrorAndExit("Error creating temporary directory to write updated default values file for umbrella chart: %s", err) } defer os.RemoveAll(tempDir) @@ -489,34 +489,45 @@ func getMaxWeight(v []Dependency) (m int) { return m } -// Search the "#!include" clauses in the default value file of the chart and replace them by the content +// Search the "include" clauses in the default value file of the chart and replace them by the content // of the corresponding file. -// Possible formats are: -// #! {{ .File.Get myfile.yaml }} -// #! {{ .File.Get myfile.yaml | . }} -// #! {{ .File.Get myfile.yaml | .tag }} -// #! {{ .File.Get myfile.yaml | indent 2 }} -// #! {{ .File.Get myfile.yaml | .tag.subTag | indent 4 }} - -// TODO TO DELETE -// #!include myfile.yaml -// #!include myfile.yaml | indent 2 -// #!include myfile.yaml | .Values.tag -// #!include myfile.yaml | .Values.tag.subTag | indent 4 +// Allows: +// - Includeing a file: +// #! {{ .File.Get myfile.yaml }} +// - Including a sub-part of a file, picking a specific tag. Tags can target a Yaml element (aka table) or a +// leaf value, but tags cannot target a list item. +// #! {{ pick (.File.Get myfile.yaml) tag }} +// - Indenting the include content: +// #! {{ .File.Get myfile.yaml | indent 2 }} +// - All combined...: +// #! {{ pick (.File.Get "myfile.yaml") "tag.subTag" | indent 4 }} // func processIncludeInValuesFile(chart *chartHapi.Chart) string { defaultValues := string(chart.GetValues().GetRaw()) - // Process includes with "| indent" -// includeFileNameExp := regexp.MustCompile(`#!include\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.Values|\.Values\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\n`) - includeFileNameExp := regexp.MustCompile(`#!\s*\{\{\s*\.File\.Get\s+([a-zA-Z0-9_\\\/.\-\(\):]+)\s*(\|\s*(\.|\.([a-zA-Z0-9_\.\-]+)))?\s*(\|\s*indent\s*(\d+))?\s*\}\}\s*\n`) + regularExpressions := []string { + // Expression #0: Process file inclusion ".File.Get" with optional "| indent" + `#!\s*\{\{\s*pick\s*\(\s*\.File\.Get\s+([a-zA-Z0-9_"\\\/.\-\(\):]+)\s*\)\s*([a-zA-Z0-9_"\.\-]+)\s*(\|\s*indent\s*(\d+))?\s*\}\}\s*\n`, + // Expression #1: Process file inclusion ".File.Get", picking a specific element of the file content "pick (.File.Get ) ", with an optional "| indent" + `#!\s*\{\{\s*\.File\.Get\s+([a-zA-Z0-9_"\\\/.\-\(\):]+)\s*(\|\s*indent\s*(\d+))?\s*\}\}\s*\n`} + + expressionNumber := 1 + includeFileNameExp := regexp.MustCompile(regularExpressions[expressionNumber-1]) match := includeFileNameExp.FindStringSubmatch(defaultValues) for ; len(match) != 0; { - fullMatch := match[0] - includeFileName := match[1] - subValuePath := match[4] - indent := match[6] + var fullMatch, includeFileName, subValuePath, indent string + if expressionNumber == 1 { + fullMatch = match[0] + includeFileName = strings.Trim (match[1], `"`) + subValuePath = strings.Trim (match[2], `"`) + indent = match[4] + } else if expressionNumber == 2 { + fullMatch = match[0] + includeFileName = strings.Trim (match[1], `"`) + subValuePath = "" + indent = match[3] + } replaced := false @@ -529,14 +540,23 @@ func processIncludeInValuesFile(chart *chartHapi.Chart) string { logErrorAndExit("Unable to read values from file \"%s\": %s", includeFileName, err) } - subData, err := data.Table(subValuePath) - if err != nil { - logErrorAndExit("Unable to find path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) - } + // Suppose that the element at the path is an element (list items are not supported) + if subData, err := data.Table(subValuePath); err == nil { + if dataToAdd, err = subData.YAML(); err != nil { + logErrorAndExit("Unable to generate a valid YAML file from values at path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) + } - dataToAdd, err = subData.YAML() - if err != nil { - logErrorAndExit("Unable to generate a value YAML file from values at path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) + // If it is not an element, then maybe it is directly a value + } else { + if val, err2 := data.PathValue(subValuePath); err2 == nil { + var ok bool + if dataToAdd, ok = val.(string); ok == false { + logErrorAndExit("Unable to find values matching path \"%s\" in values file \"%s\": %s\n%s", subValuePath, includeFileName, err, "Targeted item is most propably a list: not supported. Only elements (aka Yaml table) and leaf values are supported.") + } + + } else { + logErrorAndExit("Unable to find values matching path \"%s\" in values file \"%s\": %s", subValuePath, includeFileName, err) + } } } @@ -561,6 +581,12 @@ func processIncludeInValuesFile(chart *chartHapi.Chart) string { } match = includeFileNameExp.FindStringSubmatch(defaultValues) + + if len(match) == 0 && expressionNumber < len(regularExpressions) { + expressionNumber++ + includeFileNameExp = regexp.MustCompile(regularExpressions[expressionNumber-1]) + match = includeFileNameExp.FindStringSubmatch(defaultValues) + } } return defaultValues