feat: (6715) Add --environment argument to helm template

Signed-off-by: Oscar Forero <oscar.forero@gatech.edu>
Signed-off-by: Oscar Mauricio Forero Carrillo <oforero@ieee.org>
pull/6717/head
Oscar Forero 6 years ago committed by Oscar Mauricio Forero Carrillo
parent 9668ad4d90
commit 8563f1589f

1
.gitignore vendored

@ -17,3 +17,4 @@ vendor/
.classpath
.project
.settings/**
cmd/helm/debug.test

@ -56,7 +56,7 @@ func TestInspect(t *testing.T) {
expect := []string{
strings.Replace(strings.TrimSpace(string(cdata)), "\r", "", -1),
strings.Replace(strings.TrimSpace(string(data)), "\r", "", -1),
strings.Replace(strings.Split(string(data), "\n")[1], "\r", "", -1),
strings.Replace(strings.TrimSpace(string(readmeData)), "\r", "", -1),
}

@ -360,33 +360,6 @@ func (i *installCmd) run() error {
return write(i.out, &statusWriter{status}, outputFormat(i.output))
}
// Merges source and destination map, preferring values from the source map
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}
// vals merges values from files specified via -f/--values and
// directly via --set or --set-string or --set-file, marshaling them to YAML
func vals(valueFiles valueFiles, values []string, stringValues []string, fileValues []string, CertFile, KeyFile, CAFile string) ([]byte, error) {
@ -412,7 +385,7 @@ func vals(valueFiles valueFiles, values []string, stringValues []string, fileVal
return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
base = chartutil.MergeValues(base, currentMap)
}
// User specified a value via --set

@ -24,6 +24,7 @@ import (
"testing"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
)
@ -290,47 +291,47 @@ func TestNameTemplate(t *testing.T) {
}
func TestMergeValues(t *testing.T) {
nestedMap := map[string]interface{}{
nestedMap := chartutil.Values{
"foo": "bar",
"baz": map[string]string{
"cool": "stuff",
},
}
anotherNestedMap := map[string]interface{}{
anotherNestedMap := chartutil.Values{
"foo": "bar",
"baz": map[string]string{
"cool": "things",
"awesome": "stuff",
},
}
flatMap := map[string]interface{}{
flatMap := chartutil.Values{
"foo": "bar",
"baz": "stuff",
}
anotherFlatMap := map[string]interface{}{
anotherFlatMap := chartutil.Values{
"testing": "fun",
}
testMap := mergeValues(flatMap, nestedMap)
testMap := chartutil.MergeValues(flatMap, nestedMap)
equal := reflect.DeepEqual(testMap, nestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap)
}
testMap = mergeValues(nestedMap, flatMap)
testMap = chartutil.MergeValues(nestedMap, flatMap)
equal = reflect.DeepEqual(testMap, flatMap)
if !equal {
t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap)
}
testMap = mergeValues(nestedMap, anotherNestedMap)
testMap = chartutil.MergeValues(nestedMap, anotherNestedMap)
equal = reflect.DeepEqual(testMap, anotherNestedMap)
if !equal {
t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap)
}
testMap = mergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := map[string]interface{}{
testMap = chartutil.MergeValues(anotherFlatMap, anotherNestedMap)
expectedMap := chartutil.Values{
"testing": "fun",
"foo": "bar",
"baz": map[string]string{

@ -198,7 +198,7 @@ func (l *lintCmd) vals() ([]byte, error) {
return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
}
// Merge with the previous map
base = mergeValues(base, currentMap)
base = chartutil.MergeValues(base, currentMap)
}
// User specified a value via --set

@ -69,6 +69,7 @@ type templateCmd struct {
values []string
stringValues []string
fileValues []string
envValuesFile string
nameTemplate string
showNotes bool
releaseName string
@ -103,6 +104,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
f.StringArrayVar(&t.values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.stringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&t.fileValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.StringVar(&t.envValuesFile, "environment", "", "Use an environment values file inside the chart and the subcharts")
f.StringVar(&t.nameTemplate, "name-template", "", "Specify template used to name the release")
f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "Kubernetes version used as Capabilities.KubeVersion.Major/Minor")
f.StringArrayVarP(&t.apiVersions, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
@ -112,6 +114,7 @@ func newTemplateCmd(out io.Writer) *cobra.Command {
}
func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
println("Running template cmd")
if len(args) < 1 {
return errors.New("chart is required")
}
@ -135,6 +138,7 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
if t.namespace == "" {
t.namespace = defaultNamespace()
}
// get combined values and create config
rawVals, err := vals(t.valueFiles, t.values, t.stringValues, t.fileValues, "", "", "")
if err != nil {
@ -155,7 +159,7 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
}
// Check chart requirements to make sure all dependencies are present in /charts
c, err := chartutil.Load(t.chartPath)
c, err := chartutil.LoadWithEnvValuesFile(t.chartPath, t.envValuesFile)
if err != nil {
return prettyError(err)
}

@ -31,6 +31,9 @@ import (
"regexp"
"strings"
"runtime/debug"
"github.com/ghodss/yaml"
"github.com/golang/protobuf/ptypes/any"
"k8s.io/helm/pkg/ignore"
@ -46,6 +49,17 @@ import (
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func Load(name string) (*chart.Chart, error) {
return LoadWithEnvValuesFile(name, "")
}
// LoadWithEnvValuesFile takes a string name and a file name, tries to resolve it to a file or directory, and then loads it.
//
// This is the preferred way to load a chart. It will discover the chart encoding
// and hand off to the appropriate chart reader.
//
// If a .helmignore file is present, the directory loader will skip loading any files
// matching it. But .helmignore is not evaluated when reading out of an archive.
func LoadWithEnvValuesFile(name string, envValuesFile string) (*chart.Chart, error) {
name = filepath.FromSlash(name)
fi, err := os.Stat(name)
if err != nil {
@ -55,9 +69,9 @@ func Load(name string) (*chart.Chart, error) {
if validChart, err := IsChartDir(name); !validChart {
return nil, err
}
return LoadDir(name)
return LoadDirWithEnvValuesFiles(name, envValuesFile)
}
return LoadFile(name)
return LoadFileWithEnvValuesFile(name, envValuesFile)
}
// BufferedFile represents an archive file buffered for later processing.
@ -153,17 +167,30 @@ func loadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
// LoadArchive loads from a reader containing a compressed tar archive.
func LoadArchive(in io.Reader) (*chart.Chart, error) {
return LoadArchiveWithEnvValuesFile(in, "")
}
// LoadArchiveWithEnvValuesFile loads from a reader containing a compressed tar archive.
func LoadArchiveWithEnvValuesFile(in io.Reader, envValuesFile string) (*chart.Chart, error) {
files, err := loadArchiveFiles(in)
if err != nil {
return nil, err
}
return LoadFiles(files)
return LoadFilesWithEnvValues(files, envValuesFile)
}
// LoadFiles loads from in-memory files.
func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
debug.PrintStack()
return LoadFilesWithEnvValues(files, "None")
}
// LoadFilesWithEnvValues loads from in-memory files and loads an Environment File
func LoadFilesWithEnvValues(files []*BufferedFile, envValuesFile string) (*chart.Chart, error) {
c := &chart.Chart{}
subcharts := map[string][]*BufferedFile{}
values := Values{}
environment := Values{}
for _, f := range files {
if f.Name == "Chart.yaml" {
@ -179,7 +206,9 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
} else if f.Name == "values.toml" {
return c, errors.New("values.toml is illegal as of 2.0.0-alpha.2")
} else if f.Name == "values.yaml" {
c.Values = &chart.Config{Raw: string(f.Data)}
yaml.Unmarshal(f.Data, &values)
} else if f.Name == envValuesFile {
yaml.Unmarshal(f.Data, &environment)
} else if strings.HasPrefix(f.Name, "templates/") {
c.Templates = append(c.Templates, &chart.Template{Name: f.Name, Data: f.Data})
} else if strings.HasPrefix(f.Name, "charts/") {
@ -199,6 +228,15 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data})
}
}
MergeValues(values, environment)
valuesYml, err := values.YAML()
if err == nil {
if len(values) != 0 {
c.Values = &chart.Config{Raw: strings.TrimSpace(valuesYml)}
}
} else {
return c, fmt.Errorf("Unable to marshall values back to yaml")
}
// Ensure that we got a Chart.yaml file
if c.Metadata == nil {
@ -233,7 +271,7 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
f.Name = parts[1]
buff = append(buff, f)
}
sc, err = LoadFiles(buff)
sc, err = LoadFilesWithEnvValues(buff, envValuesFile)
}
if err != nil {
@ -248,6 +286,11 @@ func LoadFiles(files []*BufferedFile) (*chart.Chart, error) {
// LoadFile loads from an archive file.
func LoadFile(name string) (*chart.Chart, error) {
return LoadFileWithEnvValuesFile(name, "")
}
// LoadFileWithEnvValuesFile loads from an archive file.
func LoadFileWithEnvValuesFile(name string, envValuesFile string) (*chart.Chart, error) {
if fi, err := os.Stat(name); err != nil {
return nil, err
} else if fi.IsDir() {
@ -265,7 +308,7 @@ func LoadFile(name string) (*chart.Chart, error) {
return nil, err
}
c, err := LoadArchive(raw)
c, err := LoadArchiveWithEnvValuesFile(raw, envValuesFile)
if err != nil {
if err == gzip.ErrHeader {
return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err)
@ -305,6 +348,13 @@ func ensureArchive(name string, raw *os.File) error {
//
// This loads charts only from directories.
func LoadDir(dir string) (*chart.Chart, error) {
return LoadDirWithEnvValuesFiles(dir, "")
}
// LoadDirWithEnvValuesFiles loads from a directory.
//
// This loads charts only from directories.
func LoadDirWithEnvValuesFiles(dir string, envValueFiles string) (*chart.Chart, error) {
topdir, err := filepath.Abs(dir)
if err != nil {
return nil, err
@ -367,5 +417,5 @@ func LoadDir(dir string) (*chart.Chart, error) {
return c, err
}
return LoadFiles(files)
return LoadFilesWithEnvValues(files, envValueFiles)
}

@ -23,10 +23,12 @@ import (
"os"
"path"
"path/filepath"
"reflect"
"strings"
"testing"
"time"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/proto/hapi/chart"
)
@ -199,8 +201,14 @@ icon: https://example.com/64x64.png
t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Metadata.Name)
}
if c.Values.Raw != defaultValues {
t.Error("Expected chart values to be populated with default values")
values := Values{}
yaml.Unmarshal([]byte(c.Values.Raw), &values)
expectedValues := Values{}
yaml.Unmarshal([]byte(c.Values.Raw), &expectedValues)
equal := reflect.DeepEqual(values, expectedValues)
if !equal {
t.Errorf("Expected chart values to be populated with default values. Expected: %v, got %v", values, expectedValues)
// t.Error("Expected chart values to be populated with default values")
}
if len(c.Templates) != 2 {

@ -42,6 +42,33 @@ const GlobalKey = "global"
// Values represents a collection of chart values.
type Values map[string]interface{}
// MergeValues merges source and destination map, preferring values from the source map
func MergeValues(dest Values, src Values) Values {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(Values)
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(Values)
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = MergeValues(destMap, nextMap)
}
return dest
}
// YAML encodes the Values into a YAML string.
func (v Values) YAML() (string, error) {
b, err := yaml.Marshal(v)

Loading…
Cancel
Save