From 70257f5cd646140119bc59467b8600588b42d169 Mon Sep 17 00:00:00 2001 From: Matt Farina Date: Tue, 22 Jul 2025 13:00:01 -0400 Subject: [PATCH] Initial addition of v3 charts This change adds v3 charts. The code for the chart, including a loader, is present. It is based on v2 charts as a starting point. Note, this change does not make the charts available for use with Helm CLI commands or the action package. That will be in follow-up changes. Signed-off-by: Matt Farina --- internal/chart/v3/chart.go | 172 ++++ internal/chart/v3/chart_test.go | 211 +++++ internal/chart/v3/dependency.go | 82 ++ internal/chart/v3/dependency_test.go | 44 + internal/chart/v3/doc.go | 21 + internal/chart/v3/errors.go | 30 + internal/chart/v3/file.go | 27 + internal/chart/v3/fuzz_test.go | 48 + internal/chart/v3/loader/archive.go | 234 +++++ internal/chart/v3/loader/archive_test.go | 92 ++ internal/chart/v3/loader/directory.go | 121 +++ internal/chart/v3/loader/load.go | 219 +++++ internal/chart/v3/loader/load_test.go | 711 +++++++++++++++ internal/chart/v3/loader/testdata/LICENSE | 1 + .../v3/loader/testdata/albatross/Chart.yaml | 4 + .../v3/loader/testdata/albatross/values.yaml | 4 + .../v3/loader/testdata/frobnitz-1.2.3.tgz | Bin 0 -> 3420 bytes .../testdata/frobnitz.v3.reqs/.helmignore | 1 + .../testdata/frobnitz.v3.reqs/Chart.yaml | 27 + .../testdata/frobnitz.v3.reqs/INSTALL.txt | 1 + .../loader/testdata/frobnitz.v3.reqs/LICENSE | 1 + .../testdata/frobnitz.v3.reqs/README.md | 11 + .../frobnitz.v3.reqs/charts/_ignore_me | 1 + .../frobnitz.v3.reqs/charts/alpine/Chart.yaml | 5 + .../frobnitz.v3.reqs/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../frobnitz.v3.reqs/charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../testdata/frobnitz.v3.reqs/docs/README.md | 1 + .../loader/testdata/frobnitz.v3.reqs/icon.svg | 8 + .../testdata/frobnitz.v3.reqs/ignore/me.txt | 0 .../frobnitz.v3.reqs/templates/template.tpl | 1 + .../testdata/frobnitz.v3.reqs/values.yaml | 6 + .../v3/loader/testdata/frobnitz/.helmignore | 1 + .../v3/loader/testdata/frobnitz/Chart.lock | 8 + .../v3/loader/testdata/frobnitz/Chart.yaml | 27 + .../v3/loader/testdata/frobnitz/INSTALL.txt | 1 + .../chart/v3/loader/testdata/frobnitz/LICENSE | 1 + .../v3/loader/testdata/frobnitz/README.md | 11 + .../testdata/frobnitz/charts/_ignore_me | 1 + .../frobnitz/charts/alpine/Chart.yaml | 5 + .../testdata/frobnitz/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../frobnitz/charts/alpine/values.yaml | 2 + .../frobnitz/charts/mariner-4.3.2.tgz | Bin 0 -> 910 bytes .../loader/testdata/frobnitz/docs/README.md | 1 + .../v3/loader/testdata/frobnitz/icon.svg | 8 + .../v3/loader/testdata/frobnitz/ignore/me.txt | 0 .../testdata/frobnitz/templates/template.tpl | 1 + .../v3/loader/testdata/frobnitz/values.yaml | 6 + .../testdata/frobnitz_backslash-1.2.3.tgz | Bin 0 -> 3434 bytes .../testdata/frobnitz_backslash/.helmignore | 1 + .../testdata/frobnitz_backslash/Chart.lock | 8 + .../testdata/frobnitz_backslash/Chart.yaml | 27 + .../testdata/frobnitz_backslash/INSTALL.txt | 1 + .../testdata/frobnitz_backslash/LICENSE | 1 + .../testdata/frobnitz_backslash/README.md | 11 + .../frobnitz_backslash/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 910 bytes .../frobnitz_backslash/docs/README.md | 1 + .../testdata/frobnitz_backslash/icon.svg | 8 + .../testdata/frobnitz_backslash/ignore/me.txt | 0 .../frobnitz_backslash/templates/template.tpl | 1 + .../testdata/frobnitz_backslash/values.yaml | 6 + .../v3/loader/testdata/frobnitz_with_bom.tgz | Bin 0 -> 3453 bytes .../testdata/frobnitz_with_bom/.helmignore | 1 + .../testdata/frobnitz_with_bom/Chart.lock | 8 + .../testdata/frobnitz_with_bom/Chart.yaml | 27 + .../testdata/frobnitz_with_bom/INSTALL.txt | 1 + .../loader/testdata/frobnitz_with_bom/LICENSE | 1 + .../testdata/frobnitz_with_bom/README.md | 11 + .../frobnitz_with_bom/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../frobnitz_with_bom/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 910 bytes .../testdata/frobnitz_with_bom/docs/README.md | 1 + .../testdata/frobnitz_with_bom/icon.svg | 8 + .../testdata/frobnitz_with_bom/ignore/me.txt | 0 .../frobnitz_with_bom/templates/template.tpl | 1 + .../testdata/frobnitz_with_bom/values.yaml | 6 + .../frobnitz_with_dev_null/.helmignore | 1 + .../frobnitz_with_dev_null/Chart.lock | 8 + .../frobnitz_with_dev_null/Chart.yaml | 27 + .../frobnitz_with_dev_null/INSTALL.txt | 1 + .../testdata/frobnitz_with_dev_null/LICENSE | 1 + .../testdata/frobnitz_with_dev_null/README.md | 11 + .../frobnitz_with_dev_null/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 910 bytes .../frobnitz_with_dev_null/docs/README.md | 1 + .../testdata/frobnitz_with_dev_null/icon.svg | 8 + .../frobnitz_with_dev_null/ignore/me.txt | 0 .../testdata/frobnitz_with_dev_null/null | 1 + .../templates/template.tpl | 1 + .../frobnitz_with_dev_null/values.yaml | 6 + .../frobnitz_with_symlink/.helmignore | 1 + .../testdata/frobnitz_with_symlink/Chart.lock | 8 + .../testdata/frobnitz_with_symlink/Chart.yaml | 27 + .../frobnitz_with_symlink/INSTALL.txt | 1 + .../testdata/frobnitz_with_symlink/README.md | 11 + .../frobnitz_with_symlink/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 910 bytes .../frobnitz_with_symlink/docs/README.md | 1 + .../testdata/frobnitz_with_symlink/icon.svg | 8 + .../frobnitz_with_symlink/ignore/me.txt | 0 .../templates/template.tpl | 1 + .../frobnitz_with_symlink/values.yaml | 6 + internal/chart/v3/loader/testdata/genfrob.sh | 18 + .../v3/loader/testdata/mariner/Chart.yaml | 9 + .../mariner/charts/albatross-0.1.0.tgz | Bin 0 -> 282 bytes .../mariner/templates/placeholder.tpl | 1 + .../v3/loader/testdata/mariner/values.yaml | 7 + internal/chart/v3/metadata.go | 178 ++++ internal/chart/v3/metadata_test.go | 201 +++++ internal/chart/v3/util/capabilities.go | 122 +++ internal/chart/v3/util/capabilities_test.go | 84 ++ internal/chart/v3/util/chartfile.go | 96 ++ internal/chart/v3/util/chartfile_test.go | 117 +++ internal/chart/v3/util/coalesce.go | 308 +++++++ internal/chart/v3/util/coalesce_test.go | 723 +++++++++++++++ internal/chart/v3/util/compatible.go | 34 + internal/chart/v3/util/compatible_test.go | 43 + internal/chart/v3/util/create.go | 832 ++++++++++++++++++ internal/chart/v3/util/create_test.go | 172 ++++ internal/chart/v3/util/dependencies.go | 366 ++++++++ internal/chart/v3/util/dependencies_test.go | 569 ++++++++++++ internal/chart/v3/util/doc.go | 45 + internal/chart/v3/util/errors.go | 43 + internal/chart/v3/util/errors_test.go | 37 + internal/chart/v3/util/expand.go | 94 ++ internal/chart/v3/util/expand_test.go | 124 +++ internal/chart/v3/util/jsonschema.go | 113 +++ internal/chart/v3/util/jsonschema_test.go | 247 ++++++ internal/chart/v3/util/save.go | 253 ++++++ internal/chart/v3/util/save_test.go | 261 ++++++ .../Chart.yaml | 14 + .../charts/child/Chart.yaml | 6 + .../charts/child/charts/grandchild/Chart.yaml | 6 + .../charts/grandchild/templates/dummy.yaml | 7 + .../charts/child/templates/dummy.yaml | 7 + .../values.yaml | 7 + .../Chart.yaml | 20 + .../charts/child/Chart.yaml | 12 + .../charts/child/charts/grandchild/Chart.yaml | 6 + .../child/charts/grandchild/values.yaml | 2 + .../charts/child/templates/dummy.yaml | 7 + .../templates/dummy.yaml | 7 + .../chart/v3/util/testdata/chartfiletest.yaml | 20 + .../chart/v3/util/testdata/coleridge.yaml | 12 + .../dependent-chart-alias/.helmignore | 1 + .../testdata/dependent-chart-alias/Chart.lock | 8 + .../testdata/dependent-chart-alias/Chart.yaml | 29 + .../dependent-chart-alias/INSTALL.txt | 1 + .../testdata/dependent-chart-alias/LICENSE | 1 + .../testdata/dependent-chart-alias/README.md | 11 + .../dependent-chart-alias/charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../dependent-chart-alias/docs/README.md | 1 + .../testdata/dependent-chart-alias/icon.svg | 8 + .../dependent-chart-alias/ignore/me.txt | 0 .../templates/template.tpl | 1 + .../dependent-chart-alias/values.yaml | 6 + .../dependent-chart-helmignore/.helmignore | 2 + .../dependent-chart-helmignore/Chart.yaml | 17 + .../charts/.ignore_me | 0 .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../templates/template.tpl | 1 + .../dependent-chart-helmignore/values.yaml | 6 + .../.helmignore | 1 + .../Chart.yaml | 17 + .../INSTALL.txt | 1 + .../LICENSE | 1 + .../README.md | 11 + .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../templates/template.tpl | 1 + .../values.yaml | 6 + .../.helmignore | 1 + .../Chart.yaml | 24 + .../INSTALL.txt | 1 + .../LICENSE | 1 + .../README.md | 11 + .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../templates/template.tpl | 1 + .../values.yaml | 6 + .../.helmignore | 1 + .../Chart.yaml | 21 + .../INSTALL.txt | 1 + .../LICENSE | 1 + .../README.md | 11 + .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 5 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 967 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../templates/template.tpl | 1 + .../values.yaml | 6 + .../chart/v3/util/testdata/frobnitz-1.2.3.tgz | Bin 0 -> 3485 bytes .../v3/util/testdata/frobnitz/.helmignore | 1 + .../v3/util/testdata/frobnitz/Chart.lock | 8 + .../v3/util/testdata/frobnitz/Chart.yaml | 27 + .../v3/util/testdata/frobnitz/INSTALL.txt | 1 + .../chart/v3/util/testdata/frobnitz/LICENSE | 1 + .../chart/v3/util/testdata/frobnitz/README.md | 11 + .../util/testdata/frobnitz/charts/_ignore_me | 1 + .../frobnitz/charts/alpine/Chart.yaml | 5 + .../testdata/frobnitz/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 5 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 252 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 + .../frobnitz/charts/alpine/values.yaml | 2 + .../frobnitz/charts/mariner/Chart.yaml | 9 + .../mariner/charts/albatross/Chart.yaml | 5 + .../mariner/charts/albatross/values.yaml | 4 + .../charts/mariner/templates/placeholder.tpl | 1 + .../frobnitz/charts/mariner/values.yaml | 7 + .../v3/util/testdata/frobnitz/docs/README.md | 1 + .../chart/v3/util/testdata/frobnitz/icon.svg | 8 + .../v3/util/testdata/frobnitz/ignore/me.txt | 0 .../testdata/frobnitz/templates/template.tpl | 1 + .../v3/util/testdata/frobnitz/values.yaml | 6 + .../testdata/frobnitz_backslash-1.2.3.tgz | Bin 0 -> 3496 bytes internal/chart/v3/util/testdata/genfrob.sh | 14 + .../parent-chart/Chart.lock | 9 + .../parent-chart/Chart.yaml | 22 + .../parent-chart/charts/dev-v0.1.0.tgz | Bin 0 -> 333 bytes .../parent-chart/charts/prod-v0.1.0.tgz | Bin 0 -> 336 bytes .../parent-chart/envs/dev/Chart.yaml | 4 + .../parent-chart/envs/dev/values.yaml | 9 + .../parent-chart/envs/prod/Chart.yaml | 4 + .../parent-chart/envs/prod/values.yaml | 9 + .../parent-chart/templates/autoscaler.yaml | 16 + .../parent-chart/values.yaml | 10 + .../chart/v3/util/testdata/joonix/Chart.yaml | 4 + .../v3/util/testdata/joonix/charts/.gitkeep | 0 .../chart/v3/util/testdata/subpop/Chart.yaml | 41 + .../chart/v3/util/testdata/subpop/README.md | 18 + .../subpop/charts/subchart1/Chart.yaml | 36 + .../subchart1/charts/subchartA/Chart.yaml | 4 + .../charts/subchartA/templates/service.yaml | 15 + .../subchart1/charts/subchartA/values.yaml | 17 + .../subchart1/charts/subchartB/Chart.yaml | 4 + .../charts/subchartB/templates/service.yaml | 15 + .../subchart1/charts/subchartB/values.yaml | 35 + .../subpop/charts/subchart1/crds/crdA.yaml | 13 + .../charts/subchart1/templates/NOTES.txt | 1 + .../charts/subchart1/templates/service.yaml | 22 + .../subchart1/templates/subdir/role.yaml | 7 + .../templates/subdir/rolebinding.yaml | 12 + .../templates/subdir/serviceaccount.yaml | 4 + .../subpop/charts/subchart1/values.yaml | 55 ++ .../subpop/charts/subchart2/Chart.yaml | 19 + .../subchart2/charts/subchartB/Chart.yaml | 4 + .../charts/subchartB/templates/service.yaml | 15 + .../subchart2/charts/subchartB/values.yaml | 21 + .../subchart2/charts/subchartC/Chart.yaml | 4 + .../charts/subchartC/templates/service.yaml | 15 + .../subchart2/charts/subchartC/values.yaml | 21 + .../charts/subchart2/templates/service.yaml | 15 + .../subpop/charts/subchart2/values.yaml | 21 + .../v3/util/testdata/subpop/noreqs/Chart.yaml | 4 + .../subpop/noreqs/templates/service.yaml | 15 + .../util/testdata/subpop/noreqs/values.yaml | 26 + .../chart/v3/util/testdata/subpop/values.yaml | 45 + .../testdata/test-values-invalid.schema.json | 1 + .../util/testdata/test-values-negative.yaml | 14 + .../v3/util/testdata/test-values.schema.json | 67 ++ .../chart/v3/util/testdata/test-values.yaml | 17 + .../three-level-dependent-chart/README.md | 16 + .../umbrella/Chart.yaml | 19 + .../umbrella/charts/app1/Chart.yaml | 11 + .../charts/app1/charts/library/Chart.yaml | 5 + .../charts/library/templates/service.yaml | 9 + .../charts/app1/charts/library/values.yaml | 5 + .../charts/app1/templates/service.yaml | 1 + .../umbrella/charts/app1/values.yaml | 3 + .../umbrella/charts/app2/Chart.yaml | 11 + .../charts/app2/charts/library/Chart.yaml | 5 + .../charts/library/templates/service.yaml | 9 + .../charts/app2/charts/library/values.yaml | 5 + .../charts/app2/templates/service.yaml | 1 + .../umbrella/charts/app2/values.yaml | 3 + .../umbrella/charts/app3/Chart.yaml | 11 + .../charts/app3/charts/library/Chart.yaml | 5 + .../charts/library/templates/service.yaml | 9 + .../charts/app3/charts/library/values.yaml | 5 + .../charts/app3/templates/service.yaml | 1 + .../umbrella/charts/app3/values.yaml | 2 + .../umbrella/charts/app4/Chart.yaml | 9 + .../charts/app4/charts/library/Chart.yaml | 5 + .../charts/library/templates/service.yaml | 9 + .../charts/app4/charts/library/values.yaml | 5 + .../charts/app4/templates/service.yaml | 1 + .../umbrella/charts/app4/values.yaml | 3 + .../umbrella/values.yaml | 14 + internal/chart/v3/util/validate_name.go | 111 +++ internal/chart/v3/util/validate_name_test.go | 91 ++ internal/chart/v3/util/values.go | 220 +++++ internal/chart/v3/util/values_test.go | 293 ++++++ 373 files changed, 10070 insertions(+) create mode 100644 internal/chart/v3/chart.go create mode 100644 internal/chart/v3/chart_test.go create mode 100644 internal/chart/v3/dependency.go create mode 100644 internal/chart/v3/dependency_test.go create mode 100644 internal/chart/v3/doc.go create mode 100644 internal/chart/v3/errors.go create mode 100644 internal/chart/v3/file.go create mode 100644 internal/chart/v3/fuzz_test.go create mode 100644 internal/chart/v3/loader/archive.go create mode 100644 internal/chart/v3/loader/archive_test.go create mode 100644 internal/chart/v3/loader/directory.go create mode 100644 internal/chart/v3/loader/load.go create mode 100644 internal/chart/v3/loader/load_test.go create mode 100644 internal/chart/v3/loader/testdata/LICENSE create mode 100644 internal/chart/v3/loader/testdata/albatross/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/albatross/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz-1.2.3.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/.helmignore create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/INSTALL.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/LICENSE create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/_ignore_me create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/docs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/icon.svg create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/ignore/me.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/templates/template.tpl create mode 100644 internal/chart/v3/loader/testdata/frobnitz.v3.reqs/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/.helmignore create mode 100644 internal/chart/v3/loader/testdata/frobnitz/Chart.lock create mode 100644 internal/chart/v3/loader/testdata/frobnitz/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/INSTALL.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz/LICENSE create mode 100644 internal/chart/v3/loader/testdata/frobnitz/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/_ignore_me create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/alpine/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz/docs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz/icon.svg create mode 100644 internal/chart/v3/loader/testdata/frobnitz/ignore/me.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz/templates/template.tpl create mode 100644 internal/chart/v3/loader/testdata/frobnitz/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_backslash-1.2.3.tgz create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/.helmignore create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.lock create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/INSTALL.txt create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/LICENSE create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/README.md create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/_ignore_me create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/README.md create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/docs/README.md create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/icon.svg create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/ignore/me.txt create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/templates/template.tpl create mode 100755 internal/chart/v3/loader/testdata/frobnitz_backslash/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/.helmignore create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.lock create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/INSTALL.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/LICENSE create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/_ignore_me create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/docs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/icon.svg create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/ignore/me.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/templates/template.tpl create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_bom/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/.helmignore create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.lock create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/INSTALL.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/LICENSE create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/docs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/icon.svg create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/ignore/me.txt create mode 120000 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/null create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/templates/template.tpl create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_dev_null/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/.helmignore create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.lock create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/INSTALL.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/_ignore_me create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/docs/README.md create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/icon.svg create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/ignore/me.txt create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/templates/template.tpl create mode 100644 internal/chart/v3/loader/testdata/frobnitz_with_symlink/values.yaml create mode 100755 internal/chart/v3/loader/testdata/genfrob.sh create mode 100644 internal/chart/v3/loader/testdata/mariner/Chart.yaml create mode 100644 internal/chart/v3/loader/testdata/mariner/charts/albatross-0.1.0.tgz create mode 100644 internal/chart/v3/loader/testdata/mariner/templates/placeholder.tpl create mode 100644 internal/chart/v3/loader/testdata/mariner/values.yaml create mode 100644 internal/chart/v3/metadata.go create mode 100644 internal/chart/v3/metadata_test.go create mode 100644 internal/chart/v3/util/capabilities.go create mode 100644 internal/chart/v3/util/capabilities_test.go create mode 100644 internal/chart/v3/util/chartfile.go create mode 100644 internal/chart/v3/util/chartfile_test.go create mode 100644 internal/chart/v3/util/coalesce.go create mode 100644 internal/chart/v3/util/coalesce_test.go create mode 100644 internal/chart/v3/util/compatible.go create mode 100644 internal/chart/v3/util/compatible_test.go create mode 100644 internal/chart/v3/util/create.go create mode 100644 internal/chart/v3/util/create_test.go create mode 100644 internal/chart/v3/util/dependencies.go create mode 100644 internal/chart/v3/util/dependencies_test.go create mode 100644 internal/chart/v3/util/doc.go create mode 100644 internal/chart/v3/util/errors.go create mode 100644 internal/chart/v3/util/errors_test.go create mode 100644 internal/chart/v3/util/expand.go create mode 100644 internal/chart/v3/util/expand_test.go create mode 100644 internal/chart/v3/util/jsonschema.go create mode 100644 internal/chart/v3/util/jsonschema_test.go create mode 100644 internal/chart/v3/util/save.go create mode 100644 internal/chart/v3/util/save_test.go create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/values.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml create mode 100644 internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml create mode 100644 internal/chart/v3/util/testdata/chartfiletest.yaml create mode 100644 internal/chart/v3/util/testdata/coleridge.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/.helmignore create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/Chart.lock create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/INSTALL.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/LICENSE create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/docs/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/icon.svg create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/ignore/me.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/dependent-chart-alias/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/.helmignore create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/.ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/dependent-chart-helmignore/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/.helmignore create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/LICENSE create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/icon.svg create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz-1.2.3.tgz create mode 100644 internal/chart/v3/util/testdata/frobnitz/.helmignore create mode 100644 internal/chart/v3/util/testdata/frobnitz/Chart.lock create mode 100644 internal/chart/v3/util/testdata/frobnitz/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/INSTALL.txt create mode 100644 internal/chart/v3/util/testdata/frobnitz/LICENSE create mode 100644 internal/chart/v3/util/testdata/frobnitz/README.md create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/_ignore_me create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/README.md create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/alpine/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/mariner/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl create mode 100644 internal/chart/v3/util/testdata/frobnitz/charts/mariner/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz/docs/README.md create mode 100644 internal/chart/v3/util/testdata/frobnitz/icon.svg create mode 100644 internal/chart/v3/util/testdata/frobnitz/ignore/me.txt create mode 100644 internal/chart/v3/util/testdata/frobnitz/templates/template.tpl create mode 100644 internal/chart/v3/util/testdata/frobnitz/values.yaml create mode 100644 internal/chart/v3/util/testdata/frobnitz_backslash-1.2.3.tgz create mode 100755 internal/chart/v3/util/testdata/genfrob.sh create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml create mode 100644 internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml create mode 100644 internal/chart/v3/util/testdata/joonix/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/joonix/charts/.gitkeep create mode 100644 internal/chart/v3/util/testdata/subpop/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/README.md create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/crds/crdA.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/NOTES.txt create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart1/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/charts/subchart2/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/noreqs/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/noreqs/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/noreqs/values.yaml create mode 100644 internal/chart/v3/util/testdata/subpop/values.yaml create mode 100644 internal/chart/v3/util/testdata/test-values-invalid.schema.json create mode 100644 internal/chart/v3/util/testdata/test-values-negative.yaml create mode 100644 internal/chart/v3/util/testdata/test-values.schema.json create mode 100644 internal/chart/v3/util/testdata/test-values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/README.md create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml create mode 100644 internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/values.yaml create mode 100644 internal/chart/v3/util/validate_name.go create mode 100644 internal/chart/v3/util/validate_name_test.go create mode 100644 internal/chart/v3/util/values.go create mode 100644 internal/chart/v3/util/values_test.go diff --git a/internal/chart/v3/chart.go b/internal/chart/v3/chart.go new file mode 100644 index 000000000..4d59fa5ec --- /dev/null +++ b/internal/chart/v3/chart.go @@ -0,0 +1,172 @@ +/* +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 v3 + +import ( + "path/filepath" + "regexp" + "strings" +) + +// APIVersionV3 is the API version number for version 3. +const APIVersionV3 = "v3" + +// aliasNameFormat defines the characters that are legal in an alias name. +var aliasNameFormat = regexp.MustCompile("^[a-zA-Z0-9_-]+$") + +// Chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +type Chart struct { + // Raw contains the raw contents of the files originally contained in the chart archive. + // + // This should not be used except in special cases like `helm show values`, + // where we want to display the raw values, comments and all. + Raw []*File `json:"-"` + // Metadata is the contents of the Chartfile. + Metadata *Metadata `json:"metadata"` + // Lock is the contents of Chart.lock. + Lock *Lock `json:"lock"` + // Templates for this chart. + Templates []*File `json:"templates"` + // Values are default config for this chart. + Values map[string]interface{} `json:"values"` + // Schema is an optional JSON schema for imposing structure on Values + Schema []byte `json:"schema"` + // Files are miscellaneous files in a chart archive, + // e.g. README, LICENSE, etc. + Files []*File `json:"files"` + + parent *Chart + dependencies []*Chart +} + +type CRD struct { + // Name is the File.Name for the crd file + Name string + // Filename is the File obj Name including (sub-)chart.ChartFullPath + Filename string + // File is the File obj for the crd + File *File +} + +// SetDependencies replaces the chart dependencies. +func (ch *Chart) SetDependencies(charts ...*Chart) { + ch.dependencies = nil + ch.AddDependency(charts...) +} + +// Name returns the name of the chart. +func (ch *Chart) Name() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.Name +} + +// AddDependency determines if the chart is a subchart. +func (ch *Chart) AddDependency(charts ...*Chart) { + for i, x := range charts { + charts[i].parent = ch + ch.dependencies = append(ch.dependencies, x) + } +} + +// Root finds the root chart. +func (ch *Chart) Root() *Chart { + if ch.IsRoot() { + return ch + } + return ch.Parent().Root() +} + +// Dependencies are the charts that this chart depends on. +func (ch *Chart) Dependencies() []*Chart { return ch.dependencies } + +// IsRoot determines if the chart is the root chart. +func (ch *Chart) IsRoot() bool { return ch.parent == nil } + +// Parent returns a subchart's parent chart. +func (ch *Chart) Parent() *Chart { return ch.parent } + +// ChartPath returns the full path to this chart in dot notation. +func (ch *Chart) ChartPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartPath() + "." + ch.Name() + } + return ch.Name() +} + +// ChartFullPath returns the full path to this chart. +// Note that the path may not correspond to the path where the file can be found on the file system if the path +// points to an aliased subchart. +func (ch *Chart) ChartFullPath() string { + if !ch.IsRoot() { + return ch.Parent().ChartFullPath() + "/charts/" + ch.Name() + } + return ch.Name() +} + +// Validate validates the metadata. +func (ch *Chart) Validate() error { + return ch.Metadata.Validate() +} + +// AppVersion returns the appversion of the chart. +func (ch *Chart) AppVersion() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.AppVersion +} + +// CRDs returns a list of File objects in the 'crds/' directory of a Helm chart. +// Deprecated: use CRDObjects() +func (ch *Chart) CRDs() []*File { + files := []*File{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + files = append(files, f) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + files = append(files, dep.CRDs()...) + } + return files +} + +// CRDObjects returns a list of CRD objects in the 'crds/' directory of a Helm chart & subcharts +func (ch *Chart) CRDObjects() []CRD { + crds := []CRD{} + // Find all resources in the crds/ directory + for _, f := range ch.Files { + if strings.HasPrefix(f.Name, "crds/") && hasManifestExtension(f.Name) { + mycrd := CRD{Name: f.Name, Filename: filepath.Join(ch.ChartFullPath(), f.Name), File: f} + crds = append(crds, mycrd) + } + } + // Get CRDs from dependencies, too. + for _, dep := range ch.Dependencies() { + crds = append(crds, dep.CRDObjects()...) + } + return crds +} + +func hasManifestExtension(fname string) bool { + ext := filepath.Ext(fname) + return strings.EqualFold(ext, ".yaml") || strings.EqualFold(ext, ".yml") || strings.EqualFold(ext, ".json") +} diff --git a/internal/chart/v3/chart_test.go b/internal/chart/v3/chart_test.go new file mode 100644 index 000000000..f93b3356b --- /dev/null +++ b/internal/chart/v3/chart_test.go @@ -0,0 +1,211 @@ +/* +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 v3 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCRDs(t *testing.T) { + chrt := Chart{ + Files: []*File{ + { + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + { + Name: "bar.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crdsfoo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/README.md", + Data: []byte("# hello"), + }, + }, + } + + is := assert.New(t) + crds := chrt.CRDs() + is.Equal(2, len(crds)) + is.Equal("crds/foo.yaml", crds[0].Name) + is.Equal("crds/foo/bar/baz.yaml", crds[1].Name) +} + +func TestSaveChartNoRawData(t *testing.T) { + chrt := Chart{ + Raw: []*File{ + { + Name: "fhqwhgads.yaml", + Data: []byte("Everybody to the Limit"), + }, + }, + } + + is := assert.New(t) + data, err := json.Marshal(chrt) + if err != nil { + t.Fatal(err) + } + + res := &Chart{} + if err := json.Unmarshal(data, res); err != nil { + t.Fatal(err) + } + + is.Equal([]*File(nil), res.Raw) +} + +func TestMetadata(t *testing.T) { + chrt := Chart{ + Metadata: &Metadata{ + Name: "foo.yaml", + AppVersion: "1.0.0", + APIVersion: "v3", + Version: "1.0.0", + Type: "application", + }, + } + + is := assert.New(t) + + is.Equal("foo.yaml", chrt.Name()) + is.Equal("1.0.0", chrt.AppVersion()) + is.Equal(nil, chrt.Validate()) +} + +func TestIsRoot(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal(false, chrt1.IsRoot()) + is.Equal(true, chrt2.IsRoot()) +} + +func TestChartPath(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal("foo.", chrt1.ChartPath()) + is.Equal("foo", chrt2.ChartPath()) +} + +func TestChartFullPath(t *testing.T) { + chrt1 := Chart{ + parent: &Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + }, + } + + chrt2 := Chart{ + Metadata: &Metadata{ + Name: "foo", + }, + } + + is := assert.New(t) + + is.Equal("foo/charts/", chrt1.ChartFullPath()) + is.Equal("foo", chrt2.ChartFullPath()) +} + +func TestCRDObjects(t *testing.T) { + chrt := Chart{ + Files: []*File{ + { + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + { + Name: "bar.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crdsfoo/bar/baz.yaml", + Data: []byte("hello"), + }, + { + Name: "crds/README.md", + Data: []byte("# hello"), + }, + }, + } + + expected := []CRD{ + { + Name: "crds/foo.yaml", + Filename: "crds/foo.yaml", + File: &File{ + Name: "crds/foo.yaml", + Data: []byte("hello"), + }, + }, + { + Name: "crds/foo/bar/baz.yaml", + Filename: "crds/foo/bar/baz.yaml", + File: &File{ + Name: "crds/foo/bar/baz.yaml", + Data: []byte("hello"), + }, + }, + } + + is := assert.New(t) + crds := chrt.CRDObjects() + is.Equal(expected, crds) +} diff --git a/internal/chart/v3/dependency.go b/internal/chart/v3/dependency.go new file mode 100644 index 000000000..2d956b548 --- /dev/null +++ b/internal/chart/v3/dependency.go @@ -0,0 +1,82 @@ +/* +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 v3 + +import "time" + +// Dependency describes a chart upon which another chart depends. +// +// Dependencies can be used to express developer intent, or to capture the state +// of a chart. +type Dependency struct { + // Name is the name of the dependency. + // + // This must mach the name in the dependency's Chart.yaml. + Name string `json:"name" yaml:"name"` + // Version is the version (range) of this chart. + // + // A lock file will always produce a single version, while a dependency + // may contain a semantic version range. + Version string `json:"version,omitempty" yaml:"version,omitempty"` + // The URL to the repository. + // + // Appending `index.yaml` to this string should result in a URL that can be + // used to fetch the repository index. + Repository string `json:"repository" yaml:"repository"` + // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) + Condition string `json:"condition,omitempty" yaml:"condition,omitempty"` + // Tags can be used to group charts for enabling/disabling together + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + // Enabled bool determines if chart should be loaded + Enabled bool `json:"enabled,omitempty" yaml:"enabled,omitempty"` + // ImportValues holds the mapping of source values to parent key to be imported. Each item can be a + // string or pair of child/parent sublist items. + ImportValues []interface{} `json:"import-values,omitempty" yaml:"import-values,omitempty"` + // Alias usable alias to be used for the chart + Alias string `json:"alias,omitempty" yaml:"alias,omitempty"` +} + +// Validate checks for common problems with the dependency datastructure in +// the chart. This check must be done at load time before the dependency's charts are +// loaded. +func (d *Dependency) Validate() error { + if d == nil { + return ValidationError("dependencies must not contain empty or null nodes") + } + d.Name = sanitizeString(d.Name) + d.Version = sanitizeString(d.Version) + d.Repository = sanitizeString(d.Repository) + d.Condition = sanitizeString(d.Condition) + for i := range d.Tags { + d.Tags[i] = sanitizeString(d.Tags[i]) + } + if d.Alias != "" && !aliasNameFormat.MatchString(d.Alias) { + return ValidationErrorf("dependency %q has disallowed characters in the alias", d.Name) + } + return nil +} + +// Lock is a lock file for dependencies. +// +// It represents the state that the dependencies should be in. +type Lock struct { + // Generated is the date the lock file was last generated. + Generated time.Time `json:"generated"` + // Digest is a hash of the dependencies in Chart.yaml. + Digest string `json:"digest"` + // Dependencies is the list of dependencies that this lock file has locked. + Dependencies []*Dependency `json:"dependencies"` +} diff --git a/internal/chart/v3/dependency_test.go b/internal/chart/v3/dependency_test.go new file mode 100644 index 000000000..fcea19aea --- /dev/null +++ b/internal/chart/v3/dependency_test.go @@ -0,0 +1,44 @@ +/* +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 v3 + +import ( + "testing" +) + +func TestValidateDependency(t *testing.T) { + dep := &Dependency{ + Name: "example", + } + for value, shouldFail := range map[string]bool{ + "abcdefghijklmenopQRSTUVWXYZ-0123456780_": false, + "-okay": false, + "_okay": false, + "- bad": true, + " bad": true, + "bad\nvalue": true, + "bad ": true, + "bad$": true, + } { + dep.Alias = value + res := dep.Validate() + if res != nil && !shouldFail { + t.Errorf("Failed on case %q", dep.Alias) + } else if res == nil && shouldFail { + t.Errorf("Expected failure for %q", dep.Alias) + } + } +} diff --git a/internal/chart/v3/doc.go b/internal/chart/v3/doc.go new file mode 100644 index 000000000..e003833a0 --- /dev/null +++ b/internal/chart/v3/doc.go @@ -0,0 +1,21 @@ +/* +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 v3 provides chart handling for apiVersion v3 charts + +This package and its sub-packages provide handling for apiVersion v3 charts. +*/ +package v3 diff --git a/internal/chart/v3/errors.go b/internal/chart/v3/errors.go new file mode 100644 index 000000000..059e43f07 --- /dev/null +++ b/internal/chart/v3/errors.go @@ -0,0 +1,30 @@ +/* +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 v3 + +import "fmt" + +// ValidationError represents a data validation error. +type ValidationError string + +func (v ValidationError) Error() string { + return "validation: " + string(v) +} + +// ValidationErrorf takes a message and formatting options and creates a ValidationError +func ValidationErrorf(msg string, args ...interface{}) ValidationError { + return ValidationError(fmt.Sprintf(msg, args...)) +} diff --git a/internal/chart/v3/file.go b/internal/chart/v3/file.go new file mode 100644 index 000000000..ba04e106d --- /dev/null +++ b/internal/chart/v3/file.go @@ -0,0 +1,27 @@ +/* +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 v3 + +// File represents a file as a name/value pair. +// +// By convention, name is a relative path within the scope of the chart's +// base directory. +type File struct { + // Name is the path-like name of the template. + Name string `json:"name"` + // Data is the template as byte data. + Data []byte `json:"data"` +} diff --git a/internal/chart/v3/fuzz_test.go b/internal/chart/v3/fuzz_test.go new file mode 100644 index 000000000..982c26489 --- /dev/null +++ b/internal/chart/v3/fuzz_test.go @@ -0,0 +1,48 @@ +/* +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 v3 + +import ( + "testing" + + fuzz "github.com/AdaLogics/go-fuzz-headers" +) + +func FuzzMetadataValidate(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + fdp := fuzz.NewConsumer(data) + // Add random values to the metadata + md := &Metadata{} + err := fdp.GenerateStruct(md) + if err != nil { + t.Skip() + } + md.Validate() + }) +} + +func FuzzDependencyValidate(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte) { + f := fuzz.NewConsumer(data) + // Add random values to the dependenci + d := &Dependency{} + err := f.GenerateStruct(d) + if err != nil { + t.Skip() + } + d.Validate() + }) +} diff --git a/internal/chart/v3/loader/archive.go b/internal/chart/v3/loader/archive.go new file mode 100644 index 000000000..311959d56 --- /dev/null +++ b/internal/chart/v3/loader/archive.go @@ -0,0 +1,234 @@ +/* +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 loader + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "regexp" + "strings" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// MaxDecompressedChartSize is the maximum size of a chart archive that will be +// decompressed. This is the decompressed size of all the files. +// The default value is 100 MiB. +var MaxDecompressedChartSize int64 = 100 * 1024 * 1024 // Default 100 MiB + +// MaxDecompressedFileSize is the size of the largest file that Helm will attempt to load. +// The size of the file is the decompressed version of it when it is stored in an archive. +var MaxDecompressedFileSize int64 = 5 * 1024 * 1024 // Default 5 MiB + +var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`) + +// FileLoader loads a chart from a file +type FileLoader string + +// Load loads a chart +func (l FileLoader) Load() (*chart.Chart, error) { + return LoadFile(string(l)) +} + +// LoadFile loads from an archive file. +func LoadFile(name string) (*chart.Chart, error) { + if fi, err := os.Stat(name); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("cannot load a directory") + } + + raw, err := os.Open(name) + if err != nil { + return nil, err + } + defer raw.Close() + + err = ensureArchive(name, raw) + if err != nil { + return nil, err + } + + c, err := LoadArchive(raw) + if err != nil { + if err == gzip.ErrHeader { + return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err) + } + } + return c, err +} + +// ensureArchive's job is to return an informative error if the file does not appear to be a gzipped archive. +// +// Sometimes users will provide a values.yaml for an argument where a chart is expected. One common occurrence +// of this is invoking `helm template values.yaml mychart` which would otherwise produce a confusing error +// if we didn't check for this. +func ensureArchive(name string, raw *os.File) error { + defer raw.Seek(0, 0) // reset read offset to allow archive loading to proceed. + + // Check the file format to give us a chance to provide the user with more actionable feedback. + buffer := make([]byte, 512) + _, err := raw.Read(buffer) + if err != nil && err != io.EOF { + return fmt.Errorf("file '%s' cannot be read: %s", name, err) + } + + // Helm may identify achieve of the application/x-gzip as application/vnd.ms-fontobject. + // Fix for: https://github.com/helm/helm/issues/12261 + if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" && !isGZipApplication(buffer) { + // TODO: Is there a way to reliably test if a file content is YAML? ghodss/yaml accepts a wide + // variety of content (Makefile, .zshrc) as valid YAML without errors. + + // Wrong content type. Let's check if it's yaml and give an extra hint? + if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") { + return fmt.Errorf("file '%s' seems to be a YAML file, but expected a gzipped archive", name) + } + return fmt.Errorf("file '%s' does not appear to be a gzipped archive; got '%s'", name, contentType) + } + return nil +} + +// isGZipApplication checks whether the archive is of the application/x-gzip type. +func isGZipApplication(data []byte) bool { + sig := []byte("\x1F\x8B\x08") + return bytes.HasPrefix(data, sig) +} + +// LoadArchiveFiles reads in files out of an archive into memory. This function +// performs important path security checks and should always be used before +// expanding a tarball +func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) { + unzipped, err := gzip.NewReader(in) + if err != nil { + return nil, err + } + defer unzipped.Close() + + files := []*BufferedFile{} + tr := tar.NewReader(unzipped) + remainingSize := MaxDecompressedChartSize + for { + b := bytes.NewBuffer(nil) + hd, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + + if hd.FileInfo().IsDir() { + // Use this instead of hd.Typeflag because we don't have to do any + // inference chasing. + continue + } + + switch hd.Typeflag { + // We don't want to process these extension header files. + case tar.TypeXGlobalHeader, tar.TypeXHeader: + continue + } + + // Archive could contain \ if generated on Windows + delimiter := "/" + if strings.ContainsRune(hd.Name, '\\') { + delimiter = "\\" + } + + parts := strings.Split(hd.Name, delimiter) + n := strings.Join(parts[1:], delimiter) + + // Normalize the path to the / delimiter + n = strings.ReplaceAll(n, delimiter, "/") + + if path.IsAbs(n) { + return nil, errors.New("chart illegally contains absolute paths") + } + + n = path.Clean(n) + if n == "." { + // In this case, the original path was relative when it should have been absolute. + return nil, fmt.Errorf("chart illegally contains content outside the base directory: %q", hd.Name) + } + if strings.HasPrefix(n, "..") { + return nil, errors.New("chart illegally references parent directory") + } + + // In some particularly arcane acts of path creativity, it is possible to intermix + // UNIX and Windows style paths in such a way that you produce a result of the form + // c:/foo even after all the built-in absolute path checks. So we explicitly check + // for this condition. + if drivePathPattern.MatchString(n) { + return nil, errors.New("chart contains illegally named files") + } + + if parts[0] == "Chart.yaml" { + return nil, errors.New("chart yaml not in base directory") + } + + if hd.Size > remainingSize { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + if hd.Size > MaxDecompressedFileSize { + return nil, fmt.Errorf("decompressed chart file %q is larger than the maximum file size %d", hd.Name, MaxDecompressedFileSize) + } + + limitedReader := io.LimitReader(tr, remainingSize) + + bytesWritten, err := io.Copy(b, limitedReader) + if err != nil { + return nil, err + } + + remainingSize -= bytesWritten + // When the bytesWritten are less than the file size it means the limit reader ended + // copying early. Here we report that error. This is important if the last file extracted + // is the one that goes over the limit. It assumes the Size stored in the tar header + // is correct, something many applications do. + if bytesWritten < hd.Size || remainingSize <= 0 { + return nil, fmt.Errorf("decompressed chart is larger than the maximum size %d", MaxDecompressedChartSize) + } + + data := bytes.TrimPrefix(b.Bytes(), utf8bom) + + files = append(files, &BufferedFile{Name: n, Data: data}) + b.Reset() + } + + if len(files) == 0 { + return nil, errors.New("no files in chart archive") + } + return files, nil +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (*chart.Chart, error) { + files, err := LoadArchiveFiles(in) + if err != nil { + return nil, err + } + + return LoadFiles(files) +} diff --git a/internal/chart/v3/loader/archive_test.go b/internal/chart/v3/loader/archive_test.go new file mode 100644 index 000000000..d16c47563 --- /dev/null +++ b/internal/chart/v3/loader/archive_test.go @@ -0,0 +1,92 @@ +/* +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 loader + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "testing" +) + +func TestLoadArchiveFiles(t *testing.T) { + tcs := []struct { + name string + generate func(w *tar.Writer) + check func(t *testing.T, files []*BufferedFile, err error) + }{ + { + name: "empty input should return no files", + generate: func(_ *tar.Writer) {}, + check: func(t *testing.T, _ []*BufferedFile, err error) { + t.Helper() + if err.Error() != "no files in chart archive" { + t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) + } + }, + }, + { + name: "should ignore files with XGlobalHeader type", + generate: func(w *tar.Writer) { + // simulate the presence of a `pax_global_header` file like you would get when + // processing a GitHub release archive. + err := w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeXGlobalHeader, + Name: "pax_global_header", + }) + if err != nil { + t.Fatal(err) + } + + // we need to have at least one file, otherwise we'll get the "no files in chart archive" error + err = w.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: "dir/empty", + }) + if err != nil { + t.Fatal(err) + } + }, + check: func(t *testing.T, files []*BufferedFile, err error) { + t.Helper() + if err != nil { + t.Fatalf(`got unwanted error [%#v] for tar file with pax_global_header content`, err) + } + + if len(files) != 1 { + t.Fatalf(`expected to get one file but got [%v]`, files) + } + }, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + buf := &bytes.Buffer{} + gzw := gzip.NewWriter(buf) + tw := tar.NewWriter(gzw) + + tc.generate(tw) + + _ = tw.Close() + _ = gzw.Close() + + files, err := LoadArchiveFiles(buf) + tc.check(t, files, err) + }) + } +} diff --git a/internal/chart/v3/loader/directory.go b/internal/chart/v3/loader/directory.go new file mode 100644 index 000000000..947051604 --- /dev/null +++ b/internal/chart/v3/loader/directory.go @@ -0,0 +1,121 @@ +/* +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 loader + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/sympath" + "helm.sh/helm/v4/pkg/ignore" +) + +var utf8bom = []byte{0xEF, 0xBB, 0xBF} + +// DirLoader loads a chart from a directory +type DirLoader string + +// Load loads the chart +func (l DirLoader) Load() (*chart.Chart, error) { + return LoadDir(string(l)) +} + +// LoadDir loads from a directory. +// +// This loads charts only from directories. +func LoadDir(dir string) (*chart.Chart, error) { + topdir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + // Just used for errors. + c := &chart.Chart{} + + rules := ignore.Empty() + ifile := filepath.Join(topdir, ignore.HelmIgnore) + if _, err := os.Stat(ifile); err == nil { + r, err := ignore.ParseFile(ifile) + if err != nil { + return c, err + } + rules = r + } + rules.AddDefaults() + + files := []*BufferedFile{} + topdir += string(filepath.Separator) + + walk := func(name string, fi os.FileInfo, err error) error { + n := strings.TrimPrefix(name, topdir) + if n == "" { + // No need to process top level. Avoid bug with helmignore .* matching + // empty names. See issue 1779. + return nil + } + + // Normalize to / since it will also work on Windows + n = filepath.ToSlash(n) + + if err != nil { + return err + } + if fi.IsDir() { + // Directory-based ignore rules should involve skipping the entire + // contents of that directory. + if rules.Ignore(n, fi) { + return filepath.SkipDir + } + return nil + } + + // If a .helmignore file matches, skip this file. + if rules.Ignore(n, fi) { + return nil + } + + // Irregular files include devices, sockets, and other uses of files that + // are not regular files. In Go they have a file mode type bit set. + // See https://golang.org/pkg/os/#FileMode for examples. + if !fi.Mode().IsRegular() { + return fmt.Errorf("cannot load irregular file %s as it has file mode type bits set", name) + } + + if fi.Size() > MaxDecompressedFileSize { + return fmt.Errorf("chart file %q is larger than the maximum file size %d", fi.Name(), MaxDecompressedFileSize) + } + + data, err := os.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading %s: %w", n, err) + } + + data = bytes.TrimPrefix(data, utf8bom) + + files = append(files, &BufferedFile{Name: n, Data: data}) + return nil + } + if err = sympath.Walk(topdir, walk); err != nil { + return c, err + } + + return LoadFiles(files) +} diff --git a/internal/chart/v3/loader/load.go b/internal/chart/v3/loader/load.go new file mode 100644 index 000000000..30bafdad4 --- /dev/null +++ b/internal/chart/v3/loader/load.go @@ -0,0 +1,219 @@ +/* +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 loader + +import ( + "bufio" + "bytes" + "errors" + "fmt" + "io" + "maps" + "os" + "path/filepath" + "strings" + + utilyaml "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// ChartLoader loads a chart. +type ChartLoader interface { + Load() (*chart.Chart, error) +} + +// Loader returns a new ChartLoader appropriate for the given chart name +func Loader(name string) (ChartLoader, error) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + return DirLoader(name), nil + } + return FileLoader(name), nil +} + +// Load takes a string name, tries to resolve it to a file or directory, and then loads it. +// +// This is the preferred way to load a chart. It will discover the chart encoding +// and hand off to the appropriate chart reader. +// +// If a .helmignore file is present, the directory loader will skip loading any files +// matching it. But .helmignore is not evaluated when reading out of an archive. +func Load(name string) (*chart.Chart, error) { + l, err := Loader(name) + if err != nil { + return nil, err + } + return l.Load() +} + +// BufferedFile represents an archive file buffered for later processing. +type BufferedFile struct { + Name string + Data []byte +} + +// LoadFiles loads from in-memory files. +func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { + c := new(chart.Chart) + subcharts := make(map[string][]*BufferedFile) + + // do not rely on assumed ordering of files in the chart and crash + // if Chart.yaml was not coming early enough to initialize metadata + for _, f := range files { + c.Raw = append(c.Raw, &chart.File{Name: f.Name, Data: f.Data}) + if f.Name == "Chart.yaml" { + if c.Metadata == nil { + c.Metadata = new(chart.Metadata) + } + if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { + return c, fmt.Errorf("cannot load Chart.yaml: %w", err) + } + // While the documentation says the APIVersion is required, in practice there + // are cases where that's not enforced. Since this package set is for v3 charts, + // when this function is used v3 is automatically added when not present. + if c.Metadata.APIVersion == "" { + c.Metadata.APIVersion = chart.APIVersionV3 + } + } + } + for _, f := range files { + switch { + case f.Name == "Chart.yaml": + // already processed + continue + case f.Name == "Chart.lock": + c.Lock = new(chart.Lock) + if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { + return c, fmt.Errorf("cannot load Chart.lock: %w", err) + } + case f.Name == "values.yaml": + values, err := LoadValues(bytes.NewReader(f.Data)) + if err != nil { + return c, fmt.Errorf("cannot load values.yaml: %w", err) + } + c.Values = values + case f.Name == "values.schema.json": + c.Schema = f.Data + + case strings.HasPrefix(f.Name, "templates/"): + c.Templates = append(c.Templates, &chart.File{Name: f.Name, Data: f.Data}) + case strings.HasPrefix(f.Name, "charts/"): + if filepath.Ext(f.Name) == ".prov" { + c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) + continue + } + + fname := strings.TrimPrefix(f.Name, "charts/") + cname := strings.SplitN(fname, "/", 2)[0] + subcharts[cname] = append(subcharts[cname], &BufferedFile{Name: fname, Data: f.Data}) + default: + c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) + } + } + + if c.Metadata == nil { + return c, errors.New("Chart.yaml file is missing") //nolint:staticcheck + } + + if err := c.Validate(); err != nil { + return c, err + } + + for n, files := range subcharts { + var sc *chart.Chart + var err error + switch { + case strings.IndexAny(n, "_.") == 0: + continue + case filepath.Ext(n) == ".tgz": + file := files[0] + if file.Name != n { + return c, fmt.Errorf("error unpacking subchart tar in %s: expected %s, got %s", c.Name(), n, file.Name) + } + // Untar the chart and add to c.Dependencies + sc, err = LoadArchive(bytes.NewBuffer(file.Data)) + default: + // We have to trim the prefix off of every file, and ignore any file + // that is in charts/, but isn't actually a chart. + buff := make([]*BufferedFile, 0, len(files)) + for _, f := range files { + parts := strings.SplitN(f.Name, "/", 2) + if len(parts) < 2 { + continue + } + f.Name = parts[1] + buff = append(buff, f) + } + sc, err = LoadFiles(buff) + } + + if err != nil { + return c, fmt.Errorf("error unpacking subchart %s in %s: %w", n, c.Name(), err) + } + c.AddDependency(sc) + } + + return c, nil +} + +// LoadValues loads values from a reader. +// +// The reader is expected to contain one or more YAML documents, the values of which are merged. +// And the values can be either a chart's default values or a user-supplied values. +func LoadValues(data io.Reader) (map[string]interface{}, error) { + values := map[string]interface{}{} + reader := utilyaml.NewYAMLReader(bufio.NewReader(data)) + for { + currentMap := map[string]interface{}{} + raw, err := reader.Read() + if err != nil { + if err == io.EOF { + break + } + return nil, fmt.Errorf("error reading yaml document: %w", err) + } + if err := yaml.Unmarshal(raw, ¤tMap); err != nil { + return nil, fmt.Errorf("cannot unmarshal yaml document: %w", err) + } + values = MergeMaps(values, currentMap) + } + return values, nil +} + +// MergeMaps merges two maps. If a key exists in both maps, the value from b will be used. +// If the value is a map, the maps will be merged recursively. +func MergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + maps.Copy(out, a) + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = MergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} diff --git a/internal/chart/v3/loader/load_test.go b/internal/chart/v3/loader/load_test.go new file mode 100644 index 000000000..e770923ff --- /dev/null +++ b/internal/chart/v3/loader/load_test.go @@ -0,0 +1,711 @@ +/* +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 loader + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "io" + "log" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + "time" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +func TestLoadDir(t *testing.T) { + l, err := Loader("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) +} + +func TestLoadDirWithDevNull(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test only works on unix systems with /dev/null present") + } + + l, err := Loader("testdata/frobnitz_with_dev_null") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + if _, err := l.Load(); err == nil { + t.Errorf("packages with an irregular file (/dev/null) should not load") + } +} + +func TestLoadDirWithSymlink(t *testing.T) { + sym := filepath.Join("..", "LICENSE") + link := filepath.Join("testdata", "frobnitz_with_symlink", "LICENSE") + + if err := os.Symlink(sym, link); err != nil { + t.Fatal(err) + } + + defer os.Remove(link) + + l, err := Loader("testdata/frobnitz_with_symlink") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) +} + +func TestBomTestData(t *testing.T) { + testFiles := []string{"frobnitz_with_bom/.helmignore", "frobnitz_with_bom/templates/template.tpl", "frobnitz_with_bom/Chart.yaml"} + for _, file := range testFiles { + data, err := os.ReadFile("testdata/" + file) + if err != nil || !bytes.HasPrefix(data, utf8bom) { + t.Errorf("Test file has no BOM or is invalid: testdata/%s", file) + } + } + + archive, err := os.ReadFile("testdata/frobnitz_with_bom.tgz") + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + unzipped, err := gzip.NewReader(bytes.NewReader(archive)) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + defer unzipped.Close() + for _, testFile := range testFiles { + data := make([]byte, 3) + err := unzipped.Reset(bytes.NewReader(archive)) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + tr := tar.NewReader(unzipped) + for { + file, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } + if file != nil && strings.EqualFold(file.Name, testFile) { + _, err := tr.Read(data) + if err != nil { + t.Fatalf("Error reading archive frobnitz_with_bom.tgz: %s", err) + } else { + break + } + } + } + if !bytes.Equal(data, utf8bom) { + t.Fatalf("Test file has no BOM or is invalid: frobnitz_with_bom.tgz/%s", testFile) + } + } +} + +func TestLoadDirWithUTFBOM(t *testing.T) { + l, err := Loader("testdata/frobnitz_with_bom") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) + verifyBomStripped(t, c.Files) +} + +func TestLoadArchiveWithUTFBOM(t *testing.T) { + l, err := Loader("testdata/frobnitz_with_bom.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) + verifyDependenciesLock(t, c) + verifyBomStripped(t, c.Files) +} + +func TestLoadFile(t *testing.T) { + l, err := Loader("testdata/frobnitz-1.2.3.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyDependencies(t, c) +} + +func TestLoadFiles(t *testing.T) { + goodFiles := []*BufferedFile{ + { + Name: "Chart.yaml", + Data: []byte(`apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +`), + }, + { + Name: "values.yaml", + Data: []byte("var: some values"), + }, + { + Name: "values.schema.json", + Data: []byte("type: Values"), + }, + { + Name: "templates/deployment.yaml", + Data: []byte("some deployment"), + }, + { + Name: "templates/service.yaml", + Data: []byte("some service"), + }, + } + + c, err := LoadFiles(goodFiles) + if err != nil { + t.Errorf("Expected good files to be loaded, got %v", err) + } + + if c.Name() != "frobnitz" { + t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Name()) + } + + if c.Values["var"] != "some values" { + t.Error("Expected chart values to be populated with default values") + } + + if len(c.Raw) != 5 { + t.Errorf("Expected %d files, got %d", 5, len(c.Raw)) + } + + if !bytes.Equal(c.Schema, []byte("type: Values")) { + t.Error("Expected chart schema to be populated with default values") + } + + if len(c.Templates) != 2 { + t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) + } + + if _, err = LoadFiles([]*BufferedFile{}); err == nil { + t.Fatal("Expected err to be non-nil") + } + if err.Error() != "Chart.yaml file is missing" { + t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) + } +} + +// Test the order of file loading. The Chart.yaml file needs to come first for +// later comparison checks. See https://github.com/helm/helm/pull/8948 +func TestLoadFilesOrder(t *testing.T) { + goodFiles := []*BufferedFile{ + { + Name: "requirements.yaml", + Data: []byte("dependencies:"), + }, + { + Name: "values.yaml", + Data: []byte("var: some values"), + }, + + { + Name: "templates/deployment.yaml", + Data: []byte("some deployment"), + }, + { + Name: "templates/service.yaml", + Data: []byte("some service"), + }, + { + Name: "Chart.yaml", + Data: []byte(`apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +`), + }, + } + + // Capture stderr to make sure message about Chart.yaml handle dependencies + // is not present + r, w, err := os.Pipe() + if err != nil { + t.Fatalf("Unable to create pipe: %s", err) + } + stderr := log.Writer() + log.SetOutput(w) + defer func() { + log.SetOutput(stderr) + }() + + _, err = LoadFiles(goodFiles) + if err != nil { + t.Errorf("Expected good files to be loaded, got %v", err) + } + w.Close() + + var text bytes.Buffer + io.Copy(&text, r) + if text.String() != "" { + t.Errorf("Expected no message to Stderr, got %s", text.String()) + } + +} + +// Packaging the chart on a Windows machine will produce an +// archive that has \\ as delimiters. Test that we support these archives +func TestLoadFileBackslash(t *testing.T) { + c, err := Load("testdata/frobnitz_backslash-1.2.3.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyChartFileAndTemplate(t, c, "frobnitz_backslash") + verifyChart(t, c) + verifyDependencies(t, c) +} + +func TestLoadV3WithReqs(t *testing.T) { + l, err := Loader("testdata/frobnitz.v3.reqs") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + c, err := l.Load() + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyDependencies(t, c) + verifyDependenciesLock(t, c) +} + +func TestLoadInvalidArchive(t *testing.T) { + tmpdir := t.TempDir() + + writeTar := func(filename, internalPath string, body []byte) { + dest, err := os.Create(filename) + if err != nil { + t.Fatal(err) + } + zipper := gzip.NewWriter(dest) + tw := tar.NewWriter(zipper) + + h := &tar.Header{ + Name: internalPath, + Mode: 0755, + Size: int64(len(body)), + ModTime: time.Now(), + } + if err := tw.WriteHeader(h); err != nil { + t.Fatal(err) + } + if _, err := tw.Write(body); err != nil { + t.Fatal(err) + } + tw.Close() + zipper.Close() + dest.Close() + } + + for _, tt := range []struct { + chartname string + internal string + expectError string + }{ + {"illegal-dots.tgz", "../../malformed-helm-test", "chart illegally references parent directory"}, + {"illegal-dots2.tgz", "/foo/../../malformed-helm-test", "chart illegally references parent directory"}, + {"illegal-dots3.tgz", "/../../malformed-helm-test", "chart illegally references parent directory"}, + {"illegal-dots4.tgz", "./../../malformed-helm-test", "chart illegally references parent directory"}, + {"illegal-name.tgz", "./.", "chart illegally contains content outside the base directory"}, + {"illegal-name2.tgz", "/./.", "chart illegally contains content outside the base directory"}, + {"illegal-name3.tgz", "missing-leading-slash", "chart illegally contains content outside the base directory"}, + {"illegal-name4.tgz", "/missing-leading-slash", "Chart.yaml file is missing"}, + {"illegal-abspath.tgz", "//foo", "chart illegally contains absolute paths"}, + {"illegal-abspath2.tgz", "///foo", "chart illegally contains absolute paths"}, + {"illegal-abspath3.tgz", "\\\\foo", "chart illegally contains absolute paths"}, + {"illegal-abspath3.tgz", "\\..\\..\\foo", "chart illegally references parent directory"}, + + // Under special circumstances, this can get normalized to things that look like absolute Windows paths + {"illegal-abspath4.tgz", "\\.\\c:\\\\foo", "chart contains illegally named files"}, + {"illegal-abspath5.tgz", "/./c://foo", "chart contains illegally named files"}, + {"illegal-abspath6.tgz", "\\\\?\\Some\\windows\\magic", "chart illegally contains absolute paths"}, + } { + illegalChart := filepath.Join(tmpdir, tt.chartname) + writeTar(illegalChart, tt.internal, []byte("hello: world")) + _, err := Load(illegalChart) + if err == nil { + t.Fatal("expected error when unpacking illegal files") + } + if !strings.Contains(err.Error(), tt.expectError) { + t.Errorf("Expected error to contain %q, got %q for %s", tt.expectError, err.Error(), tt.chartname) + } + } + + // Make sure that absolute path gets interpreted as relative + illegalChart := filepath.Join(tmpdir, "abs-path.tgz") + writeTar(illegalChart, "/Chart.yaml", []byte("hello: world")) + _, err := Load(illegalChart) + if err.Error() != "validation: chart.metadata.name is required" { + t.Error(err) + } + + // And just to validate that the above was not spurious + illegalChart = filepath.Join(tmpdir, "abs-path2.tgz") + writeTar(illegalChart, "files/whatever.yaml", []byte("hello: world")) + _, err = Load(illegalChart) + if err.Error() != "Chart.yaml file is missing" { + t.Errorf("Unexpected error message: %s", err) + } + + // Finally, test that drive letter gets stripped off on Windows + illegalChart = filepath.Join(tmpdir, "abs-winpath.tgz") + writeTar(illegalChart, "c:\\Chart.yaml", []byte("hello: world")) + _, err = Load(illegalChart) + if err.Error() != "validation: chart.metadata.name is required" { + t.Error(err) + } +} + +func TestLoadValues(t *testing.T) { + testCases := map[string]struct { + data []byte + expctedValues map[string]interface{} + }{ + "It should load values correctly": { + data: []byte(` +foo: + image: foo:v1 +bar: + version: v2 +`), + expctedValues: map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "foo:v1", + }, + "bar": map[string]interface{}{ + "version": "v2", + }, + }, + }, + "It should load values correctly with multiple documents in one file": { + data: []byte(` +foo: + image: foo:v1 +bar: + version: v2 +--- +foo: + image: foo:v2 +`), + expctedValues: map[string]interface{}{ + "foo": map[string]interface{}{ + "image": "foo:v2", + }, + "bar": map[string]interface{}{ + "version": "v2", + }, + }, + }, + } + for testName, testCase := range testCases { + t.Run(testName, func(tt *testing.T) { + values, err := LoadValues(bytes.NewReader(testCase.data)) + if err != nil { + tt.Fatal(err) + } + if !reflect.DeepEqual(values, testCase.expctedValues) { + tt.Errorf("Expected values: %v, got %v", testCase.expctedValues, values) + } + }) + } +} + +func TestMergeValues(t *testing.T) { + nestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "stuff", + }, + } + anotherNestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + flatMap := map[string]interface{}{ + "foo": "bar", + "baz": "stuff", + } + anotherFlatMap := map[string]interface{}{ + "testing": "fun", + } + + testMap := MergeMaps(flatMap, nestedMap) + equal := reflect.DeepEqual(testMap, nestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + } + + testMap = MergeMaps(nestedMap, flatMap) + equal = reflect.DeepEqual(testMap, flatMap) + if !equal { + t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + } + + testMap = MergeMaps(nestedMap, anotherNestedMap) + equal = reflect.DeepEqual(testMap, anotherNestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + } + + testMap = MergeMaps(anotherFlatMap, anotherNestedMap) + expectedMap := map[string]interface{}{ + "testing": "fun", + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + equal = reflect.DeepEqual(testMap, expectedMap) + if !equal { + t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + } +} + +func verifyChart(t *testing.T, c *chart.Chart) { + t.Helper() + if c.Name() == "" { + t.Fatalf("No chart metadata found on %v", c) + } + t.Logf("Verifying chart %s", c.Name()) + if len(c.Templates) != 1 { + t.Errorf("Expected 1 template, got %d", len(c.Templates)) + } + + numfiles := 6 + if len(c.Files) != numfiles { + t.Errorf("Expected %d extra files, got %d", numfiles, len(c.Files)) + for _, n := range c.Files { + t.Logf("\t%s", n.Name) + } + } + + if len(c.Dependencies()) != 2 { + t.Errorf("Expected 2 dependencies, got %d (%v)", len(c.Dependencies()), c.Dependencies()) + for _, d := range c.Dependencies() { + t.Logf("\tSubchart: %s\n", d.Name()) + } + } + + expect := map[string]map[string]string{ + "alpine": { + "version": "0.1.0", + }, + "mariner": { + "version": "4.3.2", + }, + } + + for _, dep := range c.Dependencies() { + if dep.Metadata == nil { + t.Fatalf("expected metadata on dependency: %v", dep) + } + exp, ok := expect[dep.Name()] + if !ok { + t.Fatalf("Unknown dependency %s", dep.Name()) + } + if exp["version"] != dep.Metadata.Version { + t.Errorf("Expected %s version %s, got %s", dep.Name(), exp["version"], dep.Metadata.Version) + } + } + +} + +func verifyDependencies(t *testing.T, c *chart.Chart) { + t.Helper() + if len(c.Metadata.Dependencies) != 2 { + t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) + } + tests := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, + {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, + } + for i, tt := range tests { + d := c.Metadata.Dependencies[i] + if d.Name != tt.Name { + t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) + } + if d.Version != tt.Version { + t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + } + if d.Repository != tt.Repository { + t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) + } + } +} + +func verifyDependenciesLock(t *testing.T, c *chart.Chart) { + t.Helper() + if len(c.Metadata.Dependencies) != 2 { + t.Errorf("Expected 2 dependencies, got %d", len(c.Metadata.Dependencies)) + } + tests := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, + {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, + } + for i, tt := range tests { + d := c.Metadata.Dependencies[i] + if d.Name != tt.Name { + t.Errorf("Expected dependency named %q, got %q", tt.Name, d.Name) + } + if d.Version != tt.Version { + t.Errorf("Expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, d.Version) + } + if d.Repository != tt.Repository { + t.Errorf("Expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, d.Repository) + } + } +} + +func verifyFrobnitz(t *testing.T, c *chart.Chart) { + t.Helper() + verifyChartFileAndTemplate(t, c, "frobnitz") +} + +func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { + t.Helper() + if c.Metadata == nil { + t.Fatal("Metadata is nil") + } + if c.Name() != name { + t.Errorf("Expected %s, got %s", name, c.Name()) + } + if len(c.Templates) != 1 { + t.Fatalf("Expected 1 template, got %d", len(c.Templates)) + } + if c.Templates[0].Name != "templates/template.tpl" { + t.Errorf("Unexpected template: %s", c.Templates[0].Name) + } + if len(c.Templates[0].Data) == 0 { + t.Error("No template data.") + } + if len(c.Files) != 6 { + t.Fatalf("Expected 6 Files, got %d", len(c.Files)) + } + if len(c.Dependencies()) != 2 { + t.Fatalf("Expected 2 Dependency, got %d", len(c.Dependencies())) + } + if len(c.Metadata.Dependencies) != 2 { + t.Fatalf("Expected 2 Dependencies.Dependency, got %d", len(c.Metadata.Dependencies)) + } + if len(c.Lock.Dependencies) != 2 { + t.Fatalf("Expected 2 Lock.Dependency, got %d", len(c.Lock.Dependencies)) + } + + for _, dep := range c.Dependencies() { + switch dep.Name() { + case "mariner": + case "alpine": + if len(dep.Templates) != 1 { + t.Fatalf("Expected 1 template, got %d", len(dep.Templates)) + } + if dep.Templates[0].Name != "templates/alpine-pod.yaml" { + t.Errorf("Unexpected template: %s", dep.Templates[0].Name) + } + if len(dep.Templates[0].Data) == 0 { + t.Error("No template data.") + } + if len(dep.Files) != 1 { + t.Fatalf("Expected 1 Files, got %d", len(dep.Files)) + } + if len(dep.Dependencies()) != 2 { + t.Fatalf("Expected 2 Dependency, got %d", len(dep.Dependencies())) + } + default: + t.Errorf("Unexpected dependency %s", dep.Name()) + } + } +} + +func verifyBomStripped(t *testing.T, files []*chart.File) { + t.Helper() + for _, file := range files { + if bytes.HasPrefix(file.Data, utf8bom) { + t.Errorf("Byte Order Mark still present in processed file %s", file.Name) + } + } +} diff --git a/internal/chart/v3/loader/testdata/LICENSE b/internal/chart/v3/loader/testdata/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/loader/testdata/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/albatross/Chart.yaml b/internal/chart/v3/loader/testdata/albatross/Chart.yaml new file mode 100644 index 000000000..eeef737ff --- /dev/null +++ b/internal/chart/v3/loader/testdata/albatross/Chart.yaml @@ -0,0 +1,4 @@ +name: albatross +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/albatross/values.yaml b/internal/chart/v3/loader/testdata/albatross/values.yaml new file mode 100644 index 000000000..3121cd7ce --- /dev/null +++ b/internal/chart/v3/loader/testdata/albatross/values.yaml @@ -0,0 +1,4 @@ +albatross: "true" + +global: + author: Coleridge diff --git a/internal/chart/v3/loader/testdata/frobnitz-1.2.3.tgz b/internal/chart/v3/loader/testdata/frobnitz-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..de28e4120df739fc1e9a246cfce150ccb1bcea1e GIT binary patch literal 3420 zcmV-i4WsfOiwFP!000001MOT3coRh)51?=cc|Q(>gCXVPA?-HL4Xte1H?VZrX;Y&;t=T0r3+Qp`cQbhst+|p3tv&C+7)zB2)oI5d{kfViit#w7uEg zByB@60%?l4zYn^bo!Ob${m<{e|Ns1F#?G-;h7xAPs_G9~1hdH`9(87uw&u|fmCmF! z;3mCZi{mP-PH!?8Rlrn_Z3@7{3j_y%%4H`wiXm>AFI4*P6n4M-F(;&5l!IY8816bm z0pmvF&E>BPmcJ24-34gF-H^KQ2baGr@mO_3w?je0Ai)1*C&39A&DwIpUPmbY8??G$ z{u_-th5wNN3Ehw(A!DN;AFl==1~7yR#sh+OQw&5G0LXLmpN+W&wait2r%;oSw{etP zkdntZDITDIL?9hgS2J0M=`n*woddmjEJyluNuVI{2k++4*98jF50WJbQtcurMj%AJ zP)qrYaY8T@nWJC~BwUhmfG8)8^a8p#u!d5oaZe`ef-D0;D$T>1w=k@gCB3z7;#m)8 z^Q*hMRE?cwW32?IcCup0VvgDw)Rawp=DKdhjrq6{b2AP#!7!{qh_6nn3FZqNf!lM{Fm7GC3h9CHT(eq~D!lZs?w@;C2wu|7pWB$5_(SF(F?FcO4=9KePS@`%kM^ z^nWA}Q0fe_sL-pRX{4HM9v5T;Owz+PnnLGDOLzfE@opL&1#}Ol@co`ZfdA=f$*IFL zQ|qISko?!1g7|MR8E}RFk$_L^01?{;oh(g4PNDW62m$_&PPL?Dq+%{I{B?xlzus`e z_z$uP6#hp7QDCs&M^o3Z1$_2BkZgNQt&WNUV|l^>>&k?IGyymX9=Te;BW9BGr@&lD zvu@FnP^$;C93bTjkVBS+os9-OgvgXY|57 zO8hSp2$p}A91zNKg)s^kO8@J$c#!;YtvpA_h(SuaxgR)VK&z#?S<=@jG12X2Aeh`PghgQXI62ZalhEitma+ zW27LwfTyI2kitODz8r@Id6eJ;gxbb>#3a@Z+ysZzkbo45zi=re%3}iSqO*aW5(g&y z1&i$?p`Gy10+6!-JIjfe)!A|rV^D3;mmk-k0gDYh zaYG9&jJOH^!#Wyp>-L|_f5rZ*fBd@qT?8-a?zk7+y8O+!R;S2668J6hzf)%*r2TI& zR@?t(y-KS`4xlpsJ0hsl{+GmF+x)l46SAa!A$?$nd909Q&{vwT+w|9H?xoM6o<@mRvavL6Fe^s6{DYCWbFqGa9+Gwya^-%jdY))E*b=(`LBl`lb>!GqWms0OZsI~sQy-=|_` z#Z%usx;1)4&!V?Jm~gRR`UC5da*iK=LOb=1o@HBi^*yzI-}AH1Zo%HInj5$2v;99B zyK;x)|8ZvgsfX03Qf<*4^uw2R!n%O5ec##GEb7zIv>3-kR%GeLuW1 zcR|IxpZ0gMojB78J!sW81*KW42OIds-zB&S|EouH!cfO;_rmxxg;GA9_q0m&Gvw^nJHkjtfPashSS7N|a` zG64K>C8_fwpED>=bpw6RID586&AXvZY#;|k$qOS{nzBjl@sA>FhC}4hY5?ZTk>!@M zNLBiVorBgZ{k0i18S)EBnTOG%jz9+z}M7>>fuj|9z_g zW2E(eci{8YLHqrQG)edS2Y z=A*j#dH9<1_rEfJPoD|>N}E${Hl5vh?Cn*%S5Dj4JhlCpl=8jT8h5a4n^dtOe`JR? zZ2JxgZ53@&)~#i#=p(|UBZbWAOK zXnE?ZCrwZG2Hc*WO$%P=>3wV9^0NC|waz^DN>5#va30c3ss=&Hnljh<@X8gi-@Xu(IDT2Jq#?t@2b@o|fy|3Z)k_(G+r-9|0{i&gc*ycE^uj)UmVyD}=9>>X4@^gH_c{$#Lt2ld4DImRy2iWVPT zwBgE1zO=>K{EbQLSJ|3Gm%+;a^{?D&?Dlyn-K6AV)t>gJHdk#b=yml-Y#wuB?6L z?JX;NpPMnf|KA9poEdlRWbfA#D~(@XsVvU;By+DWt$R5!%z1SCXD7EZ zN5(QQe7ej2{ZqwHw0vjr5}SS8rTEj64L_WDeyYdpD31AM^zKvTM~}buuZQ(J=l8zw zL6Z|d7Ckeoc;%{5*J8fo5`H@L?Uv17d{;5{N-Fisl$V7slQ!CO-fIT`-J-bM89nRL zwl#Uz-)p&U&e`!_UtB(=s?&-K9iJ{vxPS8Ai`Po_wi~v-vaso#PYb7L`b^uL`XB3# zV{Ok4jt{p(`bgnoR`Ci?T26H|M? zd3x{%jW2(`qTPV$_OCoyKk3s8zKnCIUvAs75xuYVM?KULivK1&c>Rx2ng1OjBn-%R(f)m35;fvBG6Ry- z38*6gl)&)u=qyp=+X5#qFAvKzU@Yf|)g!sZqBM=1?N_r%17LD#`837k)RkEm7Z(>R zrD|&KL?gEdnq-(#{8E`x`ta8fEnXOc=WhA4%D zq*zFT_(GEIB4t|5e|aOw`l#d9?LQ;RpxA$rKtTRsjri5ZZOGqb&@1wf1nQ7~V3NGx z4$oeIp~wG&_kS{Lab^Ffh+rtBX;zG_i4ij}E3lM8lgDy2shK_dF8Wp}2tNY+4{OA) zHbU`VcSHQoXx1s~|09AZUY%X%_b0ag+{GLR85P@q780tE^h0RIOcqcM5_cmM#VXTU=M literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/.helmignore b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/Chart.yaml new file mode 100644 index 000000000..1b63fc3e2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/LICENSE b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/README.md b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..21ae20aad --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/docs/README.md b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/icon.svg b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/values.yaml b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz.v3.reqs/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/frobnitz/.helmignore b/internal/chart/v3/loader/testdata/frobnitz/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz/Chart.lock b/internal/chart/v3/loader/testdata/frobnitz/Chart.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/loader/testdata/frobnitz/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz/Chart.yaml new file mode 100644 index 000000000..1b63fc3e2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz/LICENSE b/internal/chart/v3/loader/testdata/frobnitz/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/frobnitz/README.md b/internal/chart/v3/loader/testdata/frobnitz/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..21ae20aad --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5c6bc4dcb370466921bbde3c433614bed548313d GIT binary patch literal 910 zcmV;919AKxiwFP!000001MQe;XcIvgh9ly&p#D%nsMs-rcQ%{s1*uoOC|E1h3uzIj z>9pCpm%Ec{#S29gR7CN>`vMg~tXln{cu*A)Zxj_g@kdp#o~;@WbarD(Tah9*5pf;@ zJDZ)IZd$kcAQSzCpeT6& z*YaKYng3jWXeyJDWh;gr0%bg-Lk)$%k4eE4Avj{C+eWYNm?Vh@ttDbJPu;c%?p|mtzAg=Vku(IR2|N9^2Gx1HbS8ycFc9|EGf{ z`qwW^pS!MDTr%g+V>IXg0v~ksmt}z?djQd2l4a`uX(4lY`$VC2&8NiuM0kR z%`9J-dn5{G?Uol0Iltky)T+zUO;r!)Uzz^A-#~QzMdz&38UD5VdW^p-ZdtVI4V%AW z@czneV;@q_uD zl)9>uC+d1mIJ0Mvzxp5(TfabAdpY~zC$K&KG~W6Cy-n3qz%fZ5@nK77*sa>(t8Uj- zZk*P(X6}x?hYrDti(_V1^g88RGw|q|U3E8Sy)F1syX5iM+^@qbGPHsx?aTw;@}`u0 zy_1(w`z`BoJoD1#s;J%T{Z;uzY4}{EblyItW^2a>WyTG?@n|f3^sJkA{KpQSe(6`@ z?~q*o7?JH3lD#|yTfp!8zrYhqp#Gl*e%7B{A}DI8MaW{Y*%IkMbN){ffF}COd{CzT zpALG1iBZJ{?3Y|3Py7mq9g#?9?0pG@t*AP5oaC(C@#r&>M_G#W1E~keC5( zW + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz/values.yaml b/internal/chart/v3/loader/testdata/frobnitz/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash-1.2.3.tgz b/internal/chart/v3/loader/testdata/frobnitz_backslash-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..dfbe88a735b256fc6c82c62beea78c78b557fc0c GIT binary patch literal 3434 zcmV-w4VCgAiwFP!000001MOT3coS6?4pd&OTqZs10a>2@fr?B3tf6NYP z2W4Ye4u+mSFBIt5UNrukpSA6)+)J;x}UyB{)|2Lbq>WG6TQqghK6c5lpqE-@9)^S>cX)&EjnFZZBEJu2+ice7b!8=2Ab?kJc935R4RAd;k!g+K>~)iV$E&Pit-kYyl9rg>QZ6oxgk zq`SdMJnP~tKEoHt=vY}c#!PTZJ1f>KN;DKuQWo)=1D%TNb8$W9WNb=;VOW6>Z=KW< z%oR8S-JLkk9x`Jtv7ZDIvO`WP@a}txes?Cixu0nnza9bXf6CbONv70POvn{N(?-zv z4;g!Z|4*xy?SD8>YswkqQK4Hw)5suOTn@+xm}Gq|G==t&ZEypU;+-_w3g{e6X8B!# z0Q^r)NlG4@o*cS1g7RN$@Z-P1V9?9_4+p%;28h@eXlH2>ax%65KnTG9iOHrBX~~#_ z3{@M!_^;L7GX5i)1eyO~KqMIHi_Dbud;zal4hH0u;S38iu*%K_4J0CLFFu(DBrhY-0E=s)X>rp2;)@r7)I!h`_)_e60a zOanGP|A(t}IywIr4%}Y<8J^TohT}J90fXsZqsANRU#nK*8rlAb1ClxC?W~I?ff)ji z&e5)0GR-tgAcrstm|dO-P?TUSw2MR{6axs~`=Pn7LPk?@d;WWd7enzHXuSP5>eRCR z4-10oKLpvJp!Ofv)#ZQmNKUKK>g4giupnUm$D3Jbknj08)#u24uT;;rpRAl;UFsa8i`Cy91?&aQ-G)nbexaL1SG{l zix|eEGo`Gqm?}mF!VP#zY6uw&6zqGluONpK?0`^OSeIDDT7i?`kQow?LGe{CB|>>j zU>$TOuu|f{gs)<;eYx1r|}sfJ!<~ zHW$r;#_(SoEg5)Bu+3{!8^QRmGwS{MuU5r-UvTP^H}k(+y=UEhiea1K z-j>~`4y>AA_2jpY?1~!Kukg+Hr&Z+5y>D}3*6Guat7pI7uYA}3L1kMGJ-6V(PVAkU z#j)EzJ^X{d7k51F@8_qMJ)kU0wnTN+j$P9o>j@?gdgs7QbNeCXi`A}ii*~mD^3m!g z8*kPO`rz8)WmQXlJlx%K=6rW_p*3IUm1ZdJZ{{0+haf2b>&A7O+(r}PKXFNz9RGy@ zjnKc}XxguDBbfeit-t>DT8-@gg#``bKaDbrV#qi!o+XtIC=ete5aNmxPgFR6J~k23 zkl-OU79Ci)Fd7~4j0kvsBT1StLt0v;0s!H3Vp+9OB1W;uiC_pDB%{sl2DPO_E}ODI zumH?;u>u5AO@Yp@?T25h0QeG2(zA<#Pd}Nuo_a5K;X;*?cS4KUQx1xcfH0n=DT~xG z-*B>OEJUHL3SeG|45uU_HR&CH_FJx$Lr{Wu0S)Cqmx_AgVNIM4i}kFb@s6d(ftjip zGsVR4G=xr7lqwbxK{Y$aor zj`pZuj>_(G+@Am5)qv5``oBiUeDx88{~BE5ng3@r>eaY3{;S4~GXKK@6#sd4A}(*B z?43>Bmq&i}`j+bPrX43WOLOpzm+pOe>cN522A8&>I&QzP@6=oC53HMWs7-R0Nh2yt zZnW%b+C8IcYwq~29oa5j<10r^er@xf2ignSmPcl;RpA}y_HOy`#!Z{p4LRRzsv4R1 z%nOl6DpLCPGIdKXe;_~km9vH?1_170zgBtA_jA8FEWiBT_8rnsz1&aJGpUVY&6G>4 z@3{$!zS&&!qJ8L_l8H9loU)Pn@6 zgYWgf=NVgGYN>NZ<-C-L{>N73_nwgdpjq+#k`am>-GB7M&}g)2|4*m`XpU)pw4?zY4&>n#yc<*@p{L#lV_dw*6+M-*4o9PCoIqh@Z=u^~KlaAi0k0)g>%X{GU6l55dWj~b zPbD$Nesa&JXLm6tCNs}}vfujMlSPlWdwb<7i*?GCxN|dg-=BYOw##TMivD@xfwIbz zr(gZ&L)v{y2V8zX;>-_)PcJB1w|>Hn=x@3BACG;rbI0f3R!zQ^O#M9TCE<(2ZPu)J zTf={~EvmFfEx59KW6sTY+ihNSVd_^E`Lk-euf5#usiOFMXO>jlC@$$dW=nNJt3{s_ z%u)@Uvm^Q6=DnvnT^Pn!kL&bU|LaErWSrc>AMiTQ}~tB&9EWsC3NfPG8^i#EzrGnd=1`vP%B45=PLE z4c|_G_EJLffH%&Ke81(@&(?MxI@kK8E8|CPYTg&IHswp5+O?n$b+{U(TsC~`_<>z~ zn{ApiEn1u`%Tx?(4*mzQ|0HV(Oj#jdK1!EV!NiL%9ensQwMNtp5_l<@bLW zP?zDUw*pj30{NDKOCPSdtL8k(s6zn$i=Q;X_-tFq+6cyf1CIOiU#pYne})C|LvtOp z?`tm!D)HknLlc$pNc{j6Fnk=^OHg?~e3O%tgXQQjmb1lZk=|lZnaU&fiz3pvm#4N| znqsm732S0wV`HRJRsHYJczOhKC{nN|sI&%L1?-Tr*#++picC-mkQY>m=&X2g#!6zc zcMhlc7{|v*hr}{*Mye!kr8p!Ha#0N`;FkV+tJkaRYRA#Om7?hc6+>jZRWS|g;32A< zlq%5}q+0YrYT+O$RuV7Xkiuk|1Q*Qn+7Utz&$NYkvCQWFzpU_tFt3RMov(WGkO z!aL|)B_s3*!2b|r{2C${|26*kKNSD#xe&3;~s7M5GS`c+2Gcp$r)^WXO;qLxv0)GGxe*Awz}?88T$ZkkJhI MAHOr*RseVa0Fp7mZvX%Q literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/.helmignore b/internal/chart/v3/loader/testdata/frobnitz_backslash/.helmignore new file mode 100755 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.lock b/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.lock new file mode 100755 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.yaml new file mode 100755 index 000000000..6a952e333 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz_backslash +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz_backslash/INSTALL.txt new file mode 100755 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/LICENSE b/internal/chart/v3/loader/testdata/frobnitz_backslash/LICENSE new file mode 100755 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/README.md b/internal/chart/v3/loader/testdata/frobnitz_backslash/README.md new file mode 100755 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/_ignore_me new file mode 100755 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml new file mode 100755 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/README.md new file mode 100755 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml new file mode 100755 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml new file mode 100755 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100755 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml new file mode 100755 index 000000000..0ac5ca6a8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service | quote }} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml new file mode 100755 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz new file mode 100755 index 0000000000000000000000000000000000000000..5c6bc4dcb370466921bbde3c433614bed548313d GIT binary patch literal 910 zcmV;919AKxiwFP!000001MQe;XcIvgh9ly&p#D%nsMs-rcQ%{s1*uoOC|E1h3uzIj z>9pCpm%Ec{#S29gR7CN>`vMg~tXln{cu*A)Zxj_g@kdp#o~;@WbarD(Tah9*5pf;@ zJDZ)IZd$kcAQSzCpeT6& z*YaKYng3jWXeyJDWh;gr0%bg-Lk)$%k4eE4Avj{C+eWYNm?Vh@ttDbJPu;c%?p|mtzAg=Vku(IR2|N9^2Gx1HbS8ycFc9|EGf{ z`qwW^pS!MDTr%g+V>IXg0v~ksmt}z?djQd2l4a`uX(4lY`$VC2&8NiuM0kR z%`9J-dn5{G?Uol0Iltky)T+zUO;r!)Uzz^A-#~QzMdz&38UD5VdW^p-ZdtVI4V%AW z@czneV;@q_uD zl)9>uC+d1mIJ0Mvzxp5(TfabAdpY~zC$K&KG~W6Cy-n3qz%fZ5@nK77*sa>(t8Uj- zZk*P(X6}x?hYrDti(_V1^g88RGw|q|U3E8Sy)F1syX5iM+^@qbGPHsx?aTw;@}`u0 zy_1(w`z`BoJoD1#s;J%T{Z;uzY4}{EblyItW^2a>WyTG?@n|f3^sJkA{KpQSe(6`@ z?~q*o7?JH3lD#|yTfp!8zrYhqp#Gl*e%7B{A}DI8MaW{Y*%IkMbN){ffF}COd{CzT zpALG1iBZJ{?3Y|3Py7mq9g#?9?0pG@t*AP5oaC(C@#r&>M_G#W1E~keC5( zW + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz_backslash/ignore/me.txt new file mode 100755 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz_backslash/templates/template.tpl new file mode 100755 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz_backslash/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_backslash/values.yaml new file mode 100755 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_backslash/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_bom.tgz new file mode 100644 index 0000000000000000000000000000000000000000..7f0edc6b2b87a1c9d815dbca7f55e56532f00d0f GIT binary patch literal 3453 zcmV-@4TAC?iwFP!000001MOT3coRh)4^-g{@_rl$2SecFA?-GsC#`KEv;`_HukwD_ zZnBeP>1H?VZrX;Y&;t=TDdHz6LP4b<50&o_J)vLgot!7=iBJU;MYLEzuvX!eN86j- zBqdF$u{EZM`}?4~nVFs0-T(al`~S~>W~>}*W+-7^Mh+#|Gt8_bS`l)n5sZ4hc*Ko* zwg1rv1+G_X4SJ16Z7?X*xK5|hDS$row^AQ2ULZIC6b>uFQ4G;wxnTLfQ&{WCAG1T+ zLD?9VgCS}|6tF?3YgqocHbDLcJ<0~^5Vila_=C&8;y|%S*3ru5z7-kGfcpGTv=f|w z(X1saRBZ&~zgDdaQrz@t<%~ zQ+#a9)+#+*_4NxS33&@gIRzhl1oi)n`at_ngOX(Z9|j=(U#rv^WKp5FLDNVzTU-vv2$-aYEi{GBk(O`+ zlH#2-Itu6-Oy>LVANBd4nv$42Ha$6nZ3N}NHgNn;uhVE`{)YpI|6YLuL~IMRvor}g znc9C8)aU=iWK&XFGUgyd)qV^!aE?KU=_S-vi0E$CS!` z{lH|Nu)*6E!a#}u>;#WoE#MLhdCDijY)G?C(UMRqN3t9sJqeIQmWGv$1U!VultBMk zXA~{w`FR?$4hjeA^WWpkg)$7-`1qgRh^yuJUpUa9{4+dZp$x~bj{*kM|2U2Z$lqwx z7-jt*4*2{(-p;yc5||bd6Sr%klrPpdtHT5_nzn-{hH) zCG`mD13SiLh8%;w(){gBf0gR4`ApumXcYgwHdtd7K+yOfZV1r-qAHO29}YFPj+A3dS5#mlg&lb-CUyMa_2&$UPezol z(I=cQIQ8)x`QNVIv+h2{@Xc^<%kEPLRn4z@;+uzeMULxV_{MwFD)Q#uw>cr}^y$ac zvtR39zH9&BvMq<6U2tJ1_IAzUnC+h&{$AIMJ0AOw^Ha+nP?jZIBD-qFuIY~T1d|89 zePE`!{m}BoYS*|$J6nJ8NcED9H){sJe{J!yswF=h?ru4AzB_u*ny>OoGZgnX^NYVj z5RCt~#&nwAMicx$T;#v(|NZv(> z@hquyK!G3$fe_c8c)Y^-^RbDLh6E3>u_$5T!YGvD85QvSMkQ&&3~6b-3IK%DiDlJ# zi5SHqLxLe}kc=|B>(rJGxopbfso-{()ixi{7y!PwlGJUH&lynW)(v`}ap6Lhl6OLj zc!L}ig)fX}Y04tqk8c!NH5MX|Rs}F`i43PyL~7DI>>RLODTkm0?+O~qfu0cEiH9(; zS}ex1h{ijF9t&owqRkW&&C?J%RgtO~L;=<8X0m;Q@xO^KUmXPFzgib)|KVDl%>S@J z^8dsDix`g)yRvuuN9AxwdAfk?mEFx~kpJE_fl<;bz{dN0{s_W<+~6JmF&cGhao|U- zQR{WG{}&b@|Ic$0ad`t}?`-P6yx*6vZK)n_+Hn$Jnq$~_>E4&79vn1nNNF3Yt zPQAJQz`8ky+9Y?GlvG)Aqh(jq?ip2EbH{h>$ad)(S2=3(tDE;c&|b*4JUnx)%FuCc z@0Jg3+_Z_^kn{DXs*!n5Ki}_2MM~dZrf$jQ59BAmd{+PXK)@aB-zx99{_Z!1=a=8x zzC-${m-^#96Wb`(Ou4lBo|~}f>&-PU*oR%|(_>jT>yfR6`xo3lDl?_SqL+sC8GZ2s z{&~>3YT1@SZpDf3XJ31!X4Yc+gX`A9A)j z?=W|Oa*!cXZJ4qm@-GCbh3^iy=V@DBYN>NZ<-C-L0moM5_nwgd7qjBKB}s}M-A2$o z_NP@xl#D1OhMau$a3WZ_mwN2xB3;ipg)2|4*m`XpU)pw4?zV(2>n#zH<*@pHL#ub` zdVg9Ffa@osQwt8fJZ+Yy`~!=wyryLz;p%$0q~CnShZ#@8i#1eV z-hCr!tLyzeqqf;hKN?zr z{`j}DE7P)btCwx+(WjtSK_z24kyV*@ZPQb4?p!zU;=Hj#|8C1Kz79IiEFM06e$0)t z17D4=)_s1hx+v|V^b$O!PbD$Nesa$zXLm6tCNs}{yx;om6Ge};du!z?i*?GC*mE`DjPc0}~w|>HnsBgHqAC7&!bH``j zR878?O#L+LMd9;=ZPu)JTEl<0EvmFfF1WILW6sTY+HGERVd|F^`Lk-euf5#u$)dP> zXO>jlC@$$dW=nNJt3@9d%u)@Svm^OG=DnvnT^P<+kL&d4fa^yVj4!M%Xg4qK@d<}o zm9;h{p1aWcYK@7jT5r-Xzf{^wp)06al~$E;|Iv!#eMft)9&t!rXwKizg0T+EC9{Vj zMVNc~{MN|hNBb5of9Kp!quaJMuUrWLWz4hU=tsD1R64MtxSUTo(r?2jL ze8I$VuZE*r6R{Gcwr!#4a(ixwx#G8KcGga7sQKgn7`I{Uv#^Z&JaIsP9OG$8+w zE&>Z`|Lg1R|6|a~>%YPR|MlPgD?p_nkZ&2dw1vgpGv`UhE!5|~xUmVwXWK&8Mlk;C z4TeDe<7#>TudpC)SgwQiZTb?g68Df9mY|G7@&_n`;bYNRyvn=(O-@b@mZQa3&K9jf za*IZJDo?SWEh3G6d1}k0DJJW7h4Gk}m}n_i<-ZM$=Z-)QMGE$Kl}2w+0Xw8@cES6D zBI8v8UHW{wHwg6 zm7?i*6+>jZRnc|p;32A{XZJg zG%Lo|#E2PKP`i{umBVs0sam-34*Fcl2r=sOKNJzax(LR9JTU%;{C~AP{vQ@d@jp}T z%+EUOs?dp*vbi{~`|pbkirZ`gQCFyxN@NI2lf!Fg2&g0_B5e%d&6DGYGGxe*Awz}? f88T$ZkRd~c3>h+H$dDmJM!4~R>e)Ct0C)fZ$tl;` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/.helmignore b/internal/chart/v3/loader/testdata/frobnitz_with_bom/.helmignore new file mode 100644 index 000000000..7a4b92da2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.lock b/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.lock new file mode 100644 index 000000000..ed43b227f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.yaml new file mode 100644 index 000000000..924fae6fc --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz_with_bom/INSTALL.txt new file mode 100644 index 000000000..77c4e724a --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/LICENSE b/internal/chart/v3/loader/testdata/frobnitz_with_bom/LICENSE new file mode 100644 index 000000000..c27b00bf2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_bom/README.md new file mode 100644 index 000000000..e9c40031b --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/_ignore_me new file mode 100644 index 000000000..a7e3a38b7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml new file mode 100644 index 000000000..6fe4f411f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/README.md new file mode 100644 index 000000000..ea7526bee --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..0732c7d7d --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..f690d53c4 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..f3e662a28 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml new file mode 100644 index 000000000..6b7cb2596 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5c6bc4dcb370466921bbde3c433614bed548313d GIT binary patch literal 910 zcmV;919AKxiwFP!000001MQe;XcIvgh9ly&p#D%nsMs-rcQ%{s1*uoOC|E1h3uzIj z>9pCpm%Ec{#S29gR7CN>`vMg~tXln{cu*A)Zxj_g@kdp#o~;@WbarD(Tah9*5pf;@ zJDZ)IZd$kcAQSzCpeT6& z*YaKYng3jWXeyJDWh;gr0%bg-Lk)$%k4eE4Avj{C+eWYNm?Vh@ttDbJPu;c%?p|mtzAg=Vku(IR2|N9^2Gx1HbS8ycFc9|EGf{ z`qwW^pS!MDTr%g+V>IXg0v~ksmt}z?djQd2l4a`uX(4lY`$VC2&8NiuM0kR z%`9J-dn5{G?Uol0Iltky)T+zUO;r!)Uzz^A-#~QzMdz&38UD5VdW^p-ZdtVI4V%AW z@czneV;@q_uD zl)9>uC+d1mIJ0Mvzxp5(TfabAdpY~zC$K&KG~W6Cy-n3qz%fZ5@nK77*sa>(t8Uj- zZk*P(X6}x?hYrDti(_V1^g88RGw|q|U3E8Sy)F1syX5iM+^@qbGPHsx?aTw;@}`u0 zy_1(w`z`BoJoD1#s;J%T{Z;uzY4}{EblyItW^2a>WyTG?@n|f3^sJkA{KpQSe(6`@ z?~q*o7?JH3lD#|yTfp!8zrYhqp#Gl*e%7B{A}DI8MaW{Y*%IkMbN){ffF}COd{CzT zpALG1iBZJ{?3Y|3Py7mq9g#?9?0pG@t*AP5oaC(C@#r&>M_G#W1E~keC5( zW + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz_with_bom/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz_with_bom/templates/template.tpl new file mode 100644 index 000000000..bb29c5491 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_bom/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_bom/values.yaml new file mode 100644 index 000000000..c24ceadf9 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_bom/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/.helmignore b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.lock b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.yaml new file mode 100644 index 000000000..1b63fc3e2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/LICENSE b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..21ae20aad --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5c6bc4dcb370466921bbde3c433614bed548313d GIT binary patch literal 910 zcmV;919AKxiwFP!000001MQe;XcIvgh9ly&p#D%nsMs-rcQ%{s1*uoOC|E1h3uzIj z>9pCpm%Ec{#S29gR7CN>`vMg~tXln{cu*A)Zxj_g@kdp#o~;@WbarD(Tah9*5pf;@ zJDZ)IZd$kcAQSzCpeT6& z*YaKYng3jWXeyJDWh;gr0%bg-Lk)$%k4eE4Avj{C+eWYNm?Vh@ttDbJPu;c%?p|mtzAg=Vku(IR2|N9^2Gx1HbS8ycFc9|EGf{ z`qwW^pS!MDTr%g+V>IXg0v~ksmt}z?djQd2l4a`uX(4lY`$VC2&8NiuM0kR z%`9J-dn5{G?Uol0Iltky)T+zUO;r!)Uzz^A-#~QzMdz&38UD5VdW^p-ZdtVI4V%AW z@czneV;@q_uD zl)9>uC+d1mIJ0Mvzxp5(TfabAdpY~zC$K&KG~W6Cy-n3qz%fZ5@nK77*sa>(t8Uj- zZk*P(X6}x?hYrDti(_V1^g88RGw|q|U3E8Sy)F1syX5iM+^@qbGPHsx?aTw;@}`u0 zy_1(w`z`BoJoD1#s;J%T{Z;uzY4}{EblyItW^2a>WyTG?@n|f3^sJkA{KpQSe(6`@ z?~q*o7?JH3lD#|yTfp!8zrYhqp#Gl*e%7B{A}DI8MaW{Y*%IkMbN){ffF}COd{CzT zpALG1iBZJ{?3Y|3Py7mq9g#?9?0pG@t*AP5oaC(C@#r&>M_G#W1E~keC5( zW + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/null b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/null new file mode 120000 index 000000000..dc1dc0cde --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/null @@ -0,0 +1 @@ +/dev/null \ No newline at end of file diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_dev_null/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/.helmignore b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.lock b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.yaml new file mode 100644 index 000000000..1b63fc3e2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/INSTALL.txt b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/_ignore_me b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..21ae20aad --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + app.kubernetes.io/name: {{.Chart.Name}} + helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.9" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..5c6bc4dcb370466921bbde3c433614bed548313d GIT binary patch literal 910 zcmV;919AKxiwFP!000001MQe;XcIvgh9ly&p#D%nsMs-rcQ%{s1*uoOC|E1h3uzIj z>9pCpm%Ec{#S29gR7CN>`vMg~tXln{cu*A)Zxj_g@kdp#o~;@WbarD(Tah9*5pf;@ zJDZ)IZd$kcAQSzCpeT6& z*YaKYng3jWXeyJDWh;gr0%bg-Lk)$%k4eE4Avj{C+eWYNm?Vh@ttDbJPu;c%?p|mtzAg=Vku(IR2|N9^2Gx1HbS8ycFc9|EGf{ z`qwW^pS!MDTr%g+V>IXg0v~ksmt}z?djQd2l4a`uX(4lY`$VC2&8NiuM0kR z%`9J-dn5{G?Uol0Iltky)T+zUO;r!)Uzz^A-#~QzMdz&38UD5VdW^p-ZdtVI4V%AW z@czneV;@q_uD zl)9>uC+d1mIJ0Mvzxp5(TfabAdpY~zC$K&KG~W6Cy-n3qz%fZ5@nK77*sa>(t8Uj- zZk*P(X6}x?hYrDti(_V1^g88RGw|q|U3E8Sy)F1syX5iM+^@qbGPHsx?aTw;@}`u0 zy_1(w`z`BoJoD1#s;J%T{Z;uzY4}{EblyItW^2a>WyTG?@n|f3^sJkA{KpQSe(6`@ z?~q*o7?JH3lD#|yTfp!8zrYhqp#Gl*e%7B{A}DI8MaW{Y*%IkMbN){ffF}COd{CzT zpALG1iBZJ{?3Y|3Py7mq9g#?9?0pG@t*AP5oaC(C@#r&>M_G#W1E~keC5( zW + + Example icon + + + diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/ignore/me.txt b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/templates/template.tpl b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/loader/testdata/frobnitz_with_symlink/values.yaml b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/loader/testdata/frobnitz_with_symlink/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/loader/testdata/genfrob.sh b/internal/chart/v3/loader/testdata/genfrob.sh new file mode 100755 index 000000000..eae68906b --- /dev/null +++ b/internal/chart/v3/loader/testdata/genfrob.sh @@ -0,0 +1,18 @@ +#!/bin/sh + +# Pack the albatross chart into the mariner chart. +echo "Packing albatross into mariner" +tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross + +echo "Packing mariner into frobnitz" +tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner +cp frobnitz/charts/mariner-4.3.2.tgz frobnitz_backslash/charts/ +cp frobnitz/charts/mariner-4.3.2.tgz frobnitz_with_bom/charts/ +cp frobnitz/charts/mariner-4.3.2.tgz frobnitz_with_dev_null/charts/ +cp frobnitz/charts/mariner-4.3.2.tgz frobnitz_with_symlink/charts/ + +# Pack the frobnitz chart. +echo "Packing frobnitz" +tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz +tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash +tar --exclude=ignore/* -zcvf frobnitz_with_bom.tgz frobnitz_with_bom diff --git a/internal/chart/v3/loader/testdata/mariner/Chart.yaml b/internal/chart/v3/loader/testdata/mariner/Chart.yaml new file mode 100644 index 000000000..4d3eea730 --- /dev/null +++ b/internal/chart/v3/loader/testdata/mariner/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v3 +name: mariner +description: A Helm chart for Kubernetes +version: 4.3.2 +home: "" +dependencies: + - name: albatross + repository: https://example.com/mariner/charts + version: "0.1.0" diff --git a/internal/chart/v3/loader/testdata/mariner/charts/albatross-0.1.0.tgz b/internal/chart/v3/loader/testdata/mariner/charts/albatross-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ec7bfbfcf38602dc9f8608c0ac171d5392c16342 GIT binary patch literal 282 zcmb2|=3oE==C@bwXB}3MVEqvDTSUIH!EbYhLiu9zX4|9FubD9lx6e9sukLKZOAYPR z3vc+X_e<+fSs~+p!*otl|3|&(9-CEL=DCGt{w%DSuBrRJSC1>xM=dl_YwEW(jz{_$v6_UC$$^FP-|^JRWLw0HMcx!?oKbo8IC=PI36pE>1>$Gu+)^M5}7 z$$#hetY_g0+syKuzR$6;zw*VQ|Jv6rGvnCa)+|`Qabx0!lWv6@6!+;)(%w30(bvo6 zpJM)eX?@`B_t$=dbZ*Qe_JXzlgTmAQUJsJ~^56Q*Px;fc4`mc9emiIWCd=_ia#rn% e%(ruS@0R6X%!7CyMmF4kA*$s6gOfpnfdK$0bcT`u literal 0 HcmV?d00001 diff --git a/internal/chart/v3/loader/testdata/mariner/templates/placeholder.tpl b/internal/chart/v3/loader/testdata/mariner/templates/placeholder.tpl new file mode 100644 index 000000000..29c11843a --- /dev/null +++ b/internal/chart/v3/loader/testdata/mariner/templates/placeholder.tpl @@ -0,0 +1 @@ +# This is a placeholder. diff --git a/internal/chart/v3/loader/testdata/mariner/values.yaml b/internal/chart/v3/loader/testdata/mariner/values.yaml new file mode 100644 index 000000000..b0ccb0086 --- /dev/null +++ b/internal/chart/v3/loader/testdata/mariner/values.yaml @@ -0,0 +1,7 @@ +# Default values for . +# This is a YAML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name: "value" + +: + test: true diff --git a/internal/chart/v3/metadata.go b/internal/chart/v3/metadata.go new file mode 100644 index 000000000..4629d571b --- /dev/null +++ b/internal/chart/v3/metadata.go @@ -0,0 +1,178 @@ +/* +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 v3 + +import ( + "path/filepath" + "strings" + "unicode" + + "github.com/Masterminds/semver/v3" +) + +// Maintainer describes a Chart maintainer. +type Maintainer struct { + // Name is a user name or organization name + Name string `json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `json:"email,omitempty"` + // URL is an optional URL to an address for the named maintainer + URL string `json:"url,omitempty"` +} + +// Validate checks valid data and sanitizes string characters. +func (m *Maintainer) Validate() error { + if m == nil { + return ValidationError("maintainers must not contain empty or null nodes") + } + m.Name = sanitizeString(m.Name) + m.Email = sanitizeString(m.Email) + m.URL = sanitizeString(m.URL) + return nil +} + +// Metadata for a Chart file. This models the structure of a Chart.yaml file. +type Metadata struct { + // The name of the chart. Required. + Name string `json:"name,omitempty"` + // The URL to a relevant project page, git repo, or contact person + Home string `json:"home,omitempty"` + // Source is the URL to the source code of this chart + Sources []string `json:"sources,omitempty"` + // A SemVer 2 conformant version string of the chart. Required. + Version string `json:"version,omitempty"` + // A one-sentence description of the chart + Description string `json:"description,omitempty"` + // A list of string keywords + Keywords []string `json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*Maintainer `json:"maintainers,omitempty"` + // The URL to an icon file. + Icon string `json:"icon,omitempty"` + // The API Version of this chart. Required. + APIVersion string `json:"apiVersion,omitempty"` + // The condition to check to enable chart + Condition string `json:"condition,omitempty"` + // The tags to check to enable chart + Tags string `json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. + AppVersion string `json:"appVersion,omitempty"` + // Whether or not this chart is deprecated + Deprecated bool `json:"deprecated,omitempty"` + // Annotations are additional mappings uninterpreted by Helm, + // made available for inspection by other applications. + Annotations map[string]string `json:"annotations,omitempty"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + KubeVersion string `json:"kubeVersion,omitempty"` + // Dependencies are a list of dependencies for a chart. + Dependencies []*Dependency `json:"dependencies,omitempty"` + // Specifies the chart type: application or library + Type string `json:"type,omitempty"` +} + +// Validate checks the metadata for known issues and sanitizes string +// characters. +func (md *Metadata) Validate() error { + if md == nil { + return ValidationError("chart.metadata is required") + } + + md.Name = sanitizeString(md.Name) + md.Description = sanitizeString(md.Description) + md.Home = sanitizeString(md.Home) + md.Icon = sanitizeString(md.Icon) + md.Condition = sanitizeString(md.Condition) + md.Tags = sanitizeString(md.Tags) + md.AppVersion = sanitizeString(md.AppVersion) + md.KubeVersion = sanitizeString(md.KubeVersion) + for i := range md.Sources { + md.Sources[i] = sanitizeString(md.Sources[i]) + } + for i := range md.Keywords { + md.Keywords[i] = sanitizeString(md.Keywords[i]) + } + + if md.APIVersion == "" { + return ValidationError("chart.metadata.apiVersion is required") + } + if md.Name == "" { + return ValidationError("chart.metadata.name is required") + } + + if md.Name != filepath.Base(md.Name) { + return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) + } + + if md.Version == "" { + return ValidationError("chart.metadata.version is required") + } + if !isValidSemver(md.Version) { + return ValidationErrorf("chart.metadata.version %q is invalid", md.Version) + } + if !isValidChartType(md.Type) { + return ValidationError("chart.metadata.type must be application or library") + } + + for _, m := range md.Maintainers { + if err := m.Validate(); err != nil { + return err + } + } + + // Aliases need to be validated here to make sure that the alias name does + // not contain any illegal characters. + dependencies := map[string]*Dependency{} + for _, dependency := range md.Dependencies { + if err := dependency.Validate(); err != nil { + return err + } + key := dependency.Name + if dependency.Alias != "" { + key = dependency.Alias + } + if dependencies[key] != nil { + return ValidationErrorf("more than one dependency with name or alias %q", key) + } + dependencies[key] = dependency + } + return nil +} + +func isValidChartType(in string) bool { + switch in { + case "", "application", "library": + return true + } + return false +} + +func isValidSemver(v string) bool { + _, err := semver.NewVersion(v) + return err == nil +} + +// sanitizeString normalize spaces and removes non-printable characters. +func sanitizeString(str string) string { + return strings.Map(func(r rune) rune { + if unicode.IsSpace(r) { + return ' ' + } + if unicode.IsPrint(r) { + return r + } + return -1 + }, str) +} diff --git a/internal/chart/v3/metadata_test.go b/internal/chart/v3/metadata_test.go new file mode 100644 index 000000000..596a03695 --- /dev/null +++ b/internal/chart/v3/metadata_test.go @@ -0,0 +1,201 @@ +/* +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 v3 + +import ( + "testing" +) + +func TestValidate(t *testing.T) { + tests := []struct { + name string + md *Metadata + err error + }{ + { + "chart without metadata", + nil, + ValidationError("chart.metadata is required"), + }, + { + "chart without apiVersion", + &Metadata{Name: "test", Version: "1.0"}, + ValidationError("chart.metadata.apiVersion is required"), + }, + { + "chart without name", + &Metadata{APIVersion: "v3", Version: "1.0"}, + ValidationError("chart.metadata.name is required"), + }, + { + "chart without name", + &Metadata{Name: "../../test", APIVersion: "v3", Version: "1.0"}, + ValidationError("chart.metadata.name \"../../test\" is invalid"), + }, + { + "chart without version", + &Metadata{Name: "test", APIVersion: "v3"}, + ValidationError("chart.metadata.version is required"), + }, + { + "chart with bad type", + &Metadata{Name: "test", APIVersion: "v3", Version: "1.0", Type: "test"}, + ValidationError("chart.metadata.type must be application or library"), + }, + { + "chart without dependency", + &Metadata{Name: "test", APIVersion: "v3", Version: "1.0", Type: "application"}, + nil, + }, + { + "dependency with valid alias", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "dependency", Alias: "legal-alias"}, + }, + }, + nil, + }, + { + "dependency with bad characters in alias", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "bad", Alias: "illegal alias"}, + }, + }, + ValidationError("dependency \"bad\" has disallowed characters in the alias"), + }, + { + "same dependency twice", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: ""}, + {Name: "foo", Alias: ""}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + "two dependencies with alias from second dependency shadowing first one", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: ""}, + {Name: "bar", Alias: "foo"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + // this case would make sense and could work in future versions of Helm, currently template rendering would + // result in undefined behaviour + "same dependency twice with different version", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Alias: "", Version: "1.2.3"}, + {Name: "foo", Alias: "", Version: "1.0.0"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + // this case would make sense and could work in future versions of Helm, currently template rendering would + // result in undefined behaviour + "two dependencies with same name but different repos", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + {Name: "foo", Repository: "repo-0"}, + {Name: "foo", Repository: "repo-1"}, + }, + }, + ValidationError("more than one dependency with name or alias \"foo\""), + }, + { + "dependencies has nil", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Dependencies: []*Dependency{ + nil, + }, + }, + ValidationError("dependencies must not contain empty or null nodes"), + }, + { + "maintainer not empty", + &Metadata{ + Name: "test", + APIVersion: "v3", + Version: "1.0", + Type: "application", + Maintainers: []*Maintainer{ + nil, + }, + }, + ValidationError("maintainers must not contain empty or null nodes"), + }, + { + "version invalid", + &Metadata{APIVersion: "3", Name: "test", Version: "1.2.3.4"}, + ValidationError("chart.metadata.version \"1.2.3.4\" is invalid"), + }, + } + + for _, tt := range tests { + result := tt.md.Validate() + if result != tt.err { + t.Errorf("expected %q, got %q in test %q", tt.err, result, tt.name) + } + } +} + +func TestValidate_sanitize(t *testing.T) { + md := &Metadata{APIVersion: "3", Name: "test", Version: "1.0", Description: "\adescr\u0081iption\rtest", Maintainers: []*Maintainer{{Name: "\r"}}} + if err := md.Validate(); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if md.Description != "description test" { + t.Fatalf("description was not sanitized: %q", md.Description) + } + if md.Maintainers[0].Name != " " { + t.Fatal("maintainer name was not sanitized") + } +} diff --git a/internal/chart/v3/util/capabilities.go b/internal/chart/v3/util/capabilities.go new file mode 100644 index 000000000..23b6d46fa --- /dev/null +++ b/internal/chart/v3/util/capabilities.go @@ -0,0 +1,122 @@ +/* +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 util + +import ( + "fmt" + "slices" + "strconv" + + "github.com/Masterminds/semver/v3" + "k8s.io/client-go/kubernetes/scheme" + + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" + + helmversion "helm.sh/helm/v4/internal/version" +) + +var ( + // The Kubernetes version can be set by LDFLAGS. In order to do that the value + // must be a string. + k8sVersionMajor = "1" + k8sVersionMinor = "20" + + // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). + DefaultVersionSet = allKnownVersions() + + // DefaultCapabilities is the default set of capabilities. + DefaultCapabilities = &Capabilities{ + KubeVersion: KubeVersion{ + Version: fmt.Sprintf("v%s.%s.0", k8sVersionMajor, k8sVersionMinor), + Major: k8sVersionMajor, + Minor: k8sVersionMinor, + }, + APIVersions: DefaultVersionSet, + HelmVersion: helmversion.Get(), + } +) + +// Capabilities describes the capabilities of the Kubernetes cluster. +type Capabilities struct { + // KubeVersion is the Kubernetes version. + KubeVersion KubeVersion + // APIVersions are supported Kubernetes API versions. + APIVersions VersionSet + // HelmVersion is the build information for this helm version + HelmVersion helmversion.BuildInfo +} + +func (capabilities *Capabilities) Copy() *Capabilities { + return &Capabilities{ + KubeVersion: capabilities.KubeVersion, + APIVersions: capabilities.APIVersions, + HelmVersion: capabilities.HelmVersion, + } +} + +// KubeVersion is the Kubernetes version. +type KubeVersion struct { + Version string // Kubernetes version + Major string // Kubernetes major version + Minor string // Kubernetes minor version +} + +// String implements fmt.Stringer +func (kv *KubeVersion) String() string { return kv.Version } + +// GitVersion returns the Kubernetes version string. +// +// Deprecated: use KubeVersion.Version. +func (kv *KubeVersion) GitVersion() string { return kv.Version } + +// ParseKubeVersion parses kubernetes version from string +func ParseKubeVersion(version string) (*KubeVersion, error) { + sv, err := semver.NewVersion(version) + if err != nil { + return nil, err + } + return &KubeVersion{ + Version: "v" + sv.String(), + Major: strconv.FormatUint(sv.Major(), 10), + Minor: strconv.FormatUint(sv.Minor(), 10), + }, nil +} + +// VersionSet is a set of Kubernetes API versions. +type VersionSet []string + +// Has returns true if the version string is in the set. +// +// vs.Has("apps/v1") +func (v VersionSet) Has(apiVersion string) bool { + return slices.Contains(v, apiVersion) +} + +func allKnownVersions() VersionSet { + // We should register the built in extension APIs as well so CRDs are + // supported in the default version set. This has caused problems with `helm + // template` in the past, so let's be safe + apiextensionsv1beta1.AddToScheme(scheme.Scheme) + apiextensionsv1.AddToScheme(scheme.Scheme) + + groups := scheme.Scheme.PrioritizedVersionsAllGroups() + vs := make(VersionSet, 0, len(groups)) + for _, gv := range groups { + vs = append(vs, gv.String()) + } + return vs +} diff --git a/internal/chart/v3/util/capabilities_test.go b/internal/chart/v3/util/capabilities_test.go new file mode 100644 index 000000000..aa9be9db8 --- /dev/null +++ b/internal/chart/v3/util/capabilities_test.go @@ -0,0 +1,84 @@ +/* +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 util + +import ( + "testing" +) + +func TestVersionSet(t *testing.T) { + vs := VersionSet{"v1", "apps/v1"} + if d := len(vs); d != 2 { + t.Errorf("Expected 2 versions, got %d", d) + } + + if !vs.Has("apps/v1") { + t.Error("Expected to find apps/v1") + } + + if vs.Has("Spanish/inquisition") { + t.Error("No one expects the Spanish/inquisition") + } +} + +func TestDefaultVersionSet(t *testing.T) { + if !DefaultVersionSet.Has("v1") { + t.Error("Expected core v1 version set") + } +} + +func TestDefaultCapabilities(t *testing.T) { + kv := DefaultCapabilities.KubeVersion + if kv.String() != "v1.20.0" { + t.Errorf("Expected default KubeVersion.String() to be v1.20.0, got %q", kv.String()) + } + if kv.Version != "v1.20.0" { + t.Errorf("Expected default KubeVersion.Version to be v1.20.0, got %q", kv.Version) + } + if kv.GitVersion() != "v1.20.0" { + t.Errorf("Expected default KubeVersion.GitVersion() to be v1.20.0, got %q", kv.Version) + } + if kv.Major != "1" { + t.Errorf("Expected default KubeVersion.Major to be 1, got %q", kv.Major) + } + if kv.Minor != "20" { + t.Errorf("Expected default KubeVersion.Minor to be 20, got %q", kv.Minor) + } +} + +func TestDefaultCapabilitiesHelmVersion(t *testing.T) { + hv := DefaultCapabilities.HelmVersion + + if hv.Version != "v4.0" { + t.Errorf("Expected default HelmVersion to be v4.0, got %q", hv.Version) + } +} + +func TestParseKubeVersion(t *testing.T) { + kv, err := ParseKubeVersion("v1.16.0") + if err != nil { + t.Errorf("Expected v1.16.0 to parse successfully") + } + if kv.Version != "v1.16.0" { + t.Errorf("Expected parsed KubeVersion.Version to be v1.16.0, got %q", kv.String()) + } + if kv.Major != "1" { + t.Errorf("Expected parsed KubeVersion.Major to be 1, got %q", kv.Major) + } + if kv.Minor != "16" { + t.Errorf("Expected parsed KubeVersion.Minor to be 16, got %q", kv.Minor) + } +} diff --git a/internal/chart/v3/util/chartfile.go b/internal/chart/v3/util/chartfile.go new file mode 100644 index 000000000..25271e1cf --- /dev/null +++ b/internal/chart/v3/util/chartfile.go @@ -0,0 +1,96 @@ +/* +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 util + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. +func LoadChartfile(filename string) (*chart.Metadata, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := new(chart.Metadata) + err = yaml.Unmarshal(b, y) + return y, err +} + +// StrictLoadChartfile loads a Chart.yaml into a *chart.Metadata using a strict unmarshaling +func StrictLoadChartfile(filename string) (*chart.Metadata, error) { + b, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + y := new(chart.Metadata) + err = yaml.UnmarshalStrict(b, y) + return y, err +} + +// SaveChartfile saves the given metadata as a Chart.yaml file at the given path. +// +// 'filename' should be the complete path and filename ('foo/Chart.yaml') +func SaveChartfile(filename string, cf *chart.Metadata) error { + out, err := yaml.Marshal(cf) + if err != nil { + return err + } + return os.WriteFile(filename, out, 0644) +} + +// IsChartDir validate a chart directory. +// +// Checks for a valid Chart.yaml. +func IsChartDir(dirName string) (bool, error) { + if fi, err := os.Stat(dirName); err != nil { + return false, err + } else if !fi.IsDir() { + return false, fmt.Errorf("%q is not a directory", dirName) + } + + chartYaml := filepath.Join(dirName, ChartfileName) + if _, err := os.Stat(chartYaml); errors.Is(err, fs.ErrNotExist) { + return false, fmt.Errorf("no %s exists in directory %q", ChartfileName, dirName) + } + + chartYamlContent, err := os.ReadFile(chartYaml) + if err != nil { + return false, fmt.Errorf("cannot read %s in directory %q", ChartfileName, dirName) + } + + chartContent := new(chart.Metadata) + if err := yaml.Unmarshal(chartYamlContent, &chartContent); err != nil { + return false, err + } + if chartContent == nil { + return false, fmt.Errorf("chart metadata (%s) missing", ChartfileName) + } + if chartContent.Name == "" { + return false, fmt.Errorf("invalid chart (%s): name must not be empty", ChartfileName) + } + + return true, nil +} diff --git a/internal/chart/v3/util/chartfile_test.go b/internal/chart/v3/util/chartfile_test.go new file mode 100644 index 000000000..c3d19c381 --- /dev/null +++ b/internal/chart/v3/util/chartfile_test.go @@ -0,0 +1,117 @@ +/* +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 util + +import ( + "testing" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +const testfile = "testdata/chartfiletest.yaml" + +func TestLoadChartfile(t *testing.T) { + f, err := LoadChartfile(testfile) + if err != nil { + t.Errorf("Failed to open %s: %s", testfile, err) + return + } + verifyChartfile(t, f, "frobnitz") +} + +func verifyChartfile(t *testing.T, f *chart.Metadata, name string) { + t.Helper() + if f == nil { //nolint:staticcheck + t.Fatal("Failed verifyChartfile because f is nil") + } + + if f.Name != name { + t.Errorf("Expected %s, got %s", name, f.Name) + } + + if f.Description != "This is a frobnitz." { + t.Errorf("Unexpected description %q", f.Description) + } + + if f.Version != "1.2.3" { + t.Errorf("Unexpected version %q", f.Version) + } + + if len(f.Maintainers) != 2 { + t.Errorf("Expected 2 maintainers, got %d", len(f.Maintainers)) + } + + if f.Maintainers[0].Name != "The Helm Team" { + t.Errorf("Unexpected maintainer name.") + } + + if f.Maintainers[1].Email != "nobody@example.com" { + t.Errorf("Unexpected maintainer email.") + } + + if len(f.Sources) != 1 { + t.Fatalf("Unexpected number of sources") + } + + if f.Sources[0] != "https://example.com/foo/bar" { + t.Errorf("Expected https://example.com/foo/bar, got %s", f.Sources) + } + + if f.Home != "http://example.com" { + t.Error("Unexpected home.") + } + + if f.Icon != "https://example.com/64x64.png" { + t.Errorf("Unexpected icon: %q", f.Icon) + } + + if len(f.Keywords) != 3 { + t.Error("Unexpected keywords") + } + + if len(f.Annotations) != 2 { + t.Fatalf("Unexpected annotations") + } + + if want, got := "extravalue", f.Annotations["extrakey"]; want != got { + t.Errorf("Want %q, but got %q", want, got) + } + + if want, got := "anothervalue", f.Annotations["anotherkey"]; want != got { + t.Errorf("Want %q, but got %q", want, got) + } + + kk := []string{"frobnitz", "sprocket", "dodad"} + for i, k := range f.Keywords { + if kk[i] != k { + t.Errorf("Expected %q, got %q", kk[i], k) + } + } +} + +func TestIsChartDir(t *testing.T) { + validChartDir, err := IsChartDir("testdata/frobnitz") + if !validChartDir { + t.Errorf("unexpected error while reading chart-directory: (%v)", err) + return + } + validChartDir, err = IsChartDir("testdata") + if validChartDir || err == nil { + t.Errorf("expected error but did not get any") + return + } +} diff --git a/internal/chart/v3/util/coalesce.go b/internal/chart/v3/util/coalesce.go new file mode 100644 index 000000000..caea2e119 --- /dev/null +++ b/internal/chart/v3/util/coalesce.go @@ -0,0 +1,308 @@ +/* +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 util + +import ( + "fmt" + "log" + "maps" + + "github.com/mitchellh/copystructure" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +func concatPrefix(a, b string) string { + if a == "" { + return b + } + return fmt.Sprintf("%s.%s", a, b) +} + +// CoalesceValues coalesces all of the values in a chart (and its subcharts). +// +// Values are coalesced together using the following rules: +// +// - Values in a higher level chart always override values in a lower-level +// dependency chart +// - Scalar values and arrays are replaced, maps are merged +// - 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{}) (Values, error) { + valsCopy, err := copyValues(vals) + if err != nil { + return vals, err + } + return coalesce(log.Printf, chrt, valsCopy, "", false) +} + +// MergeValues is used to merge the values in a chart and its subcharts. This +// is different from Coalescing as nil/null values are preserved. +// +// Values are coalesced together using the following rules: +// +// - Values in a higher level chart always override values in a lower-level +// dependency chart +// - Scalar values and arrays are replaced, maps are merged +// - A chart has access to all of the variables for it, as well as all of +// the values destined for its dependencies. +// +// Retaining Nils is useful when processes early in a Helm action or business +// logic need to retain them for when Coalescing will happen again later in the +// business logic. +func MergeValues(chrt *chart.Chart, vals map[string]interface{}) (Values, error) { + valsCopy, err := copyValues(vals) + if err != nil { + return vals, err + } + return coalesce(log.Printf, chrt, valsCopy, "", true) +} + +func copyValues(vals map[string]interface{}) (Values, error) { + v, err := copystructure.Copy(vals) + if err != nil { + return vals, err + } + + valsCopy := v.(map[string]interface{}) + // if we have an empty map, make sure it is initialized + if valsCopy == nil { + valsCopy = make(map[string]interface{}) + } + + return valsCopy, nil +} + +type printFn func(format string, v ...interface{}) + +// coalesce coalesces the dest values and the chart values, giving priority to the dest values. +// +// This is a helper function for CoalesceValues and MergeValues. +// +// Note, the merge argument specifies whether this is being used by MergeValues +// or CoalesceValues. Coalescing removes null values and their keys in some +// situations while merging keeps the null values. +func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { + coalesceValues(printf, ch, dest, prefix, merge) + return coalesceDeps(printf, ch, dest, prefix, merge) +} + +// coalesceDeps coalesces the dependencies of the given chart. +func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string, merge bool) (map[string]interface{}, error) { + for _, subchart := range chrt.Dependencies() { + if c, ok := dest[subchart.Name()]; !ok { + // If dest doesn't already have the key, create it. + dest[subchart.Name()] = make(map[string]interface{}) + } else if !istable(c) { + return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Name(), c) + } + if dv, ok := dest[subchart.Name()]; ok { + dvmap := dv.(map[string]interface{}) + subPrefix := concatPrefix(prefix, chrt.Metadata.Name) + // Get globals out of dest and merge them into dvmap. + coalesceGlobals(printf, dvmap, dest, subPrefix, merge) + // Now coalesce the rest of the values. + var err error + dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix, merge) + if err != nil { + return dest, err + } + } + } + return dest, nil +} + +// coalesceGlobals copies the globals out of src and merges them into dest. +// +// For convenience, returns dest. +func coalesceGlobals(printf printFn, dest, src map[string]interface{}, prefix string, _ bool) { + var dg, sg map[string]interface{} + + if destglob, ok := dest[GlobalKey]; !ok { + dg = make(map[string]interface{}) + } else if dg, ok = destglob.(map[string]interface{}); !ok { + printf("warning: skipping globals because destination %s is not a table.", GlobalKey) + return + } + + if srcglob, ok := src[GlobalKey]; !ok { + sg = make(map[string]interface{}) + } else if sg, ok = srcglob.(map[string]interface{}); !ok { + printf("warning: skipping globals because source %s is not a table.", GlobalKey) + return + } + + // EXPERIMENTAL: In the past, we have disallowed globals to test tables. This + // reverses that decision. It may somehow be possible to introduce a loop + // here, but I haven't found a way. So for the time being, let's allow + // tables in globals. + for key, val := range sg { + if istable(val) { + vv := copyMap(val.(map[string]interface{})) + if destv, ok := dg[key]; !ok { + // Here there is no merge. We're just adding. + dg[key] = vv + } else { + if destvmap, ok := destv.(map[string]interface{}); !ok { + printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) + } else { + // Basically, we reverse order of coalesce here to merge + // top-down. + subPrefix := concatPrefix(prefix, key) + // In this location coalesceTablesFullKey should always have + // merge set to true. The output of coalesceGlobals is run + // through coalesce where any nils will be removed. + coalesceTablesFullKey(printf, vv, destvmap, subPrefix, true) + dg[key] = vv + } + } + } else if dv, ok := dg[key]; ok && istable(dv) { + // It's not clear if this condition can actually ever trigger. + printf("key %s is table. Skipping", key) + } else { + // TODO: Do we need to do any additional checking on the value? + dg[key] = val + } + } + dest[GlobalKey] = dg +} + +func copyMap(src map[string]interface{}) map[string]interface{} { + m := make(map[string]interface{}, len(src)) + maps.Copy(m, src) + return m +} + +// coalesceValues builds up a values map for a particular chart. +// +// Values in v will override the values in the chart. +func coalesceValues(printf printFn, c *chart.Chart, v map[string]interface{}, prefix string, merge bool) { + subPrefix := concatPrefix(prefix, c.Metadata.Name) + + // Using c.Values directly when coalescing a table can cause problems where + // the original c.Values is altered. Creating a deep copy stops the problem. + // This section is fault-tolerant as there is no ability to return an error. + valuesCopy, err := copystructure.Copy(c.Values) + var vc map[string]interface{} + var ok bool + if err != nil { + // If there is an error something is wrong with copying c.Values it + // means there is a problem in the deep copying package or something + // wrong with c.Values. In this case we will use c.Values and report + // an error. + printf("warning: unable to copy values, err: %s", err) + vc = c.Values + } else { + vc, ok = valuesCopy.(map[string]interface{}) + if !ok { + // c.Values has a map[string]interface{} structure. If the copy of + // it cannot be treated as map[string]interface{} there is something + // strangely wrong. Log it and use c.Values + printf("warning: unable to convert values copy to values type") + vc = c.Values + } + } + + for key, val := range vc { + if value, ok := v[key]; ok { + if value == nil && !merge { + // When the YAML value is null and we are coalescing instead of + // merging, 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(v, key) + } else if dest, ok := value.(map[string]interface{}); ok { + // if v[key] is a table, merge nv's val table into v[key]. + src, ok := val.(map[string]interface{}) + if !ok { + // If the original value is nil, there is nothing to coalesce, so we don't print + // the warning + if val != nil { + printf("warning: skipped value for %s.%s: Not a table.", subPrefix, key) + } + } else { + // If the key is a child chart, coalesce tables with Merge set to true + merge := childChartMergeTrue(c, key, merge) + + // Because v has higher precedence than nv, dest values override src + // values. + coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key), merge) + } + } + } else { + // If the key is not in v, copy it from nv. + v[key] = val + } + } +} + +func childChartMergeTrue(chrt *chart.Chart, key string, merge bool) bool { + for _, subchart := range chrt.Dependencies() { + if subchart.Name() == key { + return true + } + } + return merge +} + +// CoalesceTables merges a source map into a destination map. +// +// dest is considered authoritative. +func CoalesceTables(dst, src map[string]interface{}) map[string]interface{} { + return coalesceTablesFullKey(log.Printf, dst, src, "", false) +} + +func MergeTables(dst, src map[string]interface{}) map[string]interface{} { + return coalesceTablesFullKey(log.Printf, dst, src, "", true) +} + +// coalesceTablesFullKey merges a source map into a destination map. +// +// dest is considered authoritative. +func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string, merge bool) map[string]interface{} { + // When --reuse-values is set but there are no modifications yet, return new values + if src == nil { + return dst + } + if dst == nil { + return src + } + for key, val := range dst { + if val == nil { + src[key] = nil + } + } + // Because dest has higher precedence than src, dest values override src + // values. + for key, val := range src { + fullkey := concatPrefix(prefix, key) + if dv, ok := dst[key]; ok && !merge && dv == nil { + delete(dst, key) + } else if !ok { + dst[key] = val + } else if istable(val) { + if istable(dv) { + coalesceTablesFullKey(printf, dv.(map[string]interface{}), val.(map[string]interface{}), fullkey, merge) + } else { + printf("warning: cannot overwrite table with non table for %s (%v)", fullkey, val) + } + } else if istable(dv) && val != nil { + printf("warning: destination for %s is a table. Ignoring non-table value (%v)", fullkey, val) + } + } + return dst +} diff --git a/internal/chart/v3/util/coalesce_test.go b/internal/chart/v3/util/coalesce_test.go new file mode 100644 index 000000000..4770b601d --- /dev/null +++ b/internal/chart/v3/util/coalesce_test.go @@ -0,0 +1,723 @@ +/* +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 util + +import ( + "encoding/json" + "fmt" + "maps" + "testing" + + "github.com/stretchr/testify/assert" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 +var testCoalesceValuesYaml = []byte(` +top: yup +bottom: null +right: Null +left: NULL +front: ~ +back: "" +nested: + boat: null + +global: + name: Ishmael + subject: Queequeg + nested: + boat: true + +pequod: + boat: null + global: + name: Stinky + harpooner: Tashtego + nested: + boat: false + sail: true + foo2: null + ahab: + scope: whale + boat: null + nested: + foo: true + boat: null + object: null +`) + +func withDeps(c *chart.Chart, deps ...*chart.Chart) *chart.Chart { + c.AddDependency(deps...) + return c +} + +func TestCoalesceValues(t *testing.T) { + is := assert.New(t) + + c := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "moby"}, + Values: map[string]interface{}{ + "back": "exists", + "bottom": "exists", + "front": "exists", + "left": "exists", + "name": "moby", + "nested": map[string]interface{}{"boat": true}, + "override": "bad", + "right": "exists", + "scope": "moby", + "top": "nope", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l0": "moby"}, + }, + "pequod": map[string]interface{}{ + "boat": "maybe", + "ahab": map[string]interface{}{ + "boat": "maybe", + "nested": map[string]interface{}{"boat": "maybe"}, + }, + }, + }, + }, + withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "pequod"}, + Values: map[string]interface{}{ + "name": "pequod", + "scope": "pequod", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l1": "pequod"}, + }, + "boat": false, + "ahab": map[string]interface{}{ + "boat": false, + "nested": map[string]interface{}{"boat": false}, + }, + }, + }, + &chart.Chart{ + Metadata: &chart.Metadata{Name: "ahab"}, + Values: map[string]interface{}{ + "global": map[string]interface{}{ + "nested": map[string]interface{}{"foo": "bar", "foo2": "bar2"}, + "nested2": map[string]interface{}{"l2": "ahab"}, + }, + "scope": "ahab", + "name": "ahab", + "boat": true, + "nested": map[string]interface{}{"foo": false, "boat": true}, + "object": map[string]interface{}{"foo": "bar"}, + }, + }, + ), + &chart.Chart{ + Metadata: &chart.Metadata{Name: "spouter"}, + Values: map[string]interface{}{ + "scope": "spouter", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l1": "spouter"}, + }, + }, + }, + ) + + vals, err := ReadValues(testCoalesceValuesYaml) + if err != nil { + t.Fatal(err) + } + + // taking a copy of the values before passing it + // to CoalesceValues as argument, so that we can + // use it for asserting later + valsCopy := make(Values, len(vals)) + maps.Copy(valsCopy, vals) + + v, err := CoalesceValues(c, vals) + if err != nil { + t.Fatal(err) + } + j, _ := json.MarshalIndent(v, "", " ") + t.Logf("Coalesced Values: %s", string(j)) + + tests := []struct { + tpl string + expect string + }{ + {"{{.top}}", "yup"}, + {"{{.back}}", ""}, + {"{{.name}}", "moby"}, + {"{{.global.name}}", "Ishmael"}, + {"{{.global.subject}}", "Queequeg"}, + {"{{.global.harpooner}}", ""}, + {"{{.pequod.name}}", "pequod"}, + {"{{.pequod.ahab.name}}", "ahab"}, + {"{{.pequod.ahab.scope}}", "whale"}, + {"{{.pequod.ahab.nested.foo}}", "true"}, + {"{{.pequod.ahab.global.name}}", "Ishmael"}, + {"{{.pequod.ahab.global.nested.foo}}", "bar"}, + {"{{.pequod.ahab.global.nested.foo2}}", ""}, + {"{{.pequod.ahab.global.subject}}", "Queequeg"}, + {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, + {"{{.pequod.global.name}}", "Ishmael"}, + {"{{.pequod.global.nested.foo}}", ""}, + {"{{.pequod.global.subject}}", "Queequeg"}, + {"{{.spouter.global.name}}", "Ishmael"}, + {"{{.spouter.global.harpooner}}", ""}, + + {"{{.global.nested.boat}}", "true"}, + {"{{.pequod.global.nested.boat}}", "true"}, + {"{{.spouter.global.nested.boat}}", "true"}, + {"{{.pequod.global.nested.sail}}", "true"}, + {"{{.spouter.global.nested.sail}}", ""}, + + {"{{.global.nested2.l0}}", "moby"}, + {"{{.global.nested2.l1}}", ""}, + {"{{.global.nested2.l2}}", ""}, + {"{{.pequod.global.nested2.l0}}", "moby"}, + {"{{.pequod.global.nested2.l1}}", "pequod"}, + {"{{.pequod.global.nested2.l2}}", ""}, + {"{{.pequod.ahab.global.nested2.l0}}", "moby"}, + {"{{.pequod.ahab.global.nested2.l1}}", "pequod"}, + {"{{.pequod.ahab.global.nested2.l2}}", "ahab"}, + {"{{.spouter.global.nested2.l0}}", "moby"}, + {"{{.spouter.global.nested2.l1}}", "spouter"}, + {"{{.spouter.global.nested2.l2}}", ""}, + } + + for _, tt := range tests { + if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { + t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) + } + } + + nullKeys := []string{"bottom", "right", "left", "front"} + for _, nullKey := range nullKeys { + if _, ok := v[nullKey]; ok { + t.Errorf("Expected key %q to be removed, still present", nullKey) + } + } + + if _, ok := v["nested"].(map[string]interface{})["boat"]; ok { + t.Error("Expected nested boat key to be removed, still present") + } + + subchart := v["pequod"].(map[string]interface{}) + if _, ok := subchart["boat"]; ok { + t.Error("Expected subchart boat key to be removed, still present") + } + + subsubchart := subchart["ahab"].(map[string]interface{}) + if _, ok := subsubchart["boat"]; ok { + t.Error("Expected sub-subchart ahab boat key to be removed, still present") + } + + if _, ok := subsubchart["nested"].(map[string]interface{})["boat"]; ok { + t.Error("Expected sub-subchart nested boat key to be removed, still present") + } + + if _, ok := subsubchart["object"]; ok { + t.Error("Expected sub-subchart object map to be removed, still present") + } + + // CoalesceValues should not mutate the passed arguments + is.Equal(valsCopy, vals) +} + +func TestMergeValues(t *testing.T) { + is := assert.New(t) + + c := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "moby"}, + Values: map[string]interface{}{ + "back": "exists", + "bottom": "exists", + "front": "exists", + "left": "exists", + "name": "moby", + "nested": map[string]interface{}{"boat": true}, + "override": "bad", + "right": "exists", + "scope": "moby", + "top": "nope", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l0": "moby"}, + }, + }, + }, + withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "pequod"}, + Values: map[string]interface{}{ + "name": "pequod", + "scope": "pequod", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l1": "pequod"}, + }, + }, + }, + &chart.Chart{ + Metadata: &chart.Metadata{Name: "ahab"}, + Values: map[string]interface{}{ + "global": map[string]interface{}{ + "nested": map[string]interface{}{"foo": "bar"}, + "nested2": map[string]interface{}{"l2": "ahab"}, + }, + "scope": "ahab", + "name": "ahab", + "boat": true, + "nested": map[string]interface{}{"foo": false, "bar": true}, + }, + }, + ), + &chart.Chart{ + Metadata: &chart.Metadata{Name: "spouter"}, + Values: map[string]interface{}{ + "scope": "spouter", + "global": map[string]interface{}{ + "nested2": map[string]interface{}{"l1": "spouter"}, + }, + }, + }, + ) + + vals, err := ReadValues(testCoalesceValuesYaml) + if err != nil { + t.Fatal(err) + } + + // taking a copy of the values before passing it + // to MergeValues as argument, so that we can + // use it for asserting later + valsCopy := make(Values, len(vals)) + maps.Copy(valsCopy, vals) + + v, err := MergeValues(c, vals) + if err != nil { + t.Fatal(err) + } + j, _ := json.MarshalIndent(v, "", " ") + t.Logf("Coalesced Values: %s", string(j)) + + tests := []struct { + tpl string + expect string + }{ + {"{{.top}}", "yup"}, + {"{{.back}}", ""}, + {"{{.name}}", "moby"}, + {"{{.global.name}}", "Ishmael"}, + {"{{.global.subject}}", "Queequeg"}, + {"{{.global.harpooner}}", ""}, + {"{{.pequod.name}}", "pequod"}, + {"{{.pequod.ahab.name}}", "ahab"}, + {"{{.pequod.ahab.scope}}", "whale"}, + {"{{.pequod.ahab.nested.foo}}", "true"}, + {"{{.pequod.ahab.global.name}}", "Ishmael"}, + {"{{.pequod.ahab.global.nested.foo}}", "bar"}, + {"{{.pequod.ahab.global.subject}}", "Queequeg"}, + {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, + {"{{.pequod.global.name}}", "Ishmael"}, + {"{{.pequod.global.nested.foo}}", ""}, + {"{{.pequod.global.subject}}", "Queequeg"}, + {"{{.spouter.global.name}}", "Ishmael"}, + {"{{.spouter.global.harpooner}}", ""}, + + {"{{.global.nested.boat}}", "true"}, + {"{{.pequod.global.nested.boat}}", "true"}, + {"{{.spouter.global.nested.boat}}", "true"}, + {"{{.pequod.global.nested.sail}}", "true"}, + {"{{.spouter.global.nested.sail}}", ""}, + + {"{{.global.nested2.l0}}", "moby"}, + {"{{.global.nested2.l1}}", ""}, + {"{{.global.nested2.l2}}", ""}, + {"{{.pequod.global.nested2.l0}}", "moby"}, + {"{{.pequod.global.nested2.l1}}", "pequod"}, + {"{{.pequod.global.nested2.l2}}", ""}, + {"{{.pequod.ahab.global.nested2.l0}}", "moby"}, + {"{{.pequod.ahab.global.nested2.l1}}", "pequod"}, + {"{{.pequod.ahab.global.nested2.l2}}", "ahab"}, + {"{{.spouter.global.nested2.l0}}", "moby"}, + {"{{.spouter.global.nested2.l1}}", "spouter"}, + {"{{.spouter.global.nested2.l2}}", ""}, + } + + for _, tt := range tests { + if o, err := ttpl(tt.tpl, v); err != nil || o != tt.expect { + t.Errorf("Expected %q to expand to %q, got %q", tt.tpl, tt.expect, o) + } + } + + // nullKeys is different from coalescing. Here the null/nil values are not + // removed. + nullKeys := []string{"bottom", "right", "left", "front"} + for _, nullKey := range nullKeys { + if vv, ok := v[nullKey]; !ok { + t.Errorf("Expected key %q to be present but it was removed", nullKey) + } else if vv != nil { + t.Errorf("Expected key %q to be null but it has a value of %v", nullKey, vv) + } + } + + if _, ok := v["nested"].(map[string]interface{})["boat"]; !ok { + t.Error("Expected nested boat key to be present but it was removed") + } + + subchart := v["pequod"].(map[string]interface{})["ahab"].(map[string]interface{}) + if _, ok := subchart["boat"]; !ok { + t.Error("Expected subchart boat key to be present but it was removed") + } + + if _, ok := subchart["nested"].(map[string]interface{})["bar"]; !ok { + t.Error("Expected subchart nested bar key to be present but it was removed") + } + + // CoalesceValues should not mutate the passed arguments + is.Equal(valsCopy, vals) +} + +func TestCoalesceTables(t *testing.T) { + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": nil, + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": nil, + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + "country": "US", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + "hole": "black", + } + + // What we expect is that anything in dst overrides anything in src, but that + // otherwise the values are coalesced. + CoalesceTables(dst, src) + + if dst["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst["name"]) + } + if dst["occupation"] != "whaler" { + t.Errorf("Unexpected occupation: %s", dst["occupation"]) + } + + addr, ok := dst["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr["street"]) + } + + if addr["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr["city"]) + } + + if addr["state"].(string) != "MA" { + t.Errorf("Unexpected state: %v", addr["state"]) + } + + if _, ok = addr["country"]; ok { + t.Error("The country is not left out.") + } + + if det, ok := dst["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst["details"]) + } else if _, ok := det["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst["boat"]) + } + + if _, ok = dst["hole"]; ok { + t.Error("The hole still exists.") + } + + dst2 := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": "US", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": "black", + } + + // What we expect is that anything in dst should have all values set, + // this happens when the --reuse-values flag is set but the chart has no modifications yet + CoalesceTables(dst2, nil) + + if dst2["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst2["name"]) + } + + addr2, ok := dst2["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr2["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr2["street"]) + } + + if addr2["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr2["city"]) + } + + if addr2["country"].(string) != "US" { + t.Errorf("Unexpected Country: %v", addr2["country"]) + } + + if det2, ok := dst2["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst2["details"]) + } else if _, ok := det2["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst2["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst2["boat"]) + } + + if dst2["hole"].(string) != "black" { + t.Errorf("Expected hole string, got %v", dst2["boat"]) + } +} + +func TestMergeTables(t *testing.T) { + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": nil, + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": nil, + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + "country": "US", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + "hole": "black", + } + + // What we expect is that anything in dst overrides anything in src, but that + // otherwise the values are coalesced. + MergeTables(dst, src) + + if dst["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst["name"]) + } + if dst["occupation"] != "whaler" { + t.Errorf("Unexpected occupation: %s", dst["occupation"]) + } + + addr, ok := dst["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr["street"]) + } + + if addr["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr["city"]) + } + + if addr["state"].(string) != "MA" { + t.Errorf("Unexpected state: %v", addr["state"]) + } + + // This is one test that is different from CoalesceTables. Because country + // is a nil value and it's not removed it's still present. + if _, ok = addr["country"]; !ok { + t.Error("The country is left out.") + } + + if det, ok := dst["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst["details"]) + } else if _, ok := det["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst["boat"]) + } + + // This is one test that is different from CoalesceTables. Because hole + // is a nil value and it's not removed it's still present. + if _, ok = dst["hole"]; !ok { + t.Error("The hole no longer exists.") + } + + dst2 := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + "country": "US", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + "hole": "black", + "nilval": nil, + } + + // What we expect is that anything in dst should have all values set, + // this happens when the --reuse-values flag is set but the chart has no modifications yet + MergeTables(dst2, nil) + + if dst2["name"] != "Ishmael" { + t.Errorf("Unexpected name: %s", dst2["name"]) + } + + addr2, ok := dst2["address"].(map[string]interface{}) + if !ok { + t.Fatal("Address went away.") + } + + if addr2["street"].(string) != "123 Spouter Inn Ct." { + t.Errorf("Unexpected address: %v", addr2["street"]) + } + + if addr2["city"].(string) != "Nantucket" { + t.Errorf("Unexpected city: %v", addr2["city"]) + } + + if addr2["country"].(string) != "US" { + t.Errorf("Unexpected Country: %v", addr2["country"]) + } + + if det2, ok := dst2["details"].(map[string]interface{}); !ok { + t.Fatalf("Details is the wrong type: %v", dst2["details"]) + } else if _, ok := det2["friends"]; !ok { + t.Error("Could not find your friends. Maybe you don't have any. :-(") + } + + if dst2["boat"].(string) != "pequod" { + t.Errorf("Expected boat string, got %v", dst2["boat"]) + } + + if dst2["hole"].(string) != "black" { + t.Errorf("Expected hole string, got %v", dst2["boat"]) + } + + if dst2["nilval"] != nil { + t.Error("Expected nilvalue to have nil value but it does not") + } +} + +func TestCoalesceValuesWarnings(t *testing.T) { + + c := withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "level1"}, + Values: map[string]interface{}{ + "name": "moby", + }, + }, + withDeps(&chart.Chart{ + Metadata: &chart.Metadata{Name: "level2"}, + Values: map[string]interface{}{ + "name": "pequod", + }, + }, + &chart.Chart{ + Metadata: &chart.Metadata{Name: "level3"}, + Values: map[string]interface{}{ + "name": "ahab", + "boat": true, + "spear": map[string]interface{}{ + "tip": true, + "sail": map[string]interface{}{ + "cotton": true, + }, + }, + }, + }, + ), + ) + + vals := map[string]interface{}{ + "level2": map[string]interface{}{ + "level3": map[string]interface{}{ + "boat": map[string]interface{}{"mast": true}, + "spear": map[string]interface{}{ + "tip": map[string]interface{}{ + "sharp": true, + }, + "sail": true, + }, + }, + }, + } + + warnings := make([]string, 0) + printf := func(format string, v ...interface{}) { + t.Logf(format, v...) + warnings = append(warnings, fmt.Sprintf(format, v...)) + } + + _, err := coalesce(printf, c, vals, "", false) + if err != nil { + t.Fatal(err) + } + + t.Logf("vals: %v", vals) + assert.Contains(t, warnings, "warning: skipped value for level1.level2.level3.boat: Not a table.") + assert.Contains(t, warnings, "warning: destination for level1.level2.level3.spear.tip is a table. Ignoring non-table value (true)") + assert.Contains(t, warnings, "warning: cannot overwrite table with non table for level1.level2.level3.spear.sail (map[cotton:true])") + +} + +func TestConcatPrefix(t *testing.T) { + assert.Equal(t, "b", concatPrefix("", "b")) + assert.Equal(t, "a.b", concatPrefix("a", "b")) +} diff --git a/internal/chart/v3/util/compatible.go b/internal/chart/v3/util/compatible.go new file mode 100644 index 000000000..d384d2d45 --- /dev/null +++ b/internal/chart/v3/util/compatible.go @@ -0,0 +1,34 @@ +/* +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 util + +import "github.com/Masterminds/semver/v3" + +// IsCompatibleRange compares a version to a constraint. +// It returns true if the version matches the constraint, and false in all other cases. +func IsCompatibleRange(constraint, ver string) bool { + sv, err := semver.NewVersion(ver) + if err != nil { + return false + } + + c, err := semver.NewConstraint(constraint) + if err != nil { + return false + } + return c.Check(sv) +} diff --git a/internal/chart/v3/util/compatible_test.go b/internal/chart/v3/util/compatible_test.go new file mode 100644 index 000000000..e17d33e35 --- /dev/null +++ b/internal/chart/v3/util/compatible_test.go @@ -0,0 +1,43 @@ +/* +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 version represents the current version of the project. +package util + +import "testing" + +func TestIsCompatibleRange(t *testing.T) { + tests := []struct { + constraint string + ver string + expected bool + }{ + {"v2.0.0-alpha.4", "v2.0.0-alpha.4", true}, + {"v2.0.0-alpha.3", "v2.0.0-alpha.4", false}, + {"v2.0.0", "v2.0.0-alpha.4", false}, + {"v2.0.0-alpha.4", "v2.0.0", false}, + {"~v2.0.0", "v2.0.1", true}, + {"v2", "v2.0.0", true}, + {">2.0.0", "v2.1.1", true}, + {"v2.1.*", "v2.1.1", true}, + } + + for _, tt := range tests { + if IsCompatibleRange(tt.constraint, tt.ver) != tt.expected { + t.Errorf("expected constraint %s to be %v for %s", tt.constraint, tt.expected, tt.ver) + } + } +} diff --git a/internal/chart/v3/util/create.go b/internal/chart/v3/util/create.go new file mode 100644 index 000000000..72fed5955 --- /dev/null +++ b/internal/chart/v3/util/create.go @@ -0,0 +1,832 @@ +/* +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 util + +import ( + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/chart/v3/loader" +) + +// chartName is a regular expression for testing the supplied name of a chart. +// This regular expression is probably stricter than it needs to be. We can relax it +// somewhat. Newline characters, as well as $, quotes, +, parens, and % are known to be +// problematic. +var chartName = regexp.MustCompile("^[a-zA-Z0-9._-]+$") + +const ( + // ChartfileName is the default Chart file name. + ChartfileName = "Chart.yaml" + // ValuesfileName is the default values file name. + ValuesfileName = "values.yaml" + // SchemafileName is the default values schema file name. + SchemafileName = "values.schema.json" + // TemplatesDir is the relative directory name for templates. + TemplatesDir = "templates" + // ChartsDir is the relative directory name for charts dependencies. + ChartsDir = "charts" + // TemplatesTestsDir is the relative directory name for tests. + TemplatesTestsDir = TemplatesDir + sep + "tests" + // IgnorefileName is the name of the Helm ignore file. + IgnorefileName = ".helmignore" + // IngressFileName is the name of the example ingress file. + IngressFileName = TemplatesDir + sep + "ingress.yaml" + // HTTPRouteFileName is the name of the example HTTPRoute file. + HTTPRouteFileName = TemplatesDir + sep + "httproute.yaml" + // DeploymentName is the name of the example deployment file. + DeploymentName = TemplatesDir + sep + "deployment.yaml" + // ServiceName is the name of the example service file. + ServiceName = TemplatesDir + sep + "service.yaml" + // ServiceAccountName is the name of the example serviceaccount file. + ServiceAccountName = TemplatesDir + sep + "serviceaccount.yaml" + // HorizontalPodAutoscalerName is the name of the example hpa file. + HorizontalPodAutoscalerName = TemplatesDir + sep + "hpa.yaml" + // NotesName is the name of the example NOTES.txt file. + NotesName = TemplatesDir + sep + "NOTES.txt" + // HelpersName is the name of the example helpers file. + HelpersName = TemplatesDir + sep + "_helpers.tpl" + // TestConnectionName is the name of the example test file. + TestConnectionName = TemplatesTestsDir + sep + "test-connection.yaml" +) + +// maxChartNameLength is lower than the limits we know of with certain file systems, +// and with certain Kubernetes fields. +const maxChartNameLength = 250 + +const sep = string(filepath.Separator) + +const defaultChartfile = `apiVersion: v3 +name: %s +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" +` + +const defaultValues = `# Default values for %s. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# This will set the replicaset count more information can be found here: https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/ +replicaCount: 1 + +# This sets the container image more information can be found here: https://kubernetes.io/docs/concepts/containers/images/ +image: + repository: nginx + # This sets the pull policy for images. + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +# This is for the secrets for pulling an image from a private repository more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +imagePullSecrets: [] +# This is to override the chart name. +nameOverride: "" +fullnameOverride: "" + +# This section builds out the service account more information can be found here: https://kubernetes.io/docs/concepts/security/service-accounts/ +serviceAccount: + # Specifies whether a service account should be created + create: true + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# This is for setting Kubernetes Annotations to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +podAnnotations: {} +# This is for setting Kubernetes Labels to a Pod. +# For more information checkout: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/ +service: + # This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: ClusterIP + # This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports + port: 80 + +# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/ +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- Expose the service via gateway-api HTTPRoute +# Requires Gateway API resources and suitable controller installed within the cluster +# (see: https://gateway-api.sigs.k8s.io/guides/) +httpRoute: + # HTTPRoute enabled. + enabled: false + # HTTPRoute annotations. + annotations: {} + # Which Gateways this Route is attached to. + parentRefs: + - name: gateway + sectionName: http + # namespace: default + # Hostnames matching HTTP header. + hostnames: + - chart-example.local + # List of rules and filters applied. + rules: + - matches: + - path: + type: PathPrefix + value: /headers + # filters: + # - type: RequestHeaderModifier + # requestHeaderModifier: + # set: + # - name: My-Overwrite-Header + # value: this-is-the-only-value + # remove: + # - User-Agent + # - matches: + # - path: + # type: PathPrefix + # value: /echo + # headers: + # - name: version + # value: v2 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# This is to setup the liveness and readiness probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: / + port: http +readinessProbe: + httpGet: + path: / + port: http + +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: [] +# - name: foo +# secret: +# secretName: mysecret +# optional: false + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: [] +# - name: foo +# mountPath: "/etc/foo" +# readOnly: true + +nodeSelector: {} + +tolerations: [] + +affinity: {} +` + +const defaultIgnore = `# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +` + +const defaultIngress = `{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.ingress.className }} + ingressClassName: {{ . }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- with .pathType }} + pathType: {{ . }} + {{- end }} + backend: + service: + name: {{ include ".fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} +` + +const defaultHTTPRoute = `{{- if .Values.httpRoute.enabled -}} +{{- $fullName := include ".fullname" . -}} +{{- $svcPort := .Values.service.port -}} +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $fullName }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.httpRoute.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + parentRefs: + {{- with .Values.httpRoute.parentRefs }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml . | nindent 4 }} + {{- end }} + rules: + {{- range .Values.httpRoute.rules }} + {{- with .matches }} + - matches: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .filters }} + filters: + {{- toYaml . | nindent 8 }} + {{- end }} + backendRefs: + - name: {{ $fullName }} + port: {{ $svcPort }} + weight: 1 + {{- end }} +{{- end }} +` + +const defaultDeployment = `apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include ".selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include ".labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include ".serviceAccountName" . }} + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +` + +const defaultService = `apiVersion: v1 +kind: Service +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include ".selectorLabels" . | nindent 4 }} +` + +const defaultServiceAccount = `{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include ".serviceAccountName" . }} + labels: + {{- include ".labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} +` + +const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include ".fullname" . }} + labels: + {{- include ".labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include ".fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} +` + +const defaultNotes = `1. Get the application URL by running these commands: +{{- if .Values.httpRoute.enabled }} +{{- if .Values.httpRoute.hostnames }} + export APP_HOSTNAME={{ .Values.httpRoute.hostnames | first }} +{{- else }} + export APP_HOSTNAME=$(kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o jsonpath="{.spec.listeners[0].hostname}") + {{- end }} +{{- if and .Values.httpRoute.rules (first .Values.httpRoute.rules).matches (first (first .Values.httpRoute.rules).matches).path.value }} + echo "Visit http://$APP_HOSTNAME{{ (first (first .Values.httpRoute.rules).matches).path.value }} to use your application" + + NOTE: Your HTTPRoute depends on the listener configuration of your gateway and your HTTPRoute rules. + The rules can be set for path, method, header and query parameters. + You can check the gateway configuration with 'kubectl get --namespace {{(first .Values.httpRoute.parentRefs).namespace | default .Release.Namespace }} gateway/{{ (first .Values.httpRoute.parentRefs).name }} -o yaml' +{{- end }} +{{- else if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include ".fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include ".fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include ".fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include ".name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} +` + +const defaultHelpers = `{{/* +Expand the name of the chart. +*/}} +{{- define ".name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define ".fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define ".chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define ".labels" -}} +helm.sh/chart: {{ include ".chart" . }} +{{ include ".selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define ".selectorLabels" -}} +app.kubernetes.io/name: {{ include ".name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define ".serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include ".fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} +` + +const defaultTestConnection = `apiVersion: v1 +kind: Pod +metadata: + name: "{{ include ".fullname" . }}-test-connection" + labels: + {{- include ".labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include ".fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never +` + +// Stderr is an io.Writer to which error messages can be written +// +// In Helm 4, this will be replaced. It is needed in Helm 3 to preserve API backward +// compatibility. +var Stderr io.Writer = os.Stderr + +// CreateFrom creates a new chart, but scaffolds it from the src chart. +func CreateFrom(chartfile *chart.Metadata, dest, src string) error { + schart, err := loader.Load(src) + if err != nil { + return fmt.Errorf("could not load %s: %w", src, err) + } + + schart.Metadata = chartfile + + var updatedTemplates []*chart.File + + for _, template := range schart.Templates { + newData := transform(string(template.Data), schart.Name()) + updatedTemplates = append(updatedTemplates, &chart.File{Name: template.Name, Data: newData}) + } + + schart.Templates = updatedTemplates + b, err := yaml.Marshal(schart.Values) + if err != nil { + return fmt.Errorf("reading values file: %w", err) + } + + var m map[string]interface{} + if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { + return fmt.Errorf("transforming values file: %w", err) + } + schart.Values = m + + // SaveDir looks for the file values.yaml when saving rather than the values + // key in order to preserve the comments in the YAML. The name placeholder + // needs to be replaced on that file. + for _, f := range schart.Raw { + if f.Name == ValuesfileName { + f.Data = transform(string(f.Data), schart.Name()) + } + } + + return SaveDir(schart, dest) +} + +// Create creates a new chart in a directory. +// +// Inside of dir, this will create a directory based on the name of +// chartfile.Name. It will then write the Chart.yaml into this directory and +// create the (empty) appropriate directories. +// +// The returned string will point to the newly created directory. It will be +// an absolute path, even if the provided base directory was relative. +// +// If dir does not exist, this will return an error. +// If Chart.yaml or any directories cannot be created, this will return an +// error. In such a case, this will attempt to clean up by removing the +// new chart directory. +func Create(name, dir string) (string, error) { + + // Sanity-check the name of a chart so user doesn't create one that causes problems. + if err := validateChartName(name); err != nil { + return "", err + } + + path, err := filepath.Abs(dir) + if err != nil { + return path, err + } + + if fi, err := os.Stat(path); err != nil { + return path, err + } else if !fi.IsDir() { + return path, fmt.Errorf("no such directory %s", path) + } + + cdir := filepath.Join(path, name) + if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { + return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) + } + + // Note: If adding a new template below (i.e., to `helm create`) which is disabled by default (similar to hpa and + // ingress below); or making an existing template disabled by default, add the enabling condition in + // `TestHelmCreateChart_CheckDeprecatedWarnings` in `pkg/lint/lint_test.go` to make it run through deprecation checks + // with latest Kubernetes version. + files := []struct { + path string + content []byte + }{ + { + // Chart.yaml + path: filepath.Join(cdir, ChartfileName), + content: []byte(fmt.Sprintf(defaultChartfile, name)), + }, + { + // values.yaml + path: filepath.Join(cdir, ValuesfileName), + content: []byte(fmt.Sprintf(defaultValues, name)), + }, + { + // .helmignore + path: filepath.Join(cdir, IgnorefileName), + content: []byte(defaultIgnore), + }, + { + // ingress.yaml + path: filepath.Join(cdir, IngressFileName), + content: transform(defaultIngress, name), + }, + { + // httproute.yaml + path: filepath.Join(cdir, HTTPRouteFileName), + content: transform(defaultHTTPRoute, name), + }, + { + // deployment.yaml + path: filepath.Join(cdir, DeploymentName), + content: transform(defaultDeployment, name), + }, + { + // service.yaml + path: filepath.Join(cdir, ServiceName), + content: transform(defaultService, name), + }, + { + // serviceaccount.yaml + path: filepath.Join(cdir, ServiceAccountName), + content: transform(defaultServiceAccount, name), + }, + { + // hpa.yaml + path: filepath.Join(cdir, HorizontalPodAutoscalerName), + content: transform(defaultHorizontalPodAutoscaler, name), + }, + { + // NOTES.txt + path: filepath.Join(cdir, NotesName), + content: transform(defaultNotes, name), + }, + { + // _helpers.tpl + path: filepath.Join(cdir, HelpersName), + content: transform(defaultHelpers, name), + }, + { + // test-connection.yaml + path: filepath.Join(cdir, TestConnectionName), + content: transform(defaultTestConnection, name), + }, + } + + for _, file := range files { + if _, err := os.Stat(file.path); err == nil { + // There is no handle to a preferred output stream here. + fmt.Fprintf(Stderr, "WARNING: File %q already exists. Overwriting.\n", file.path) + } + if err := writeFile(file.path, file.content); err != nil { + return cdir, err + } + } + // Need to add the ChartsDir explicitly as it does not contain any file OOTB + if err := os.MkdirAll(filepath.Join(cdir, ChartsDir), 0755); err != nil { + return cdir, err + } + return cdir, nil +} + +// transform performs a string replacement of the specified source for +// a given key with the replacement string +func transform(src, replacement string) []byte { + return []byte(strings.ReplaceAll(src, "", replacement)) +} + +func writeFile(name string, content []byte) error { + if err := os.MkdirAll(filepath.Dir(name), 0755); err != nil { + return err + } + return os.WriteFile(name, content, 0644) +} + +func validateChartName(name string) error { + if name == "" || len(name) > maxChartNameLength { + return fmt.Errorf("chart name must be between 1 and %d characters", maxChartNameLength) + } + if !chartName.MatchString(name) { + return fmt.Errorf("chart name must match the regular expression %q", chartName.String()) + } + return nil +} diff --git a/internal/chart/v3/util/create_test.go b/internal/chart/v3/util/create_test.go new file mode 100644 index 000000000..b3b58cc5a --- /dev/null +++ b/internal/chart/v3/util/create_test.go @@ -0,0 +1,172 @@ +/* +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 util + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/chart/v3/loader" +) + +func TestCreate(t *testing.T) { + tdir := t.TempDir() + + c, err := Create("foo", tdir) + if err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + mychart, err := loader.LoadDir(c) + if err != nil { + t.Fatalf("Failed to load newly created chart %q: %s", c, err) + } + + if mychart.Name() != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) + } + + for _, f := range []string{ + ChartfileName, + DeploymentName, + HelpersName, + IgnorefileName, + NotesName, + ServiceAccountName, + ServiceName, + TemplatesDir, + TemplatesTestsDir, + TestConnectionName, + ValuesfileName, + } { + if _, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } + } +} + +func TestCreateFrom(t *testing.T) { + tdir := t.TempDir() + + cf := &chart.Metadata{ + APIVersion: chart.APIVersionV3, + Name: "foo", + Version: "0.1.0", + } + srcdir := "./testdata/frobnitz/charts/mariner" + + if err := CreateFrom(cf, tdir, srcdir); err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + c := filepath.Join(tdir, cf.Name) + mychart, err := loader.LoadDir(c) + if err != nil { + t.Fatalf("Failed to load newly created chart %q: %s", c, err) + } + + if mychart.Name() != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Name()) + } + + for _, f := range []string{ + ChartfileName, + ValuesfileName, + filepath.Join(TemplatesDir, "placeholder.tpl"), + } { + if _, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } + + // Check each file to make sure has been replaced + b, err := os.ReadFile(filepath.Join(dir, f)) + if err != nil { + t.Errorf("Unable to read file %s: %s", f, err) + } + if bytes.Contains(b, []byte("")) { + t.Errorf("File %s contains ", f) + } + } +} + +// TestCreate_Overwrite is a regression test for making sure that files are overwritten. +func TestCreate_Overwrite(t *testing.T) { + tdir := t.TempDir() + + var errlog bytes.Buffer + + if _, err := Create("foo", tdir); err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + tplname := filepath.Join(dir, "templates/hpa.yaml") + writeFile(tplname, []byte("FOO")) + + // Now re-run the create + Stderr = &errlog + if _, err := Create("foo", tdir); err != nil { + t.Fatal(err) + } + + data, err := os.ReadFile(tplname) + if err != nil { + t.Fatal(err) + } + + if string(data) == "FOO" { + t.Fatal("File that should have been modified was not.") + } + + if errlog.Len() == 0 { + t.Errorf("Expected warnings about overwriting files.") + } +} + +func TestValidateChartName(t *testing.T) { + for name, shouldPass := range map[string]bool{ + "": false, + "abcdefghijklmnopqrstuvwxyz-_.": true, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": true, + "$hello": false, + "Hellô": false, + "he%%o": false, + "he\nllo": false, + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "abcdefghijklmnopqrstuvwxyz-_." + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ-_.": false, + } { + if err := validateChartName(name); (err != nil) == shouldPass { + t.Errorf("test for %q failed", name) + } + } +} diff --git a/internal/chart/v3/util/dependencies.go b/internal/chart/v3/util/dependencies.go new file mode 100644 index 000000000..bd5032ce4 --- /dev/null +++ b/internal/chart/v3/util/dependencies.go @@ -0,0 +1,366 @@ +/* +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 util + +import ( + "log/slog" + "strings" + + "github.com/mitchellh/copystructure" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// 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, true) +} + +// processDependencyConditions disables charts based on condition path value in values +func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) { + if reqs == nil { + return + } + for _, r := range reqs { + for c := range strings.SplitSeq(strings.TrimSpace(r.Condition), ",") { + if len(c) > 0 { + // retrieve value + vv, err := cvals.PathValue(cpath + c) + if err == nil { + // if not bool, warn + if bv, ok := vv.(bool); ok { + r.Enabled = bv + break + } + slog.Warn("returned non-bool value", "path", c, "chart", r.Name) + } else if _, ok := err.(ErrNoValue); !ok { + // this is a real error + slog.Warn("the method PathValue returned error", slog.Any("error", err)) + } + } + } + } +} + +// 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 { + return + } + for _, r := range reqs { + var hasTrue, hasFalse bool + for _, k := range r.Tags { + if b, ok := vt[k]; ok { + // if not bool, warn + if bv, ok := b.(bool); ok { + if bv { + hasTrue = true + } else { + hasFalse = true + } + } else { + slog.Warn("returned non-bool value", "tag", k, "chart", r.Name) + } + } + } + if !hasTrue && hasFalse { + r.Enabled = false + } else if hasTrue || !hasTrue && !hasFalse { + r.Enabled = true + } + } +} + +// getAliasDependency finds the chart for an alias dependency and copies parts that will be modified +func getAliasDependency(charts []*chart.Chart, dep *chart.Dependency) *chart.Chart { + for _, c := range charts { + if c == nil { + continue + } + if c.Name() != dep.Name { + continue + } + if !IsCompatibleRange(dep.Version, c.Metadata.Version) { + continue + } + + out := *c + out.Metadata = copyMetadata(c.Metadata) + + // empty dependencies and shallow copy all dependencies, otherwise parent info may be corrupted if + // there is more than one dependency aliasing this chart + out.SetDependencies() + for _, dependency := range c.Dependencies() { + cpy := *dependency + out.AddDependency(&cpy) + } + + if dep.Alias != "" { + out.Metadata.Name = dep.Alias + } + return &out + } + return nil +} + +func copyMetadata(metadata *chart.Metadata) *chart.Metadata { + md := *metadata + + if md.Dependencies != nil { + dependencies := make([]*chart.Dependency, len(md.Dependencies)) + for i := range md.Dependencies { + dependency := *md.Dependencies[i] + dependencies[i] = &dependency + } + md.Dependencies = dependencies + } + return &md +} + +// processDependencyEnabled removes disabled charts from dependencies +func processDependencyEnabled(c *chart.Chart, v map[string]interface{}, path string) error { + if c.Metadata.Dependencies == nil { + return nil + } + + var chartDependencies []*chart.Chart + // If any dependency is not a part of Chart.yaml + // then this should be added to chartDependencies. + // However, if the dependency is already specified in Chart.yaml + // we should not add it, as it would be processed from Chart.yaml anyway. + +Loop: + for _, existing := range c.Dependencies() { + for _, req := range c.Metadata.Dependencies { + if existing.Name() == req.Name && IsCompatibleRange(req.Version, existing.Metadata.Version) { + continue Loop + } + } + chartDependencies = append(chartDependencies, existing) + } + + for _, req := range c.Metadata.Dependencies { + if req == nil { + continue + } + if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { + chartDependencies = append(chartDependencies, chartDependency) + } + if req.Alias != "" { + req.Name = req.Alias + } + } + c.SetDependencies(chartDependencies...) + + // set all to true + 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) + // make a map of charts to remove + rm := map[string]struct{}{} + for _, r := range c.Metadata.Dependencies { + if !r.Enabled { + // remove disabled chart + rm[r.Name] = struct{}{} + } + } + // don't keep disabled charts in new slice + cd := []*chart.Chart{} + copy(cd, c.Dependencies()[:0]) + for _, n := range c.Dependencies() { + if _, ok := rm[n.Metadata.Name]; !ok { + cd = append(cd, n) + } + } + // don't keep disabled charts in metadata + cdMetadata := []*chart.Dependency{} + copy(cdMetadata, c.Metadata.Dependencies[:0]) + for _, n := range c.Metadata.Dependencies { + if _, ok := rm[n.Name]; !ok { + 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...) + c.SetDependencies(cd...) + + return nil +} + +// pathToMap creates a nested map given a YAML path in dot notation. +func pathToMap(path string, data map[string]interface{}) map[string]interface{} { + if path == "." { + return data + } + return set(parsePath(path), data) +} + +func set(path []string, data map[string]interface{}) map[string]interface{} { + if len(path) == 0 { + return nil + } + cur := data + for i := len(path) - 1; i >= 0; i-- { + cur = map[string]interface{}{path[i]: cur} + } + return cur +} + +// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. +func processImportValues(c *chart.Chart, merge bool) error { + if c.Metadata.Dependencies == nil { + return nil + } + // combine chart values and empty config to get Values + var cvals Values + var err error + if merge { + cvals, err = MergeValues(c, nil) + } else { + 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 { + 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 { + slog.Warn("ImportValues missing table from chart", "chart", r.Name, slog.Any("error", err)) + continue + } + // create value map from child to be merged into parent + if merge { + b = MergeTables(b, pathToMap(parent, vv.AsMap())) + } else { + b = CoalesceTables(b, pathToMap(parent, vv.AsMap())) + } + case string: + child := "exports." + iv + outiv = append(outiv, map[string]string{ + "child": child, + "parent": ".", + }) + vm, err := cvals.Table(r.Name + "." + child) + if err != nil { + slog.Warn("ImportValues missing table", slog.Any("error", err)) + continue + } + if merge { + b = MergeTables(b, vm.AsMap()) + } else { + b = CoalesceTables(b, vm.AsMap()) + } + } + } + r.ImportValues = outiv + } + + // Imported values from a child to a parent chart have a lower priority than + // the parents values. This enables parent charts to import a large section + // from a child and then override select parts. This is why b is merged into + // cvals in the code below and not the other way around. + if merge { + // deep copying the cvals as there are cases where pointers can end + // up in the cvals when they are copied onto b in ways that break things. + cvals = deepCopyMap(cvals) + c.Values = MergeTables(cvals, b) + } else { + // Trimming the nil values from cvals is needed for backwards compatibility. + // Previously, the b value had been populated with cvals along with some + // overrides. This caused the coalescing functionality to remove the + // nil/null values. This trimming is for backwards compat. + cvals = trimNilValues(cvals) + c.Values = CoalesceTables(cvals, b) + } + + return nil +} + +func deepCopyMap(vals map[string]interface{}) map[string]interface{} { + valsCopy, err := copystructure.Copy(vals) + if err != nil { + return vals + } + return valsCopy.(map[string]interface{}) +} + +func trimNilValues(vals map[string]interface{}) map[string]interface{} { + valsCopy, err := copystructure.Copy(vals) + if err != nil { + return vals + } + valsCopyMap := valsCopy.(map[string]interface{}) + for key, val := range valsCopyMap { + if val == nil { + // Iterate over the values and remove nil keys + delete(valsCopyMap, key) + } else if istable(val) { + // Recursively call into ourselves to remove keys from inner tables + valsCopyMap[key] = trimNilValues(val.(map[string]interface{})) + } + } + + return valsCopyMap +} + +// processDependencyImportValues imports specified chart values from child to parent. +func processDependencyImportValues(c *chart.Chart, merge bool) error { + for _, d := range c.Dependencies() { + // recurse + if err := processDependencyImportValues(d, merge); err != nil { + return err + } + } + return processImportValues(c, merge) +} diff --git a/internal/chart/v3/util/dependencies_test.go b/internal/chart/v3/util/dependencies_test.go new file mode 100644 index 000000000..55839fe65 --- /dev/null +++ b/internal/chart/v3/util/dependencies_test.go @@ -0,0 +1,569 @@ +/* +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 util + +import ( + "os" + "path/filepath" + "sort" + "strconv" + "testing" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/chart/v3/loader" +) + +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 +} + +func TestLoadDependency(t *testing.T) { + tests := []*chart.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "https://example.com/charts"}, + {Name: "mariner", Version: "4.3.2", Repository: "https://example.com/charts"}, + } + + check := func(deps []*chart.Dependency) { + if len(deps) != 2 { + t.Errorf("expected 2 dependencies, got %d", len(deps)) + } + for i, tt := range tests { + if deps[i].Name != tt.Name { + t.Errorf("expected dependency named %q, got %q", tt.Name, deps[i].Name) + } + if deps[i].Version != tt.Version { + t.Errorf("expected dependency named %q to have version %q, got %q", tt.Name, tt.Version, deps[i].Version) + } + if deps[i].Repository != tt.Repository { + t.Errorf("expected dependency named %q to have repository %q, got %q", tt.Name, tt.Repository, deps[i].Repository) + } + } + } + c := loadChart(t, "testdata/frobnitz") + check(c.Metadata.Dependencies) + check(c.Lock.Dependencies) +} + +func 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, "testdata/subpop") + t.Run(tc.name, func(t *testing.T) { + if err := processDependencyEnabled(c, tc.v, ""); 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) + } + } + }) + } +} + +// extractChartNames 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 +} + +func TestProcessDependencyImportValues(t *testing.T) { + c := loadChart(t, "testdata/subpop") + + 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" + + // These values are imported from the child chart to the parent. Parent + // values take precedence over imported values. This enables importing a + // large section from a child chart and overriding a selection from it. + e["overridden-chart1.SC1bool"] = "false" + e["overridden-chart1.SC1float"] = "3.141592" + e["overridden-chart1.SC1int"] = "99" + e["overridden-chart1.SC1string"] = "pollywog" + 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"] = "jabberwocky" + e["overridden-chartA.SPextra4"] = "true" + + // These values are imported from the child chart to the parent. Parent + // values take precedence over imported values. This enables importing a + // large section from a child chart and overriding a selection from it. + e["overridden-chartA-B.SCAbool"] = "true" + e["overridden-chartA-B.SCAfloat"] = "41.3" + e["overridden-chartA-B.SCAint"] = "808" + e["overridden-chartA-B.SCAstring"] = "jabberwocky" + e["overridden-chartA-B.SCBbool"] = "false" + e["overridden-chartA-B.SCBfloat"] = "1.99" + e["overridden-chartA-B.SCBint"] = "77" + e["overridden-chartA-B.SCBstring"] = "jango" + 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" + + if err := processDependencyImportValues(c, false); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + cc := Values(c.Values) + for kk, vv := range e { + pv, err := cc.PathValue(kk) + if err != nil { + t.Fatalf("retrieving import values table %v %v", kk, err) + } + + switch pv := pv.(type) { + case float64: + if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { + t.Errorf("failed to match imported float value %v with expected %v for key %q", s, vv, kk) + } + case bool: + if b := strconv.FormatBool(pv); b != vv { + t.Errorf("failed to match imported bool value %v with expected %v for key %q", b, vv, kk) + } + default: + if pv != vv { + t.Errorf("failed to match imported string value %q with expected %q for key %q", pv, vv, kk) + } + } + } + + // Since this was processed with coalescing there should be no null values. + // Here we verify that. + _, err := cc.PathValue("ensurenull") + if err == nil { + t.Error("expect nil value not found but found it") + } + switch xerr := err.(type) { + case ErrNoValue: + // We found what we expected + default: + t.Errorf("expected an ErrNoValue but got %q instead", xerr) + } + + c = loadChart(t, "testdata/subpop") + if err := processDependencyImportValues(c, true); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + cc = Values(c.Values) + val, err := cc.PathValue("ensurenull") + if err != nil { + t.Error("expect value but ensurenull was not found") + } + if val != nil { + t.Errorf("expect nil value but got %q instead", val) + } +} + +func TestProcessDependencyImportValuesFromSharedDependencyToAliases(t *testing.T) { + c := loadChart(t, "testdata/chart-with-import-from-aliased-dependencies") + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + if err := processDependencyImportValues(c, true); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + e := make(map[string]string) + + e["foo-defaults.defaultValue"] = "42" + e["bar-defaults.defaultValue"] = "42" + + e["foo.defaults.defaultValue"] = "42" + e["bar.defaults.defaultValue"] = "42" + + e["foo.grandchild.defaults.defaultValue"] = "42" + e["bar.grandchild.defaults.defaultValue"] = "42" + + cValues := Values(c.Values) + for kk, vv := range e { + pv, err := cValues.PathValue(kk) + if err != nil { + t.Fatalf("retrieving import values table %v %v", kk, err) + } + if pv != vv { + t.Errorf("failed to match imported value %v with expected %v", pv, vv) + } + } +} + +func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { + c := loadChart(t, "testdata/three-level-dependent-chart/umbrella") + + e := make(map[string]string) + + // The order of precedence should be: + // 1. User specified values (e.g CLI) + // 2. Parent chart values + // 3. Imported values + // 4. Sub-chart values + // The 4 app charts here deal with things differently: + // - app1 has a port value set in the umbrella chart. It does not import any + // values so the value from the umbrella chart should be used. + // - app2 has a value in the app chart and imports from the library. The + // app chart value should take precedence. + // - app3 has no value in the app chart and imports the value from the library + // chart. The library chart value should be used. + // - app4 has a value in the app chart and does not import the value from the + // library chart. The app charts value should be used. + e["app1.service.port"] = "3456" + e["app2.service.port"] = "8080" + e["app3.service.port"] = "9090" + e["app4.service.port"] = "1234" + if err := processDependencyImportValues(c, true); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + cc := Values(c.Values) + for kk, vv := range e { + pv, err := cc.PathValue(kk) + if err != nil { + t.Fatalf("retrieving import values table %v %v", kk, err) + } + + switch pv := pv.(type) { + case float64: + if s := strconv.FormatFloat(pv, 'f', -1, 64); s != vv { + t.Errorf("failed to match imported float value %v with expected %v", s, vv) + } + default: + if pv != vv { + t.Errorf("failed to match imported string value %q with expected %q", pv, vv) + } + } + } +} + +func TestProcessDependencyImportValuesForEnabledCharts(t *testing.T) { + c := loadChart(t, "testdata/import-values-from-enabled-subchart/parent-chart") + nameOverride := "parent-chart-prod" + + if err := processDependencyImportValues(c, true); err != nil { + t.Fatalf("processing import values dependencies %v", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 1 { + t.Fatal("expected no changes in dependencies") + } + + if len(c.Metadata.Dependencies) != 1 { + t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) + } + + prodDependencyValues := c.Dependencies()[0].Values + if prodDependencyValues["nameOverride"] != nameOverride { + t.Fatalf("dependency chart name should be %s but got %s", nameOverride, prodDependencyValues["nameOverride"]) + } +} + +func TestGetAliasDependency(t *testing.T) { + c := loadChart(t, "testdata/frobnitz") + req := c.Metadata.Dependencies + + if len(req) == 0 { + t.Fatalf("there are no dependencies to test") + } + + // Success case + aliasChart := getAliasDependency(c.Dependencies(), req[0]) + if aliasChart == nil { + t.Fatalf("failed to get dependency chart for alias %s", req[0].Name) + } + if req[0].Alias != "" { + if aliasChart.Name() != req[0].Alias { + t.Fatalf("dependency chart name should be %s but got %s", req[0].Alias, aliasChart.Name()) + } + } else if aliasChart.Name() != req[0].Name { + t.Fatalf("dependency chart name should be %s but got %s", req[0].Name, aliasChart.Name()) + } + + if req[0].Version != "" { + if !IsCompatibleRange(req[0].Version, aliasChart.Metadata.Version) { + t.Fatalf("dependency chart version is not in the compatible range") + } + } + + // Failure case + req[0].Name = "something-else" + if aliasChart := getAliasDependency(c.Dependencies(), req[0]); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Name()) + } + + req[0].Version = "something else which is not in the compatible range" + if IsCompatibleRange(req[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 := loadChart(t, "testdata/dependent-chart-alias") + req := c.Metadata.Dependencies + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 3 { + t.Fatal("expected alias dependencies to be added") + } + + if len(c.Dependencies()) != len(c.Metadata.Dependencies) { + t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) + } + + aliasChart := getAliasDependency(c.Dependencies(), req[2]) + + if aliasChart == nil { + t.Fatalf("failed to get dependency chart for alias %s", req[2].Name) + } + if aliasChart.Parent() != c { + t.Fatalf("dependency chart has wrong parent, expected %s but got %s", c.Name(), aliasChart.Parent().Name()) + } + if req[2].Alias != "" { + if aliasChart.Name() != req[2].Alias { + t.Fatalf("dependency chart name should be %s but got %s", req[2].Alias, aliasChart.Name()) + } + } else if aliasChart.Name() != req[2].Name { + t.Fatalf("dependency chart name should be %s but got %s", req[2].Name, aliasChart.Name()) + } + + req[2].Name = "dummy-name" + if aliasChart := getAliasDependency(c.Dependencies(), req[2]); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Name()) + } + +} + +func TestDependentChartWithSubChartsAbsentInDependency(t *testing.T) { + c := loadChart(t, "testdata/dependent-chart-no-requirements-yaml") + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatal("expected no changes in dependencies") + } +} + +func TestDependentChartWithSubChartsHelmignore(t *testing.T) { + // FIXME what does this test? + loadChart(t, "testdata/dependent-chart-helmignore") +} + +func TestDependentChartsWithSubChartsSymlink(t *testing.T) { + joonix := filepath.Join("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 := loadChart(t, joonix) + + if c.Name() != "joonix" { + t.Fatalf("unexpected chart name: %s", c.Name()) + } + if n := len(c.Dependencies()); n != 1 { + t.Fatalf("expected 1 dependency for this chart, but got %d", n) + } +} + +func TestDependentChartsWithSubchartsAllSpecifiedInDependency(t *testing.T) { + c := loadChart(t, "testdata/dependent-chart-with-all-in-requirements-yaml") + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatal("expected no changes in dependencies") + } + + if len(c.Dependencies()) != len(c.Metadata.Dependencies) { + t.Fatalf("expected number of chart dependencies %d, but got %d", len(c.Metadata.Dependencies), len(c.Dependencies())) + } +} + +func TestDependentChartsWithSomeSubchartsSpecifiedInDependency(t *testing.T) { + c := loadChart(t, "testdata/dependent-chart-with-mixed-requirements-yaml") + + if len(c.Dependencies()) != 2 { + t.Fatalf("expected 2 dependencies for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatal("expected no changes in dependencies") + } + + if len(c.Metadata.Dependencies) != 1 { + t.Fatalf("expected 1 dependency specified in Chart.yaml, got %d", len(c.Metadata.Dependencies)) + } +} + +func validateDependencyTree(t *testing.T, c *chart.Chart) { + t.Helper() + for _, dependency := range c.Dependencies() { + if dependency.Parent() != c { + if dependency.Parent() != c { + t.Fatalf("dependency chart %s has wrong parent, expected %s but got %s", dependency.Name(), c.Name(), dependency.Parent().Name()) + } + } + // recurse entire tree + validateDependencyTree(t, dependency) + } +} + +func TestChartWithDependencyAliasedTwiceAndDoublyReferencedSubDependency(t *testing.T) { + c := loadChart(t, "testdata/chart-with-dependency-aliased-twice") + + if len(c.Dependencies()) != 1 { + t.Fatalf("expected one dependency for this chart, but got %d", len(c.Dependencies())) + } + + if err := processDependencyEnabled(c, c.Values, ""); err != nil { + t.Fatalf("expected no errors but got %q", err) + } + + if len(c.Dependencies()) != 2 { + t.Fatal("expected two dependencies after processing aliases") + } + validateDependencyTree(t, c) +} diff --git a/internal/chart/v3/util/doc.go b/internal/chart/v3/util/doc.go new file mode 100644 index 000000000..002d5babc --- /dev/null +++ b/internal/chart/v3/util/doc.go @@ -0,0 +1,45 @@ +/* +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 util contains tools for working with charts. + +Charts are described in the chart package (pkg/chart). +This package provides utilities for serializing and deserializing charts. + +A chart can be represented on the file system in one of two ways: + + - As a directory that contains a Chart.yaml file and other chart things. + - As a tarred gzipped file containing a directory that then contains a + Chart.yaml file. + +This package provides utilities for working with those file formats. + +The preferred way of loading a chart is using 'loader.Load`: + + chart, err := loader.Load(filename) + +This will attempt to discover whether the file at 'filename' is a directory or +a chart archive. It will then load accordingly. + +For accepting raw compressed tar file data from an io.Reader, the +'loader.LoadArchive()' will read in the data, uncompress it, and unpack it +into a Chart. + +When creating charts in memory, use the 'helm.sh/helm/pkg/chart' +package directly. +*/ +package util // import chartutil "helm.sh/helm/v4/internal/chart/v3/util" diff --git a/internal/chart/v3/util/errors.go b/internal/chart/v3/util/errors.go new file mode 100644 index 000000000..a175b9758 --- /dev/null +++ b/internal/chart/v3/util/errors.go @@ -0,0 +1,43 @@ +/* +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 util + +import ( + "fmt" +) + +// ErrNoTable indicates that a chart does not have a matching table. +type ErrNoTable struct { + Key string +} + +func (e ErrNoTable) Error() string { return fmt.Sprintf("%q is not a table", e.Key) } + +// ErrNoValue indicates that Values does not contain a key with a value +type ErrNoValue struct { + Key string +} + +func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) } + +type ErrInvalidChartName struct { + Name string +} + +func (e ErrInvalidChartName) Error() string { + return fmt.Sprintf("%q is not a valid chart name", e.Name) +} diff --git a/internal/chart/v3/util/errors_test.go b/internal/chart/v3/util/errors_test.go new file mode 100644 index 000000000..b8ae86384 --- /dev/null +++ b/internal/chart/v3/util/errors_test.go @@ -0,0 +1,37 @@ +/* +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 util + +import ( + "testing" +) + +func TestErrorNoTableDoesNotPanic(t *testing.T) { + x := "empty" + + y := ErrNoTable{x} + + t.Logf("error is: %s", y) +} + +func TestErrorNoValueDoesNotPanic(t *testing.T) { + x := "empty" + + y := ErrNoValue{x} + + t.Logf("error is: %s", y) +} diff --git a/internal/chart/v3/util/expand.go b/internal/chart/v3/util/expand.go new file mode 100644 index 000000000..6cbbeabf2 --- /dev/null +++ b/internal/chart/v3/util/expand.go @@ -0,0 +1,94 @@ +/* +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 util + +import ( + "errors" + "fmt" + "io" + "os" + "path/filepath" + + securejoin "github.com/cyphar/filepath-securejoin" + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/chart/v3/loader" +) + +// Expand uncompresses and extracts a chart into the specified directory. +func Expand(dir string, r io.Reader) error { + files, err := loader.LoadArchiveFiles(r) + if err != nil { + return err + } + + // Get the name of the chart + var chartName string + for _, file := range files { + if file.Name == "Chart.yaml" { + ch := &chart.Metadata{} + if err := yaml.Unmarshal(file.Data, ch); err != nil { + return fmt.Errorf("cannot load Chart.yaml: %w", err) + } + chartName = ch.Name + } + } + if chartName == "" { + return errors.New("chart name not specified") + } + + // Find the base directory + // The directory needs to be cleaned prior to passing to SecureJoin or the location may end up + // being wrong or returning an error. This was introduced in v0.4.0. + dir = filepath.Clean(dir) + chartdir, err := securejoin.SecureJoin(dir, chartName) + if err != nil { + return err + } + + // Copy all files verbatim. We don't parse these files because parsing can remove + // comments. + for _, file := range files { + outpath, err := securejoin.SecureJoin(chartdir, file.Name) + if err != nil { + return err + } + + // Make sure the necessary subdirs get created. + basedir := filepath.Dir(outpath) + if err := os.MkdirAll(basedir, 0755); err != nil { + return err + } + + if err := os.WriteFile(outpath, file.Data, 0644); err != nil { + return err + } + } + + return nil +} + +// ExpandFile expands the src file into the dest directory. +func ExpandFile(dest, src string) error { + h, err := os.Open(src) + if err != nil { + return err + } + defer h.Close() + return Expand(dest, h) +} diff --git a/internal/chart/v3/util/expand_test.go b/internal/chart/v3/util/expand_test.go new file mode 100644 index 000000000..280995f7e --- /dev/null +++ b/internal/chart/v3/util/expand_test.go @@ -0,0 +1,124 @@ +/* +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 util + +import ( + "os" + "path/filepath" + "testing" +) + +func TestExpand(t *testing.T) { + dest := t.TempDir() + + reader, err := os.Open("testdata/frobnitz-1.2.3.tgz") + if err != nil { + t.Fatal(err) + } + + if err := Expand(dest, reader); err != nil { + t.Fatal(err) + } + + expectedChartPath := filepath.Join(dest, "frobnitz") + fi, err := os.Stat(expectedChartPath) + if err != nil { + t.Fatal(err) + } + if !fi.IsDir() { + t.Fatalf("expected a chart directory at %s", expectedChartPath) + } + + dir, err := os.Open(expectedChartPath) + if err != nil { + t.Fatal(err) + } + + fis, err := dir.Readdir(0) + if err != nil { + t.Fatal(err) + } + + expectLen := 11 + if len(fis) != expectLen { + t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) + } + + for _, fi := range fis { + expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) + if err != nil { + t.Fatal(err) + } + // os.Stat can return different values for directories, based on the OS + // for Linux, for example, os.Stat always returns the size of the directory + // (value-4096) regardless of the size of the contents of the directory + mode := expect.Mode() + if !mode.IsDir() { + if fi.Size() != expect.Size() { + t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + } + } + } +} + +func TestExpandFile(t *testing.T) { + dest := t.TempDir() + + if err := ExpandFile(dest, "testdata/frobnitz-1.2.3.tgz"); err != nil { + t.Fatal(err) + } + + expectedChartPath := filepath.Join(dest, "frobnitz") + fi, err := os.Stat(expectedChartPath) + if err != nil { + t.Fatal(err) + } + if !fi.IsDir() { + t.Fatalf("expected a chart directory at %s", expectedChartPath) + } + + dir, err := os.Open(expectedChartPath) + if err != nil { + t.Fatal(err) + } + + fis, err := dir.Readdir(0) + if err != nil { + t.Fatal(err) + } + + expectLen := 11 + if len(fis) != expectLen { + t.Errorf("Expected %d files, but got %d", expectLen, len(fis)) + } + + for _, fi := range fis { + expect, err := os.Stat(filepath.Join("testdata", "frobnitz", fi.Name())) + if err != nil { + t.Fatal(err) + } + // os.Stat can return different values for directories, based on the OS + // for Linux, for example, os.Stat always returns the size of the directory + // (value-4096) regardless of the size of the contents of the directory + mode := expect.Mode() + if !mode.IsDir() { + if fi.Size() != expect.Size() { + t.Errorf("Expected %s to have size %d, got %d", fi.Name(), expect.Size(), fi.Size()) + } + } + } +} diff --git a/internal/chart/v3/util/jsonschema.go b/internal/chart/v3/util/jsonschema.go new file mode 100644 index 000000000..9fe35904e --- /dev/null +++ b/internal/chart/v3/util/jsonschema.go @@ -0,0 +1,113 @@ +/* +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 util + +import ( + "bytes" + "errors" + "fmt" + "log/slog" + "strings" + + "github.com/santhosh-tekuri/jsonschema/v6" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// ValidateAgainstSchema checks that values does not violate the structure laid out in schema +func ValidateAgainstSchema(chrt *chart.Chart, values map[string]interface{}) error { + var sb strings.Builder + if chrt.Schema != nil { + slog.Debug("chart name", "chart-name", chrt.Name()) + err := ValidateAgainstSingleSchema(values, chrt.Schema) + if err != nil { + sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) + sb.WriteString(err.Error()) + } + } + slog.Debug("number of dependencies in the chart", "dependencies", len(chrt.Dependencies())) + // For each dependency, recursively call this function with the coalesced values + for _, subchart := range chrt.Dependencies() { + subchartValues := values[subchart.Name()].(map[string]interface{}) + if err := ValidateAgainstSchema(subchart, subchartValues); err != nil { + sb.WriteString(err.Error()) + } + } + + if sb.Len() > 0 { + return errors.New(sb.String()) + } + + return nil +} + +// ValidateAgainstSingleSchema checks that values does not violate the structure laid out in this schema +func ValidateAgainstSingleSchema(values Values, schemaJSON []byte) (reterr error) { + defer func() { + if r := recover(); r != nil { + reterr = fmt.Errorf("unable to validate schema: %s", r) + } + }() + + // This unmarshal function leverages UseNumber() for number precision. The parser + // used for values does this as well. + schema, err := jsonschema.UnmarshalJSON(bytes.NewReader(schemaJSON)) + if err != nil { + return err + } + slog.Debug("unmarshalled JSON schema", "schema", schemaJSON) + + compiler := jsonschema.NewCompiler() + err = compiler.AddResource("file:///values.schema.json", schema) + if err != nil { + return err + } + + validator, err := compiler.Compile("file:///values.schema.json") + if err != nil { + return err + } + + err = validator.Validate(values.AsMap()) + if err != nil { + return JSONSchemaValidationError{err} + } + + return nil +} + +// Note, JSONSchemaValidationError is used to wrap the error from the underlying +// validation package so that Helm has a clean interface and the validation package +// could be replaced without changing the Helm SDK API. + +// JSONSchemaValidationError is the error returned when there is a schema validation +// error. +type JSONSchemaValidationError struct { + embeddedErr error +} + +// Error prints the error message +func (e JSONSchemaValidationError) Error() string { + errStr := e.embeddedErr.Error() + + // This string prefixes all of our error details. Further up the stack of helm error message + // building more detail is provided to users. This is removed. + errStr = strings.TrimPrefix(errStr, "jsonschema validation failed with 'file:///values.schema.json#'\n") + + // The extra new line is needed for when there are sub-charts. + return errStr + "\n" +} diff --git a/internal/chart/v3/util/jsonschema_test.go b/internal/chart/v3/util/jsonschema_test.go new file mode 100644 index 000000000..0a3820377 --- /dev/null +++ b/internal/chart/v3/util/jsonschema_test.go @@ -0,0 +1,247 @@ +/* +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 util + +import ( + "os" + "testing" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +func TestValidateAgainstSingleSchema(t *testing.T) { + values, err := ReadValuesFile("./testdata/test-values.yaml") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + schema, err := os.ReadFile("./testdata/test-values.schema.json") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + + if err := ValidateAgainstSingleSchema(values, schema); err != nil { + t.Errorf("Error validating Values against Schema: %s", err) + } +} + +func TestValidateAgainstInvalidSingleSchema(t *testing.T) { + values, err := ReadValuesFile("./testdata/test-values.yaml") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + schema, err := os.ReadFile("./testdata/test-values-invalid.schema.json") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + + var errString string + if err := ValidateAgainstSingleSchema(values, schema); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `"file:///values.schema.json#" is not valid against metaschema: jsonschema validation failed with 'https://json-schema.org/draft/2020-12/schema#' +- at '': got number, want boolean or object` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} + +func TestValidateAgainstSingleSchemaNegative(t *testing.T) { + values, err := ReadValuesFile("./testdata/test-values-negative.yaml") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + schema, err := os.ReadFile("./testdata/test-values.schema.json") + if err != nil { + t.Fatalf("Error reading JSON file: %s", err) + } + + var errString string + if err := ValidateAgainstSingleSchema(values, schema); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `- at '': missing property 'employmentInfo' +- at '/age': minimum: got -5, want 0 +` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} + +const subchartSchema = `{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Values", + "type": "object", + "properties": { + "age": { + "description": "Age", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "age" + ] +} +` + +const subchartSchema2020 = `{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Values", + "type": "object", + "properties": { + "data": { + "type": "array", + "contains": { "type": "string" }, + "unevaluatedItems": { "type": "number" } + } + }, + "required": ["data"] +} +` + +func TestValidateAgainstSchema(t *testing.T) { + subchartJSON := []byte(subchartSchema) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "age": 25, + }, + } + + if err := ValidateAgainstSchema(chrt, vals); err != nil { + t.Errorf("Error validating Values against Schema: %s", err) + } +} + +func TestValidateAgainstSchemaNegative(t *testing.T) { + subchartJSON := []byte(subchartSchema) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{}, + } + + var errString string + if err := ValidateAgainstSchema(chrt, vals); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `subchart: +- at '': missing property 'age' +` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} + +func TestValidateAgainstSchema2020(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{"hello", 12}, + }, + } + + if err := ValidateAgainstSchema(chrt, vals); err != nil { + t.Errorf("Error validating Values against Schema: %s", err) + } +} + +func TestValidateAgainstSchema2020Negative(t *testing.T) { + subchartJSON := []byte(subchartSchema2020) + subchart := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "subchart", + }, + Schema: subchartJSON, + } + chrt := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "chrt", + }, + } + chrt.AddDependency(subchart) + + vals := map[string]interface{}{ + "name": "John", + "subchart": map[string]interface{}{ + "data": []any{12}, + }, + } + + var errString string + if err := ValidateAgainstSchema(chrt, vals); err == nil { + t.Fatalf("Expected an error, but got nil") + } else { + errString = err.Error() + } + + expectedErrString := `subchart: +- at '/data': no items match contains schema + - at '/data/0': got number, want string +` + if errString != expectedErrString { + t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) + } +} diff --git a/internal/chart/v3/util/save.go b/internal/chart/v3/util/save.go new file mode 100644 index 000000000..3125cc3c9 --- /dev/null +++ b/internal/chart/v3/util/save.go @@ -0,0 +1,253 @@ +/* +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 util + +import ( + "archive/tar" + "compress/gzip" + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "time" + + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") + +// SaveDir saves a chart as files in a directory. +// +// This takes the chart name, and creates a new subdirectory inside of the given dest +// directory, writing the chart's contents to that subdirectory. +func SaveDir(c *chart.Chart, dest string) error { + // Create the chart directory + err := validateName(c.Name()) + if err != nil { + return err + } + outdir := filepath.Join(dest, c.Name()) + if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { + return fmt.Errorf("file %s already exists and is not a directory", outdir) + } + if err := os.MkdirAll(outdir, 0755); err != nil { + return err + } + + // Save the chart file. + if err := SaveChartfile(filepath.Join(outdir, ChartfileName), c.Metadata); err != nil { + return err + } + + // Save values.yaml + for _, f := range c.Raw { + if f.Name == ValuesfileName { + vf := filepath.Join(outdir, ValuesfileName) + if err := writeFile(vf, f.Data); err != nil { + return err + } + } + } + + // Save values.schema.json if it exists + if c.Schema != nil { + filename := filepath.Join(outdir, SchemafileName) + if err := writeFile(filename, c.Schema); err != nil { + return err + } + } + + // Save templates and files + for _, o := range [][]*chart.File{c.Templates, c.Files} { + for _, f := range o { + n := filepath.Join(outdir, f.Name) + if err := writeFile(n, f.Data); err != nil { + return err + } + } + } + + // Save dependencies + base := filepath.Join(outdir, ChartsDir) + for _, dep := range c.Dependencies() { + // Here, we write each dependency as a tar file. + if _, err := Save(dep, base); err != nil { + return fmt.Errorf("saving %s: %w", dep.ChartFullPath(), err) + } + } + return nil +} + +// Save creates an archived chart to the given directory. +// +// This takes an existing chart and a destination directory. +// +// If the directory is /foo, and the chart is named bar, with version 1.0.0, this +// will generate /foo/bar-1.0.0.tgz. +// +// This returns the absolute path to the chart archive file. +func Save(c *chart.Chart, outDir string) (string, error) { + if err := c.Validate(); err != nil { + return "", fmt.Errorf("chart validation: %w", err) + } + + filename := fmt.Sprintf("%s-%s.tgz", c.Name(), c.Metadata.Version) + filename = filepath.Join(outDir, filename) + dir := filepath.Dir(filename) + if stat, err := os.Stat(dir); err != nil { + if errors.Is(err, fs.ErrNotExist) { + if err2 := os.MkdirAll(dir, 0755); err2 != nil { + return "", err2 + } + } else { + return "", fmt.Errorf("stat %s: %w", dir, err) + } + } else if !stat.IsDir() { + return "", fmt.Errorf("is not a directory: %s", dir) + } + + f, err := os.Create(filename) + if err != nil { + return "", err + } + + // Wrap in gzip writer + zipper := gzip.NewWriter(f) + zipper.Extra = headerBytes + zipper.Comment = "Helm" + + // Wrap in tar writer + twriter := tar.NewWriter(zipper) + rollback := false + defer func() { + twriter.Close() + zipper.Close() + f.Close() + if rollback { + os.Remove(filename) + } + }() + + if err := writeTarContents(twriter, c, ""); err != nil { + rollback = true + return filename, err + } + return filename, nil +} + +func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { + err := validateName(c.Name()) + if err != nil { + return err + } + base := filepath.Join(prefix, c.Name()) + + // Save Chart.yaml + cdata, err := yaml.Marshal(c.Metadata) + if err != nil { + return err + } + if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil { + return err + } + + // Save Chart.lock + if c.Lock != nil { + ldata, err := yaml.Marshal(c.Lock) + if err != nil { + return err + } + if err := writeToTar(out, filepath.Join(base, "Chart.lock"), ldata); err != nil { + return err + } + } + + // Save values.yaml + for _, f := range c.Raw { + if f.Name == ValuesfileName { + if err := writeToTar(out, filepath.Join(base, ValuesfileName), f.Data); err != nil { + return err + } + } + } + + // Save values.schema.json if it exists + if c.Schema != nil { + if !json.Valid(c.Schema) { + return errors.New("invalid JSON in " + SchemafileName) + } + if err := writeToTar(out, filepath.Join(base, SchemafileName), c.Schema); err != nil { + return err + } + } + + // Save templates + for _, f := range c.Templates { + n := filepath.Join(base, f.Name) + if err := writeToTar(out, n, f.Data); err != nil { + return err + } + } + + // Save files + for _, f := range c.Files { + n := filepath.Join(base, f.Name) + if err := writeToTar(out, n, f.Data); err != nil { + return err + } + } + + // Save dependencies + for _, dep := range c.Dependencies() { + if err := writeTarContents(out, dep, filepath.Join(base, ChartsDir)); err != nil { + return err + } + } + return nil +} + +// writeToTar writes a single file to a tar archive. +func writeToTar(out *tar.Writer, name string, body []byte) error { + // TODO: Do we need to create dummy parent directory names if none exist? + h := &tar.Header{ + Name: filepath.ToSlash(name), + Mode: 0644, + Size: int64(len(body)), + ModTime: time.Now(), + } + if err := out.WriteHeader(h); err != nil { + return err + } + _, err := out.Write(body) + return err +} + +// If the name has directory name has characters which would change the location +// they need to be removed. +func validateName(name string) error { + nname := filepath.Base(name) + + if nname != name { + return ErrInvalidChartName{name} + } + + return nil +} diff --git a/internal/chart/v3/util/save_test.go b/internal/chart/v3/util/save_test.go new file mode 100644 index 000000000..852675bb0 --- /dev/null +++ b/internal/chart/v3/util/save_test.go @@ -0,0 +1,261 @@ +/* +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 util + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "io" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "testing" + "time" + + chart "helm.sh/helm/v4/internal/chart/v3" + "helm.sh/helm/v4/internal/chart/v3/loader" +) + +func TestSave(t *testing.T) { + tmp := t.TempDir() + + for _, dest := range []string{tmp, filepath.Join(tmp, "newdir")} { + t.Run("outDir="+dest, func(t *testing.T) { + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV3, + Name: "ahab", + Version: "1.2.3", + }, + Lock: &chart.Lock{ + Digest: "testdigest", + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + Schema: []byte("{\n \"title\": \"Values\"\n}"), + } + chartWithInvalidJSON := withSchema(*c, []byte("{")) + + where, err := Save(c, dest) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + if !strings.HasPrefix(where, dest) { + t.Fatalf("Expected %q to start with %q", where, dest) + } + if !strings.HasSuffix(where, ".tgz") { + t.Fatalf("Expected %q to end with .tgz", where) + } + + c2, err := loader.LoadFile(where) + if err != nil { + t.Fatal(err) + } + if c2.Name() != c.Name() { + t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) + } + if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { + t.Fatal("Files data did not match") + } + + if !bytes.Equal(c.Schema, c2.Schema) { + indentation := 4 + formattedExpected := Indent(indentation, string(c.Schema)) + formattedActual := Indent(indentation, string(c2.Schema)) + t.Fatalf("Schema data did not match.\nExpected:\n%s\nActual:\n%s", formattedExpected, formattedActual) + } + if _, err := Save(&chartWithInvalidJSON, dest); err == nil { + t.Fatalf("Invalid JSON was not caught while saving chart") + } + + c.Metadata.APIVersion = chart.APIVersionV3 + where, err = Save(c, dest) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + c2, err = loader.LoadFile(where) + if err != nil { + t.Fatal(err) + } + if c2.Lock == nil { + t.Fatal("Expected v3 chart archive to contain a Chart.lock file") + } + if c2.Lock.Digest != c.Lock.Digest { + t.Fatal("Chart.lock data did not match") + } + }) + } + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV3, + Name: "../ahab", + Version: "1.2.3", + }, + Lock: &chart.Lock{ + Digest: "testdigest", + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + } + _, err := Save(c, tmp) + if err == nil { + t.Fatal("Expected error saving chart with invalid name") + } +} + +// Creates a copy with a different schema; does not modify anything. +func withSchema(chart chart.Chart, schema []byte) chart.Chart { + chart.Schema = schema + return chart +} + +func Indent(n int, text string) string { + startOfLine := regexp.MustCompile(`(?m)^`) + indentation := strings.Repeat(" ", n) + return startOfLine.ReplaceAllLiteralString(text, indentation) +} + +func TestSavePreservesTimestamps(t *testing.T) { + // Test executes so quickly that if we don't subtract a second, the + // check will fail because `initialCreateTime` will be identical to the + // written timestamp for the files. + initialCreateTime := time.Now().Add(-1 * time.Second) + + tmp := t.TempDir() + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV3, + Name: "ahab", + Version: "1.2.3", + }, + Values: map[string]interface{}{ + "imageName": "testimage", + "imageId": 42, + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + Schema: []byte("{\n \"title\": \"Values\"\n}"), + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + + allHeaders, err := retrieveAllHeadersFromTar(where) + if err != nil { + t.Fatalf("Failed to parse tar: %v", err) + } + + for _, header := range allHeaders { + if header.ModTime.Before(initialCreateTime) { + t.Fatalf("File timestamp not preserved: %v", header.ModTime) + } + } +} + +// We could refactor `load.go` to use this `retrieveAllHeadersFromTar` function +// as well, so we are not duplicating components of the code which iterate +// through the tar. +func retrieveAllHeadersFromTar(path string) ([]*tar.Header, error) { + raw, err := os.Open(path) + if err != nil { + return nil, err + } + defer raw.Close() + + unzipped, err := gzip.NewReader(raw) + if err != nil { + return nil, err + } + defer unzipped.Close() + + tr := tar.NewReader(unzipped) + headers := []*tar.Header{} + for { + hd, err := tr.Next() + if err == io.EOF { + break + } + + if err != nil { + return nil, err + } + + headers = append(headers, hd) + } + + return headers, nil +} + +func TestSaveDir(t *testing.T) { + tmp := t.TempDir() + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV3, + Name: "ahab", + Version: "1.2.3", + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + Templates: []*chart.File{ + {Name: path.Join(TemplatesDir, "nested", "dir", "thing.yaml"), Data: []byte("abc: {{ .Values.abc }}")}, + }, + } + + if err := SaveDir(c, tmp); err != nil { + t.Fatalf("Failed to save: %s", err) + } + + c2, err := loader.LoadDir(tmp + "/ahab") + if err != nil { + t.Fatal(err) + } + + if c2.Name() != c.Name() { + t.Fatalf("Expected chart archive to have %q, got %q", c.Name(), c2.Name()) + } + + if len(c2.Templates) != 1 || c2.Templates[0].Name != c.Templates[0].Name { + t.Fatal("Templates data did not match") + } + + if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name { + t.Fatal("Files data did not match") + } + + tmp2 := t.TempDir() + c.Metadata.Name = "../ahab" + pth := filepath.Join(tmp2, "tmpcharts") + if err := os.MkdirAll(filepath.Join(pth), 0755); err != nil { + t.Fatal(err) + } + + if err := SaveDir(c, pth); err.Error() != "\"../ahab\" is not a valid chart name" { + t.Fatalf("Did not get expected error for chart named %q", c.Name()) + } +} diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml new file mode 100644 index 000000000..4a4da7996 --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: chart-with-dependency-aliased-twice +type: application +version: 1.0.0 + +dependencies: + - name: child + alias: foo + version: 1.0.0 + - name: child + alias: bar + version: 1.0.0 + diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml new file mode 100644 index 000000000..0f3afd8c6 --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: child +type: application +version: 1.0.0 + diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml new file mode 100644 index 000000000..3e0bf725b --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: grandchild +type: application +version: 1.0.0 + diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml new file mode 100644 index 000000000..1830492ef --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/charts/grandchild/templates/dummy.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }}-{{ .Values.from }} +data: + {{- toYaml .Values | nindent 2 }} + diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml new file mode 100644 index 000000000..b5d55af7c --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/charts/child/templates/dummy.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }} +data: + {{- toYaml .Values | nindent 2 }} + diff --git a/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/values.yaml b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/values.yaml new file mode 100644 index 000000000..695521a4a --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-dependency-aliased-twice/values.yaml @@ -0,0 +1,7 @@ +foo: + grandchild: + from: foo +bar: + grandchild: + from: bar + diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml new file mode 100644 index 000000000..f2f0610b5 --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/Chart.yaml @@ -0,0 +1,20 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: chart-with-dependency-aliased-twice +type: application +version: 1.0.0 + +dependencies: + - name: child + alias: foo + version: 1.0.0 + import-values: + - parent: foo-defaults + child: defaults + - name: child + alias: bar + version: 1.0.0 + import-values: + - parent: bar-defaults + child: defaults + diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml new file mode 100644 index 000000000..08ccac9e5 --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: child +type: application +version: 1.0.0 + +dependencies: + - name: grandchild + version: 1.0.0 + import-values: + - parent: defaults + child: defaults diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml new file mode 100644 index 000000000..3e0bf725b --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v3 +appVersion: 1.0.0 +name: grandchild +type: application +version: 1.0.0 + diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml new file mode 100644 index 000000000..f51c594f4 --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/charts/grandchild/values.yaml @@ -0,0 +1,2 @@ +defaults: + defaultValue: "42" \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml new file mode 100644 index 000000000..3140f53dd --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/charts/child/templates/dummy.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }} +data: + {{ .Values.defaults | toYaml }} + diff --git a/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml new file mode 100644 index 000000000..a2b62c95a --- /dev/null +++ b/internal/chart/v3/util/testdata/chart-with-import-from-aliased-dependencies/templates/dummy.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Chart.Name }} +data: + {{ toYaml .Values.defaults | indent 2 }} + diff --git a/internal/chart/v3/util/testdata/chartfiletest.yaml b/internal/chart/v3/util/testdata/chartfiletest.yaml new file mode 100644 index 000000000..d222c8f8d --- /dev/null +++ b/internal/chart/v3/util/testdata/chartfiletest.yaml @@ -0,0 +1,20 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue diff --git a/internal/chart/v3/util/testdata/coleridge.yaml b/internal/chart/v3/util/testdata/coleridge.yaml new file mode 100644 index 000000000..b6579628b --- /dev/null +++ b/internal/chart/v3/util/testdata/coleridge.yaml @@ -0,0 +1,12 @@ +poet: "Coleridge" +title: "Rime of the Ancient Mariner" +stanza: ["at", "length", "did", "cross", "an", "Albatross"] + +mariner: + with: "crossbow" + shot: "ALBATROSS" + +water: + water: + where: "everywhere" + nor: "any drop to drink" diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/.helmignore b/internal/chart/v3/util/testdata/dependent-chart-alias/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.lock b/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.yaml new file mode 100644 index 000000000..b8773d0d3 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/Chart.yaml @@ -0,0 +1,29 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts + alias: mariners2 + - name: mariner + version: "4.3.2" + repository: https://example.com/charts + alias: mariners1 diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/INSTALL.txt b/internal/chart/v3/util/testdata/dependent-chart-alias/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/LICENSE b/internal/chart/v3/util/testdata/dependent-chart-alias/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/README.md b/internal/chart/v3/util/testdata/dependent-chart-alias/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/_ignore_me b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/README.md b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/internal/chart/v3/util/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/docs/README.md b/internal/chart/v3/util/testdata/dependent-chart-alias/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/icon.svg b/internal/chart/v3/util/testdata/dependent-chart-alias/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/ignore/me.txt b/internal/chart/v3/util/testdata/dependent-chart-alias/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/templates/template.tpl b/internal/chart/v3/util/testdata/dependent-chart-alias/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/dependent-chart-alias/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-alias/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-alias/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/.helmignore b/internal/chart/v3/util/testdata/dependent-chart-helmignore/.helmignore new file mode 100644 index 000000000..8a71bc82e --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/.helmignore @@ -0,0 +1,2 @@ +ignore/ +.* diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/Chart.yaml new file mode 100644 index 000000000..8b4ad8cdd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/.ignore_me b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/.ignore_me new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/_ignore_me b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/README.md b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/templates/template.tpl b/internal/chart/v3/util/testdata/dependent-chart-helmignore/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/dependent-chart-helmignore/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-helmignore/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-helmignore/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/.helmignore b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..8b4ad8cdd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/Chart.yaml @@ -0,0 +1,17 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/LICENSE b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/README.md b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/icon.svg b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-no-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..06283093e --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..6543799d0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3190136b050e62c628b3c817fd963ac9dc4a9e25 GIT binary patch literal 967 zcmV;&133I2iwFR+9h6)E1MQb_y%a`Bd>#= zhbqf2w!b6}wZ9@rvIhtwzm?~C&+QLm+G2!l%`$_aUgS(@pdjdX3a%E}VXVc7`)dg( zL%IRN2}c1D3xoMi2w@WuWOMZ?5i&3FelBVyq_Ce_8fREdP%NDf<&d!wu3{&VUQNs{N%vK$Ha~k^gB2!0bO7r0ic0 zbqCp*X#j?=|H@GNONsuE)&Idbh>2MX(Z}|+=@*sLod*wS?A8Ugp#mMPuMO0NnZmos9_rr z3xpDL+otj~lRh?B4hHFTl+d5-8NBXmUXB~EyF^bx7m*+!*g>obczvGF|8xkWsHN8; z%#+wiWP{=2pC*98@$VNzzsll&G#D7&11-;D>HT0x|DV2?6}a~*p46@R|2l??e_8dX z@Bb>D3t~VC_*wjq2Dy!6J-_5ME%%J+xmsbK5a#qO>ZV?Wt`d|TDdrB^B&LqFr@lYdUA zs&4-JAg3&uc4dA0gv*bXU9QePa9^8wR{HovA)j@)JOAIkak0Jl)aKqA_3XLM#?H=V z-{tlG=xy^os=1Z><53;&+C4=zp|%rwv!)UyY=%k_t%Y|wNRQl`C6N_Z&S;S+~wb1=)2Uh_4I81`y>DO zoLPSt=btB&x{CUK`0DA&){efW_O36RxnsYZNT0r;JbTLR{H4M3C$8(Cee==aQ~Gbq p$`5v3?NB{4-j0XQHf literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/frobnitz-1.2.3.tgz b/internal/chart/v3/util/testdata/frobnitz-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..8731dce02cc9603e7813a07670a7d057129a8b20 GIT binary patch literal 3485 zcmV;O4Px>iiwFRyACz1G1MOT3TolzBhwPeez8~c^`*9rfidUF@%`Sp~AdnJ>5AaDU z!|q{sVs>YnnFUtlqF_quB{faUuIRnpn-?EtYMQ2^*3EomX<1&iyrx3=%6GcL-ZQhy z0xPGp&V1kbUS~2{Cc{ke6XkwQ2Zcfrs?h-PsMU%`g^F+wfo zL8wDRm4reI6iT&PP51##6W)^>R*olGbSoqaAqQ_yhsZKB@6e9xIo!ub1erDSpOg?A zpPUlk6n&ua&=SNQ=3k`|=U<~xYK0d?p(NDmP(Pq(iktDo?|lAU(+(^&se?v_)XRbffp-h5waGrOJcjvSVHsT( zdQRkcK}mcT7$gN!tr8J-k|PV5Qh@+^r)C)|0KP1083K1oDmgsfQLI(HW7p#_@t z(5$0iy|E!_3mPx+32O&mfh%zZCSGKrh2bckVQmfHpiba(u1j7NX$%T z+c0_kHaLhH*NcrPHDW7-msVJ)7aEBW1|;esS}WcBpOBoA8k3ZS^SOLu_uft(?Lgz?Jv;jTES!i_RQ60%i@Y{f!|Iw^B zjrMo<`kzXx(h?e#p#Q0~YDxd|1>EzG3$`_7Ff;5O2I1b|RsJg#p7Nj2XeIgY3pi(7 zE=lv>Dct-&JU%9Fa6E3(H+~=9_+O<~dd7dWN`=J#zCa*&uEs|ztD_6L{Chz33gI$Y zU?3R5kp|ch5e^b~U?e$UW`I>7a?;1aY)CT}L6elpo?}>`cV2)j(lj%fV8B6$R7v!Y zv4qeH_Mb+rR!a7tFW{bkG3v-QNdka3`L85yk^dScA<2JVAP~d= z=hO?1YNYTnVnCh~PBJNjl@%k{NTYK~aZCuagJg7$$z&YViJ1XNe7j180wkFM30!5E zB%dE{G$8+L&T!t+IHJI-|A+AO|06QV|L+qFf;7#5ygXcF-ATwu%OtHd53n4DrS({T zzQn-4Y1H@sH;og>EB|Y5UH=Km|Kk(XCiT_H00x&Fg0Qn(5wBY@XY_xB6>;w`vRi;=ZuV% z)`JXWyNR=pPHm!Vo@Pk290Y?5t1|;cpfKxbs~(YvW}YiP@SvYU!o9+i|6Oj{5YaJ< z^M;9y(1#cPSB5Iw_Ft%0Bjo>v5`ev>0_=Rt#EnkN5v>S=o0%kDol#*S}rQF|YR=(R#*X?cx$zl|?P-#10D^-e$@| z{0|vZyInqGZ9F^zo{+Cx4n_p1kmM z5Fp7fX1^HwZQSEwFAPkcx+ihc?8LFF=0#7Iy*KBN12dCilNUBy(m~%jdUNXzbXc3J z)_$|--Cq{J+;0a+C_X!NdD$0#GJYF=T=)90`Im+bv0k13(e}yZGe2r~Z2Z-zEoDhZ z4{x70<>`Hn_k){{sg^envv-W!(R%-AeDaew)9i^$X9qo5w#X6x%Au`6C(eBDf29*E zJK&cz!7;zr>8(Acjjob6@0{NA`SN+m(FdD-v};T7@Co)|yw!A|taHRD|Gy{f^}k%0 zKF*Y)>-je_dce?bJ&3VOFZJ9sF7UI|MWa@~0xRc-6NfkV`+P))13$LOJK6tqR{6B9 z*?y~|7v-Jro4Po2|EI})UXF=+_QM-%B4f+*LdsuyZDhsOPJD-!%?eYBHU9F?4_q&8 zqnx+)a^LL{s}0yQNl!Zn|D?Yx;Z`dle3Q48M}Te81{2P*zTNi$KGCB=6<=Y`K4KkclaIaI#+zKU`6k5 z`V8H@x~M4a`~rOJFIP&A{sVB#)m8h85(nw&y&HO7sr-Ok*KjO&*3 zXO}nA7*uZ@3@Y7Xsa(H$)d9Q1w*30Rl}WotuibR~@JV*a+H+s*yxPB@x7|Mc+Lsps zORK7@_V!^G8xLLkabor4Po7OIt^Dlg>h6x6CDZfX+`7!J+`z86ezfr7?#+gP$hT6e z$V11Y@+kZJHceVqa!K<~H%$p;v2iJUEQthP_!*w(WO=Mn!|+vTdRN% zf&=`5?w9{>qyIPRZa}a4pH^L8|5FLqB>C?PG`RoI75uHY`!5b!XvT(Az6_G1bRb3~ z0ZEkEnhPw9zIG>|7$mNrQL$f0;6AZ0qn zDikr9z(TTU8RF4`Qq#CAIe}+Pbh<~JTDZTOpjAyEn_-2^vUKMyhM?X=RVE#{Lz^<$ z7{&^8b#}e*bqB)v=+4Kjvn__JNl2>Ulk`X4^>G{iZ`5p{yOsa$@BbkEPxAlz2Dh32 zM$HEDCjS+l`9CUDEPemSCkPZb`3dq^Q2lGNd`z|w=Zhfa@BGH$RsMUdq4&~Ow@s>R_T4U=b3W;sVgXu*Q9F!Tao;~ovkd1w3IL~^_;MD*y~M=Z!2|Eog@5B^tb zw9@+T69mj`(MddU^!DpR4jQh|4H~RXvW00f)FT!86b&^tB}_YH z{w;0n+-4Q8YN9Gj4;|bv``oORueJ1xUJO@1)@4*sRbExe>F1XR59#r*4SPnPFM6rv z;;5`6N2V*rt?N;-{Da;nmVEN&q#w)hj~xYJ@BDYyMWP#fApFD1Q75{}PQ>eiJFAjk z?Sel8M)dx8Q(8vbz7+)u>yW>cJ#lP&^^`?79liHnFL=3X%B5Xhbl+EYK@aNqq3Ej# zeytwxz&|AL&i~5$HUWRN|4)TPg0%nNC%BvWzu#bxH~Ej|-b4PY)GF!wPd-6|@gH&8 z>xX}0oF8Nq-wV;fmT1j@tQjH`q2bUECYg?p0`7+Y@7EdRjsI0jPy0`$(MtJ0K7p`L z{}$)DIPJJBu+Ar6$HWXy3PEKik{4nFf)8FGh=V#BjhtvRIo}gtAt{yvJR>9vT1bu) zQw7ma8)IeN4tP$eEK~xK02Av;;zEK12@)hokRU;V1PKx(NRS{wf&>W?BuJ1Tp;7RE LkUR>*0C)fZJ*e(i literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/frobnitz/.helmignore b/internal/chart/v3/util/testdata/frobnitz/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/internal/chart/v3/util/testdata/frobnitz/Chart.lock b/internal/chart/v3/util/testdata/frobnitz/Chart.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/Chart.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/internal/chart/v3/util/testdata/frobnitz/Chart.yaml b/internal/chart/v3/util/testdata/frobnitz/Chart.yaml new file mode 100644 index 000000000..1b63fc3e2 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/Chart.yaml @@ -0,0 +1,27 @@ +apiVersion: v3 +name: frobnitz +description: This is a frobnitz. +version: "1.2.3" +keywords: + - frobnitz + - sprocket + - dodad +maintainers: + - name: The Helm Team + email: helm@example.com + - name: Someone Else + email: nobody@example.com +sources: + - https://example.com/foo/bar +home: http://example.com +icon: https://example.com/64x64.png +annotations: + extrakey: extravalue + anotherkey: anothervalue +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts diff --git a/internal/chart/v3/util/testdata/frobnitz/INSTALL.txt b/internal/chart/v3/util/testdata/frobnitz/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/internal/chart/v3/util/testdata/frobnitz/LICENSE b/internal/chart/v3/util/testdata/frobnitz/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/internal/chart/v3/util/testdata/frobnitz/README.md b/internal/chart/v3/util/testdata/frobnitz/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/_ignore_me b/internal/chart/v3/util/testdata/frobnitz/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/Chart.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/Chart.yaml new file mode 100644 index 000000000..2a2c9c883 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://helm.sh/helm diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/README.md b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/README.md new file mode 100644 index 000000000..b30b949dd --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install ./alpine`. diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..aea109c75 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..61cb62051110b55f3d08213dc81dcf0b1c2d8e53 GIT binary patch literal 252 zcmVDc zVQyr3R8em|NM&qo0PNJUs=_c72H?(liabH@pWIst-7YSIyL+rhEHrIN(t?QZE=F{y zgNRfS&$pa5Ly`mMk2OB%pV`*9knW7FlL-Joo@KED7*{C$d;N~z z37$S{+}wvSU9}|VtF|fRpv9Ve>8dWo|9?5B+RE}Y9CFh-x#(Bq8Vck^V=NUiPLCKa z8z5CF#JgK!4>;$4Fm+FUst4d+{(+nP|0&J+e}(;l^U4@w-{=?s0RR8vgVbLD3;+OM Cs&R<` literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..5bbae10af --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + app.kubernetes.io/managed-by: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/alpine/values.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/mariner/Chart.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/Chart.yaml new file mode 100644 index 000000000..4d3eea730 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v3 +name: mariner +description: A Helm chart for Kubernetes +version: 4.3.2 +home: "" +dependencies: + - name: albatross + repository: https://example.com/mariner/charts + version: "0.1.0" diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml new file mode 100644 index 000000000..da605991b --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: albatross +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml new file mode 100644 index 000000000..3121cd7ce --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml @@ -0,0 +1,4 @@ +albatross: "true" + +global: + author: Coleridge diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl new file mode 100644 index 000000000..29c11843a --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/templates/placeholder.tpl @@ -0,0 +1 @@ +# This is a placeholder. diff --git a/internal/chart/v3/util/testdata/frobnitz/charts/mariner/values.yaml b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/values.yaml new file mode 100644 index 000000000..b0ccb0086 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/charts/mariner/values.yaml @@ -0,0 +1,7 @@ +# Default values for . +# This is a YAML-formatted file. https://github.com/toml-lang/toml +# Declare name/value pairs to be passed into your templates. +# name: "value" + +: + test: true diff --git a/internal/chart/v3/util/testdata/frobnitz/docs/README.md b/internal/chart/v3/util/testdata/frobnitz/docs/README.md new file mode 100644 index 000000000..d40747caf --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/docs/README.md @@ -0,0 +1 @@ +This is a placeholder for documentation. diff --git a/internal/chart/v3/util/testdata/frobnitz/icon.svg b/internal/chart/v3/util/testdata/frobnitz/icon.svg new file mode 100644 index 000000000..892130606 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/icon.svg @@ -0,0 +1,8 @@ + + + Example icon + + + diff --git a/internal/chart/v3/util/testdata/frobnitz/ignore/me.txt b/internal/chart/v3/util/testdata/frobnitz/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/frobnitz/templates/template.tpl b/internal/chart/v3/util/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/internal/chart/v3/util/testdata/frobnitz/values.yaml b/internal/chart/v3/util/testdata/frobnitz/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/internal/chart/v3/util/testdata/frobnitz/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/internal/chart/v3/util/testdata/frobnitz_backslash-1.2.3.tgz b/internal/chart/v3/util/testdata/frobnitz_backslash-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6929659514d35d2a2fe1098c1c097194378cc3b5 GIT binary patch literal 3496 zcmV;Z4Oj9XiwFRyACz1G1MOT3ToiR4hwMx@-;eUlKKvc@#1m$BXJ3MVAdnJ>5AaC} z!|bp-F}t(P%mOQNsF;#^q^4=viQaXWhmSHfO;b_pG9Ou5mZz3yDwMB$rz`CJXLdnY zR~K|!wdVT~XLkO-`TzejzxnDQ%Ve1;enLhTPULVj&KczazJ@}f&}lS4+-kMr zR)MLVTNi**rB9msk`vbUz8cDJo#h4>Nu2N>k zO=JYP{h^TQNseGC3ojC;87U6He|Q}wD$A|y1yQ0HszPKrq%DtO^<0DufUr73@dsyN z86t<|#UFY`kLzV7oHFz95BB0b!8wyOBMAnPvf{bd6}_c?%aIm)9eZXETgE3uk3UM(A0f zXh?q*GsEM;trJHg^LZABcPGNRmy6R@VV*c7GLo#=;M#XK z=}8M|){|y}A{*QroVLLGC0^mJo3B8Xr~(lWvZRIKD4t<$o+n5Ma?aK3(1f!B1KfI4 zqbd|@QjZV%nU>Iac+me&F8a_7XsrFOR%x}EQX$xXT1+F^|Nejn{tGG%<#G)^8^as_ zHSnO@`ma!_r1jq)i0q$lqHDE%6e27h{i9`(ko^EQzy%BLMIo+*FE1|-%~PQaYmj5` zBIU3RagyDYL|i+Zz2(!CImZ*RGCVw7F18}|*B|GMKpv&%jZp}u(IRl=rwm5k^@Gw! zAw0?PNHjbvQZQ1Y6PybY5FX=5xp+uuV@ZM+$+IaI(t~{10|IQ~|E})Uid(&F;eIwn z(@}^S&$S`)hJA1(>|8H)qST1JsJ-;Ul6s+&Na2P=KSV2}2l-D-NllAQPDc5B-Zx`- z)&Dx|Hv1n_O7iay)S2>TK$+n+(lj*439E@T^QdTj6Ep?)p>1dbdWy5qaLdDUq#(g> z10Lib^52)%U*qjROp9sN8ceYNME_s1|9*XV;6Ez()|A1_uy?r~O@UYbD;4hgU#(M1 z`rjXL@>wov^WP~v$p7$!*!aN-s7dduF}%q?rc%1+f8a_W$-h4k44$iv%*yK7g083@ z5Mx28OcoprMsT=+^hAV$L>?G%4wxBW6^NYl@nkMZGZw*;l*yiBSfFW;3CH zBS~mV!av3mMhk8A;){}b=_lMV|3foGj*rIhCjTmhyZq}gtwNH2f8dczF6b!($?*|T zhveckrI+-?{lSC$^CZ0II8XY_32eImr-Q#F|IZ)rz<)98$oa4Uz#IRS>f7|cMukcG z-yaADvB1f2Ay5qsK1NK#bHYwGWw5eh-WpnTPA!fI$#swd_Ys+lBMGq{0IF|StIz;7 zGr)nX4UzQo!;QP@|Jps2x7?2^@T&jSZt;IisZ&Vtf4^W5Nz)7{C_qKgod}(@Y}`uo z0Lf!mT8|VKN&@_w#+~B-z6=6+m4D6c>%T^d|M&&BO8M$l0E28HT;oaux(t8^Jy8k2 z3hrG0<%|IM!Ut@u|EJdJFqIPef0#lktpAe#_v_=%{LgTvhB8d|odxvj|0&$+zc7VX zEBSwZfvEpEBV(oYAPf3#BG2KjO$^P`3=WrrP>^MHR)7c;W`efrA&Kbbx$*-K`YI&c zEAA}+u7GWr7@|da!-RVt!;AbwXRprx(`hvd!T(chbdvo013@!e1S|`h_d-CkvkS%< zI}Pks(e|;}r`{e=9vwenN5b1<-#@gux-T~(ZbH`quZ>PGjnCM5!u+x2BAUS@A}TQP*W9phfqD!0(ooe;4xj$uoTe+w~mtde0H9XFS#}KCxAK z^pZ~Ou<+q+CND()kTtd2SGB$k5Wax*VjB zSEoPu(;V}pg{MORj(;)x#kgm__gYvgGA{J3wN|*{RFRzxb2!+lb@D>%-<>8aBjwb^b@&CsoY+ zsNJ#gSEIL-Cm%h$ect4!_c`7VZ9b+(-aOpiF@8tu{iD%IPu@(oCoP>F@?`lUN5U(I zwuYQI^L^lzPOR)eKyrp-ey`J8drTW$EpOgAv*+^_^HO3CHv4GTmeAoD_F=r$bfCO* zEC+-crJSB6SDV6B?H#}y*&~81{W0zj)xoKSRXK9N@t$u~9njfJ)yt&`!BRU-T zu}#6r{-<*)rghB?SRJ#d;C$b-#o7BmP3iM;Z1l4q-dGbAS5XjF@zQG}E3bCqJG5*z zCACBoDF6Jx^|Cg~d228C-5$BxfIO4@v_l=3{8tBv3Y3M`%-nXOYB6nlD(T%PhFo@B zFCO#Hyj8~&?WY{?5*vS6@b$!-)6+gV>xi4N>!+e&KNp4X&Z}_j?X_j@m)n|Onx%M$ z-?6T99%ECk-r>S*YR4R#ZZ~~!+%bG+V93w<9}K1V8`9OG;L1d;BWR_;3^$M z4^AL<=2vE2x12w_yqU&;y>T$4Y>TC8{pwW*><-)V>jzgR?;gE&)A7S6*&%DseX;Xu z|DxV@`|xXDUI;F$uBqPJhgobqbnV9pHIqJhHmR)Yv!83aJ9d^%FL-n7GP`mEyXN}Q zDHnHdHUvezm0FD-Iv!m>+26Nm(sNQun}51#+Bff(M*dvAaeaD{eL8byWy-5t3v`^l z7n!-QqTt5s>;89e%;cq)=dL+?>Po-q(|7HYhrO{esqIfI$|@J%*mdpdZe@q!ZJCNL zt(wwI*|L+%g<-ZRC!QS|x!Zlf{kqUjPh8mk1t{pOR zq;~Kq`bGG#+tP+HQNkCw0?vH3x_rB$Q}bh=+h#<@o-aF+E<1{%Q#KW>7#ONHCGMY6 zavpt-Usha_)_&jmx3qC{n^nH5iLNR;ba2D$bF)^y)-oVwF}eD&E~7%K3#v;`KfgS5 zNRNMQ*faWk@k=ciN9PrlU60D;AM`%4?jI<=fArys=KiVB0j7d zb)u{6L;?}o8B2M!3;GNg(fi{~=~-?2Ru(C&L;h0!#If-;lNa4|^xk{D=;i9kmv(g_ zzOU*6FVyiv@mCoEtsd}=e@M_2`B&bf3)uAd9|qk($^Y{Qn#TWoO9px4Kjgie{#UEi z()XYI0+0D0k=YH?Ke5iwFp5uu7+_1XW|6F!gpPsd)DI%Lj*kMGCja*q4Du%bn9|+< zQ)_fm{f}QDyr+L#@LXgzDhsanq1Q37gOkHxn+fNI+<*{+7YO1>f`)fa^qHJb2e9B2 z%K@GdiUln=$BCta@TZNjvULG?PB>XO2}A)zJWo;=5+q2FAVGoz2@)hokRU;V1PKx( WNRS{wf&>XZ!T$k(as0gicmMzx5%RPE literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/genfrob.sh b/internal/chart/v3/util/testdata/genfrob.sh new file mode 100755 index 000000000..35fdd59f2 --- /dev/null +++ b/internal/chart/v3/util/testdata/genfrob.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +# Pack the albatross chart into the mariner chart. +echo "Packing albatross into mariner" +tar -zcvf mariner/charts/albatross-0.1.0.tgz albatross + +echo "Packing mariner into frobnitz" +tar -zcvf frobnitz/charts/mariner-4.3.2.tgz mariner +tar -zcvf frobnitz_backslash/charts/mariner-4.3.2.tgz mariner + +# Pack the frobnitz chart. +echo "Packing frobnitz" +tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz +tar --exclude=ignore/* -zcvf frobnitz_backslash-1.2.3.tgz frobnitz_backslash diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock new file mode 100644 index 000000000..b2f17fb39 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: dev + repository: file://envs/dev + version: v0.1.0 +- name: prod + repository: file://envs/prod + version: v0.1.0 +digest: sha256:9403fc24f6cf9d6055820126cf7633b4bd1fed3c77e4880c674059f536346182 +generated: "2020-02-03T10:38:51.180474+01:00" diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml new file mode 100644 index 000000000..0b3e9958b --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v3 +name: parent-chart +version: v0.1.0 +appVersion: v0.1.0 +dependencies: + - name: dev + repository: "file://envs/dev" + version: ">= 0.0.1" + condition: dev.enabled,global.dev.enabled + tags: + - dev + import-values: + - data + + - name: prod + repository: "file://envs/prod" + version: ">= 0.0.1" + condition: prod.enabled,global.prod.enabled + tags: + - prod + import-values: + - data \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..d28e1621c86a56affb0617a912930d982ee5d09c GIT binary patch literal 333 zcmV-T0kZxdiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK}PYr`-Mg>%lY5bWGeZqs!5+TB+M-CZQ2GbE0Y9n>MvEZ~_~beXUgrQc z1sW=VuODc zVQyr3R8em|NM&qo0PK~)YJ)%!hCTZf13f1l6W36$d4NhGy$?F13%a|^u9EiYi-y+X zrIcVxVZX~T{~R1){(qg==KlCX61K0@waFSFA{Kc*RYY7?#Dhw*eUYg{p-|-sX1h%7 z6TnrrSY98ByIH2oEUQmBkeoRjtJ5jyR=-iu i)>JGtn?PqS;UNZ5Boc}Ioc90#0RR69wG({+3;+PL5}8~8 literal 0 HcmV?d00001 diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml new file mode 100644 index 000000000..72427c097 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: dev +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml new file mode 100644 index 000000000..38f03484d --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml @@ -0,0 +1,9 @@ +# Dev values parent-chart +nameOverride: parent-chart-dev +exports: + data: + resources: + autoscaler: + minReplicas: 1 + maxReplicas: 3 + targetCPUUtilizationPercentage: 80 diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml new file mode 100644 index 000000000..058ab3942 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +name: prod +version: v0.1.0 +appVersion: v0.1.0 \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml new file mode 100644 index 000000000..10cc756b2 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml @@ -0,0 +1,9 @@ +# Prod values parent-chart +nameOverride: parent-chart-prod +exports: + data: + resources: + autoscaler: + minReplicas: 2 + maxReplicas: 5 + targetCPUUtilizationPercentage: 90 diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml new file mode 100644 index 000000000..976e5a8f1 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml @@ -0,0 +1,16 @@ +################################################################################################### +# parent-chart horizontal pod autoscaler +################################################################################################### +apiVersion: autoscaling/v1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ .Release.Name }}-autoscaler + namespace: {{ .Release.Namespace }} +spec: + scaleTargetRef: + apiVersion: apps/v1beta1 + kind: Deployment + name: {{ .Release.Name }} + minReplicas: {{ required "A valid .Values.resources.autoscaler.minReplicas entry required!" .Values.resources.autoscaler.minReplicas }} + maxReplicas: {{ required "A valid .Values.resources.autoscaler.maxReplicas entry required!" .Values.resources.autoscaler.maxReplicas }} + targetCPUUtilizationPercentage: {{ required "A valid .Values.resources.autoscaler.targetCPUUtilizationPercentage!" .Values.resources.autoscaler.targetCPUUtilizationPercentage }} \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml new file mode 100644 index 000000000..b812f0a33 --- /dev/null +++ b/internal/chart/v3/util/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml @@ -0,0 +1,10 @@ +# Default values for parent-chart. +nameOverride: parent-chart +tags: + dev: false + prod: true +resources: + autoscaler: + minReplicas: 0 + maxReplicas: 0 + targetCPUUtilizationPercentage: 99 \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/joonix/Chart.yaml b/internal/chart/v3/util/testdata/joonix/Chart.yaml new file mode 100644 index 000000000..1860a3df1 --- /dev/null +++ b/internal/chart/v3/util/testdata/joonix/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: joonix +version: 1.2.3 diff --git a/internal/chart/v3/util/testdata/joonix/charts/.gitkeep b/internal/chart/v3/util/testdata/joonix/charts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/internal/chart/v3/util/testdata/subpop/Chart.yaml b/internal/chart/v3/util/testdata/subpop/Chart.yaml new file mode 100644 index 000000000..53e9ec502 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/Chart.yaml @@ -0,0 +1,41 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: parentchart +version: 0.1.0 +dependencies: + - name: subchart1 + repository: http://localhost:10191 + version: 0.1.0 + condition: subchart1.enabled + tags: + - front-end + - subchart1 + import-values: + - child: SC1data + parent: imported-chart1 + - child: SC1data + parent: overridden-chart1 + - child: imported-chartA + parent: imported-chartA + - child: imported-chartA-B + parent: imported-chartA-B + - child: overridden-chartA-B + parent: overridden-chartA-B + - child: SCBexported1A + parent: . + - SCBexported2 + - SC1exported1 + + - name: subchart2 + repository: http://localhost:10191 + version: 0.1.0 + condition: subchart2.enabled + tags: + - back-end + - subchart2 + + - name: subchart2 + alias: subchart2alias + repository: http://localhost:10191 + version: 0.1.0 + condition: subchart2alias.enabled diff --git a/internal/chart/v3/util/testdata/subpop/README.md b/internal/chart/v3/util/testdata/subpop/README.md new file mode 100644 index 000000000..e43fbfe9c --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/README.md @@ -0,0 +1,18 @@ +## Subpop + +This chart is for testing the processing of enabled/disabled charts +via conditions and tags. + +Currently there are three levels: + +```` +parent +-1 tags: front-end, subchart1 +--A tags: front-end, subchartA +--B tags: front-end, subchartB +-2 tags: back-end, subchart2 +--B tags: back-end, subchartB +--C tags: back-end, subchartC +```` + +Tags and conditions are currently in requirements.yaml files. \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/Chart.yaml new file mode 100644 index 000000000..1539fb97d --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/Chart.yaml @@ -0,0 +1,36 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subchart1 +version: 0.1.0 +dependencies: + - name: subcharta + repository: http://localhost:10191 + version: 0.1.0 + condition: subcharta.enabled + tags: + - front-end + - subcharta + import-values: + - child: SCAdata + parent: imported-chartA + - child: SCAdata + parent: overridden-chartA + - child: SCAdata + parent: imported-chartA-B + + - name: subchartb + repository: http://localhost:10191 + version: 0.1.0 + condition: subchartb.enabled + import-values: + - child: SCBdata + parent: imported-chartB + - child: SCBdata + parent: imported-chartA-B + - child: exports.SCBexported2 + parent: exports.SCBexported2 + - SCBexported1 + + tags: + - front-end + - subchartb diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml new file mode 100644 index 000000000..2755a821b --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subcharta +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml new file mode 100644 index 000000000..27501e1e0 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml new file mode 100644 index 000000000..f0381ae6a --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml @@ -0,0 +1,17 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# subchartA +service: + name: apache + type: ClusterIP + externalPort: 80 + internalPort: 80 +SCAdata: + SCAbool: false + SCAfloat: 3.1 + SCAint: 55 + SCAstring: "jabba" + SCAnested1: + SCAnested2: true + diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml new file mode 100644 index 000000000..bf12fe8f3 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subchartb +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml new file mode 100644 index 000000000..27501e1e0 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml new file mode 100644 index 000000000..774fdd75c --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml @@ -0,0 +1,35 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 + +SCBdata: + SCBbool: true + SCBfloat: 7.77 + SCBint: 33 + SCBstring: "boba" + +exports: + SCBexported1: + SCBexported1A: + SCBexported1B: 1965 + + SCBexported2: + SCBexported2A: "blaster" + +global: + kolla: + nova: + api: + all: + port: 8774 + metadata: + all: + port: 8775 + + + diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/crds/crdA.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/crds/crdA.yaml new file mode 100644 index 000000000..fca77fd4b --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/crds/crdA.yaml @@ -0,0 +1,13 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: testCRDs +spec: + group: testCRDGroups + names: + kind: TestCRD + listKind: TestCRDList + plural: TestCRDs + shortNames: + - tc + singular: authconfig diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/NOTES.txt b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/NOTES.txt new file mode 100644 index 000000000..4bdf443f6 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/NOTES.txt @@ -0,0 +1 @@ +Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/service.yaml new file mode 100644 index 000000000..fee94dced --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + app.kubernetes.io/instance: "{{ .Release.Name }}" + kube-version/major: "{{ .Capabilities.KubeVersion.Major }}" + kube-version/minor: "{{ .Capabilities.KubeVersion.Minor }}" + kube-version/version: "v{{ .Capabilities.KubeVersion.Major }}.{{ .Capabilities.KubeVersion.Minor }}.0" +{{- if .Capabilities.APIVersions.Has "helm.k8s.io/test" }} + kube-api-version/test: v1 +{{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml new file mode 100644 index 000000000..91b954e5f --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/role.yaml @@ -0,0 +1,7 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ .Chart.Name }}-role +rules: +- resources: ["*"] + verbs: ["get","list","watch"] diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml new file mode 100644 index 000000000..5d193f1a6 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ .Chart.Name }}-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ .Chart.Name }}-role +subjects: +- kind: ServiceAccount + name: {{ .Chart.Name }}-sa + namespace: default diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml new file mode 100644 index 000000000..7126c7d89 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Chart.Name }}-sa diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart1/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart1/values.yaml new file mode 100644 index 000000000..a974e316a --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart1/values.yaml @@ -0,0 +1,55 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# subchart1 +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 + + +SC1data: + SC1bool: true + SC1float: 3.14 + SC1int: 100 + SC1string: "dollywood" + SC1extra1: 11 + +imported-chartA: + SC1extra2: 1.337 + +overridden-chartA: + SCAbool: true + SCAfloat: 3.14 + SCAint: 100 + SCAstring: "jabbathehut" + SC1extra3: true + +imported-chartA-B: + SC1extra5: "tiller" + +overridden-chartA-B: + SCAbool: true + SCAfloat: 3.33 + SCAint: 555 + SCAstring: "wormwood" + SCAextra1: 23 + + SCBbool: true + SCBfloat: 0.25 + SCBint: 98 + SCBstring: "murkwood" + SCBextra1: 13 + + SC1extra6: 77 + +SCBexported1A: + SC1extra7: true + +exports: + SC1exported1: + global: + SC1exported2: + all: + SC1exported3: "SC1expstr" \ No newline at end of file diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/Chart.yaml new file mode 100644 index 000000000..e77657040 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subchart2 +version: 0.1.0 +dependencies: + - name: subchartb + repository: http://localhost:10191 + version: 0.1.0 + condition: subchartb.enabled + tags: + - back-end + - subchartb + - name: subchartc + repository: http://localhost:10191 + version: 0.1.0 + condition: subchartc.enabled + tags: + - back-end + - subchartc diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml new file mode 100644 index 000000000..bf12fe8f3 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subchartb +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml new file mode 100644 index 000000000..fb3dfc445 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: subchart2-{{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: subchart2-{{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml new file mode 100644 index 000000000..e8c0ef5e5 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: subchartc +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml new file mode 100644 index 000000000..27501e1e0 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/templates/service.yaml new file mode 100644 index 000000000..27501e1e0 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/charts/subchart2/values.yaml b/internal/chart/v3/util/testdata/subpop/charts/subchart2/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/charts/subchart2/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/internal/chart/v3/util/testdata/subpop/noreqs/Chart.yaml b/internal/chart/v3/util/testdata/subpop/noreqs/Chart.yaml new file mode 100644 index 000000000..09eb05a96 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/noreqs/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v3 +description: A Helm chart for Kubernetes +name: parentchart +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/subpop/noreqs/templates/service.yaml b/internal/chart/v3/util/testdata/subpop/noreqs/templates/service.yaml new file mode 100644 index 000000000..27501e1e0 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/noreqs/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app.kubernetes.io/name: {{ .Chart.Name }} diff --git a/internal/chart/v3/util/testdata/subpop/noreqs/values.yaml b/internal/chart/v3/util/testdata/subpop/noreqs/values.yaml new file mode 100644 index 000000000..4ed3b7ad3 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/noreqs/values.yaml @@ -0,0 +1,26 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + + +# switch-like +tags: + front-end: true + back-end: false diff --git a/internal/chart/v3/util/testdata/subpop/values.yaml b/internal/chart/v3/util/testdata/subpop/values.yaml new file mode 100644 index 000000000..ba70ed406 --- /dev/null +++ b/internal/chart/v3/util/testdata/subpop/values.yaml @@ -0,0 +1,45 @@ +# parent/values.yaml + +imported-chart1: + SPextra1: "helm rocks" + +overridden-chart1: + SC1bool: false + SC1float: 3.141592 + SC1int: 99 + SC1string: "pollywog" + SPextra2: 42 + + +imported-chartA: + SPextra3: 1.337 + +overridden-chartA: + SCAbool: true + SCAfloat: 41.3 + SCAint: 808 + SCAstring: "jabberwocky" + SPextra4: true + +imported-chartA-B: + SPextra5: "k8s" + +overridden-chartA-B: + SCAbool: true + SCAfloat: 41.3 + SCAint: 808 + SCAstring: "jabberwocky" + SCBbool: false + SCBfloat: 1.99 + SCBint: 77 + SCBstring: "jango" + SPextra6: 111 + +tags: + front-end: true + back-end: false + +subchart2alias: + enabled: false + +ensurenull: null diff --git a/internal/chart/v3/util/testdata/test-values-invalid.schema.json b/internal/chart/v3/util/testdata/test-values-invalid.schema.json new file mode 100644 index 000000000..35a16a2c4 --- /dev/null +++ b/internal/chart/v3/util/testdata/test-values-invalid.schema.json @@ -0,0 +1 @@ + 1E1111111 diff --git a/internal/chart/v3/util/testdata/test-values-negative.yaml b/internal/chart/v3/util/testdata/test-values-negative.yaml new file mode 100644 index 000000000..5a1250bff --- /dev/null +++ b/internal/chart/v3/util/testdata/test-values-negative.yaml @@ -0,0 +1,14 @@ +firstname: John +lastname: Doe +age: -5 +likesCoffee: true +addresses: + - city: Springfield + street: Main + number: 12345 + - city: New York + street: Broadway + number: 67890 +phoneNumbers: + - "(888) 888-8888" + - "(555) 555-5555" diff --git a/internal/chart/v3/util/testdata/test-values.schema.json b/internal/chart/v3/util/testdata/test-values.schema.json new file mode 100644 index 000000000..4df89bbe8 --- /dev/null +++ b/internal/chart/v3/util/testdata/test-values.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "addresses": { + "description": "List of addresses", + "items": { + "properties": { + "city": { + "type": "string" + }, + "number": { + "type": "number" + }, + "street": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, + "age": { + "description": "Age", + "minimum": 0, + "type": "integer" + }, + "employmentInfo": { + "properties": { + "salary": { + "minimum": 0, + "type": "number" + }, + "title": { + "type": "string" + } + }, + "required": [ + "salary" + ], + "type": "object" + }, + "firstname": { + "description": "First name", + "type": "string" + }, + "lastname": { + "type": "string" + }, + "likesCoffee": { + "type": "boolean" + }, + "phoneNumbers": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "firstname", + "lastname", + "addresses", + "employmentInfo" + ], + "title": "Values", + "type": "object" +} diff --git a/internal/chart/v3/util/testdata/test-values.yaml b/internal/chart/v3/util/testdata/test-values.yaml new file mode 100644 index 000000000..042dea664 --- /dev/null +++ b/internal/chart/v3/util/testdata/test-values.yaml @@ -0,0 +1,17 @@ +firstname: John +lastname: Doe +age: 25 +likesCoffee: true +employmentInfo: + title: Software Developer + salary: 100000 +addresses: + - city: Springfield + street: Main + number: 12345 + - city: New York + street: Broadway + number: 67890 +phoneNumbers: + - "(888) 888-8888" + - "(555) 555-5555" diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/README.md b/internal/chart/v3/util/testdata/three-level-dependent-chart/README.md new file mode 100644 index 000000000..536bb9792 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/README.md @@ -0,0 +1,16 @@ +# Three Level Dependent Chart + +This chart is for testing the processing of multi-level dependencies. + +Consists of the following charts: + +- Library Chart +- App Chart (Uses Library Chart as dependency, 2x: app1/app2) +- Umbrella Chart (Has all the app charts as dependencies) + +The precedence is as follows: `library < app < umbrella` + +Catches two use-cases: + +- app overwriting library (app2) +- umbrella overwriting app and library (app1) diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml new file mode 100644 index 000000000..1026f8901 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/Chart.yaml @@ -0,0 +1,19 @@ +apiVersion: v3 +name: umbrella +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 + +dependencies: +- name: app1 + version: 0.1.0 + condition: app1.enabled +- name: app2 + version: 0.1.0 + condition: app2.enabled +- name: app3 + version: 0.1.0 + condition: app3.enabled +- name: app4 + version: 0.1.0 + condition: app4.enabled diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml new file mode 100644 index 000000000..5bdf21570 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v3 +name: app1 +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 + +dependencies: +- name: library + version: 0.1.0 + import-values: + - defaults diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml new file mode 100644 index 000000000..9bc306361 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: library +description: A Helm chart for Kubernetes +type: library +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml new file mode 100644 index 000000000..3fd398b53 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml new file mode 100644 index 000000000..0c08b6cd2 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml @@ -0,0 +1,5 @@ +exports: + defaults: + service: + type: ClusterIP + port: 9090 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml new file mode 100644 index 000000000..8ed8ddf1f --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml @@ -0,0 +1 @@ +{{- include "library.service" . }} diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml new file mode 100644 index 000000000..3728aa930 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml @@ -0,0 +1,3 @@ +service: + type: ClusterIP + port: 1234 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml new file mode 100644 index 000000000..1313ce4e9 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v3 +name: app2 +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 + +dependencies: +- name: library + version: 0.1.0 + import-values: + - defaults diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml new file mode 100644 index 000000000..9bc306361 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: library +description: A Helm chart for Kubernetes +type: library +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml new file mode 100644 index 000000000..3fd398b53 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml new file mode 100644 index 000000000..0c08b6cd2 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml @@ -0,0 +1,5 @@ +exports: + defaults: + service: + type: ClusterIP + port: 9090 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml new file mode 100644 index 000000000..8ed8ddf1f --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml @@ -0,0 +1 @@ +{{- include "library.service" . }} diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml new file mode 100644 index 000000000..98bd6d24b --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml @@ -0,0 +1,3 @@ +service: + type: ClusterIP + port: 8080 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml new file mode 100644 index 000000000..1a80533d0 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v3 +name: app3 +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 + +dependencies: +- name: library + version: 0.1.0 + import-values: + - defaults diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml new file mode 100644 index 000000000..9bc306361 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: library +description: A Helm chart for Kubernetes +type: library +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml new file mode 100644 index 000000000..3fd398b53 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/templates/service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml new file mode 100644 index 000000000..0c08b6cd2 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/charts/library/values.yaml @@ -0,0 +1,5 @@ +exports: + defaults: + service: + type: ClusterIP + port: 9090 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml new file mode 100644 index 000000000..8ed8ddf1f --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/templates/service.yaml @@ -0,0 +1 @@ +{{- include "library.service" . }} diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml new file mode 100644 index 000000000..b738e2a57 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app3/values.yaml @@ -0,0 +1,2 @@ +service: + type: ClusterIP diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml new file mode 100644 index 000000000..886b4b1e4 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v3 +name: app4 +description: A Helm chart for Kubernetes +type: application +version: 0.1.0 + +dependencies: +- name: library + version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml new file mode 100644 index 000000000..9bc306361 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v3 +name: library +description: A Helm chart for Kubernetes +type: library +version: 0.1.0 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml new file mode 100644 index 000000000..3fd398b53 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/templates/service.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Service +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml new file mode 100644 index 000000000..0c08b6cd2 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/charts/library/values.yaml @@ -0,0 +1,5 @@ +exports: + defaults: + service: + type: ClusterIP + port: 9090 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml new file mode 100644 index 000000000..8ed8ddf1f --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/templates/service.yaml @@ -0,0 +1 @@ +{{- include "library.service" . }} diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml new file mode 100644 index 000000000..3728aa930 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/charts/app4/values.yaml @@ -0,0 +1,3 @@ +service: + type: ClusterIP + port: 1234 diff --git a/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/values.yaml b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/values.yaml new file mode 100644 index 000000000..de0bafa51 --- /dev/null +++ b/internal/chart/v3/util/testdata/three-level-dependent-chart/umbrella/values.yaml @@ -0,0 +1,14 @@ +app1: + enabled: true + service: + type: ClusterIP + port: 3456 + +app2: + enabled: true + +app3: + enabled: true + +app4: + enabled: true diff --git a/internal/chart/v3/util/validate_name.go b/internal/chart/v3/util/validate_name.go new file mode 100644 index 000000000..6595e085d --- /dev/null +++ b/internal/chart/v3/util/validate_name.go @@ -0,0 +1,111 @@ +/* +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 util + +import ( + "errors" + "fmt" + "regexp" +) + +// validName is a regular expression for resource names. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +var validName = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`) + +var ( + // errMissingName indicates that a release (name) was not provided. + errMissingName = errors.New("no name provided") + + // errInvalidName indicates that an invalid release name was provided + errInvalidName = fmt.Errorf( + "invalid release name, must match regex %s and the length must not be longer than 53", + validName.String()) + + // errInvalidKubernetesName indicates that the name does not meet the Kubernetes + // restrictions on metadata names. + errInvalidKubernetesName = fmt.Errorf( + "invalid metadata name, must match regex %s and the length must not be longer than 253", + validName.String()) +) + +const ( + // According to the Kubernetes docs (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#rfc-1035-label-names) + // some resource names have a max length of 63 characters while others have a max + // length of 253 characters. As we cannot be sure the resources used in a chart, we + // therefore need to limit it to 63 chars and reserve 10 chars for additional part to name + // of the resource. The reason is that chart maintainers can use release name as part of + // the resource name (and some additional chars). + maxReleaseNameLen = 53 + // maxMetadataNameLen is the maximum length Kubernetes allows for any name. + maxMetadataNameLen = 253 +) + +// ValidateReleaseName performs checks for an entry for a Helm release name +// +// For Helm to allow a name, it must be below a certain character count (53) and also match +// a regular expression. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +func ValidateReleaseName(name string) error { + // This case is preserved for backwards compatibility + if name == "" { + return errMissingName + + } + if len(name) > maxReleaseNameLen || !validName.MatchString(name) { + return errInvalidName + } + return nil +} + +// ValidateMetadataName validates the name field of a Kubernetes metadata object. +// +// Empty strings, strings longer than 253 chars, or strings that don't match the regexp +// will fail. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)* +// +// This follows the above regular expression (but requires a full string match, not partial). +// +// The Kubernetes documentation is here, though it is not entirely correct: +// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +// +// Deprecated: remove in Helm 4. Name validation now uses rules defined in +// pkg/lint/rules.validateMetadataNameFunc() +func ValidateMetadataName(name string) error { + if name == "" || len(name) > maxMetadataNameLen || !validName.MatchString(name) { + return errInvalidKubernetesName + } + return nil +} diff --git a/internal/chart/v3/util/validate_name_test.go b/internal/chart/v3/util/validate_name_test.go new file mode 100644 index 000000000..cfc62a0f7 --- /dev/null +++ b/internal/chart/v3/util/validate_name_test.go @@ -0,0 +1,91 @@ +/* +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 util + +import "testing" + +// TestValidateReleaseName is a regression test for ValidateName +// +// Kubernetes has strict naming conventions for resource names. This test represents +// those conventions. +// +// See https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +// +// NOTE: At the time of this writing, the docs above say that names cannot begin with +// digits. However, `kubectl`'s regular expression explicit allows this, and +// Kubernetes (at least as of 1.18) also accepts resources whose names begin with digits. +func TestValidateReleaseName(t *testing.T) { + names := map[string]bool{ + "": false, + "foo": true, + "foo.bar1234baz.seventyone": true, + "FOO": false, + "123baz": true, + "foo.BAR.baz": false, + "one-two": true, + "-two": false, + "one_two": false, + "a..b": false, + "%^&#$%*@^*@&#^": false, + "example:com": false, + "example%%com": false, + "a1111111111111111111111111111111111111111111111111111111111z": false, + } + for input, expectPass := range names { + if err := ValidateReleaseName(input); (err == nil) != expectPass { + st := "fail" + if expectPass { + st = "succeed" + } + t.Errorf("Expected %q to %s", input, st) + } + } +} + +func TestValidateMetadataName(t *testing.T) { + names := map[string]bool{ + "": false, + "foo": true, + "foo.bar1234baz.seventyone": true, + "FOO": false, + "123baz": true, + "foo.BAR.baz": false, + "one-two": true, + "-two": false, + "one_two": false, + "a..b": false, + "%^&#$%*@^*@&#^": false, + "example:com": false, + "example%%com": false, + "a1111111111111111111111111111111111111111111111111111111111z": true, + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z" + + "a1111111111111111111111111111111111111111111111111111111111z": false, + } + for input, expectPass := range names { + if err := ValidateMetadataName(input); (err == nil) != expectPass { + st := "fail" + if expectPass { + st = "succeed" + } + t.Errorf("Expected %q to %s", input, st) + } + } +} diff --git a/internal/chart/v3/util/values.go b/internal/chart/v3/util/values.go new file mode 100644 index 000000000..8e1a14b45 --- /dev/null +++ b/internal/chart/v3/util/values.go @@ -0,0 +1,220 @@ +/* +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 util + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "sigs.k8s.io/yaml" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +// GlobalKey is the name of the Values key that is used for storing global vars. +const GlobalKey = "global" + +// Values represents a collection of chart values. +type Values map[string]interface{} + +// YAML encodes the Values into a YAML string. +func (v Values) YAML() (string, error) { + b, err := yaml.Marshal(v) + return string(b), err +} + +// Table gets a table (YAML subsection) from a Values object. +// +// The table is returned as a Values. +// +// Compound table names may be specified with dots: +// +// foo.bar +// +// The above will be evaluated as "The table bar inside the table +// foo". +// +// An ErrNoTable is returned if the table does not exist. +func (v Values) Table(name string) (Values, error) { + table := v + var err error + + for _, n := range parsePath(name) { + if table, err = tableLookup(table, n); err != nil { + break + } + } + return table, err +} + +// AsMap is a utility function for converting Values to a map[string]interface{}. +// +// It protects against nil map panics. +func (v Values) AsMap() map[string]interface{} { + if len(v) == 0 { + return map[string]interface{}{} + } + return v +} + +// Encode writes serialized Values information to the given io.Writer. +func (v Values) Encode(w io.Writer) error { + out, err := yaml.Marshal(v) + if err != nil { + return err + } + _, err = w.Write(out) + return err +} + +func tableLookup(v Values, simple string) (Values, error) { + v2, ok := v[simple] + if !ok { + return v, ErrNoTable{simple} + } + if vv, ok := v2.(map[string]interface{}); ok { + return vv, nil + } + + // This catches a case where a value is of type Values, but doesn't (for some + // reason) match the map[string]interface{}. This has been observed in the + // wild, and might be a result of a nil map of type Values. + if vv, ok := v2.(Values); ok { + return vv, nil + } + + return Values{}, ErrNoTable{simple} +} + +// ReadValues will parse YAML byte data into a Values. +func ReadValues(data []byte) (vals Values, err error) { + err = yaml.Unmarshal(data, &vals) + if len(vals) == 0 { + vals = Values{} + } + return vals, err +} + +// ReadValuesFile will parse a YAML file into a map of values. +func ReadValuesFile(filename string) (Values, error) { + data, err := os.ReadFile(filename) + if err != nil { + return map[string]interface{}{}, err + } + return ReadValues(data) +} + +// ReleaseOptions represents the additional release options needed +// for the composition of the final values struct +type ReleaseOptions struct { + Name string + Namespace string + Revision int + IsUpgrade bool + IsInstall bool +} + +// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files +// +// This takes both ReleaseOptions and Capabilities to merge into the render values. +func ToRenderValues(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities) (Values, error) { + return ToRenderValuesWithSchemaValidation(chrt, chrtVals, options, caps, false) +} + +// ToRenderValuesWithSchemaValidation composes the struct from the data coming from the Releases, Charts and Values files +// +// This takes both ReleaseOptions and Capabilities to merge into the render values. +func ToRenderValuesWithSchemaValidation(chrt *chart.Chart, chrtVals map[string]interface{}, options ReleaseOptions, caps *Capabilities, skipSchemaValidation bool) (Values, error) { + if caps == nil { + caps = DefaultCapabilities + } + top := map[string]interface{}{ + "Chart": chrt.Metadata, + "Capabilities": caps, + "Release": map[string]interface{}{ + "Name": options.Name, + "Namespace": options.Namespace, + "IsUpgrade": options.IsUpgrade, + "IsInstall": options.IsInstall, + "Revision": options.Revision, + "Service": "Helm", + }, + } + + vals, err := CoalesceValues(chrt, chrtVals) + if err != nil { + return top, err + } + + if !skipSchemaValidation { + if err := ValidateAgainstSchema(chrt, vals); err != nil { + return top, fmt.Errorf("values don't meet the specifications of the schema(s) in the following chart(s):\n%w", err) + } + } + + top["Values"] = vals + return top, nil +} + +// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok +} + +// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path. +// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods. +// Given the following YAML data the value at path "chapter.one.title" is "Loomings". +// +// chapter: +// one: +// title: "Loomings" +func (v Values) PathValue(path string) (interface{}, error) { + if path == "" { + return nil, errors.New("YAML path cannot be empty") + } + return v.pathValue(parsePath(path)) +} + +func (v Values) pathValue(path []string) (interface{}, error) { + if len(path) == 1 { + // if exists must be root key not table + if _, ok := v[path[0]]; ok && !istable(v[path[0]]) { + return v[path[0]], nil + } + return nil, ErrNoValue{path[0]} + } + + key, path := path[len(path)-1], path[:len(path)-1] + // get our table for table path + t, err := v.Table(joinPath(path...)) + if err != nil { + return nil, ErrNoValue{key} + } + // check table for key and ensure value is not a table + if k, ok := t[key]; ok && !istable(k) { + return k, nil + } + return nil, ErrNoValue{key} +} + +func parsePath(key string) []string { return strings.Split(key, ".") } + +func joinPath(path ...string) string { return strings.Join(path, ".") } diff --git a/internal/chart/v3/util/values_test.go b/internal/chart/v3/util/values_test.go new file mode 100644 index 000000000..34c664581 --- /dev/null +++ b/internal/chart/v3/util/values_test.go @@ -0,0 +1,293 @@ +/* +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 util + +import ( + "bytes" + "fmt" + "testing" + "text/template" + + chart "helm.sh/helm/v4/internal/chart/v3" +) + +func TestReadValues(t *testing.T) { + doc := `# Test YAML parse +poet: "Coleridge" +title: "Rime of the Ancient Mariner" +stanza: + - "at" + - "length" + - "did" + - cross + - an + - Albatross + +mariner: + with: "crossbow" + shot: "ALBATROSS" + +water: + water: + where: "everywhere" + nor: "any drop to drink" +` + + data, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Error parsing bytes: %s", err) + } + matchValues(t, data) + + tests := []string{`poet: "Coleridge"`, "# Just a comment", ""} + + for _, tt := range tests { + data, err = ReadValues([]byte(tt)) + if err != nil { + t.Fatalf("Error parsing bytes (%s): %s", tt, err) + } + if data == nil { + t.Errorf(`YAML string "%s" gave a nil map`, tt) + } + } +} + +func TestToRenderValues(t *testing.T) { + + chartValues := map[string]interface{}{ + "name": "al Rashid", + "where": map[string]interface{}{ + "city": "Basrah", + "title": "caliph", + }, + } + + overrideValues := map[string]interface{}{ + "name": "Haroun", + "where": map[string]interface{}{ + "city": "Baghdad", + "date": "809 CE", + }, + } + + 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 := ReleaseOptions{ + Name: "Seven Voyages", + Namespace: "default", + Revision: 1, + IsInstall: true, + } + + res, err := ToRenderValuesWithSchemaValidation(c, overrideValues, o, nil, false) + if 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"].(*Capabilities).APIVersions.Has("v1") { + t.Error("Expected Capabilities to have v1 as an API") + } + if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" { + t.Error("Expected Capabilities to have a Kube version") + } + + vals := res["Values"].(Values) + 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) + } + } +} + +func TestReadValuesFile(t *testing.T) { + data, err := ReadValuesFile("./testdata/coleridge.yaml") + if err != nil { + t.Fatalf("Error reading YAML file: %s", err) + } + matchValues(t, data) +} + +func ExampleValues() { + doc := ` +title: "Moby Dick" +chapter: + one: + title: "Loomings" + two: + title: "The Carpet-Bag" + three: + title: "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + panic(err) + } + ch1, err := d.Table("chapter.one") + if err != nil { + panic("could not find chapter one") + } + fmt.Print(ch1["title"]) + // Output: + // Loomings +} + +func TestTable(t *testing.T) { + doc := ` +title: "Moby Dick" +chapter: + one: + title: "Loomings" + two: + title: "The Carpet-Bag" + three: + title: "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Failed to parse the White Whale: %s", err) + } + + if _, err := d.Table("title"); err == nil { + t.Fatalf("Title is not a table.") + } + + if _, err := d.Table("chapter"); err != nil { + t.Fatalf("Failed to get the chapter table: %s\n%v", err, d) + } + + if v, err := d.Table("chapter.one"); err != nil { + t.Errorf("Failed to get chapter.one: %s", err) + } else if v["title"] != "Loomings" { + t.Errorf("Unexpected title: %s", v["title"]) + } + + if _, err := d.Table("chapter.three"); err != nil { + t.Errorf("Chapter three is missing: %s\n%v", err, d) + } + + if _, err := d.Table("chapter.OneHundredThirtySix"); err == nil { + t.Errorf("I think you mean 'Epilogue'") + } +} + +func matchValues(t *testing.T, data map[string]interface{}) { + t.Helper() + if data["poet"] != "Coleridge" { + t.Errorf("Unexpected poet: %s", data["poet"]) + } + + if o, err := ttpl("{{len .stanza}}", data); err != nil { + t.Errorf("len stanza: %s", err) + } else if o != "6" { + t.Errorf("Expected 6, got %s", o) + } + + if o, err := ttpl("{{.mariner.shot}}", data); err != nil { + t.Errorf(".mariner.shot: %s", err) + } else if o != "ALBATROSS" { + t.Errorf("Expected that mariner shot ALBATROSS") + } + + if o, err := ttpl("{{.water.water.where}}", data); err != nil { + t.Errorf(".water.water.where: %s", err) + } else if o != "everywhere" { + t.Errorf("Expected water water everywhere") + } +} + +func ttpl(tpl string, v map[string]interface{}) (string, error) { + var b bytes.Buffer + tt := template.Must(template.New("t").Parse(tpl)) + err := tt.Execute(&b, v) + return b.String(), err +} + +func TestPathValue(t *testing.T) { + doc := ` +title: "Moby Dick" +chapter: + one: + title: "Loomings" + two: + title: "The Carpet-Bag" + three: + title: "The Spouter Inn" +` + d, err := ReadValues([]byte(doc)) + if err != nil { + t.Fatalf("Failed to parse the White Whale: %s", err) + } + + if v, err := d.PathValue("chapter.one.title"); err != nil { + t.Errorf("Got error instead of title: %s\n%v", err, d) + } else if v != "Loomings" { + t.Errorf("No error but got wrong value for title: %s\n%v", err, d) + } + if _, err := d.PathValue("chapter.one.doesnotexist"); err == nil { + t.Errorf("Non-existent key should return error: %s\n%v", err, d) + } + if _, err := d.PathValue("chapter.doesnotexist.one"); err == nil { + t.Errorf("Non-existent key in middle of path should return error: %s\n%v", err, d) + } + if _, err := d.PathValue(""); err == nil { + t.Error("Asking for the value from an empty path should yield an error") + } + if v, err := d.PathValue("title"); err == nil { + if v != "Moby Dick" { + t.Errorf("Failed to return values for root key title") + } + } +}