github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/pkg/chartutil/dependencies_test.go (about)

     1  /*
     2  Copyright The Helm Authors.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6  
     7  http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  package chartutil
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"sort"
    21  	"strconv"
    22  	"testing"
    23  
    24  	"github.com/stefanmcshane/helm/pkg/chart"
    25  	"github.com/stefanmcshane/helm/pkg/chart/loader"
    26  )
    27  
    28  func loadChart(t *testing.T, path string) *chart.Chart {
    29  	t.Helper()
    30  	c, err := loader.Load(path)
    31  	if err != nil {
    32  		t.Fatalf("failed to load testdata: %s", err)
    33  	}
    34  	return c
    35  }
    36  
    37  func TestLoadDependency(t *testing.T) {
    38  	tests := []*chart.Dependency{
    39  		{Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"},
    40  		{Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"},
    41  	}
    42  
    43  	check := func(deps []*chart.Dependency) {
    44  		if len(deps) != 2 {
    45  			t.Errorf("expected 2 dependencies, got %d", len(deps))
    46  		}
    47  		for i, tt := range tests {
    48  			if deps[i].Name != tt.Name {
    49  				t.Errorf("expected dependency named %q, got %q", tt.Name, deps[i].Name)
    50  			}
    51  			if deps[i].Version != tt.Version {
    52  				t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version)
    53  			}
    54  			if deps[i].Repository != tt.Repository {
    55  				t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository)
    56  			}
    57  		}
    58  	}
    59  	c := loadChart(t, "testdata/frobnitz")
    60  	check(c.Metadata.Dependencies)
    61  	check(c.Lock.Dependencies)
    62  }
    63  
    64  func TestDependencyEnabled(t *testing.T) {
    65  	type M = map[string]interface{}
    66  	tests := []struct {
    67  		name string
    68  		v    M
    69  		e    []string // expected charts including duplicates in alphanumeric order
    70  	}{{
    71  		"tags with no effect",
    72  		M{"tags": M{"nothinguseful": false}},
    73  		[]string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"},
    74  	}, {
    75  		"tags disabling a group",
    76  		M{"tags": M{"front-end": false}},
    77  		[]string{"parentchart"},
    78  	}, {
    79  		"tags disabling a group and enabling a different group",
    80  		M{"tags": M{"front-end": false, "back-end": true}},
    81  		[]string{"parentchart", "parentchart.subchart2", "parentchart.subchart2.subchartb", "parentchart.subchart2.subchartc"},
    82  	}, {
    83  		"tags disabling only children, children still enabled since tag front-end=true in values.yaml",
    84  		M{"tags": M{"subcharta": false, "subchartb": false}},
    85  		[]string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb"},
    86  	}, {
    87  		"tags disabling all parents/children with additional tag re-enabling a parent",
    88  		M{"tags": M{"front-end": false, "subchart1": true, "back-end": false}},
    89  		[]string{"parentchart", "parentchart.subchart1"},
    90  	}, {
    91  		"conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml",
    92  		M{"subchart1": M{"enabled": true}, "subchart2": M{"enabled": true}},
    93  		[]string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2"},
    94  	}, {
    95  		"conditions disabling the parent charts, effectively disabling children",
    96  		M{"subchart1": M{"enabled": false}, "subchart2": M{"enabled": false}},
    97  		[]string{"parentchart"},
    98  	}, {
    99  		"conditions a child using the second condition path of child's condition",
   100  		M{"subchart1": M{"subcharta": M{"enabled": false}}},
   101  		[]string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subchartb"},
   102  	}, {
   103  		"tags enabling a parent/child group with condition disabling one child",
   104  		M{"subchart2": M{"subchartc": M{"enabled": false}}, "tags": M{"back-end": true}},
   105  		[]string{"parentchart", "parentchart.subchart1", "parentchart.subchart1.subcharta", "parentchart.subchart1.subchartb", "parentchart.subchart2", "parentchart.subchart2.subchartb"},
   106  	}, {
   107  		"tags will not enable a child if parent is explicitly disabled with condition",
   108  		M{"subchart1": M{"enabled": false}, "tags": M{"front-end": true}},
   109  		[]string{"parentchart"},
   110  	}, {
   111  		"subcharts with alias also respect conditions",
   112  		M{"subchart1": M{"enabled": false}, "subchart2alias": M{"enabled": true, "subchartb": M{"enabled": true}}},
   113  		[]string{"parentchart", "parentchart.subchart2alias", "parentchart.subchart2alias.subchartb"},
   114  	}}
   115  
   116  	for _, tc := range tests {
   117  		c := loadChart(t, "testdata/subpop")
   118  		t.Run(tc.name, func(t *testing.T) {
   119  			if err := processDependencyEnabled(c, tc.v, ""); err != nil {
   120  				t.Fatalf("error processing enabled dependencies %v", err)
   121  			}
   122  
   123  			names := extractChartNames(c)
   124  			if len(names) != len(tc.e) {
   125  				t.Fatalf("slice lengths do not match got %v, expected %v", len(names), len(tc.e))
   126  			}
   127  			for i := range names {
   128  				if names[i] != tc.e[i] {
   129  					t.Fatalf("slice values do not match got %v, expected %v", names, tc.e)
   130  				}
   131  			}
   132  		})
   133  	}
   134  }
   135  
   136  // extractCharts recursively searches chart dependencies returning all charts found
   137  func extractChartNames(c *chart.Chart) []string {
   138  	var out []string
   139  	var fn func(c *chart.Chart)
   140  	fn = func(c *chart.Chart) {
   141  		out = append(out, c.ChartPath())
   142  		for _, d := range c.Dependencies() {
   143  			fn(d)
   144  		}
   145  	}
   146  	fn(c)
   147  	sort.Strings(out)
   148  	return out
   149  }
   150  
   151  func TestProcessDependencyImportValues(t *testing.T) {
   152  	c := loadChart(t, "testdata/subpop")
   153  
   154  	e := make(map[string]string)
   155  
   156  	e["imported-chart1.SC1bool"] = "true"
   157  	e["imported-chart1.SC1float"] = "3.14"
   158  	e["imported-chart1.SC1int"] = "100"
   159  	e["imported-chart1.SC1string"] = "dollywood"
   160  	e["imported-chart1.SC1extra1"] = "11"
   161  	e["imported-chart1.SPextra1"] = "helm rocks"
   162  	e["imported-chart1.SC1extra1"] = "11"
   163  
   164  	e["imported-chartA.SCAbool"] = "false"
   165  	e["imported-chartA.SCAfloat"] = "3.1"
   166  	e["imported-chartA.SCAint"] = "55"
   167  	e["imported-chartA.SCAstring"] = "jabba"
   168  	e["imported-chartA.SPextra3"] = "1.337"
   169  	e["imported-chartA.SC1extra2"] = "1.337"
   170  	e["imported-chartA.SCAnested1.SCAnested2"] = "true"
   171  
   172  	e["imported-chartA-B.SCAbool"] = "false"
   173  	e["imported-chartA-B.SCAfloat"] = "3.1"
   174  	e["imported-chartA-B.SCAint"] = "55"
   175  	e["imported-chartA-B.SCAstring"] = "jabba"
   176  
   177  	e["imported-chartA-B.SCBbool"] = "true"
   178  	e["imported-chartA-B.SCBfloat"] = "7.77"
   179  	e["imported-chartA-B.SCBint"] = "33"
   180  	e["imported-chartA-B.SCBstring"] = "boba"
   181  	e["imported-chartA-B.SPextra5"] = "k8s"
   182  	e["imported-chartA-B.SC1extra5"] = "tiller"
   183  
   184  	e["overridden-chart1.SC1bool"] = "false"
   185  	e["overridden-chart1.SC1float"] = "3.141592"
   186  	e["overridden-chart1.SC1int"] = "99"
   187  	e["overridden-chart1.SC1string"] = "pollywog"
   188  	e["overridden-chart1.SPextra2"] = "42"
   189  
   190  	e["overridden-chartA.SCAbool"] = "true"
   191  	e["overridden-chartA.SCAfloat"] = "41.3"
   192  	e["overridden-chartA.SCAint"] = "808"
   193  	e["overridden-chartA.SCAstring"] = "jabberwocky"
   194  	e["overridden-chartA.SPextra4"] = "true"
   195  
   196  	e["overridden-chartA-B.SCAbool"] = "true"
   197  	e["overridden-chartA-B.SCAfloat"] = "41.3"
   198  	e["overridden-chartA-B.SCAint"] = "808"
   199  	e["overridden-chartA-B.SCAstring"] = "jabberwocky"
   200  	e["overridden-chartA-B.SCBbool"] = "false"
   201  	e["overridden-chartA-B.SCBfloat"] = "1.99"
   202  	e["overridden-chartA-B.SCBint"] = "77"
   203  	e["overridden-chartA-B.SCBstring"] = "jango"
   204  	e["overridden-chartA-B.SPextra6"] = "111"
   205  	e["overridden-chartA-B.SCAextra1"] = "23"
   206  	e["overridden-chartA-B.SCBextra1"] = "13"
   207  	e["overridden-chartA-B.SC1extra6"] = "77"
   208  
   209  	// `exports` style
   210  	e["SCBexported1B"] = "1965"
   211  	e["SC1extra7"] = "true"
   212  	e["SCBexported2A"] = "blaster"
   213  	e["global.SC1exported2.all.SC1exported3"] = "SC1expstr"
   214  
   215  	if err := processDependencyImportValues(c); err != nil {
   216  		t.Fatalf("processing import values dependencies %v", err)
   217  	}
   218  	cc := Values(c.Values)
   219  	for kk, vv := range e {
   220  		pv, err := cc.PathValue(kk)
   221  		if err != nil {
   222  			t.Fatalf("retrieving import values table %v %v", kk, err)
   223  		}
   224  
   225  		switch pv := pv.(type) {
   226  		case float64:
   227  			if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv {
   228  				t.Errorf("failed to match imported float value %v with expected %v", s, vv)
   229  			}
   230  		case bool:
   231  			if b := strconv.FormatBool(pv); b != vv {
   232  				t.Errorf("failed to match imported bool value %v with expected %v", b, vv)
   233  			}
   234  		default:
   235  			if pv != vv {
   236  				t.Errorf("failed to match imported string value %q with expected %q", pv, vv)
   237  			}
   238  		}
   239  	}
   240  }
   241  
   242  func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) {
   243  	c := loadChart(t, "testdata/three-level-dependent-chart/umbrella")
   244  
   245  	e := make(map[string]string)
   246  
   247  	e["app1.service.port"] = "3456"
   248  	e["app2.service.port"] = "8080"
   249  
   250  	if err := processDependencyImportValues(c); err != nil {
   251  		t.Fatalf("processing import values dependencies %v", err)
   252  	}
   253  	cc := Values(c.Values)
   254  	for kk, vv := range e {
   255  		pv, err := cc.PathValue(kk)
   256  		if err != nil {
   257  			t.Fatalf("retrieving import values table %v %v", kk, err)
   258  		}
   259  
   260  		switch pv := pv.(type) {
   261  		case float64:
   262  			if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv {
   263  				t.Errorf("failed to match imported float value %v with expected %v", s, vv)
   264  			}
   265  		default:
   266  			if pv != vv {
   267  				t.Errorf("failed to match imported string value %q with expected %q", pv, vv)
   268  			}
   269  		}
   270  	}
   271  }
   272  
   273  func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) {
   274  	c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart")
   275  	nameOverride := "parent-chart-prod"
   276  
   277  	if err := processDependencyImportValues(c); err != nil {
   278  		t.Fatalf("processing import values dependencies %v", err)
   279  	}
   280  
   281  	if len(c.Dependencies()) != 2 {
   282  		t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
   283  	}
   284  
   285  	if err := processDependencyEnabled(c, c.Values, ""); err != nil {
   286  		t.Fatalf("expected no errors but got %q", err)
   287  	}
   288  
   289  	if len(c.Dependencies()) != 1 {
   290  		t.Fatal("expected no changes in dependencies")
   291  	}
   292  
   293  	if len(c.Metadata.Dependencies) != 1 {
   294  		t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies))
   295  	}
   296  
   297  	prodDependencyValues := c.Dependencies()[0].Values
   298  	if prodDependencyValues["nameOverride"] != nameOverride {
   299  		t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"])
   300  	}
   301  }
   302  
   303  func TestGetAliasDependency(t *testing.T) {
   304  	c := loadChart(t, "testdata/frobnitz")
   305  	req := c.Metadata.Dependencies
   306  
   307  	if len(req) == 0 {
   308  		t.Fatalf("there are no dependencies to test")
   309  	}
   310  
   311  	// Success case
   312  	aliasChart := getAliasDependency(c.Dependencies(), req[0])
   313  	if aliasChart == nil {
   314  		t.Fatalf("failed to get dependency chart for alias %s", req[0].Name)
   315  	}
   316  	if req[0].Alias != "" {
   317  		if aliasChart.Name() != req[0].Alias {
   318  			t.Fatalf("dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name())
   319  		}
   320  	} else if aliasChart.Name() != req[0].Name {
   321  		t.Fatalf("dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name())
   322  	}
   323  
   324  	if req[0].Version != "" {
   325  		if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) {
   326  			t.Fatalf("dependency chart version is not in the compatible range")
   327  		}
   328  	}
   329  
   330  	// Failure case
   331  	req[0].Name = "something-else"
   332  	if aliasChart := getAliasDependency(c.Dependencies(), req[0]); aliasChart != nil {
   333  		t.Fatalf("expected no chart but got %s", aliasChart.Name())
   334  	}
   335  
   336  	req[0].Version = "something else which is not in the compatible range"
   337  	if IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) {
   338  		t.Fatalf("dependency chart version which is not in the compatible range should cause a failure other than a success ")
   339  	}
   340  }
   341  
   342  func TestDependentChartAliases(t *testing.T) {
   343  	c := loadChart(t, "testdata/dependent-chart-alias")
   344  	req := c.Metadata.Dependencies
   345  
   346  	if len(c.Dependencies()) != 2 {
   347  		t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
   348  	}
   349  
   350  	if err := processDependencyEnabled(c, c.Values, ""); err != nil {
   351  		t.Fatalf("expected no errors but got %q", err)
   352  	}
   353  
   354  	if len(c.Dependencies()) != 3 {
   355  		t.Fatal("expected alias dependencies to be added")
   356  	}
   357  
   358  	if len(c.Dependencies()) != len(c.Metadata.Dependencies) {
   359  		t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies()))
   360  	}
   361  
   362  	aliasChart := getAliasDependency(c.Dependencies(), req[2])
   363  
   364  	if aliasChart == nil {
   365  		t.Fatalf("failed to get dependency chart for alias %s", req[2].Name)
   366  	}
   367  	if req[2].Alias != "" {
   368  		if aliasChart.Name() != req[2].Alias {
   369  			t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name())
   370  		}
   371  	} else if aliasChart.Name() != req[2].Name {
   372  		t.Fatalf("dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name())
   373  	}
   374  
   375  	req[2].Name = "dummy-name"
   376  	if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil {
   377  		t.Fatalf("expected no chart but got %s", aliasChart.Name())
   378  	}
   379  
   380  }
   381  
   382  func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) {
   383  	c := loadChart(t, "testdata/dependent-chart-no-requirements-yaml")
   384  
   385  	if len(c.Dependencies()) != 2 {
   386  		t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
   387  	}
   388  
   389  	if err := processDependencyEnabled(c, c.Values, ""); err != nil {
   390  		t.Fatalf("expected no errors but got %q", err)
   391  	}
   392  
   393  	if len(c.Dependencies()) != 2 {
   394  		t.Fatal("expected no changes in dependencies")
   395  	}
   396  }
   397  
   398  func TestDependentChartWithSubChartsHelmignore(t *testing.T) {
   399  	// FIXME what does this test?
   400  	loadChart(t, "testdata/dependent-chart-helmignore")
   401  }
   402  
   403  func TestDependentChartsWithSubChartsSymlink(t *testing.T) {
   404  	joonix := filepath.Join("testdata", "joonix")
   405  	if err := os.Symlink(filepath.Join("..", "..", "frobnitz"), filepath.Join(joonix, "charts", "frobnitz")); err != nil {
   406  		t.Fatal(err)
   407  	}
   408  	defer os.RemoveAll(filepath.Join(joonix, "charts", "frobnitz"))
   409  	c := loadChart(t, joonix)
   410  
   411  	if c.Name() != "joonix" {
   412  		t.Fatalf("unexpected chart name: %s", c.Name())
   413  	}
   414  	if n := len(c.Dependencies()); n != 1 {
   415  		t.Fatalf("expected 1 dependency for this chart, but got %d", n)
   416  	}
   417  }
   418  
   419  func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) {
   420  	c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml")
   421  
   422  	if len(c.Dependencies()) != 2 {
   423  		t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
   424  	}
   425  
   426  	if err := processDependencyEnabled(c, c.Values, ""); err != nil {
   427  		t.Fatalf("expected no errors but got %q", err)
   428  	}
   429  
   430  	if len(c.Dependencies()) != 2 {
   431  		t.Fatal("expected no changes in dependencies")
   432  	}
   433  
   434  	if len(c.Dependencies()) != len(c.Metadata.Dependencies) {
   435  		t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies()))
   436  	}
   437  }
   438  
   439  func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) {
   440  	c := loadChart(t, "testdata/dependent-chart-with-mixed-requirements-yaml")
   441  
   442  	if len(c.Dependencies()) != 2 {
   443  		t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies()))
   444  	}
   445  
   446  	if err := processDependencyEnabled(c, c.Values, ""); err != nil {
   447  		t.Fatalf("expected no errors but got %q", err)
   448  	}
   449  
   450  	if len(c.Dependencies()) != 2 {
   451  		t.Fatal("expected no changes in dependencies")
   452  	}
   453  
   454  	if len(c.Metadata.Dependencies) != 1 {
   455  		t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies))
   456  	}
   457  }