From e5f6e39ad35624e665bd7f9fa196854c9429ff1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Lambert?= Date: Tue, 1 Sep 2020 17:34:36 +0700 Subject: [PATCH] fix: do not merge and import values from disabled dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the 2nd PR splitted from #6876, and should be merged after #8677 Prior to this PR, values from disabled dependencies would still be merged and imported to the parent values. This didn't have much consequences, but is incompatible with an implemention of values templating. This PR makes the following changes: - `pkg/chartutil/coalesce.go` and `pkg/chartutil/dependencies.go` are no longer recursive, recursivity is handled by `pkg/engine/engine.go` - `chartutils.ToRenderValues` does not merge chart values anymore, this is done by `pkg/engine/engine.go` - `pkg/engine/engine.go` now uses a recursive `Engine.updateRenderValues` function that: - parses and merge `values.yaml` - validates the values along the schema - evaluates if sub-charts are enabled - recursively treat the enabled subcharts - import the values of the enabled subcharts - some `pkg/actions` have been addapted to the new way of merging values Signed-off-by: Aurélien Lambert --- pkg/action/install.go | 4 - pkg/action/package.go | 6 + pkg/action/upgrade.go | 6 +- pkg/chartutil/coalesce.go | 60 ++++ pkg/chartutil/coalesce_test.go | 132 +++++++- pkg/chartutil/dependencies.go | 117 +++---- pkg/chartutil/dependencies_test.go | 89 ++++- pkg/chartutil/values.go | 21 +- pkg/chartutil/values_test.go | 7 +- pkg/engine/engine.go | 74 ++++ pkg/engine/engine_test.go | 318 +++++++++++++++++- pkg/engine/testdata/dependencies/Chart.yaml | 37 ++ .../charts/condition_false/Chart.yaml | 4 + .../charts/condition_false/values.yaml | 1 + .../charts/condition_null/Chart.yaml | 4 + .../charts/condition_null/values.yaml | 1 + .../charts/condition_true/Chart.yaml | 4 + .../charts/condition_true/values.yaml | 1 + .../charts/import_values/Chart.yaml | 4 + .../charts/import_values/values.yaml | 6 + .../dependencies/charts/tags_false/Chart.yaml | 4 + .../charts/tags_false/values.yaml | 1 + .../dependencies/charts/tags_sub/Chart.yaml | 15 + .../tags_sub/charts/tags_sub_false/Chart.yaml | 4 + .../charts/tags_sub_false/values.yaml | 1 + .../tags_sub/charts/tags_sub_true/Chart.yaml | 4 + .../tags_sub/charts/tags_sub_true/values.yaml | 1 + .../dependencies/charts/tags_sub/values.yaml | 1 + .../dependencies/charts/tags_true/Chart.yaml | 4 + .../dependencies/charts/tags_true/values.yaml | 1 + pkg/engine/testdata/dependencies/values.yaml | 7 + 31 files changed, 817 insertions(+), 122 deletions(-) create mode 100644 pkg/engine/testdata/dependencies/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_false/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_false/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_null/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_null/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_true/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/condition_true/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/import_values/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/import_values/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_false/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_false/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_sub/values.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_true/Chart.yaml create mode 100644 pkg/engine/testdata/dependencies/charts/tags_true/values.yaml create mode 100644 pkg/engine/testdata/dependencies/values.yaml diff --git a/pkg/action/install.go b/pkg/action/install.go index 00fb208b0..b9efc243f 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -206,10 +206,6 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release. i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") } - if err := chartutil.ProcessDependencies(chrt, vals); err != nil { - return nil, err - } - // Make sure if Atomic is set, that wait is set as well. This makes it so // the user doesn't have to specify both i.Wait = i.Wait || i.Atomic diff --git a/pkg/action/package.go b/pkg/action/package.go index 0a927cd41..69845d23f 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -60,6 +60,12 @@ func (p *Package) Run(path string, vals map[string]interface{}) (string, error) return "", err } + combinedVals, err := chartutil.CoalesceRoot(ch, vals) + if err != nil { + return "", err + } + ch.Values = combinedVals + // If version is set, modify the version. if p.Version != "" { if err := setVersion(ch, p.Version); err != nil { diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index e7c2aec25..e46844246 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -198,10 +198,6 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, err } - if err := chartutil.ProcessDependencies(chart, vals); err != nil { - return nil, nil, err - } - // Increment revision count. This is passed to templates, and also stored on // the release object. revision := lastRelease.Version + 1 @@ -445,7 +441,7 @@ func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newV u.cfg.Log("reusing the old release's values") // We have to regenerate the old coalesced values: - oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) + oldVals, err := chartutil.CoalesceRoot(current.Chart, current.Config) if err != nil { return nil, errors.Wrap(err, "failed to rebuild old values") } diff --git a/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go index 4ff957ea0..3423693f2 100644 --- a/pkg/chartutil/coalesce.go +++ b/pkg/chartutil/coalesce.go @@ -35,6 +35,8 @@ import ( // - A chart has access to all of the variables for it, as well as all of // the values destined for its dependencies. func CoalesceValues(chrt *chart.Chart, vals map[string]interface{}) (map[string]interface{}, error) { + // create a copy of vals and then pass it to coalesce + // and coalesceDeps, as both will mutate the passed values v, err := copystructure.Copy(vals) if err != nil { return vals, err @@ -186,6 +188,9 @@ func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { // values. for key, val := range src { if dv, ok := dst[key]; ok && dv == nil { + // When the YAML value is null, we remove the value's key. + // This allows Helm's various sources of values (value files or --set) to + // remove incompatible keys from any previous chart, file, or set values. delete(dst, key) } else if !ok { dst[key] = val @@ -201,3 +206,58 @@ func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { } return dst } + +// CoalesceTablesUpdate merges a source map into a destination map. +// +// src is considered authoritative. +func CoalesceTablesUpdate(dst, src map[string]interface{}) map[string]interface{} { + if dst == nil || src == nil { + return dst + } + // src values override dest values. + for key, val := range src { + // We do not remove the null values, to let value templates delete values of sub-charts + if dv, ok := dst[key]; !ok { + } else if istable(val) { + if istable(dv) { + CoalesceTablesUpdate(dv.(map[string]interface{}), + val.(map[string]interface{})) + continue + } else { + log.Printf("warning: overwriting not table with table for %s (%v)", key, dv) + } + } else if istable(dv) { + log.Printf("warning: overwriting table with non table for %s (%v)", key, dv) + } + dst[key] = val + } + return dst +} + +// CoalesceDep returns the render values for subchart, +// merged with subchart values and dest global +func CoalesceDep(subchart *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { + dv, ok := dest[subchart.Name()] + if !ok { + // If dest doesn't already have the key, create it. + dv = map[string]interface{}{} + dest[subchart.Name()] = dv + } else if !istable(dv) { + return dest, errors.Errorf("type mismatch on %s: %t", subchart.Name(), dv) + } + dvmap := dv.(map[string]interface{}) + + // Get globals out of dest and merge them into dvmap. + coalesceGlobals(dvmap, dest) + + // Now coalesce the rest of the values. + coalesceValues(subchart, dvmap) + return dvmap, nil +} + +// CoalesceRoot merges dest with chrt values, +// it returns dest for a similar behavior with CoalesceDep +func CoalesceRoot(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { + coalesceValues(chrt, dest) + return dest, nil +} diff --git a/pkg/chartutil/coalesce_test.go b/pkg/chartutil/coalesce_test.go index 13dbb4f6c..112d07376 100644 --- a/pkg/chartutil/coalesce_test.go +++ b/pkg/chartutil/coalesce_test.go @@ -18,6 +18,7 @@ package chartutil import ( "encoding/json" + "reflect" "testing" "github.com/stretchr/testify/assert" @@ -179,8 +180,9 @@ func TestCoalesceValues(t *testing.T) { is.Equal(valsCopy, vals) } -func TestCoalesceTables(t *testing.T) { - dst := map[string]interface{}{ +// Returns authoritative values +func getMainData() map[string]interface{} { + return map[string]interface{}{ "name": "Ishmael", "address": map[string]interface{}{ "street": "123 Spouter Inn Ct.", @@ -193,7 +195,11 @@ func TestCoalesceTables(t *testing.T) { "boat": "pequod", "hole": nil, } - src := map[string]interface{}{ +} + +// Returns non-authoritative values +func getSecondaryData() map[string]interface{} { + return map[string]interface{}{ "occupation": "whaler", "address": map[string]interface{}{ "state": "MA", @@ -206,11 +212,12 @@ func TestCoalesceTables(t *testing.T) { }, "hole": "black", } +} - // What we expect is that anything in dst overrides anything in src, but that - // otherwise the values are coalesced. - CoalesceTables(dst, src) - +// Tests the coalessing of getMainData() and getSecondaryData() +func testCoalescedData(t *testing.T, dst map[string]interface{}, cleanNil bool) { + // What we expect is that anything in getMainData() overrides anything in + // getSecondaryData(), but that otherwise the values are coalesced. if dst["name"] != "Ishmael" { t.Errorf("Unexpected name: %s", dst["name"]) } @@ -235,8 +242,10 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Unexpected state: %v", addr["state"]) } - if _, ok = addr["country"]; ok { + if n, ok := addr["country"]; cleanNil && ok { t.Error("The country is not left out.") + } else if !cleanNil && (!ok || n != nil) { + t.Error("The country is not nil.") } if det, ok := dst["details"].(map[string]interface{}); !ok { @@ -245,14 +254,103 @@ func TestCoalesceTables(t *testing.T) { t.Error("Could not find your friends. Maybe you don't have any. :-(") } - if dst["boat"].(string) != "pequod" { + if bo, ok := dst["boat"].(string); !ok { + t.Fatalf("boat is the wrong type: %v", dst["boat"]) + } else if bo != "pequod" { t.Errorf("Expected boat string, got %v", dst["boat"]) } - if _, ok = dst["hole"]; ok { + if n, ok := dst["hole"]; cleanNil && ok { t.Error("The hole still exists.") + } else if !cleanNil && (!ok || n != nil) { + t.Error("The hole is not nil.") + } +} + +func TestCoalesceTables(t *testing.T) { + dst := getMainData() + src := getSecondaryData() + + CoalesceTables(dst, src) + + testCoalescedData(t, dst, true) +} + +func TestCoalesceTablesUpdate(t *testing.T) { + src := getMainData() + dst := getSecondaryData() + + CoalesceTablesUpdate(dst, src) + + testCoalescedData(t, dst, false) +} + +func TestCoalesceDep(t *testing.T) { + src := map[string]interface{}{ + // global object should be transferred to subchart + "global": map[string]interface{}{ + "IP": "192.168.0.1", + "port": 8080, + }, + // subchart object should be coallesced with chart values and returned + "subchart": getMainData(), + // any other field should be ignored + "other": map[string]interface{}{ + "type": "car", + }, + } + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Values: getSecondaryData(), + } + subchart.Values["global"] = map[string]interface{}{ + "port": 80, + "service": "users", } + dst, err := CoalesceDep(subchart, src) + if err != nil { + t.Fatal(err) + } + if d, ok := src["subchart"]; !ok { + t.Fatal("subchart went away.") + } else if dm, ok := d.(map[string]interface{}); !ok { + t.Fatalf("subchart has now wrong type: %t", d) + } else if reflect.ValueOf(dst).Pointer() != reflect.ValueOf(dm).Pointer() { + t.Error("CoalesceDep must return subchart map.") + } + + testCoalescedData(t, dst, true) + + glob, ok := dst["global"].(map[string]interface{}) + if !ok { + t.Fatal("global went away.") + } + + if glob["IP"].(string) != "192.168.0.1" { + t.Errorf("Unexpected IP: %v", glob["IP"]) + } + + if glob["port"].(int) != 8080 { + t.Errorf("Unexpected port: %v", glob["port"]) + } + + if glob["service"].(string) != "users" { + t.Errorf("Unexpected service: %v", glob["service"]) + } + + if _, ok := dst["other"]; ok { + t.Error("Unexpected field other.") + } + + if _, ok := dst["type"]; ok { + t.Error("Unexpected field type.") + } +} + +func TestCoalesceNil(t *testing.T) { dst2 := map[string]interface{}{ "name": "Ishmael", "address": map[string]interface{}{ @@ -306,3 +404,17 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Expected hole string, got %v", dst2["boat"]) } } + +func TestCoalesceRoot(t *testing.T) { + dst := getMainData() + chart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "root", + }, + Values: getSecondaryData(), + } + + CoalesceRoot(chart, dst) + + testCoalescedData(t, dst, true) +} diff --git a/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go index 6fae607f5..c97657f87 100644 --- a/pkg/chartutil/dependencies.go +++ b/pkg/chartutil/dependencies.go @@ -22,16 +22,8 @@ import ( "helm.sh/helm/v3/pkg/chart" ) -// ProcessDependencies checks through this chart's dependencies, processing accordingly. -func ProcessDependencies(c *chart.Chart, v Values) error { - if err := processDependencyEnabled(c, v, ""); err != nil { - return err - } - return processDependencyImportValues(c) -} - // processDependencyConditions disables charts based on condition path value in values -func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) { +func processDependencyConditions(reqs []*chart.Dependency, cvals Values) { if reqs == nil { return } @@ -39,7 +31,7 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath s for _, c := range strings.Split(strings.TrimSpace(r.Condition), ",") { if len(c) > 0 { // retrieve value - vv, err := cvals.PathValue(cpath + c) + vv, err := cvals.PathValue(c) if err == nil { // if not bool, warn if bv, ok := vv.(bool); ok { @@ -58,18 +50,14 @@ func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath s } // processDependencyTags disables charts based on tags in values -func processDependencyTags(reqs []*chart.Dependency, cvals Values) { - if reqs == nil { - return - } - vt, err := cvals.Table("tags") - if err != nil { +func processDependencyTags(reqs []*chart.Dependency, tags map[string]interface{}) { + if reqs == nil || tags == nil { return } for _, r := range reqs { var hasTrue, hasFalse bool for _, k := range r.Tags { - if b, ok := vt[k]; ok { + if b, ok := tags[k]; ok { // if not bool, warn if bv, ok := b.(bool); ok { if bv { @@ -90,6 +78,14 @@ func processDependencyTags(reqs []*chart.Dependency, cvals Values) { } } +func GetTags(cvals Values) map[string]interface{} { + vt, err := cvals.Table("tags") + if err != nil { + return nil + } + return vt +} + func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart { for _, c := range charts { if c == nil { @@ -114,8 +110,8 @@ func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Cha return nil } -// processDependencyEnabled removes disabled charts from dependencies -func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { +// ProcessDependencyEnabled removes disabled charts from dependencies +func ProcessDependencyEnabled(c *chart.Chart, v map[string]interface{}, tags map[string]interface{}) error { if c.Metadata.Dependencies == nil { return nil } @@ -150,13 +146,9 @@ Loop: for _, lr := range c.Metadata.Dependencies { lr.Enabled = true } - cvals, err := CoalesceValues(c, v) - if err != nil { - return err - } // flag dependencies as enabled/disabled - processDependencyTags(c.Metadata.Dependencies, cvals) - processDependencyConditions(c.Metadata.Dependencies, cvals, path) + processDependencyTags(c.Metadata.Dependencies, tags) + processDependencyConditions(c.Metadata.Dependencies, v) // make a map of charts to remove rm := map[string]struct{}{} for _, r := range c.Metadata.Dependencies { @@ -181,14 +173,6 @@ Loop: cdMetadata = append(cdMetadata, n) } } - - // recursively call self to process sub dependencies - for _, t := range cd { - subpath := path + t.Metadata.Name + "." - if err := processDependencyEnabled(t, cvals, subpath); err != nil { - return err - } - } // set the correct dependencies in metadata c.Metadata.Dependencies = nil c.Metadata.Dependencies = append(c.Metadata.Dependencies, cdMetadata...) @@ -217,70 +201,47 @@ func set(path []string, data map[string]interface{}) map[string]interface{} { } // processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. -func processImportValues(c *chart.Chart) error { +func processImportValues(c *chart.Chart, cvals Values) error { if c.Metadata.Dependencies == nil { return nil } - // combine chart values and empty config to get Values - var cvals Values - cvals, err := CoalesceValues(c, nil) - if err != nil { - return err - } - b := make(map[string]interface{}) + // import values from each dependency if specified in import-values for _, r := range c.Metadata.Dependencies { - var outiv []interface{} for _, riv := range r.ImportValues { + var child, parent string switch iv := riv.(type) { case map[string]interface{}: - child := iv["child"].(string) - parent := iv["parent"].(string) - - outiv = append(outiv, map[string]string{ - "child": child, - "parent": parent, - }) - - // get child table - vv, err := cvals.Table(r.Name + "." + child) - if err != nil { - log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err) - continue - } - // create value map from child to be merged into parent - b = CoalesceTables(cvals, pathToMap(parent, vv)) + child = iv["child"].(string) + parent = iv["parent"].(string) case string: - child := "exports." + iv - outiv = append(outiv, map[string]string{ - "child": child, - "parent": ".", - }) - vm, err := cvals.Table(r.Name + "." + child) - if err != nil { - log.Printf("Warning: ImportValues missing table: %v", err) - continue - } - b = CoalesceTables(b, vm) + child = "exports." + iv + parent = "." + } + // get child table + vv, err := cvals.Table(r.Name + "." + child) + if err != nil { + log.Printf("Warning: ImportValues missing table %s from chart %s: %v", child, r.Name, err) + continue } + // create value map from child to be merged into parent + CoalesceTables(cvals, pathToMap(parent, vv)) } - // set our formatted import values - r.ImportValues = outiv } - // set the new values - c.Values = CoalesceTables(b, cvals) - return nil } -// processDependencyImportValues imports specified chart values from child to parent. -func processDependencyImportValues(c *chart.Chart) error { +// ProcessDependencyImportValues imports specified chart values from child to parent. +// +// v is expected to have existing path for every sub chart +func ProcessDependencyImportValues(c *chart.Chart, v map[string]interface{}) error { for _, d := range c.Dependencies() { // recurse - if err := processDependencyImportValues(d); err != nil { + dv := v[d.Name()].(map[string]interface{}) + if err := ProcessDependencyImportValues(d, dv); err != nil { return err } } - return processImportValues(c) + return processImportValues(c, v) } diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go index 342d7fe87..9707c2818 100644 --- a/pkg/chartutil/dependencies_test.go +++ b/pkg/chartutil/dependencies_test.go @@ -17,6 +17,7 @@ package chartutil import ( "os" "path/filepath" + "reflect" "sort" "strconv" "testing" @@ -61,6 +62,35 @@ func TestLoadDependency(t *testing.T) { check(c.Lock.Dependencies) } +// recProcessDependencyEnabled is mostly a simplified version of +// Engine.recUpdateRenderValues, for testing only dependencies +func recProcessDependencyEnabled(c *chart.Chart, v map[string]interface{}, tags map[string]interface{}) error { + // get the local values + var err error + if c.IsRoot() { + v, err = CoalesceRoot(c, v) + tags = GetTags(v) + } else { + v, err = CoalesceDep(c, v) + } + if err != nil { + return err + } + // Remove all disabled dependencies + err = ProcessDependencyEnabled(c, v, tags) + if err != nil { + return err + } + // Recursive upudate on enabled dependencies + for _, child := range c.Dependencies() { + err = recProcessDependencyEnabled(child, v, tags) + if err != nil { + return err + } + } + return nil +} + func TestDependencyEnabled(t *testing.T) { type M = map[string]interface{} tests := []struct { @@ -116,7 +146,7 @@ func TestDependencyEnabled(t *testing.T) { for _, tc := range tests { c := loadChart(t, "testdata/subpop") t.Run(tc.name, func(t *testing.T) { - if err := processDependencyEnabled(c, tc.v, ""); err != nil { + if err := recProcessDependencyEnabled(c, tc.v, nil); err != nil { t.Fatalf("error processing enabled dependencies %v", err) } @@ -212,12 +242,16 @@ func TestProcessDependencyImportValues(t *testing.T) { e["SCBexported2A"] = "blaster" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" - if err := processDependencyImportValues(c); err != nil { + var cvals Values + cvals, err := CoalesceValues(c, nil) + if err != nil { + t.Fatalf("coalescing values %v", err) + } + if err := ProcessDependencyImportValues(c, cvals); err != nil { t.Fatalf("processing import values dependencies %v", err) } - cc := Values(c.Values) for kk, vv := range e { - pv, err := cc.PathValue(kk) + pv, err := cvals.PathValue(kk) if err != nil { t.Fatalf("retrieving import values table %v %v", kk, err) } @@ -243,7 +277,12 @@ func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") nameOverride := "parent-chart-prod" - if err := processDependencyImportValues(c); err != nil { + cvals, err := CoalesceValues(c, nil) + if err != nil { + t.Fatalf("coalescing values %v", err) + } + + if err := ProcessDependencyImportValues(c, cvals); err != nil { t.Fatalf("processing import values dependencies %v", err) } @@ -251,7 +290,7 @@ func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) } - if err := processDependencyEnabled(c, c.Values, ""); err != nil { + if err := recProcessDependencyEnabled(c, c.Values, nil); err != nil { t.Fatalf("expected no errors but got %q", err) } @@ -315,7 +354,7 @@ func TestDependentChartAliases(t *testing.T) { t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) } - if err := processDependencyEnabled(c, c.Values, ""); err != nil { + if err := ProcessDependencyEnabled(c, c.Values, GetTags(c.Values)); err != nil { t.Fatalf("expected no errors but got %q", err) } @@ -336,7 +375,7 @@ func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) } - if err := processDependencyEnabled(c, c.Values, ""); err != nil { + if err := ProcessDependencyEnabled(c, c.Values, GetTags(c.Values)); err != nil { t.Fatalf("expected no errors but got %q", err) } @@ -373,7 +412,7 @@ func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) } - if err := processDependencyEnabled(c, c.Values, ""); err != nil { + if err := ProcessDependencyEnabled(c, c.Values, GetTags(c.Values)); err != nil { t.Fatalf("expected no errors but got %q", err) } @@ -393,7 +432,7 @@ func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) } - if err := processDependencyEnabled(c, c.Values, ""); err != nil { + if err := ProcessDependencyEnabled(c, c.Values, GetTags(c.Values)); err != nil { t.Fatalf("expected no errors but got %q", err) } @@ -405,3 +444,33 @@ func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) } } + +func TestGetTags(t *testing.T) { + type M = map[string]interface{} + tests := []struct { + name string + vals M + tags M + }{{ + "normal tags", + M{"tags": M{"a": true, "b": false}}, + M{"a": true, "b": false}, + }, { + "not an object tags", + M{"tags": []interface{}{"a", "b"}}, + nil, + }, { + "no tags", + M{"no_tags": M{"a": true, "b": false}}, + nil, + }} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tags := GetTags(tt.vals) + if !reflect.DeepEqual(tags, tt.tags) { + t.Fatalf("tags map do not match got %v, expected %v", tags, tt.tags) + } + }) + } +} diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 4e618e4ca..893648f80 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -17,11 +17,11 @@ limitations under the License. package chartutil import ( - "fmt" "io" "io/ioutil" "strings" + "github.com/mitchellh/copystructure" "github.com/pkg/errors" "sigs.k8s.io/yaml" @@ -134,6 +134,7 @@ func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options top := map[string]interface{}{ "Chart": chrt.Metadata, "Capabilities": caps, + "Values": nil, "Release": map[string]interface{}{ "Name": options.Name, "Namespace": options.Namespace, @@ -144,17 +145,17 @@ func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options }, } - vals, err := CoalesceValues(chrt, chrtVals) - if err != nil { - return top, err - } - - if err := ValidateAgainstSchema(chrt, vals); err != nil { - errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" - return top, fmt.Errorf(errFmt, err.Error()) + // if we have an empty map, make sure it is initialized + if chrtVals == nil { + top["Values"] = map[string]interface{}{} + } else { + vals, err := copystructure.Copy(chrtVals) + if err != nil { + return top, err + } + top["Values"] = vals.(map[string]interface{}) } - top["Values"] = vals return top, nil } diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go index 5338f42be..237847503 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chartutil/values_test.go @@ -141,9 +141,10 @@ func TestToRenderValues(t *testing.T) { } where := vals["where"].(map[string]interface{}) expects := map[string]string{ - "city": "Baghdad", - "date": "809 CE", - "title": "caliph", + "city": "Baghdad", + "date": "809 CE", + // ToRenderValues no longer coallesce chart values + // "title": "caliph", } for field, expect := range expects { if got := where[field]; got != expect { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index a78e7eafb..cacd4b136 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -64,6 +64,11 @@ type Engine struct { // section contains a value named "bar", that value will be passed on to the // bar chart during render time. func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { + // update values and dependencies + if err := e.updateRenderValues(chrt, values); err != nil { + return nil, err + } + // parse templates with the updated values tmap := allTemplates(chrt, values) return e.render(tmap) } @@ -297,6 +302,75 @@ func cleanupExecError(filename string, err error) error { return err } +// updateRenderValues update render values with chart values. +func (e Engine) updateRenderValues(c *chart.Chart, vals chartutil.Values) error { + var sb strings.Builder + // update values and dependencies + if err := e.recUpdateRenderValues(c, vals, nil, &sb); err != nil { + return err + } + // Check for values validation errors + if sb.Len() > 0 { + errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" + return fmt.Errorf(errFmt, sb.String()) + } + // import values from dependenvies + if err := chartutil.ProcessDependencyImportValues(c, vals["Values"].(map[string]interface{})); err != nil { + return err + } + + return nil +} + +func (e Engine) recUpdateRenderValues(c *chart.Chart, vals chartutil.Values, tags map[string]interface{}, sb *strings.Builder) error { + next := map[string]interface{}{ + "Chart": c.Metadata, + "Files": newFiles(c.Files), + "Release": vals["Release"], + "Capabilities": vals["Capabilities"], + "Values": nil, + } + + // If there is a {{.Values.ThisChart}} in the parent metadata, + // copy that into the {{.Values}} for this template. + var nvals map[string]interface{} + var err error + if c.IsRoot() { + nvals, err = chartutil.CoalesceRoot(c, vals["Values"].(map[string]interface{})) + } else { + nvals, err = chartutil.CoalesceDep(c, vals["Values"].(map[string]interface{})) + } + if err != nil { + return err + } + next["Values"] = nvals + // Get validations errors of chart values + if c.Schema != nil { + err = chartutil.ValidateAgainstSingleSchema(nvals, c.Schema) + if err != nil { + sb.WriteString(fmt.Sprintf("%s:\n", c.Name())) + sb.WriteString(err.Error()) + } + } + // Get tags of the root + if c.IsRoot() { + tags = chartutil.GetTags(nvals) + } + // Remove all disabled dependencies + err = chartutil.ProcessDependencyEnabled(c, nvals, tags) + if err != nil { + return err + } + // Recursive upudate on enabled dependencies + for _, child := range c.Dependencies() { + err = e.recUpdateRenderValues(child, next, tags, sb) + if err != nil { + return err + } + } + return nil +} + func sortTemplates(tpls map[string]renderable) []string { keys := make([]string, len(tpls)) i := 0 diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go index 1bb9aa4b2..745cf256f 100644 --- a/pkg/engine/engine_test.go +++ b/pkg/engine/engine_test.go @@ -18,11 +18,13 @@ package engine import ( "fmt" + "sort" "strings" "sync" "testing" "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" ) @@ -153,7 +155,9 @@ func TestRenderRefsOrdering(t *testing.T) { } for i := 0; i < 100; i++ { - out, err := Render(parentChart, chartutil.Values{}) + out, err := Render(parentChart, chartutil.Values{ + "Values": map[string]interface{}{}, + }) if err != nil { t.Fatalf("Failed to render templates: %s", err) } @@ -333,7 +337,9 @@ func TestRenderDependency(t *testing.T) { }, }) - out, err := Render(ch, map[string]interface{}{}) + out, err := Render(ch, map[string]interface{}{ + "Values": map[string]interface{}{}, + }) if err != nil { t.Fatalf("failed to render chart: %s", err) } @@ -738,3 +744,311 @@ func TestRenderRecursionLimit(t *testing.T) { } } + +func TestUpdateRenderValues_dependencies(t *testing.T) { + values := map[string]interface{}{} + rv := map[string]interface{}{ + "Release": map[string]interface{}{ + "Name": "Test Name", + }, + "Values": values, + } + c := loadChart(t, "testdata/dependencies") + + if err := new(Engine).updateRenderValues(c, rv); err != nil { + t.Fatal(err) + } + // check for conditions + if vm, ok := values["condition_true"]; !ok { + t.Errorf("chart 'condition_true' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'condition_true' not evaluated") + } + } + if _, ok := values["condition_false"]; ok { + t.Errorf("chart 'condition_false' evaluated") + } + if vm, ok := values["condition_null"]; !ok { + t.Errorf("chart 'condition_null' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'condition_null' not evaluated") + } + } + // check for tags + if vm, ok := values["tags_true"]; !ok { + t.Errorf("chart 'tags_true' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'tags_true' not evaluated") + } + } + if _, ok := values["tags_false"]; ok { + t.Errorf("chart 'tags_false' evaluated") + } + // check for sub tags + if vm, ok := values["tags_sub"]; !ok { + t.Errorf("chart 'tags_sub' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'tags_sub' not evaluated") + } + if vm, ok := m["tags_sub_true"]; !ok { + t.Errorf("chart 'tags_sub/tags_sub_true' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'tags_sub/tags_sub_true' not evaluated") + } + } + if _, ok := m["tags_sub/tags_sub_false"]; ok { + t.Errorf("chart 'tags_sub/tags_sub_false' evaluated") + } + } + // check for import-values + if vm, ok := values["import_values"]; !ok { + t.Errorf("chart 'import_values' not evaluated") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["evaluated"]; !ok || !v.(bool) { + t.Errorf("chart 'import_values' not evaluated") + } + } + if vm, ok := values["importValues"]; !ok { + t.Errorf("value 'importValues' not imported") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["imported"]; !ok || !v.(bool) { + t.Errorf("value 'importValues.imported' not imported") + } + } + if vm, ok := values["subImport"]; !ok { + t.Errorf("value 'subImport' not imported") + } else { + m := vm.(map[string]interface{}) + if v, ok := m["old"]; !ok { + t.Errorf("value 'subImport.old' not imported") + } else if vs, ok := v.(string); !ok || vs != "values.yaml" { + t.Errorf("wrong 'subImport.old' imported: %v", v) + } + } + + names := extractChartNames(c) + except := []string{ + "parentchart", + "parentchart.condition_null", + "parentchart.condition_true", + "parentchart.import_values", + "parentchart.tags_sub", + "parentchart.tags_sub.tags_sub_true", + "parentchart.tags_true", + } + if len(names) != len(except) { + t.Errorf("dependencies values do not match got %v, expected %v", names, except) + } else { + for i := range names { + if names[i] != except[i] { + t.Errorf("dependencies values do not match got %v, expected %v", names, except) + break + } + } + } +} + +// copied from chartutil/values_test.go:TestToRenderValues +// because ToRenderValues no longer coalesces chart values +func TestUpdateRenderValues_ToRenderValues(t *testing.T) { + + chartValues := map[string]interface{}{ + "name": "al Rashid", + "where": map[string]interface{}{ + "city": "Basrah", + "title": "caliph", + }, + } + + overideValues := map[string]interface{}{ + "name": "Haroun", + "where": map[string]interface{}{ + "city": "Baghdad", + "date": "809 CE", + "title": "caliph", + }, + } + + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "test"}, + Templates: []*chart.File{}, + Values: chartValues, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + } + c.AddDependency(&chart.Chart{ + Metadata: &chart.Metadata{Name: "where"}, + }) + + o := chartutil.ReleaseOptions{ + Name: "Seven Voyages", + Namespace: "default", + Revision: 1, + IsInstall: true, + } + + res, err := chartutil.ToRenderValues(c, overideValues, o, nil) + if err != nil { + t.Fatal(err) + } + if err = new(Engine).updateRenderValues(c, res); err != nil { + t.Fatal(err) + } + + // Ensure that the top-level values are all set. + if name := res["Chart"].(*chart.Metadata).Name; name != "test" { + t.Errorf("Expected chart name 'test', got %q", name) + } + relmap := res["Release"].(map[string]interface{}) + if name := relmap["Name"]; name.(string) != "Seven Voyages" { + t.Errorf("Expected release name 'Seven Voyages', got %q", name) + } + if namespace := relmap["Namespace"]; namespace.(string) != "default" { + t.Errorf("Expected namespace 'default', got %q", namespace) + } + if revision := relmap["Revision"]; revision.(int) != 1 { + t.Errorf("Expected revision '1', got %d", revision) + } + if relmap["IsUpgrade"].(bool) { + t.Error("Expected upgrade to be false.") + } + if !relmap["IsInstall"].(bool) { + t.Errorf("Expected install to be true.") + } + if !res["Capabilities"].(*chartutil.Capabilities).APIVersions.Has("v1") { + t.Error("Expected Capabilities to have v1 as an API") + } + if res["Capabilities"].(*chartutil.Capabilities).KubeVersion.Major != "1" { + t.Error("Expected Capabilities to have a Kube version") + } + + vals := res["Values"].(map[string]interface{}) + if vals["name"] != "Haroun" { + t.Errorf("Expected 'Haroun', got %q (%v)", vals["name"], vals) + } + where := vals["where"].(map[string]interface{}) + expects := map[string]string{ + "city": "Baghdad", + "date": "809 CE", + "title": "caliph", + } + for field, expect := range expects { + if got := where[field]; got != expect { + t.Errorf("Expected %q, got %q (%v)", expect, got, where) + } + } +} + +// copied from chartutil/dependencies_test.go:TestDependencyEnabled +// because ProcessDependencyEnabled is no longer recursive +func TestUpdateRenderValues_TestDependencyEnabled(t *testing.T) { + type M = map[string]interface{} + tests := []struct { + name string + v M + e []string // expected charts including duplicates in alphanumeric order + }{{ + "tags with no effect", + M{"tags": M{"nothinguseful": false}}, + []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"}, + }, { + "tags disabling a group", + M{"tags": M{"front-end": false}}, + []string{"parentchart"}, + }, { + "tags disabling a group and enabling a different group", + M{"tags": M{"front-end": false, "back-end": true}}, + []string{"parentchart", "parentchart.subchart2", "parentchart.subchart2.subchartb", "parentchart.subchart2.subchartc"}, + }, { + "tags disabling only children, children still enabled since tag front-end=true in values.yaml", + M{"tags": M{"subcharta": false, "subchartb": false}}, + []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"}, + }, { + "tags disabling all parents/children with additional tag re-enabling a parent", + M{"tags": M{"front-end": false, "subchart1": true, "back-end": false}}, + []string{"parentchart", "parentchart.subchart1"}, + }, { + "conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml", + M{"subchart1": M{"enabled": true}, "subchart2": M{"enabled": true}}, + []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2"}, + }, { + "conditions disabling the parent charts, effectively disabling children", + M{"subchart1": M{"enabled": false}, "subchart2": M{"enabled": false}}, + []string{"parentchart"}, + }, { + "conditions a child using the second condition path of child's condition", + M{"subchart1": M{"subcharta": M{"enabled": false}}}, + []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subchartb"}, + }, { + "tags enabling a parent/child group with condition disabling one child", + M{"subchart2": M{"subchartc": M{"enabled": false}}, "tags": M{"back-end": true}}, + []string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2", "parentchart.subchart2.subchartb"}, + }, { + "tags will not enable a child if parent is explicitly disabled with condition", + M{"subchart1": M{"enabled": false}, "tags": M{"front-end": true}}, + []string{"parentchart"}, + }, { + "subcharts with alias also respect conditions", + M{"subchart1": M{"enabled": false}, "subchart2alias": M{"enabled": true, "subchartb": M{"enabled": true}}}, + []string{"parentchart", "parentchart.subchart2alias", "parentchart.subchart2alias.subchartb"}, + }} + + for _, tc := range tests { + c := loadChart(t, "../chartutil/testdata/subpop") + vals := map[string]interface{}{"Values": tc.v} + t.Run(tc.name, func(t *testing.T) { + if err := new(Engine).updateRenderValues(c, vals); err != nil { + t.Fatalf("error processing enabled dependencies %v", err) + } + + names := extractChartNames(c) + if len(names) != len(tc.e) { + t.Fatalf("slice lengths do not match got %v, expected %v", len(names), len(tc.e)) + } + for i := range names { + if names[i] != tc.e[i] { + t.Fatalf("slice values do not match got %v, expected %v", names, tc.e) + } + } + }) + } +} + +// copied from chartutil/dependencies_test.go:loadChart +func loadChart(t *testing.T, path string) *chart.Chart { + t.Helper() + c, err := loader.Load(path) + if err != nil { + t.Fatalf("failed to load testdata: %s", err) + } + return c +} + +// copied from chartutil/dependencies_test.go:extractChartNames +// extractCharts recursively searches chart dependencies returning all charts found +func extractChartNames(c *chart.Chart) []string { + var out []string + var fn func(c *chart.Chart) + fn = func(c *chart.Chart) { + out = append(out, c.ChartPath()) + for _, d := range c.Dependencies() { + fn(d) + } + } + fn(c) + sort.Strings(out) + return out +} diff --git a/pkg/engine/testdata/dependencies/Chart.yaml b/pkg/engine/testdata/dependencies/Chart.yaml new file mode 100644 index 000000000..135783344 --- /dev/null +++ b/pkg/engine/testdata/dependencies/Chart.yaml @@ -0,0 +1,37 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: parentchart +version: 0.1.0 +dependencies: +- name: condition_true + repository: http://localhost:10191 + version: 0.1.0 + condition: condition.true +- name: condition_false + repository: http://localhost:10191 + version: 0.1.0 + condition: condition.false +- name: condition_null + repository: http://localhost:10191 + version: 0.1.0 + condition: condition.null +- name: tags_true + repository: http://localhost:10191 + version: 0.1.0 + tags: + - true_tag +- name: tags_false + repository: http://localhost:10191 + version: 0.1.0 + tags: + - false_tag +- name: import_values + repository: http://localhost:10191 + version: 0.1.0 + import-values: + - child: importValues + parent: importValues + - child: importTemplate + parent: importTemplate + - child: import + parent: subImport diff --git a/pkg/engine/testdata/dependencies/charts/condition_false/Chart.yaml b/pkg/engine/testdata/dependencies/charts/condition_false/Chart.yaml new file mode 100644 index 000000000..97d99b10f --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_false/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: condition_false +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/condition_false/values.yaml b/pkg/engine/testdata/dependencies/charts/condition_false/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_false/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/condition_null/Chart.yaml b/pkg/engine/testdata/dependencies/charts/condition_null/Chart.yaml new file mode 100644 index 000000000..1ee319d98 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_null/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: condition_null +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/condition_null/values.yaml b/pkg/engine/testdata/dependencies/charts/condition_null/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_null/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/condition_true/Chart.yaml b/pkg/engine/testdata/dependencies/charts/condition_true/Chart.yaml new file mode 100644 index 000000000..056d53ceb --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_true/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: condition_true +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/condition_true/values.yaml b/pkg/engine/testdata/dependencies/charts/condition_true/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/condition_true/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/import_values/Chart.yaml b/pkg/engine/testdata/dependencies/charts/import_values/Chart.yaml new file mode 100644 index 000000000..0dd82bdea --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/import_values/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: import_values +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/import_values/values.yaml b/pkg/engine/testdata/dependencies/charts/import_values/values.yaml new file mode 100644 index 000000000..043397fc1 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/import_values/values.yaml @@ -0,0 +1,6 @@ +evaluated: true +import: + old: "values.yaml" + common: "values.yaml" +importValues: + imported: true diff --git a/pkg/engine/testdata/dependencies/charts/tags_false/Chart.yaml b/pkg/engine/testdata/dependencies/charts/tags_false/Chart.yaml new file mode 100644 index 000000000..c2a160441 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_false/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: tags_false +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/tags_false/values.yaml b/pkg/engine/testdata/dependencies/charts/tags_false/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_false/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/Chart.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/Chart.yaml new file mode 100644 index 000000000..bda28393d --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/Chart.yaml @@ -0,0 +1,15 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: tags_sub +version: 0.1.0 +dependencies: +- name: tags_sub_true + repository: http://localhost:10191 + version: 0.1.0 + tags: + - true_tag +- name: tags_sub_false + repository: http://localhost:10191 + version: 0.1.0 + tags: + - false_tag diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/Chart.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/Chart.yaml new file mode 100644 index 000000000..cb6ff80d4 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: tags_sub_false +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/values.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_false/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/Chart.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/Chart.yaml new file mode 100644 index 000000000..02dc90e37 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: tags_sub_true +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/values.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/charts/tags_sub_true/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/tags_sub/values.yaml b/pkg/engine/testdata/dependencies/charts/tags_sub/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_sub/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/charts/tags_true/Chart.yaml b/pkg/engine/testdata/dependencies/charts/tags_true/Chart.yaml new file mode 100644 index 000000000..fe64c0b2a --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_true/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +name: tags_true +version: 0.1.0 diff --git a/pkg/engine/testdata/dependencies/charts/tags_true/values.yaml b/pkg/engine/testdata/dependencies/charts/tags_true/values.yaml new file mode 100644 index 000000000..b1b9f96a5 --- /dev/null +++ b/pkg/engine/testdata/dependencies/charts/tags_true/values.yaml @@ -0,0 +1 @@ +evaluated: true diff --git a/pkg/engine/testdata/dependencies/values.yaml b/pkg/engine/testdata/dependencies/values.yaml new file mode 100644 index 000000000..9148fc233 --- /dev/null +++ b/pkg/engine/testdata/dependencies/values.yaml @@ -0,0 +1,7 @@ +condition: + "true": true + "false": false + "null": null +tags: + true_tag: true + false_tag: false