/* Copyright The Helm Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package chartutil import ( "os" "path/filepath" "sort" "testing" "strconv" "k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/version" ) func TestLoadRequirements(t *testing.T) { c, err := Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } verifyRequirements(t, c) } func TestLoadRequirementsLock(t *testing.T) { c, err := Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } verifyRequirementsLock(t, c) } func TestRequirementsTagsNonValue(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags with no effect v := &chart.Config{Raw: "tags:\n nothinguseful: false\n\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsTagsDisabledL1(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags disabling a group v := &chart.Config{Raw: "tags:\n front-end: false\n\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsTagsEnabledL1(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags disabling a group and enabling a different group v := &chart.Config{Raw: "tags:\n front-end: false\n\n back-end: true\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart2", "subchartb", "subchartc"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsTagsDisabledL2(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags disabling only children, children still enabled since tag front-end=true in values.yaml v := &chart.Config{Raw: "tags:\n subcharta: false\n\n subchartb: false\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsTagsDisabledL1Mixed(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags disabling all parents/children with additional tag re-enabling a parent v := &chart.Config{Raw: "tags:\n front-end: false\n\n subchart1: true\n\n back-end: false\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsConditionsNonValue(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags with no effect v := &chart.Config{Raw: "subchart1:\n nothinguseful: false\n\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsConditionsEnabledL1Both(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml v := &chart.Config{Raw: "subchart1:\n enabled: true\nsubchart2:\n enabled: true\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsConditionsDisabledL1Both(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // conditions disabling the parent charts, effectively disabling children v := &chart.Config{Raw: "subchart1:\n enabled: false\nsubchart2:\n enabled: false\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsConditionsSecond(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // conditions a child using the second condition path of child's condition v := &chart.Config{Raw: "subchart1:\n subcharta:\n enabled: false\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsCombinedDisabledL2(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags enabling a parent/child group with condition disabling one child v := &chart.Config{Raw: "subchart2:\n subchartc:\n enabled: false\ntags:\n back-end: true\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsCombinedDisabledL1(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } // tags will not enable a child if parent is explicitly disabled with condition v := &chart.Config{Raw: "subchart1:\n enabled: false\ntags:\n front-end: true\n"} // expected charts including duplicates in alphanumeric order e := []string{"parentchart"} verifyRequirementsEnabled(t, c, v, e) } func TestRequirementsAliasCondition(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } v := &chart.Config{Raw: "subchart1:\n enabled: false\nsubchart2alias:\n enabled: true\n subchartb:\n enabled: true\n"} e := []string{"parentchart", "subchart2alias", "subchartb"} verifyRequirementsEnabled(t, c, v, e) } func verifyRequirementsEnabled(t *testing.T, c *chart.Chart, v *chart.Config, e []string) { out := []*chart.Chart{} err := ProcessRequirementsEnabled(c, v) if err != nil { t.Errorf("Error processing enabled requirements %v", err) } out = extractCharts(c, out) // build list of chart names p := []string{} for _, r := range out { p = append(p, r.Metadata.Name) } //sort alphanumeric and compare to expectations sort.Strings(p) if len(p) != len(e) { t.Errorf("Error slice lengths do not match got %v, expected %v", len(p), len(e)) return } for i := range p { if p[i] != e[i] { t.Errorf("Error slice values do not match got %v, expected %v", p[i], e[i]) } } } // extractCharts recursively searches chart dependencies returning all charts found func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart { if len(c.Metadata.Name) > 0 { out = append(out, c) } for _, d := range c.Dependencies { out = extractCharts(d, out) } return out } func TestProcessRequirementsImportValues(t *testing.T) { c, err := Load("testdata/subpop") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } v := &chart.Config{Raw: ""} e := make(map[string]string) e["imported-chart1.SC1bool"] = "true" e["imported-chart1.SC1float"] = "3.14" e["imported-chart1.SC1int"] = "100" e["imported-chart1.SC1string"] = "dollywood" e["imported-chart1.SC1extra1"] = "11" e["imported-chart1.SPextra1"] = "helm rocks" e["imported-chart1.SC1extra1"] = "11" e["imported-chartA.SCAbool"] = "false" e["imported-chartA.SCAfloat"] = "3.1" e["imported-chartA.SCAint"] = "55" e["imported-chartA.SCAstring"] = "jabba" e["imported-chartA.SPextra3"] = "1.337" e["imported-chartA.SC1extra2"] = "1.337" e["imported-chartA.SCAnested1.SCAnested2"] = "true" e["imported-chartA-B.SCAbool"] = "false" e["imported-chartA-B.SCAfloat"] = "3.1" e["imported-chartA-B.SCAint"] = "55" e["imported-chartA-B.SCAstring"] = "jabba" e["imported-chartA-B.SCBbool"] = "true" e["imported-chartA-B.SCBfloat"] = "7.77" e["imported-chartA-B.SCBint"] = "33" e["imported-chartA-B.SCBstring"] = "boba" e["imported-chartA-B.SPextra5"] = "k8s" e["imported-chartA-B.SC1extra5"] = "tiller" e["overridden-chart1.SC1bool"] = "true" e["overridden-chart1.SC1float"] = "3.14" e["overridden-chart1.SC1int"] = "100" e["overridden-chart1.SC1string"] = "dollywood" e["overridden-chart1.SPextra2"] = "42" e["overridden-chartA.SCAbool"] = "true" e["overridden-chartA.SCAfloat"] = "41.3" e["overridden-chartA.SCAint"] = "808" e["overridden-chartA.SCAstring"] = "jaberwocky" e["overridden-chartA.SPextra4"] = "true" e["overridden-chartA-B.SCAbool"] = "true" e["overridden-chartA-B.SCAfloat"] = "3.33" e["overridden-chartA-B.SCAint"] = "555" e["overridden-chartA-B.SCAstring"] = "wormwood" e["overridden-chartA-B.SCBbool"] = "true" e["overridden-chartA-B.SCBfloat"] = "0.25" e["overridden-chartA-B.SCBint"] = "98" e["overridden-chartA-B.SCBstring"] = "murkwood" e["overridden-chartA-B.SPextra6"] = "111" e["overridden-chartA-B.SCAextra1"] = "23" e["overridden-chartA-B.SCBextra1"] = "13" e["overridden-chartA-B.SC1extra6"] = "77" // `exports` style e["SCBexported1B"] = "1965" e["SC1extra7"] = "true" e["SCBexported2A"] = "blaster" e["global.SC1exported2.all.SC1exported3"] = "SC1expstr" e["SCCdata.SCCstring"] = "mugwort" e["SCCdata.SCCint"] = "42" verifyRequirementsImportValues(t, c, v, e) } func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v *chart.Config, e map[string]string) { err := ProcessRequirementsImportValues(c) if err != nil { t.Errorf("Error processing import values requirements %v", err) } cv := c.GetValues() cc, err := ReadValues([]byte(cv.Raw)) if err != nil { t.Errorf("Error reading import values %v", err) } for kk, vv := range e { pv, err := cc.PathValue(kk) if err != nil { t.Fatalf("Error retrieving import values table %v %v", kk, err) return } switch pv.(type) { case float64: s := strconv.FormatFloat(pv.(float64), 'f', -1, 64) if s != vv { t.Errorf("Failed to match imported float field %v with value %v with expected %v", kk, s, vv) return } case bool: b := strconv.FormatBool(pv.(bool)) if b != vv { t.Errorf("Failed to match imported bool field %v with value %v with expected %v", kk, b, vv) return } default: if pv.(string) != vv { t.Errorf("Failed to match imported string field %v with value %v with expected %v", kk, pv, vv) return } } } } func TestGetAliasDependency(t *testing.T) { c, err := Load("testdata/frobnitz") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } req, err := LoadRequirements(c) if err != nil { t.Fatalf("Failed to load requirement for testdata: %s", err) } if len(req.Dependencies) == 0 { t.Fatalf("There are no requirements to test") } // Success case aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]) if aliasChart == nil { t.Fatalf("Failed to get dependency chart for alias %s", req.Dependencies[0].Name) } if req.Dependencies[0].Alias != "" { if aliasChart.Metadata.Name != req.Dependencies[0].Alias { t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Alias, aliasChart.Metadata.Name) } } else if aliasChart.Metadata.Name != req.Dependencies[0].Name { t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name) } if req.Dependencies[0].Version != "" { if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { t.Fatalf("Dependency chart version is not in the compatible range") } } // Failure case resetName := req.Dependencies[0].Name req.Dependencies[0].Name = "something-else" if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) } // Add a bad alias name req.Dependencies[0].Name = resetName req.Dependencies[0].Alias = "$foobar" if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) } req.Dependencies[0].Version = "something else which is not in the compatible range" if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ") } } func TestDependentChartAliases(t *testing.T) { c, err := Load("testdata/dependent-chart-alias") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } if len(c.Dependencies) == 0 { t.Fatal("There are no dependencies to run this test") } origLength := len(c.Dependencies) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { t.Fatalf("Expected no errors but got %q", err) } if len(c.Dependencies) == origLength { t.Fatal("Expected alias dependencies to be added, but did not got that") } reqmts, err := LoadRequirements(c) if err != nil { t.Fatalf("Cannot load requirements for test chart, %v", err) } if len(c.Dependencies) != len(reqmts.Dependencies) { t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies)) } } func TestDependentChartWithSubChartsAbsentInRequirements(t *testing.T) { c, err := Load("testdata/dependent-chart-no-requirements-yaml") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } if len(c.Dependencies) != 2 { t.Fatalf("Expected 2 dependencies for this chart, but got %d", len(c.Dependencies)) } origLength := len(c.Dependencies) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { t.Fatalf("Expected no errors but got %q", err) } if len(c.Dependencies) != origLength { t.Fatal("Expected no changes in dependencies to be, but did something got changed") } } func TestDependentChartWithSubChartsHelmignore(t *testing.T) { if _, err := Load("testdata/dependent-chart-helmignore"); err != nil { t.Fatalf("Failed to load testdata: %s", err) } } func TestDependentChartsWithSubChartsSymlink(t *testing.T) { joonix := "testdata/joonix" if err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")); err != nil { t.Fatal(err) } defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz")) c, err := Load(joonix) if err != nil { t.Fatalf("Failed to load testdata: %s", err) } if c.Metadata.Name != "joonix" { t.Fatalf("Unexpected chart name: %s", c.Metadata.Name) } if n := len(c.Dependencies); n != 1 { t.Fatalf("Expected 1 dependency for this chart, but got %d", n) } } func TestDependentChartsWithSubchartsAllSpecifiedInRequirements(t *testing.T) { c, err := Load("testdata/dependent-chart-with-all-in-requirements-yaml") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } if len(c.Dependencies) == 0 { t.Fatal("There are no dependencies to run this test") } origLength := len(c.Dependencies) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { t.Fatalf("Expected no errors but got %q", err) } if len(c.Dependencies) != origLength { t.Fatal("Expected no changes in dependencies to be, but did something got changed") } reqmts, err := LoadRequirements(c) if err != nil { t.Fatalf("Cannot load requirements for test chart, %v", err) } if len(c.Dependencies) != len(reqmts.Dependencies) { t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies)) } } func TestDependentChartsWithSomeSubchartsSpecifiedInRequirements(t *testing.T) { c, err := Load("testdata/dependent-chart-with-mixed-requirements-yaml") if err != nil { t.Fatalf("Failed to load testdata: %s", err) } if len(c.Dependencies) == 0 { t.Fatal("There are no dependencies to run this test") } origLength := len(c.Dependencies) if err := ProcessRequirementsEnabled(c, c.Values); err != nil { t.Fatalf("Expected no errors but got %q", err) } if len(c.Dependencies) != origLength { t.Fatal("Expected no changes in dependencies to be, but did something got changed") } reqmts, err := LoadRequirements(c) if err != nil { t.Fatalf("Cannot load requirements for test chart, %v", err) } if len(c.Dependencies) <= len(reqmts.Dependencies) { t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(reqmts.Dependencies), len(c.Dependencies)) } } func TestAliasRegexp(t *testing.T) { for name, shouldPass := range map[string]bool{ "abcdefghijklmnopqrstuvwxyzABCDEFG0987654321_-": true, "$foo": false, "bar$": false, "foo\nbar": false, } { if aliasRegexp.MatchString(name) != shouldPass { t.Errorf("name %q failed to pass its test", name) } } }