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 }