From 7ff00a3099c1d286fd32df30c0b9fbd1351a285f Mon Sep 17 00:00:00 2001 From: HARPHUNA <109001160+HARPHUNA@users.noreply.github.com> Date: Tue, 1 Nov 2022 09:22:42 +0100 Subject: [PATCH] Delete pkg directory Signed-off-by: HARPHUNA <109001160+HARPHUNA@users.noreply.github.com> --- pkg/action/action.go | 421 -------- pkg/action/action_test.go | 283 ------ pkg/action/dependency.go | 230 ----- pkg/action/dependency_test.go | 152 --- pkg/action/doc.go | 22 - pkg/action/get.go | 47 - pkg/action/get_values.go | 60 -- pkg/action/history.go | 58 -- pkg/action/hooks.go | 151 --- pkg/action/install.go | 767 --------------- pkg/action/install_test.go | 719 -------------- pkg/action/lazyclient.go | 197 ---- pkg/action/lint.go | 129 --- pkg/action/lint_test.go | 160 ---- pkg/action/list.go | 324 ------- pkg/action/list_test.go | 368 ------- pkg/action/package.go | 182 ---- pkg/action/package_test.go | 125 --- pkg/action/pull.go | 166 ---- pkg/action/push.go | 70 -- pkg/action/registry_login.go | 43 - pkg/action/registry_logout.go | 38 - pkg/action/release_testing.go | 138 --- pkg/action/resource_policy.go | 46 - pkg/action/rollback.go | 246 ----- pkg/action/show.go | 156 --- pkg/action/show_test.go | 153 --- pkg/action/status.go | 51 - .../charts/chart-missing-deps/Chart.yaml | 2 - .../chart-missing-deps/requirements.lock | 6 - .../chart-missing-deps/requirements.yaml | 7 - ...art-with-compressed-dependencies-2.1.8.tgz | Bin 10962 -> 0 bytes .../Chart.yaml | 2 - .../charts/mariadb-4.3.1.tgz | Bin 8401 -> 0 bytes .../requirements.lock | 6 - .../requirements.yaml | 7 - .../chart-with-no-templates-dir/Chart.yaml | 5 - .../chart-with-schema-negative/Chart.yaml | 7 - .../templates/empty.yaml | 1 - .../values.schema.json | 67 -- .../chart-with-schema-negative/values.yaml | 14 - .../charts/chart-with-schema/Chart.yaml | 7 - .../chart-with-schema/extra-values.yaml | 2 - .../chart-with-schema/templates/empty.yaml | 1 - .../chart-with-schema/values.schema.json | 67 -- .../charts/chart-with-schema/values.yaml | 17 - ...t-with-uncompressed-dependencies-2.1.8.tgz | Bin 10953 -> 0 bytes .../.helmignore | 5 - .../Chart.yaml | 20 - .../README.md | 3 - .../charts/mariadb/.helmignore | 1 - .../charts/mariadb/Chart.yaml | 21 - .../charts/mariadb/README.md | 143 --- .../docker-entrypoint-initdb.d/README.md | 3 - .../charts/mariadb/templates/NOTES.txt | 35 - .../charts/mariadb/templates/_helpers.tpl | 53 -- .../templates/initialization-configmap.yaml | 12 - .../mariadb/templates/master-configmap.yaml | 15 - .../mariadb/templates/master-statefulset.yaml | 187 ---- .../charts/mariadb/templates/master-svc.yaml | 29 - .../charts/mariadb/templates/secrets.yaml | 38 - .../mariadb/templates/slave-configmap.yaml | 15 - .../mariadb/templates/slave-statefulset.yaml | 193 ---- .../charts/mariadb/templates/slave-svc.yaml | 31 - .../charts/mariadb/templates/test-runner.yaml | 44 - .../charts/mariadb/templates/tests.yaml | 9 - .../charts/mariadb/values.yaml | 233 ----- .../requirements.lock | 6 - .../requirements.yaml | 7 - .../templates/NOTES.txt | 1 - .../values.yaml | 254 ----- .../charts/compressedchart-0.1.0.tar.gz | Bin 477 -> 0 bytes .../testdata/charts/compressedchart-0.1.0.tgz | Bin 477 -> 0 bytes .../testdata/charts/compressedchart-0.2.0.tgz | Bin 477 -> 0 bytes .../testdata/charts/compressedchart-0.3.0.tgz | Bin 477 -> 0 bytes .../compressedchart-with-hyphens-0.1.0.tgz | Bin 548 -> 0 bytes .../charts/corrupted-compressed-chart.tgz | 0 .../charts/decompressedchart/Chart.yaml | 4 - .../charts/decompressedchart/values.yaml | 4 - .../multiplecharts-lint-chart-1/Chart.yaml | 4 - .../templates/configmap.yaml | 6 - .../multiplecharts-lint-chart-1/values.yaml | 1 - .../multiplecharts-lint-chart-2/Chart.yaml | 4 - .../templates/configmap.yaml | 5 - .../multiplecharts-lint-chart-2/values.yaml | 2 - .../charts/pre-release-chart-0.1.0-alpha.tgz | Bin 355 -> 0 bytes .../output/list-compressed-deps-tgz.txt | 3 - .../testdata/output/list-compressed-deps.txt | 3 - .../testdata/output/list-missing-deps.txt | 3 - .../output/list-uncompressed-deps-tgz.txt | 3 - .../output/list-uncompressed-deps.txt | 3 - pkg/action/testdata/rbac.txt | 25 - pkg/action/uninstall.go | 226 ----- pkg/action/uninstall_test.go | 97 -- pkg/action/upgrade.go | 574 ----------- pkg/action/upgrade_test.go | 390 -------- pkg/action/validate.go | 184 ---- pkg/action/validate_test.go | 123 --- pkg/action/verify.go | 59 -- pkg/chart/chart.go | 173 ---- pkg/chart/chart_test.go | 211 ---- pkg/chart/dependency.go | 82 -- pkg/chart/dependency_test.go | 44 - pkg/chart/errors.go | 30 - pkg/chart/file.go | 27 - pkg/chart/loader/archive.go | 196 ---- pkg/chart/loader/archive_test.go | 90 -- pkg/chart/loader/directory.go | 120 --- pkg/chart/loader/load.go | 200 ---- pkg/chart/loader/load_test.go | 649 ------------- pkg/chart/loader/testdata/LICENSE | 1 - .../loader/testdata/albatross/Chart.yaml | 4 - .../loader/testdata/albatross/values.yaml | 4 - pkg/chart/loader/testdata/frobnitz-1.2.3.tgz | Bin 3482 -> 0 bytes pkg/chart/loader/testdata/frobnitz.v1.tgz | Bin 3525 -> 0 bytes .../loader/testdata/frobnitz.v1/.helmignore | 1 - .../loader/testdata/frobnitz.v1/Chart.lock | 8 - .../loader/testdata/frobnitz.v1/Chart.yaml | 20 - .../loader/testdata/frobnitz.v1/INSTALL.txt | 1 - pkg/chart/loader/testdata/frobnitz.v1/LICENSE | 1 - .../loader/testdata/frobnitz.v1/README.md | 11 - .../testdata/frobnitz.v1/charts/_ignore_me | 1 - .../frobnitz.v1/charts/alpine/Chart.yaml | 5 - .../frobnitz.v1/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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../frobnitz.v1/charts/alpine/values.yaml | 2 - .../frobnitz.v1/charts/mariner-4.3.2.tgz | Bin 967 -> 0 bytes .../testdata/frobnitz.v1/docs/README.md | 1 - .../loader/testdata/frobnitz.v1/icon.svg | 8 - .../loader/testdata/frobnitz.v1/ignore/me.txt | 0 .../testdata/frobnitz.v1/requirements.yaml | 7 - .../frobnitz.v1/templates/template.tpl | 1 - .../loader/testdata/frobnitz.v1/values.yaml | 6 - .../testdata/frobnitz.v2.reqs/.helmignore | 1 - .../testdata/frobnitz.v2.reqs/Chart.yaml | 20 - .../testdata/frobnitz.v2.reqs/INSTALL.txt | 1 - .../loader/testdata/frobnitz.v2.reqs/LICENSE | 1 - .../testdata/frobnitz.v2.reqs/README.md | 11 - .../frobnitz.v2.reqs/charts/_ignore_me | 1 - .../frobnitz.v2.reqs/charts/alpine/Chart.yaml | 5 - .../frobnitz.v2.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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../frobnitz.v2.reqs/charts/mariner-4.3.2.tgz | Bin 967 -> 0 bytes .../testdata/frobnitz.v2.reqs/docs/README.md | 1 - .../loader/testdata/frobnitz.v2.reqs/icon.svg | 8 - .../testdata/frobnitz.v2.reqs/ignore/me.txt | 0 .../frobnitz.v2.reqs/requirements.yaml | 7 - .../frobnitz.v2.reqs/templates/template.tpl | 1 - .../testdata/frobnitz.v2.reqs/values.yaml | 6 - .../loader/testdata/frobnitz/.helmignore | 1 - pkg/chart/loader/testdata/frobnitz/Chart.lock | 8 - pkg/chart/loader/testdata/frobnitz/Chart.yaml | 27 - .../loader/testdata/frobnitz/INSTALL.txt | 1 - pkg/chart/loader/testdata/frobnitz/LICENSE | 1 - pkg/chart/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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../frobnitz/charts/alpine/values.yaml | 2 - .../frobnitz/charts/mariner-4.3.2.tgz | Bin 967 -> 0 bytes .../loader/testdata/frobnitz/docs/README.md | 1 - pkg/chart/loader/testdata/frobnitz/icon.svg | 8 - .../loader/testdata/frobnitz/ignore/me.txt | 0 .../testdata/frobnitz/templates/template.tpl | 1 - .../loader/testdata/frobnitz/values.yaml | 6 - .../testdata/frobnitz_backslash-1.2.3.tgz | Bin 3490 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 - .../loader/testdata/frobnitz_with_bom.tgz | Bin 3523 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 - pkg/chart/loader/testdata/genfrob.sh | 14 - pkg/chart/loader/testdata/mariner/Chart.yaml | 9 - .../mariner/charts/albatross-0.1.0.tgz | Bin 306 -> 0 bytes .../mariner/templates/placeholder.tpl | 1 - pkg/chart/loader/testdata/mariner/values.yaml | 7 - pkg/chart/metadata.go | 163 ---- pkg/chart/metadata_test.go | 124 --- pkg/chartutil/capabilities.go | 126 --- pkg/chartutil/capabilities_test.go | 84 -- pkg/chartutil/chartfile.go | 93 -- pkg/chartutil/chartfile_test.go | 121 --- pkg/chartutil/coalesce.go | 227 ----- pkg/chartutil/coalesce_test.go | 409 -------- pkg/chartutil/compatible.go | 34 - pkg/chartutil/compatible_test.go | 43 - pkg/chartutil/create.go | 687 -------------- pkg/chartutil/create_test.go | 173 ---- pkg/chartutil/dependencies.go | 285 ------ pkg/chartutil/dependencies_test.go | 457 --------- pkg/chartutil/doc.go | 44 - pkg/chartutil/errors.go | 35 - pkg/chartutil/errors_test.go | 37 - pkg/chartutil/expand.go | 91 -- pkg/chartutil/expand_test.go | 124 --- pkg/chartutil/jsonschema.go | 87 -- pkg/chartutil/jsonschema_test.go | 143 --- pkg/chartutil/save.go | 244 ----- pkg/chartutil/save_test.go | 237 ----- pkg/chartutil/testdata/chartfiletest.yaml | 20 - pkg/chartutil/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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 252 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 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 252 -> 0 bytes .../charts/alpine/templates/alpine-pod.yaml | 14 - .../charts/alpine/values.yaml | 2 - .../charts/mariner-4.3.2.tgz | Bin 967 -> 0 bytes .../docs/README.md | 1 - .../icon.svg | 8 - .../ignore/me.txt | 0 .../templates/template.tpl | 1 - .../values.yaml | 6 - pkg/chartutil/testdata/frobnitz-1.2.3.tgz | Bin 3485 -> 0 bytes pkg/chartutil/testdata/frobnitz/.helmignore | 1 - pkg/chartutil/testdata/frobnitz/Chart.lock | 8 - pkg/chartutil/testdata/frobnitz/Chart.yaml | 27 - pkg/chartutil/testdata/frobnitz/INSTALL.txt | 1 - pkg/chartutil/testdata/frobnitz/LICENSE | 1 - pkg/chartutil/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 252 -> 0 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 - .../testdata/frobnitz/docs/README.md | 1 - pkg/chartutil/testdata/frobnitz/icon.svg | 8 - pkg/chartutil/testdata/frobnitz/ignore/me.txt | 0 .../testdata/frobnitz/templates/template.tpl | 1 - pkg/chartutil/testdata/frobnitz/values.yaml | 6 - .../testdata/frobnitz_backslash-1.2.3.tgz | Bin 3496 -> 0 bytes pkg/chartutil/testdata/genfrob.sh | 14 - .../parent-chart/Chart.lock | 9 - .../parent-chart/Chart.yaml | 22 - .../parent-chart/charts/dev-v0.1.0.tgz | Bin 333 -> 0 bytes .../parent-chart/charts/prod-v0.1.0.tgz | Bin 336 -> 0 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 - pkg/chartutil/testdata/joonix/Chart.yaml | 4 - pkg/chartutil/testdata/joonix/charts/.gitkeep | 0 pkg/chartutil/testdata/subpop/Chart.yaml | 41 - pkg/chartutil/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 - .../testdata/subpop/noreqs/Chart.yaml | 4 - .../subpop/noreqs/templates/service.yaml | 15 - .../testdata/subpop/noreqs/values.yaml | 26 - pkg/chartutil/testdata/subpop/values.yaml | 43 - .../testdata/test-values-negative.yaml | 14 - .../testdata/test-values.schema.json | 67 -- pkg/chartutil/testdata/test-values.yaml | 17 - .../three-level-dependent-chart/README.md | 16 - .../umbrella/Chart.yaml | 13 - .../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/values.yaml | 8 - pkg/chartutil/validate_name.go | 112 --- pkg/chartutil/validate_name_test.go | 91 -- pkg/chartutil/values.go | 212 ----- pkg/chartutil/values_test.go | 292 ------ pkg/cli/environment.go | 230 ----- pkg/cli/environment_test.go | 248 ----- pkg/cli/output/output.go | 140 --- pkg/cli/values/options.go | 138 --- pkg/cli/values/options_test.go | 88 -- pkg/downloader/chart_downloader.go | 425 --------- pkg/downloader/chart_downloader_test.go | 347 ------- pkg/downloader/doc.go | 23 - pkg/downloader/manager.go | 898 ------------------ pkg/downloader/manager_test.go | 574 ----------- pkg/downloader/testdata/helm-test-key.pub | Bin 1243 -> 0 bytes pkg/downloader/testdata/helm-test-key.secret | Bin 2545 -> 0 bytes .../testdata/local-subchart-0.1.0.tgz | Bin 259 -> 0 bytes .../testdata/local-subchart/Chart.yaml | 3 - pkg/downloader/testdata/repositories.yaml | 28 - .../repository/encoded-url-index.yaml | 15 - .../repository/kubernetes-charts-index.yaml | 49 - .../testdata/repository/malformed-index.yaml | 16 - .../repository/testing-basicauth-index.yaml | 15 - .../repository/testing-ca-file-index.yaml | 15 - .../repository/testing-https-index.yaml | 15 - ...g-https-insecureskip-tls-verify-index.yaml | 14 - .../testdata/repository/testing-index.yaml | 43 - .../repository/testing-querystring-index.yaml | 16 - .../repository/testing-relative-index.yaml | 28 - ...testing-relative-trailing-slash-index.yaml | 28 - pkg/downloader/testdata/signtest-0.1.0.tgz | Bin 973 -> 0 bytes .../testdata/signtest-0.1.0.tgz.prov | 21 - pkg/downloader/testdata/signtest/.helmignore | 5 - pkg/downloader/testdata/signtest/Chart.yaml | 4 - .../testdata/signtest/alpine/Chart.yaml | 7 - .../testdata/signtest/alpine/README.md | 9 - .../signtest/alpine/templates/alpine-pod.yaml | 14 - .../testdata/signtest/alpine/values.yaml | 2 - .../testdata/signtest/templates/pod.yaml | 10 - pkg/downloader/testdata/signtest/values.yaml | 0 pkg/engine/doc.go | 23 - pkg/engine/engine.go | 401 -------- pkg/engine/engine_test.go | 832 ---------------- pkg/engine/files.go | 160 ---- pkg/engine/files_test.go | 98 -- pkg/engine/funcs.go | 177 ---- pkg/engine/funcs_test.go | 178 ---- pkg/engine/lookup_func.go | 124 --- pkg/gates/doc.go | 20 - pkg/gates/gates.go | 38 - pkg/gates/gates_test.go | 56 -- pkg/getter/doc.go | 21 - pkg/getter/getter.go | 193 ---- pkg/getter/getter_test.go | 80 -- pkg/getter/httpgetter.go | 157 --- pkg/getter/httpgetter_test.go | 531 ----------- pkg/getter/ocigetter.go | 84 -- pkg/getter/ocigetter_test.go | 30 - pkg/getter/plugingetter.go | 102 -- pkg/getter/plugingetter_test.go | 101 -- pkg/getter/testdata/ca.crt | 25 - pkg/getter/testdata/client.crt | 21 - pkg/getter/testdata/client.key | 27 - pkg/getter/testdata/empty-0.0.1.tgz | Bin 130 -> 0 bytes pkg/getter/testdata/plugins/testgetter/get.sh | 8 - .../testdata/plugins/testgetter/plugin.yaml | 15 - .../testdata/plugins/testgetter2/get.sh | 8 - .../testdata/plugins/testgetter2/plugin.yaml | 10 - .../testdata/repository/local/index.yaml | 3 - .../testdata/repository/repositories.yaml | 15 - pkg/helmpath/home.go | 44 - pkg/helmpath/home_unix_test.go | 46 - pkg/helmpath/home_windows_test.go | 43 - pkg/helmpath/lazypath.go | 72 -- pkg/helmpath/lazypath_darwin.go | 34 - pkg/helmpath/lazypath_darwin_test.go | 86 -- pkg/helmpath/lazypath_unix.go | 45 - pkg/helmpath/lazypath_unix_test.go | 86 -- pkg/helmpath/lazypath_windows.go | 24 - pkg/helmpath/lazypath_windows_test.go | 89 -- pkg/helmpath/xdg/xdg.go | 34 - pkg/kube/client.go | 674 ------------- pkg/kube/client_test.go | 522 ---------- pkg/kube/config.go | 30 - pkg/kube/converter.go | 69 -- pkg/kube/factory.go | 48 - pkg/kube/fake/fake.go | 117 --- pkg/kube/fake/printer.go | 110 --- pkg/kube/interface.go | 82 -- pkg/kube/ready.go | 411 -------- pkg/kube/ready_test.go | 566 ----------- pkg/kube/resource.go | 85 -- pkg/kube/resource_policy.go | 26 - pkg/kube/resource_test.go | 61 -- pkg/kube/result.go | 28 - pkg/kube/wait.go | 152 --- pkg/kube/wait_test.go | 42 - pkg/lint/lint.go | 37 - pkg/lint/lint_test.go | 153 --- pkg/lint/rules/chartfile.go | 210 ---- pkg/lint/rules/chartfile_test.go | 247 ----- pkg/lint/rules/dependencies.go | 82 -- pkg/lint/rules/dependencies_test.go | 99 -- pkg/lint/rules/deprecations.go | 95 -- pkg/lint/rules/deprecations_test.go | 41 - pkg/lint/rules/template.go | 339 ------- pkg/lint/rules/template_test.go | 464 --------- pkg/lint/rules/testdata/albatross/Chart.yaml | 5 - .../testdata/albatross/templates/_helpers.tpl | 16 - .../testdata/albatross/templates/fail.yaml | 1 - .../testdata/albatross/templates/svc.yaml | 19 - pkg/lint/rules/testdata/albatross/values.yaml | 1 - .../testdata/anotherbadchartfile/Chart.yaml | 15 - .../rules/testdata/badchartfile/Chart.yaml | 11 - .../rules/testdata/badchartfile/values.yaml | 1 - .../rules/testdata/badvaluesfile/Chart.yaml | 6 - .../templates/badvaluesfile.yaml | 2 - .../rules/testdata/badvaluesfile/values.yaml | 2 - pkg/lint/rules/testdata/goodone/Chart.yaml | 5 - .../testdata/goodone/templates/goodone.yaml | 2 - pkg/lint/rules/testdata/goodone/values.yaml | 1 - .../testdata/multi-template-fail/Chart.yaml | 21 - .../templates/multi-fail.yaml | 13 - pkg/lint/rules/testdata/v3-fail/Chart.yaml | 21 - .../testdata/v3-fail/templates/_helpers.tpl | 63 -- .../v3-fail/templates/deployment.yaml | 56 -- .../testdata/v3-fail/templates/ingress.yaml | 62 -- .../testdata/v3-fail/templates/service.yaml | 17 - pkg/lint/rules/testdata/v3-fail/values.yaml | 66 -- .../rules/testdata/withsubchart/Chart.yaml | 16 - .../withsubchart/charts/subchart/Chart.yaml | 6 - .../charts/subchart/templates/subchart.yaml | 2 - .../withsubchart/charts/subchart/values.yaml | 2 - .../withsubchart/templates/mainchart.yaml | 2 - .../rules/testdata/withsubchart/values.yaml | 0 pkg/lint/rules/values.go | 87 -- pkg/lint/rules/values_test.go | 175 ---- pkg/lint/support/doc.go | 22 - pkg/lint/support/message.go | 76 -- pkg/lint/support/message_test.go | 80 -- pkg/plugin/cache/cache.go | 67 -- pkg/plugin/hooks.go | 29 - pkg/plugin/installer/base.go | 45 - pkg/plugin/installer/base_test.go | 48 - pkg/plugin/installer/doc.go | 17 - pkg/plugin/installer/http_installer.go | 268 ------ pkg/plugin/installer/http_installer_test.go | 350 ------- pkg/plugin/installer/installer.go | 135 --- pkg/plugin/installer/installer_test.go | 40 - pkg/plugin/installer/local_installer.go | 57 -- pkg/plugin/installer/local_installer_test.go | 50 - pkg/plugin/installer/vcs_installer.go | 176 ---- pkg/plugin/installer/vcs_installer_test.go | 181 ---- pkg/plugin/plugin.go | 282 ------ pkg/plugin/plugin_test.go | 378 -------- .../plugdir/bad/duplicate-entries/plugin.yaml | 11 - .../plugdir/good/downloader/plugin.yaml | 11 - .../testdata/plugdir/good/echo/plugin.yaml | 8 - .../testdata/plugdir/good/hello/hello.sh | 9 - .../testdata/plugdir/good/hello/plugin.yaml | 9 - pkg/postrender/exec.go | 109 --- pkg/postrender/exec_test.go | 193 ---- pkg/postrender/postrender.go | 29 - pkg/provenance/doc.go | 37 - pkg/provenance/sign.go | 424 --------- pkg/provenance/sign_test.go | 345 ------- pkg/provenance/testdata/hashtest-1.2.3.tgz | Bin 399 -> 0 bytes .../testdata/hashtest-1.2.3.tgz.prov | 21 - pkg/provenance/testdata/hashtest.sha256 | 1 - pkg/provenance/testdata/hashtest/.helmignore | 5 - pkg/provenance/testdata/hashtest/Chart.yaml | 4 - pkg/provenance/testdata/hashtest/values.yaml | 4 - .../testdata/helm-password-key.secret | Bin 2562 -> 0 bytes pkg/provenance/testdata/helm-test-key.pub | Bin 1243 -> 0 bytes pkg/provenance/testdata/helm-test-key.secret | Bin 2545 -> 0 bytes pkg/provenance/testdata/msgblock.yaml | 8 - pkg/provenance/testdata/msgblock.yaml.asc | 22 - .../testdata/msgblock.yaml.tampered | 21 - pkg/provenance/testdata/regen-hashtest.sh | 3 - pkg/pusher/doc.go | 21 - pkg/pusher/ocipusher.go | 106 --- pkg/pusher/ocipusher_test.go | 36 - pkg/pusher/pusher.go | 95 -- pkg/pusher/pusher_test.go | 68 -- pkg/registry/client.go | 643 ------------- pkg/registry/client_test.go | 373 -------- pkg/registry/constants.go | 37 - pkg/registry/util.go | 131 --- pkg/release/hook.go | 106 --- pkg/release/info.go | 36 - pkg/release/mock.go | 116 --- pkg/release/release.go | 49 - pkg/release/responses.go | 24 - pkg/release/status.go | 49 - pkg/releaseutil/filter.go | 78 -- pkg/releaseutil/filter_test.go | 59 -- pkg/releaseutil/kind_sorter.go | 158 --- pkg/releaseutil/kind_sorter_test.go | 335 ------- pkg/releaseutil/manifest.go | 72 -- pkg/releaseutil/manifest_sorter.go | 233 ----- pkg/releaseutil/manifest_sorter_test.go | 228 ----- pkg/releaseutil/manifest_test.go | 61 -- pkg/releaseutil/sorter.go | 78 -- pkg/releaseutil/sorter_test.go | 108 --- pkg/repo/chartrepo.go | 313 ------ pkg/repo/chartrepo_test.go | 419 -------- pkg/repo/doc.go | 93 -- pkg/repo/index.go | 356 ------- pkg/repo/index_test.go | 528 ---------- pkg/repo/repo.go | 123 --- pkg/repo/repo_test.go | 227 ----- pkg/repo/repotest/doc.go | 20 - pkg/repo/repotest/server.go | 425 --------- pkg/repo/repotest/server_test.go | 118 --- .../repotest/testdata/examplechart-0.1.0.tgz | Bin 500 -> 0 bytes .../testdata/examplechart/.helmignore | 21 - .../repotest/testdata/examplechart/Chart.yaml | 4 - .../testdata/examplechart/values.yaml | 4 - pkg/repo/testdata/chartmuseum-index.yaml | 54 -- .../testdata/local-index-annotations.yaml | 54 -- pkg/repo/testdata/local-index-unordered.yaml | 52 - pkg/repo/testdata/local-index.yaml | 52 - pkg/repo/testdata/old-repositories.yaml | 3 - pkg/repo/testdata/repositories.yaml | 8 - .../testdata/repository/frobnitz-1.2.3.tgz | Bin 3485 -> 0 bytes .../testdata/repository/sprocket-1.1.0.tgz | Bin 414 -> 0 bytes .../testdata/repository/sprocket-1.2.0.tgz | Bin 413 -> 0 bytes .../repository/universe/zarthal-1.0.0.tgz | Bin 411 -> 0 bytes pkg/repo/testdata/server/index.yaml | 39 - pkg/repo/testdata/server/test.txt | 1 - pkg/storage/driver/cfgmaps.go | 257 ----- pkg/storage/driver/cfgmaps_test.go | 241 ----- pkg/storage/driver/driver.go | 105 -- pkg/storage/driver/labels.go | 48 - pkg/storage/driver/labels_test.go | 49 - pkg/storage/driver/memory.go | 240 ----- pkg/storage/driver/memory_test.go | 289 ------ pkg/storage/driver/mock_test.go | 265 ------ pkg/storage/driver/records.go | 124 --- pkg/storage/driver/records_test.go | 240 ----- pkg/storage/driver/secrets.go | 250 ----- pkg/storage/driver/secrets_test.go | 241 ----- pkg/storage/driver/sql.go | 496 ---------- pkg/storage/driver/sql_test.go | 463 --------- pkg/storage/driver/util.go | 85 -- pkg/storage/storage.go | 266 ------ pkg/storage/storage_test.go | 560 ----------- pkg/strvals/doc.go | 32 - pkg/strvals/parser.go | 549 ----------- pkg/strvals/parser_test.go | 756 --------------- pkg/time/time.go | 91 -- pkg/time/time_test.go | 83 -- pkg/uploader/chart_uploader.go | 58 -- pkg/uploader/doc.go | 20 - 718 files changed, 46291 deletions(-) delete mode 100644 pkg/action/action.go delete mode 100644 pkg/action/action_test.go delete mode 100644 pkg/action/dependency.go delete mode 100644 pkg/action/dependency_test.go delete mode 100644 pkg/action/doc.go delete mode 100644 pkg/action/get.go delete mode 100644 pkg/action/get_values.go delete mode 100644 pkg/action/history.go delete mode 100644 pkg/action/hooks.go delete mode 100644 pkg/action/install.go delete mode 100644 pkg/action/install_test.go delete mode 100644 pkg/action/lazyclient.go delete mode 100644 pkg/action/lint.go delete mode 100644 pkg/action/lint_test.go delete mode 100644 pkg/action/list.go delete mode 100644 pkg/action/list_test.go delete mode 100644 pkg/action/package.go delete mode 100644 pkg/action/package_test.go delete mode 100644 pkg/action/pull.go delete mode 100644 pkg/action/push.go delete mode 100644 pkg/action/registry_login.go delete mode 100644 pkg/action/registry_logout.go delete mode 100644 pkg/action/release_testing.go delete mode 100644 pkg/action/resource_policy.go delete mode 100644 pkg/action/rollback.go delete mode 100644 pkg/action/show.go delete mode 100644 pkg/action/show_test.go delete mode 100644 pkg/action/status.go delete mode 100755 pkg/action/testdata/charts/chart-missing-deps/Chart.yaml delete mode 100755 pkg/action/testdata/charts/chart-missing-deps/requirements.lock delete mode 100755 pkg/action/testdata/charts/chart-missing-deps/requirements.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz delete mode 100755 pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz delete mode 100755 pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock delete mode 100755 pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json delete mode 100644 pkg/action/testdata/charts/chart-with-schema-negative/values.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema/Chart.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema/extra-values.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-schema/values.schema.json delete mode 100644 pkg/action/testdata/charts/chart-with-schema/values.yaml delete mode 100644 pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/.helmignore delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/Chart.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/README.md delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/.helmignore delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/Chart.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/README.md delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt delete mode 100755 pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml delete mode 100644 pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz delete mode 100644 pkg/action/testdata/charts/compressedchart-0.1.0.tgz delete mode 100644 pkg/action/testdata/charts/compressedchart-0.2.0.tgz delete mode 100644 pkg/action/testdata/charts/compressedchart-0.3.0.tgz delete mode 100644 pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz delete mode 100644 pkg/action/testdata/charts/corrupted-compressed-chart.tgz delete mode 100644 pkg/action/testdata/charts/decompressedchart/Chart.yaml delete mode 100644 pkg/action/testdata/charts/decompressedchart/values.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml delete mode 100644 pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml delete mode 100644 pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz delete mode 100644 pkg/action/testdata/output/list-compressed-deps-tgz.txt delete mode 100644 pkg/action/testdata/output/list-compressed-deps.txt delete mode 100644 pkg/action/testdata/output/list-missing-deps.txt delete mode 100644 pkg/action/testdata/output/list-uncompressed-deps-tgz.txt delete mode 100644 pkg/action/testdata/output/list-uncompressed-deps.txt delete mode 100644 pkg/action/testdata/rbac.txt delete mode 100644 pkg/action/uninstall.go delete mode 100644 pkg/action/uninstall_test.go delete mode 100644 pkg/action/upgrade.go delete mode 100644 pkg/action/upgrade_test.go delete mode 100644 pkg/action/validate.go delete mode 100644 pkg/action/validate_test.go delete mode 100644 pkg/action/verify.go delete mode 100644 pkg/chart/chart.go delete mode 100644 pkg/chart/chart_test.go delete mode 100644 pkg/chart/dependency.go delete mode 100644 pkg/chart/dependency_test.go delete mode 100644 pkg/chart/errors.go delete mode 100644 pkg/chart/file.go delete mode 100644 pkg/chart/loader/archive.go delete mode 100644 pkg/chart/loader/archive_test.go delete mode 100644 pkg/chart/loader/directory.go delete mode 100644 pkg/chart/loader/load.go delete mode 100644 pkg/chart/loader/load_test.go delete mode 100644 pkg/chart/loader/testdata/LICENSE delete mode 100644 pkg/chart/loader/testdata/albatross/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/albatross/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz-1.2.3.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/Chart.lock delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/LICENSE delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz.v1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz/Chart.lock delete mode 100644 pkg/chart/loader/testdata/frobnitz/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz/LICENSE delete mode 100644 pkg/chart/loader/testdata/frobnitz/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz/ignore/me.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/.helmignore delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/LICENSE delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/README.md delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/icon.svg delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl delete mode 100755 pkg/chart/loader/testdata/frobnitz_backslash/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt delete mode 120000 pkg/chart/loader/testdata/frobnitz_with_dev_null/null delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl delete mode 100644 pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml delete mode 100755 pkg/chart/loader/testdata/genfrob.sh delete mode 100644 pkg/chart/loader/testdata/mariner/Chart.yaml delete mode 100644 pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz delete mode 100644 pkg/chart/loader/testdata/mariner/templates/placeholder.tpl delete mode 100644 pkg/chart/loader/testdata/mariner/values.yaml delete mode 100644 pkg/chart/metadata.go delete mode 100644 pkg/chart/metadata_test.go delete mode 100644 pkg/chartutil/capabilities.go delete mode 100644 pkg/chartutil/capabilities_test.go delete mode 100644 pkg/chartutil/chartfile.go delete mode 100644 pkg/chartutil/chartfile_test.go delete mode 100644 pkg/chartutil/coalesce.go delete mode 100644 pkg/chartutil/coalesce_test.go delete mode 100644 pkg/chartutil/compatible.go delete mode 100644 pkg/chartutil/compatible_test.go delete mode 100644 pkg/chartutil/create.go delete mode 100644 pkg/chartutil/create_test.go delete mode 100644 pkg/chartutil/dependencies.go delete mode 100644 pkg/chartutil/dependencies_test.go delete mode 100644 pkg/chartutil/doc.go delete mode 100644 pkg/chartutil/errors.go delete mode 100644 pkg/chartutil/errors_test.go delete mode 100644 pkg/chartutil/expand.go delete mode 100644 pkg/chartutil/expand_test.go delete mode 100644 pkg/chartutil/jsonschema.go delete mode 100644 pkg/chartutil/jsonschema_test.go delete mode 100644 pkg/chartutil/save.go delete mode 100644 pkg/chartutil/save_test.go delete mode 100644 pkg/chartutil/testdata/chartfiletest.yaml delete mode 100644 pkg/chartutil/testdata/coleridge.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/.helmignore delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/Chart.lock delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/LICENSE delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/docs/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/icon.svg delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/dependent-chart-alias/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz-1.2.3.tgz delete mode 100644 pkg/chartutil/testdata/frobnitz/.helmignore delete mode 100644 pkg/chartutil/testdata/frobnitz/Chart.lock delete mode 100644 pkg/chartutil/testdata/frobnitz/Chart.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/INSTALL.txt delete mode 100644 pkg/chartutil/testdata/frobnitz/LICENSE delete mode 100644 pkg/chartutil/testdata/frobnitz/README.md delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/_ignore_me delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/README.md delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl delete mode 100644 pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz/docs/README.md delete mode 100644 pkg/chartutil/testdata/frobnitz/icon.svg delete mode 100644 pkg/chartutil/testdata/frobnitz/ignore/me.txt delete mode 100644 pkg/chartutil/testdata/frobnitz/templates/template.tpl delete mode 100644 pkg/chartutil/testdata/frobnitz/values.yaml delete mode 100644 pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz delete mode 100755 pkg/chartutil/testdata/genfrob.sh delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/prod-v0.1.0.tgz delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml delete mode 100644 pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml delete mode 100644 pkg/chartutil/testdata/joonix/Chart.yaml delete mode 100644 pkg/chartutil/testdata/joonix/charts/.gitkeep delete mode 100644 pkg/chartutil/testdata/subpop/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/README.md delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/noreqs/Chart.yaml delete mode 100644 pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/subpop/noreqs/values.yaml delete mode 100644 pkg/chartutil/testdata/subpop/values.yaml delete mode 100644 pkg/chartutil/testdata/test-values-negative.yaml delete mode 100644 pkg/chartutil/testdata/test-values.schema.json delete mode 100644 pkg/chartutil/testdata/test-values.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/README.md delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml delete mode 100644 pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml delete mode 100644 pkg/chartutil/validate_name.go delete mode 100644 pkg/chartutil/validate_name_test.go delete mode 100644 pkg/chartutil/values.go delete mode 100644 pkg/chartutil/values_test.go delete mode 100644 pkg/cli/environment.go delete mode 100644 pkg/cli/environment_test.go delete mode 100644 pkg/cli/output/output.go delete mode 100644 pkg/cli/values/options.go delete mode 100644 pkg/cli/values/options_test.go delete mode 100644 pkg/downloader/chart_downloader.go delete mode 100644 pkg/downloader/chart_downloader_test.go delete mode 100644 pkg/downloader/doc.go delete mode 100644 pkg/downloader/manager.go delete mode 100644 pkg/downloader/manager_test.go delete mode 100644 pkg/downloader/testdata/helm-test-key.pub delete mode 100644 pkg/downloader/testdata/helm-test-key.secret delete mode 100644 pkg/downloader/testdata/local-subchart-0.1.0.tgz delete mode 100644 pkg/downloader/testdata/local-subchart/Chart.yaml delete mode 100644 pkg/downloader/testdata/repositories.yaml delete mode 100644 pkg/downloader/testdata/repository/encoded-url-index.yaml delete mode 100644 pkg/downloader/testdata/repository/kubernetes-charts-index.yaml delete mode 100644 pkg/downloader/testdata/repository/malformed-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-basicauth-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-ca-file-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-https-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-https-insecureskip-tls-verify-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-querystring-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-relative-index.yaml delete mode 100644 pkg/downloader/testdata/repository/testing-relative-trailing-slash-index.yaml delete mode 100644 pkg/downloader/testdata/signtest-0.1.0.tgz delete mode 100644 pkg/downloader/testdata/signtest-0.1.0.tgz.prov delete mode 100644 pkg/downloader/testdata/signtest/.helmignore delete mode 100644 pkg/downloader/testdata/signtest/Chart.yaml delete mode 100644 pkg/downloader/testdata/signtest/alpine/Chart.yaml delete mode 100644 pkg/downloader/testdata/signtest/alpine/README.md delete mode 100644 pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml delete mode 100644 pkg/downloader/testdata/signtest/alpine/values.yaml delete mode 100644 pkg/downloader/testdata/signtest/templates/pod.yaml delete mode 100644 pkg/downloader/testdata/signtest/values.yaml delete mode 100644 pkg/engine/doc.go delete mode 100644 pkg/engine/engine.go delete mode 100644 pkg/engine/engine_test.go delete mode 100644 pkg/engine/files.go delete mode 100644 pkg/engine/files_test.go delete mode 100644 pkg/engine/funcs.go delete mode 100644 pkg/engine/funcs_test.go delete mode 100644 pkg/engine/lookup_func.go delete mode 100644 pkg/gates/doc.go delete mode 100644 pkg/gates/gates.go delete mode 100644 pkg/gates/gates_test.go delete mode 100644 pkg/getter/doc.go delete mode 100644 pkg/getter/getter.go delete mode 100644 pkg/getter/getter_test.go delete mode 100644 pkg/getter/httpgetter.go delete mode 100644 pkg/getter/httpgetter_test.go delete mode 100644 pkg/getter/ocigetter.go delete mode 100644 pkg/getter/ocigetter_test.go delete mode 100644 pkg/getter/plugingetter.go delete mode 100644 pkg/getter/plugingetter_test.go delete mode 100644 pkg/getter/testdata/ca.crt delete mode 100644 pkg/getter/testdata/client.crt delete mode 100644 pkg/getter/testdata/client.key delete mode 100644 pkg/getter/testdata/empty-0.0.1.tgz delete mode 100755 pkg/getter/testdata/plugins/testgetter/get.sh delete mode 100644 pkg/getter/testdata/plugins/testgetter/plugin.yaml delete mode 100755 pkg/getter/testdata/plugins/testgetter2/get.sh delete mode 100644 pkg/getter/testdata/plugins/testgetter2/plugin.yaml delete mode 100644 pkg/getter/testdata/repository/local/index.yaml delete mode 100644 pkg/getter/testdata/repository/repositories.yaml delete mode 100644 pkg/helmpath/home.go delete mode 100644 pkg/helmpath/home_unix_test.go delete mode 100644 pkg/helmpath/home_windows_test.go delete mode 100644 pkg/helmpath/lazypath.go delete mode 100644 pkg/helmpath/lazypath_darwin.go delete mode 100644 pkg/helmpath/lazypath_darwin_test.go delete mode 100644 pkg/helmpath/lazypath_unix.go delete mode 100644 pkg/helmpath/lazypath_unix_test.go delete mode 100644 pkg/helmpath/lazypath_windows.go delete mode 100644 pkg/helmpath/lazypath_windows_test.go delete mode 100644 pkg/helmpath/xdg/xdg.go delete mode 100644 pkg/kube/client.go delete mode 100644 pkg/kube/client_test.go delete mode 100644 pkg/kube/config.go delete mode 100644 pkg/kube/converter.go delete mode 100644 pkg/kube/factory.go delete mode 100644 pkg/kube/fake/fake.go delete mode 100644 pkg/kube/fake/printer.go delete mode 100644 pkg/kube/interface.go delete mode 100644 pkg/kube/ready.go delete mode 100644 pkg/kube/ready_test.go delete mode 100644 pkg/kube/resource.go delete mode 100644 pkg/kube/resource_policy.go delete mode 100644 pkg/kube/resource_test.go delete mode 100644 pkg/kube/result.go delete mode 100644 pkg/kube/wait.go delete mode 100644 pkg/kube/wait_test.go delete mode 100644 pkg/lint/lint.go delete mode 100644 pkg/lint/lint_test.go delete mode 100644 pkg/lint/rules/chartfile.go delete mode 100644 pkg/lint/rules/chartfile_test.go delete mode 100644 pkg/lint/rules/dependencies.go delete mode 100644 pkg/lint/rules/dependencies_test.go delete mode 100644 pkg/lint/rules/deprecations.go delete mode 100644 pkg/lint/rules/deprecations_test.go delete mode 100644 pkg/lint/rules/template.go delete mode 100644 pkg/lint/rules/template_test.go delete mode 100644 pkg/lint/rules/testdata/albatross/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/albatross/templates/_helpers.tpl delete mode 100644 pkg/lint/rules/testdata/albatross/templates/fail.yaml delete mode 100644 pkg/lint/rules/testdata/albatross/templates/svc.yaml delete mode 100644 pkg/lint/rules/testdata/albatross/values.yaml delete mode 100644 pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/badchartfile/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/badchartfile/values.yaml delete mode 100644 pkg/lint/rules/testdata/badvaluesfile/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml delete mode 100644 pkg/lint/rules/testdata/badvaluesfile/values.yaml delete mode 100644 pkg/lint/rules/testdata/goodone/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/goodone/templates/goodone.yaml delete mode 100644 pkg/lint/rules/testdata/goodone/values.yaml delete mode 100644 pkg/lint/rules/testdata/multi-template-fail/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml delete mode 100644 pkg/lint/rules/testdata/v3-fail/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl delete mode 100644 pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml delete mode 100644 pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml delete mode 100644 pkg/lint/rules/testdata/v3-fail/templates/service.yaml delete mode 100644 pkg/lint/rules/testdata/v3-fail/values.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml delete mode 100644 pkg/lint/rules/testdata/withsubchart/values.yaml delete mode 100644 pkg/lint/rules/values.go delete mode 100644 pkg/lint/rules/values_test.go delete mode 100644 pkg/lint/support/doc.go delete mode 100644 pkg/lint/support/message.go delete mode 100644 pkg/lint/support/message_test.go delete mode 100644 pkg/plugin/cache/cache.go delete mode 100644 pkg/plugin/hooks.go delete mode 100644 pkg/plugin/installer/base.go delete mode 100644 pkg/plugin/installer/base_test.go delete mode 100644 pkg/plugin/installer/doc.go delete mode 100644 pkg/plugin/installer/http_installer.go delete mode 100644 pkg/plugin/installer/http_installer_test.go delete mode 100644 pkg/plugin/installer/installer.go delete mode 100644 pkg/plugin/installer/installer_test.go delete mode 100644 pkg/plugin/installer/local_installer.go delete mode 100644 pkg/plugin/installer/local_installer_test.go delete mode 100644 pkg/plugin/installer/vcs_installer.go delete mode 100644 pkg/plugin/installer/vcs_installer_test.go delete mode 100644 pkg/plugin/plugin.go delete mode 100644 pkg/plugin/plugin_test.go delete mode 100644 pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml delete mode 100644 pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml delete mode 100644 pkg/plugin/testdata/plugdir/good/echo/plugin.yaml delete mode 100755 pkg/plugin/testdata/plugdir/good/hello/hello.sh delete mode 100644 pkg/plugin/testdata/plugdir/good/hello/plugin.yaml delete mode 100644 pkg/postrender/exec.go delete mode 100644 pkg/postrender/exec_test.go delete mode 100644 pkg/postrender/postrender.go delete mode 100644 pkg/provenance/doc.go delete mode 100644 pkg/provenance/sign.go delete mode 100644 pkg/provenance/sign_test.go delete mode 100644 pkg/provenance/testdata/hashtest-1.2.3.tgz delete mode 100755 pkg/provenance/testdata/hashtest-1.2.3.tgz.prov delete mode 100644 pkg/provenance/testdata/hashtest.sha256 delete mode 100644 pkg/provenance/testdata/hashtest/.helmignore delete mode 100644 pkg/provenance/testdata/hashtest/Chart.yaml delete mode 100644 pkg/provenance/testdata/hashtest/values.yaml delete mode 100644 pkg/provenance/testdata/helm-password-key.secret delete mode 100644 pkg/provenance/testdata/helm-test-key.pub delete mode 100644 pkg/provenance/testdata/helm-test-key.secret delete mode 100644 pkg/provenance/testdata/msgblock.yaml delete mode 100644 pkg/provenance/testdata/msgblock.yaml.asc delete mode 100644 pkg/provenance/testdata/msgblock.yaml.tampered delete mode 100755 pkg/provenance/testdata/regen-hashtest.sh delete mode 100644 pkg/pusher/doc.go delete mode 100644 pkg/pusher/ocipusher.go delete mode 100644 pkg/pusher/ocipusher_test.go delete mode 100644 pkg/pusher/pusher.go delete mode 100644 pkg/pusher/pusher_test.go delete mode 100644 pkg/registry/client.go delete mode 100644 pkg/registry/client_test.go delete mode 100644 pkg/registry/constants.go delete mode 100644 pkg/registry/util.go delete mode 100644 pkg/release/hook.go delete mode 100644 pkg/release/info.go delete mode 100644 pkg/release/mock.go delete mode 100644 pkg/release/release.go delete mode 100644 pkg/release/responses.go delete mode 100644 pkg/release/status.go delete mode 100644 pkg/releaseutil/filter.go delete mode 100644 pkg/releaseutil/filter_test.go delete mode 100644 pkg/releaseutil/kind_sorter.go delete mode 100644 pkg/releaseutil/kind_sorter_test.go delete mode 100644 pkg/releaseutil/manifest.go delete mode 100644 pkg/releaseutil/manifest_sorter.go delete mode 100644 pkg/releaseutil/manifest_sorter_test.go delete mode 100644 pkg/releaseutil/manifest_test.go delete mode 100644 pkg/releaseutil/sorter.go delete mode 100644 pkg/releaseutil/sorter_test.go delete mode 100644 pkg/repo/chartrepo.go delete mode 100644 pkg/repo/chartrepo_test.go delete mode 100644 pkg/repo/doc.go delete mode 100644 pkg/repo/index.go delete mode 100644 pkg/repo/index_test.go delete mode 100644 pkg/repo/repo.go delete mode 100644 pkg/repo/repo_test.go delete mode 100644 pkg/repo/repotest/doc.go delete mode 100644 pkg/repo/repotest/server.go delete mode 100644 pkg/repo/repotest/server_test.go delete mode 100644 pkg/repo/repotest/testdata/examplechart-0.1.0.tgz delete mode 100644 pkg/repo/repotest/testdata/examplechart/.helmignore delete mode 100644 pkg/repo/repotest/testdata/examplechart/Chart.yaml delete mode 100644 pkg/repo/repotest/testdata/examplechart/values.yaml delete mode 100644 pkg/repo/testdata/chartmuseum-index.yaml delete mode 100644 pkg/repo/testdata/local-index-annotations.yaml delete mode 100644 pkg/repo/testdata/local-index-unordered.yaml delete mode 100644 pkg/repo/testdata/local-index.yaml delete mode 100644 pkg/repo/testdata/old-repositories.yaml delete mode 100644 pkg/repo/testdata/repositories.yaml delete mode 100644 pkg/repo/testdata/repository/frobnitz-1.2.3.tgz delete mode 100644 pkg/repo/testdata/repository/sprocket-1.1.0.tgz delete mode 100644 pkg/repo/testdata/repository/sprocket-1.2.0.tgz delete mode 100644 pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz delete mode 100644 pkg/repo/testdata/server/index.yaml delete mode 100644 pkg/repo/testdata/server/test.txt delete mode 100644 pkg/storage/driver/cfgmaps.go delete mode 100644 pkg/storage/driver/cfgmaps_test.go delete mode 100644 pkg/storage/driver/driver.go delete mode 100644 pkg/storage/driver/labels.go delete mode 100644 pkg/storage/driver/labels_test.go delete mode 100644 pkg/storage/driver/memory.go delete mode 100644 pkg/storage/driver/memory_test.go delete mode 100644 pkg/storage/driver/mock_test.go delete mode 100644 pkg/storage/driver/records.go delete mode 100644 pkg/storage/driver/records_test.go delete mode 100644 pkg/storage/driver/secrets.go delete mode 100644 pkg/storage/driver/secrets_test.go delete mode 100644 pkg/storage/driver/sql.go delete mode 100644 pkg/storage/driver/sql_test.go delete mode 100644 pkg/storage/driver/util.go delete mode 100644 pkg/storage/storage.go delete mode 100644 pkg/storage/storage_test.go delete mode 100644 pkg/strvals/doc.go delete mode 100644 pkg/strvals/parser.go delete mode 100644 pkg/strvals/parser_test.go delete mode 100644 pkg/time/time.go delete mode 100644 pkg/time/time_test.go delete mode 100644 pkg/uploader/chart_uploader.go delete mode 100644 pkg/uploader/doc.go diff --git a/pkg/action/action.go b/pkg/action/action.go deleted file mode 100644 index 82760250f..000000000 --- a/pkg/action/action.go +++ /dev/null @@ -1,421 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/discovery" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" -) - -// Timestamper is a function capable of producing a timestamp.Timestamper. -// -// By default, this is a time.Time function from the Helm time package. This can -// be overridden for testing though, so that timestamps are predictable. -var Timestamper = time.Now - -var ( - // errMissingChart indicates that a chart was not provided. - errMissingChart = errors.New("no chart provided") - // errMissingRelease indicates that a release (name) was not provided. - errMissingRelease = errors.New("no release provided") - // errInvalidRevision indicates that an invalid release revision number was provided. - errInvalidRevision = errors.New("invalid release revision") - // errPending indicates that another instance of Helm is already applying an operation on a release. - errPending = errors.New("another operation (install/upgrade/rollback) is in progress") -) - -// ValidName is a regular expression for resource names. -// -// DEPRECATED: This will be removed in Helm 4, and is no longer used here. See -// pkg/lint/rules.validateMetadataNameFunc for the replacement. -// -// 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])?)*$`) - -// Configuration injects the dependencies that all actions share. -type Configuration struct { - // RESTClientGetter is an interface that loads Kubernetes clients. - RESTClientGetter RESTClientGetter - - // Releases stores records of releases. - Releases *storage.Storage - - // KubeClient is a Kubernetes API client. - KubeClient kube.Interface - - // RegistryClient is a client for working with registries - RegistryClient *registry.Client - - // Capabilities describes the capabilities of the Kubernetes cluster. - Capabilities *chartutil.Capabilities - - Log func(string, ...interface{}) -} - -// renderResources renders the templates in a chart -// -// TODO: This function is badly in need of a refactor. -// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed -// This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, dryRun bool) ([]*release.Hook, *bytes.Buffer, string, error) { - hs := []*release.Hook{} - b := bytes.NewBuffer(nil) - - caps, err := cfg.getCapabilities() - if err != nil { - return hs, b, "", err - } - - if ch.Metadata.KubeVersion != "" { - if !chartutil.IsCompatibleRange(ch.Metadata.KubeVersion, caps.KubeVersion.String()) { - return hs, b, "", errors.Errorf("chart requires kubeVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, caps.KubeVersion.String()) - } - } - - var files map[string]string - var err2 error - - // A `helm template` or `helm install --dry-run` should not talk to the remote cluster. - // It will break in interesting and exotic ways because other data (e.g. discovery) - // is mocked. It is not up to the template author to decide when the user wants to - // connect to the cluster. So when the user says to dry run, respect the user's - // wishes and do not connect to the cluster. - if !dryRun && cfg.RESTClientGetter != nil { - restConfig, err := cfg.RESTClientGetter.ToRESTConfig() - if err != nil { - return hs, b, "", err - } - files, err2 = engine.RenderWithClient(ch, values, restConfig) - } else { - files, err2 = engine.Render(ch, values) - } - - if err2 != nil { - return hs, b, "", err2 - } - - // NOTES.txt gets rendered like all the other files, but because it's not a hook nor a resource, - // pull it out of here into a separate file so that we can actually use the output of the rendered - // text file. We have to spin through this map because the file contains path information, so we - // look for terminating NOTES.txt. We also remove it from the files so that we don't have to skip - // it in the sortHooks. - var notesBuffer bytes.Buffer - for k, v := range files { - if strings.HasSuffix(k, notesFileSuffix) { - if subNotes || (k == path.Join(ch.Name(), "templates", notesFileSuffix)) { - // If buffer contains data, add newline before adding more - if notesBuffer.Len() > 0 { - notesBuffer.WriteString("\n") - } - notesBuffer.WriteString(v) - } - delete(files, k) - } - } - notes := notesBuffer.String() - - // Sort hooks, manifests, and partials. Only hooks and manifests are returned, - // as partials are not used after renderer.Render. Empty manifests are also - // removed here. - hs, manifests, err := releaseutil.SortManifests(files, caps.APIVersions, releaseutil.InstallOrder) - if err != nil { - // By catching parse errors here, we can prevent bogus releases from going - // to Kubernetes. - // - // We return the files as a big blob of data to help the user debug parser - // errors. - for name, content := range files { - if strings.TrimSpace(content) == "" { - continue - } - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", name, content) - } - return hs, b, "", err - } - - // Aggregate all valid manifests into one big doc. - fileWritten := make(map[string]bool) - - if includeCrds { - for _, crd := range ch.CRDObjects() { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", crd.Name, string(crd.File.Data[:])) - } else { - err = writeToFile(outputDir, crd.Filename, string(crd.File.Data[:]), fileWritten[crd.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[crd.Name] = true - } - } - } - - for _, m := range manifests { - if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) - } else { - newDir := outputDir - if useReleaseName { - newDir = filepath.Join(outputDir, releaseName) - } - // NOTE: We do not have to worry about the post-renderer because - // output dir is only used by `helm template`. In the next major - // release, we should move this logic to template only as it is not - // used by install or upgrade - err = writeToFile(newDir, m.Name, m.Content, fileWritten[m.Name]) - if err != nil { - return hs, b, "", err - } - fileWritten[m.Name] = true - } - } - - if pr != nil { - b, err = pr.Run(b) - if err != nil { - return hs, b, notes, errors.Wrap(err, "error while running post render on files") - } - } - - return hs, b, notes, nil -} - -// RESTClientGetter gets the rest client -type RESTClientGetter interface { - ToRESTConfig() (*rest.Config, error) - ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) - ToRESTMapper() (meta.RESTMapper, error) -} - -// DebugLog sets the logger that writes debug strings -type DebugLog func(format string, v ...interface{}) - -// capabilities builds a Capabilities from discovery information. -func (cfg *Configuration) getCapabilities() (*chartutil.Capabilities, error) { - if cfg.Capabilities != nil { - return cfg.Capabilities, nil - } - dc, err := cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return nil, errors.Wrap(err, "could not get Kubernetes discovery client") - } - // force a discovery cache invalidation to always fetch the latest server version/capabilities. - dc.Invalidate() - kubeVersion, err := dc.ServerVersion() - if err != nil { - return nil, errors.Wrap(err, "could not get server version from Kubernetes") - } - // Issue #6361: - // Client-Go emits an error when an API service is registered but unimplemented. - // We trap that error here and print a warning. But since the discovery client continues - // building the API object, it is correctly populated with all valid APIs. - // See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642 - apiVersions, err := GetVersionSet(dc) - if err != nil { - if discovery.IsGroupDiscoveryFailedError(err) { - cfg.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err) - cfg.Log("WARNING: To fix this, kubectl delete apiservice ") - } else { - return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes") - } - } - - cfg.Capabilities = &chartutil.Capabilities{ - APIVersions: apiVersions, - KubeVersion: chartutil.KubeVersion{ - Version: kubeVersion.GitVersion, - Major: kubeVersion.Major, - Minor: kubeVersion.Minor, - }, - HelmVersion: chartutil.DefaultCapabilities.HelmVersion, - } - return cfg.Capabilities, nil -} - -// KubernetesClientSet creates a new kubernetes ClientSet based on the configuration -func (cfg *Configuration) KubernetesClientSet() (kubernetes.Interface, error) { - conf, err := cfg.RESTClientGetter.ToRESTConfig() - if err != nil { - return nil, errors.Wrap(err, "unable to generate config for kubernetes client") - } - - return kubernetes.NewForConfig(conf) -} - -// Now generates a timestamp -// -// If the configuration has a Timestamper on it, that will be used. -// Otherwise, this will use time.Now(). -func (cfg *Configuration) Now() time.Time { - return Timestamper() -} - -func (cfg *Configuration) releaseContent(name string, version int) (*release.Release, error) { - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("releaseContent: Release name is invalid: %s", name) - } - - if version <= 0 { - return cfg.Releases.Last(name) - } - - return cfg.Releases.Get(name, version) -} - -// GetVersionSet retrieves a set of available k8s API versions -func GetVersionSet(client discovery.ServerResourcesInterface) (chartutil.VersionSet, error) { - groups, resources, err := client.ServerGroupsAndResources() - if err != nil && !discovery.IsGroupDiscoveryFailedError(err) { - return chartutil.DefaultVersionSet, errors.Wrap(err, "could not get apiVersions from Kubernetes") - } - - // FIXME: The Kubernetes test fixture for cli appears to always return nil - // for calls to Discovery().ServerGroupsAndResources(). So in this case, we - // return the default API list. This is also a safe value to return in any - // other odd-ball case. - if len(groups) == 0 && len(resources) == 0 { - return chartutil.DefaultVersionSet, nil - } - - versionMap := make(map[string]interface{}) - versions := []string{} - - // Extract the groups - for _, g := range groups { - for _, gv := range g.Versions { - versionMap[gv.GroupVersion] = struct{}{} - } - } - - // Extract the resources - var id string - var ok bool - for _, r := range resources { - for _, rl := range r.APIResources { - - // A Kind at a GroupVersion can show up more than once. We only want - // it displayed once in the final output. - id = path.Join(r.GroupVersion, rl.Kind) - if _, ok = versionMap[id]; !ok { - versionMap[id] = struct{}{} - } - } - } - - // Convert to a form that NewVersionSet can use - for k := range versionMap { - versions = append(versions, k) - } - - return chartutil.VersionSet(versions), nil -} - -// recordRelease with an update operation in case reuse has been set. -func (cfg *Configuration) recordRelease(r *release.Release) { - if err := cfg.Releases.Update(r); err != nil { - cfg.Log("warning: Failed to update release %s: %s", r.Name, err) - } -} - -// Init initializes the action configuration -func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namespace, helmDriver string, log DebugLog) error { - kc := kube.New(getter) - kc.Log = log - - lazyClient := &lazyClient{ - namespace: namespace, - clientFn: kc.Factory.KubernetesClientSet, - } - - var store *storage.Storage - switch helmDriver { - case "secret", "secrets", "": - d := driver.NewSecrets(newSecretClient(lazyClient)) - d.Log = log - store = storage.Init(d) - case "configmap", "configmaps": - d := driver.NewConfigMaps(newConfigMapClient(lazyClient)) - d.Log = log - store = storage.Init(d) - case "memory": - var d *driver.Memory - if cfg.Releases != nil { - if mem, ok := cfg.Releases.Driver.(*driver.Memory); ok { - // This function can be called more than once (e.g., helm list --all-namespaces). - // If a memory driver was already initialized, re-use it but set the possibly new namespace. - // We re-use it in case some releases where already created in the existing memory driver. - d = mem - } - } - if d == nil { - d = driver.NewMemory() - } - d.SetNamespace(namespace) - store = storage.Init(d) - case "sql": - d, err := driver.NewSQL( - os.Getenv("HELM_DRIVER_SQL_CONNECTION_STRING"), - log, - namespace, - ) - if err != nil { - panic(fmt.Sprintf("Unable to instantiate SQL driver: %v", err)) - } - store = storage.Init(d) - default: - // Not sure what to do here. - panic("Unknown driver in HELM_DRIVER: " + helmDriver) - } - - cfg.RESTClientGetter = getter - cfg.KubeClient = kc - cfg.Releases = store - cfg.Log = log - - return nil -} diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go deleted file mode 100644 index c816c84af..000000000 --- a/pkg/action/action_test.go +++ /dev/null @@ -1,283 +0,0 @@ -/* -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 action - -import ( - "flag" - "io/ioutil" - "testing" - - fakeclientset "k8s.io/client-go/kubernetes/fake" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" - "helm.sh/helm/v3/pkg/time" -) - -var verbose = flag.Bool("test.log", false, "enable test logging") - -func actionConfigFixture(t *testing.T) *Configuration { - t.Helper() - - registryClient, err := registry.NewClient() - if err != nil { - t.Fatal(err) - } - - return &Configuration{ - Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, - Capabilities: chartutil.DefaultCapabilities, - RegistryClient: registryClient, - Log: func(format string, v ...interface{}) { - t.Helper() - if *verbose { - t.Logf(format, v...) - } - }, - } -} - -var manifestWithHook = `kind: ConfigMap -metadata: - name: test-cm - annotations: - "helm.sh/hook": post-install,pre-delete,post-upgrade -data: - name: value` - -var manifestWithTestHook = `kind: Pod - metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test - spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command - ` - -var rbacManifests = `apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: schedule-agents -rules: -- apiGroups: [""] - resources: ["pods", "pods/exec", "pods/log"] - verbs: ["*"] - ---- - -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: schedule-agents - namespace: {{ default .Release.Namespace}} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: schedule-agents -subjects: -- kind: ServiceAccount - name: schedule-agents - namespace: {{ .Release.Namespace }} -` - -type chartOptions struct { - *chart.Chart -} - -type chartOption func(*chartOptions) - -func buildChart(opts ...chartOption) *chart.Chart { - c := &chartOptions{ - Chart: &chart.Chart{ - // TODO: This should be more complete. - Metadata: &chart.Metadata{ - APIVersion: "v1", - Name: "hello", - Version: "0.1.0", - }, - // This adds a basic template and hooks. - Templates: []*chart.File{ - {Name: "templates/hello", Data: []byte("hello: world")}, - {Name: "templates/hooks", Data: []byte(manifestWithHook)}, - }, - }, - } - - for _, opt := range opts { - opt(c) - } - - return c.Chart -} - -func withName(name string) chartOption { - return func(opts *chartOptions) { - opts.Metadata.Name = name - } -} - -func withSampleValues() chartOption { - values := map[string]interface{}{ - "someKey": "someValue", - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - "anotherNestedKey": map[string]interface{}{ - "yetAnotherNestedKey": map[string]interface{}{ - "youReadyForAnotherNestedKey": "No", - }, - }, - }, - } - return func(opts *chartOptions) { - opts.Values = values - } -} - -func withValues(values map[string]interface{}) chartOption { - return func(opts *chartOptions) { - opts.Values = values - } -} - -func withNotes(notes string) chartOption { - return func(opts *chartOptions) { - opts.Templates = append(opts.Templates, &chart.File{ - Name: "templates/NOTES.txt", - Data: []byte(notes), - }) - } -} - -func withDependency(dependencyOpts ...chartOption) chartOption { - return func(opts *chartOptions) { - opts.AddDependency(buildChart(dependencyOpts...)) - } -} - -func withMetadataDependency(dependency chart.Dependency) chartOption { - return func(opts *chartOptions) { - opts.Metadata.Dependencies = append(opts.Metadata.Dependencies, &dependency) - } -} - -func withSampleTemplates() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - // This adds basic templates and partials. - {Name: "templates/goodbye", Data: []byte("goodbye: world")}, - {Name: "templates/empty", Data: []byte("")}, - {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, - {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withSampleIncludingIncorrectTemplates() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - // This adds basic templates and partials. - {Name: "templates/goodbye", Data: []byte("goodbye: world")}, - {Name: "templates/empty", Data: []byte("")}, - {Name: "templates/incorrect", Data: []byte("{{ .Values.bad.doh }}")}, - {Name: "templates/with-partials", Data: []byte(`hello: {{ template "_planet" . }}`)}, - {Name: "templates/partials/_planet", Data: []byte(`{{define "_planet"}}Earth{{end}}`)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withMultipleManifestTemplate() chartOption { - return func(opts *chartOptions) { - sampleTemplates := []*chart.File{ - {Name: "templates/rbac", Data: []byte(rbacManifests)}, - } - opts.Templates = append(opts.Templates, sampleTemplates...) - } -} - -func withKube(version string) chartOption { - return func(opts *chartOptions) { - opts.Metadata.KubeVersion = version - } -} - -// releaseStub creates a release stub, complete with the chartStub as its chart. -func releaseStub() *release.Release { - return namedReleaseStub("angry-panda", release.StatusDeployed) -} - -func namedReleaseStub(name string, status release.Status) *release.Release { - now := time.Now() - return &release.Release{ - Name: name, - Info: &release.Info{ - FirstDeployed: now, - LastDeployed: now, - Status: status, - Description: "Named Release Stub", - }, - Chart: buildChart(withSampleTemplates()), - Config: map[string]interface{}{"name": "value"}, - Version: 1, - Hooks: []*release.Hook{ - { - Name: "test-cm", - Kind: "ConfigMap", - Path: "test-cm", - Manifest: manifestWithHook, - Events: []release.HookEvent{ - release.HookPostInstall, - release.HookPreDelete, - }, - }, - { - Name: "finding-nemo", - Kind: "Pod", - Path: "finding-nemo", - Manifest: manifestWithTestHook, - Events: []release.HookEvent{ - release.HookTest, - }, - }, - }, - } -} - -func TestGetVersionSet(t *testing.T) { - client := fakeclientset.NewSimpleClientset() - - vs, err := GetVersionSet(client.Discovery()) - if err != nil { - t.Error(err) - } - - if !vs.Has("v1") { - t.Errorf("Expected supported versions to at least include v1.") - } - if vs.Has("nosuchversion/v1") { - t.Error("Non-existent version is reported found.") - } -} diff --git a/pkg/action/dependency.go b/pkg/action/dependency.go deleted file mode 100644 index 3265f1f17..000000000 --- a/pkg/action/dependency.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -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 action - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/gosuri/uitable" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// Dependency is the action for building a given chart's dependency tree. -// -// It provides the implementation of 'helm dependency' and its respective subcommands. -type Dependency struct { - Verify bool - Keyring string - SkipRefresh bool - ColumnWidth uint -} - -// NewDependency creates a new Dependency object with the given configuration. -func NewDependency() *Dependency { - return &Dependency{ - ColumnWidth: 80, - } -} - -// List executes 'helm dependency list'. -func (d *Dependency) List(chartpath string, out io.Writer) error { - c, err := loader.Load(chartpath) - if err != nil { - return err - } - - if c.Metadata.Dependencies == nil { - fmt.Fprintf(out, "WARNING: no dependencies at %s\n", filepath.Join(chartpath, "charts")) - return nil - } - - d.printDependencies(chartpath, out, c) - fmt.Fprintln(out) - d.printMissing(chartpath, out, c.Metadata.Dependencies) - return nil -} - -// dependencyStatus returns a string describing the status of a dependency viz a viz the parent chart. -func (d *Dependency) dependencyStatus(chartpath string, dep *chart.Dependency, parent *chart.Chart) string { - filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") - - // If a chart is unpacked, this will check the unpacked chart's `charts/` directory for tarballs. - // Technically, this is COMPLETELY unnecessary, and should be removed in Helm 4. It is here - // to preserved backward compatibility. In Helm 2/3, there is a "difference" between - // the tgz version (which outputs "ok" if it unpacks) and the loaded version (which outputs - // "unpacked"). Early in Helm 2's history, this would have made a difference. But it no - // longer does. However, since this code shipped with Helm 3, the output must remain stable - // until Helm 4. - switch archives, err := filepath.Glob(filepath.Join(chartpath, "charts", filename)); { - case err != nil: - return "bad pattern" - case len(archives) > 1: - // See if the second part is a SemVer - found := []string{} - for _, arc := range archives { - // we need to trip the prefix dirs and the extension off. - filename = strings.TrimSuffix(filepath.Base(arc), ".tgz") - maybeVersion := strings.TrimPrefix(filename, fmt.Sprintf("%s-", dep.Name)) - - if _, err := semver.StrictNewVersion(maybeVersion); err == nil { - // If the version parsed without an error, it is possibly a valid - // version. - found = append(found, arc) - } - } - - if l := len(found); l == 1 { - // If we get here, we do the same thing as in len(archives) == 1. - if r := statArchiveForStatus(found[0], dep); r != "" { - return r - } - - // Fall through and look for directories - } else if l > 1 { - return "too many matches" - } - - // The sanest thing to do here is to fall through and see if we have any directory - // matches. - - case len(archives) == 1: - archive := archives[0] - if r := statArchiveForStatus(archive, dep); r != "" { - return r - } - - } - // End unnecessary code. - - var depChart *chart.Chart - for _, item := range parent.Dependencies() { - if item.Name() == dep.Name { - depChart = item - } - } - - if depChart == nil { - return "missing" - } - - if depChart.Metadata.Version != dep.Version { - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return "invalid version" - } - - v, err := semver.NewVersion(depChart.Metadata.Version) - if err != nil { - return "invalid version" - } - - if !constraint.Check(v) { - return "wrong version" - } - } - - return "unpacked" -} - -// stat an archive and return a message if the stat is successful -// -// This is a refactor of the code originally in dependencyStatus. It is here to -// support legacy behavior, and should be removed in Helm 4. -func statArchiveForStatus(archive string, dep *chart.Dependency) string { - if _, err := os.Stat(archive); err == nil { - c, err := loader.Load(archive) - if err != nil { - return "corrupt" - } - if c.Name() != dep.Name { - return "misnamed" - } - - if c.Metadata.Version != dep.Version { - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return "invalid version" - } - - v, err := semver.NewVersion(c.Metadata.Version) - if err != nil { - return "invalid version" - } - - if !constraint.Check(v) { - return "wrong version" - } - } - return "ok" - } - return "" -} - -// printDependencies prints all of the dependencies in the yaml file. -func (d *Dependency) printDependencies(chartpath string, out io.Writer, c *chart.Chart) { - table := uitable.New() - table.MaxColWidth = d.ColumnWidth - table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") - for _, row := range c.Metadata.Dependencies { - table.AddRow(row.Name, row.Version, row.Repository, d.dependencyStatus(chartpath, row, c)) - } - fmt.Fprintln(out, table) -} - -// printMissing prints warnings about charts that are present on disk, but are -// not in Chart.yaml. -func (d *Dependency) printMissing(chartpath string, out io.Writer, reqs []*chart.Dependency) { - folder := filepath.Join(chartpath, "charts/*") - files, err := filepath.Glob(folder) - if err != nil { - fmt.Fprintln(out, err) - return - } - - for _, f := range files { - fi, err := os.Stat(f) - if err != nil { - fmt.Fprintf(out, "Warning: %s\n", err) - } - // Skip anything that is not a directory and not a tgz file. - if !fi.IsDir() && filepath.Ext(f) != ".tgz" { - continue - } - c, err := loader.Load(f) - if err != nil { - fmt.Fprintf(out, "WARNING: %q is not a chart.\n", f) - continue - } - found := false - for _, d := range reqs { - if d.Name == c.Name() { - found = true - break - } - } - if !found { - fmt.Fprintf(out, "WARNING: %q is not in Chart.yaml.\n", f) - } - } -} diff --git a/pkg/action/dependency_test.go b/pkg/action/dependency_test.go deleted file mode 100644 index c29587aec..000000000 --- a/pkg/action/dependency_test.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -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 action - -import ( - "bytes" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -func TestList(t *testing.T) { - for _, tcase := range []struct { - chart string - golden string - }{ - { - chart: "testdata/charts/chart-with-compressed-dependencies", - golden: "output/list-compressed-deps.txt", - }, - { - chart: "testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz", - golden: "output/list-compressed-deps-tgz.txt", - }, - { - chart: "testdata/charts/chart-with-uncompressed-dependencies", - golden: "output/list-uncompressed-deps.txt", - }, - { - chart: "testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz", - golden: "output/list-uncompressed-deps-tgz.txt", - }, - { - chart: "testdata/charts/chart-missing-deps", - golden: "output/list-missing-deps.txt", - }, - } { - buf := bytes.Buffer{} - if err := NewDependency().List(tcase.chart, &buf); err != nil { - t.Fatal(err) - } - test.AssertGoldenString(t, buf.String(), tcase.golden) - } -} - -// TestDependencyStatus_Dashes is a regression test to make sure that dashes in -// chart names do not cause resolution problems. -func TestDependencyStatus_Dashes(t *testing.T) { - // Make a temp dir - dir := t.TempDir() - - chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { - t.Fatal(err) - } - - // Add some fake charts - first := buildChart(withName("first-chart")) - _, err := chartutil.Save(first, chartpath) - if err != nil { - t.Fatal(err) - } - - second := buildChart(withName("first-chart-second-chart")) - _, err = chartutil.Save(second, chartpath) - if err != nil { - t.Fatal(err) - } - - dep := &chart.Dependency{ - Name: "first-chart", - Version: "0.1.0", - } - - // Now try to get the deps - stat := NewDependency().dependencyStatus(dir, dep, first) - if stat != "ok" { - t.Errorf("Unexpected status: %q", stat) - } -} - -func TestStatArchiveForStatus(t *testing.T) { - // Make a temp dir - dir := t.TempDir() - - chartpath := filepath.Join(dir, "charts") - if err := os.MkdirAll(chartpath, 0700); err != nil { - t.Fatal(err) - } - - // unsaved chart - lilith := buildChart(withName("lilith")) - - // dep referring to chart - dep := &chart.Dependency{ - Name: "lilith", - Version: "1.2.3", - } - - is := assert.New(t) - - lilithpath := filepath.Join(chartpath, "lilith-1.2.3.tgz") - is.Empty(statArchiveForStatus(lilithpath, dep)) - - // save the chart (version 0.1.0, because that is the default) - where, err := chartutil.Save(lilith, chartpath) - is.NoError(err) - - // Should get "wrong version" because we asked for 1.2.3 and got 0.1.0 - is.Equal("wrong version", statArchiveForStatus(where, dep)) - - // Break version on dep - dep = &chart.Dependency{ - Name: "lilith", - Version: "1.2.3.4.5", - } - is.Equal("invalid version", statArchiveForStatus(where, dep)) - - // Break the name - dep = &chart.Dependency{ - Name: "lilith2", - Version: "1.2.3", - } - is.Equal("misnamed", statArchiveForStatus(where, dep)) - - // Now create the right version - dep = &chart.Dependency{ - Name: "lilith", - Version: "0.1.0", - } - is.Equal("ok", statArchiveForStatus(where, dep)) -} diff --git a/pkg/action/doc.go b/pkg/action/doc.go deleted file mode 100644 index 3c91bd618..000000000 --- a/pkg/action/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 action contains the logic for each action that Helm can perform. -// -// This is a library for calling top-level Helm actions like 'install', -// 'upgrade', or 'list'. Actions approximately match the command line -// invocations that the Helm client uses. -package action diff --git a/pkg/action/get.go b/pkg/action/get.go deleted file mode 100644 index f44b53307..000000000 --- a/pkg/action/get.go +++ /dev/null @@ -1,47 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/release" -) - -// Get is the action for checking a given release's information. -// -// It provides the implementation of 'helm get' and its respective subcommands (except `helm get values`). -type Get struct { - cfg *Configuration - - // Initializing Version to 0 will get the latest revision of the release. - Version int -} - -// NewGet creates a new Get object with the given configuration. -func NewGet(cfg *Configuration) *Get { - return &Get{ - cfg: cfg, - } -} - -// Run executes 'helm get' against the given release. -func (g *Get) Run(name string) (*release.Release, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - return g.cfg.releaseContent(name, g.Version) -} diff --git a/pkg/action/get_values.go b/pkg/action/get_values.go deleted file mode 100644 index 9c32db213..000000000 --- a/pkg/action/get_values.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/chartutil" -) - -// GetValues is the action for checking a given release's values. -// -// It provides the implementation of 'helm get values'. -type GetValues struct { - cfg *Configuration - - Version int - AllValues bool -} - -// NewGetValues creates a new GetValues object with the given configuration. -func NewGetValues(cfg *Configuration) *GetValues { - return &GetValues{ - cfg: cfg, - } -} - -// Run executes 'helm get values' against the given release. -func (g *GetValues) Run(name string) (map[string]interface{}, error) { - if err := g.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - rel, err := g.cfg.releaseContent(name, g.Version) - if err != nil { - return nil, err - } - - // If the user wants all values, compute the values and return. - if g.AllValues { - cfg, err := chartutil.CoalesceValues(rel.Chart, rel.Config) - if err != nil { - return nil, err - } - return cfg, nil - } - return rel.Config, nil -} diff --git a/pkg/action/history.go b/pkg/action/history.go deleted file mode 100644 index 0430aaf7a..000000000 --- a/pkg/action/history.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -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 action - -import ( - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// History is the action for checking the release's ledger. -// -// It provides the implementation of 'helm history'. -// It returns all the revisions for a specific release. -// To list up to one revision of every release in one specific, or in all, -// namespaces, see the List action. -type History struct { - cfg *Configuration - - Max int - Version int -} - -// NewHistory creates a new History object with the given configuration. -func NewHistory(cfg *Configuration) *History { - return &History{ - cfg: cfg, - } -} - -// Run executes 'helm history' against the given release. -func (h *History) Run(name string) ([]*release.Release, error) { - if err := h.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("release name is invalid: %s", name) - } - - h.cfg.Log("getting history for release %s", name) - return h.cfg.Releases.History(name) -} diff --git a/pkg/action/hooks.go b/pkg/action/hooks.go deleted file mode 100644 index 40c1ffdb6..000000000 --- a/pkg/action/hooks.go +++ /dev/null @@ -1,151 +0,0 @@ -/* -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 action - -import ( - "bytes" - "sort" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// execHook executes all of the hooks for the given hook event. -func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent, timeout time.Duration) error { - executingHooks := []*release.Hook{} - - for _, h := range rl.Hooks { - for _, e := range h.Events { - if e == hook { - executingHooks = append(executingHooks, h) - } - } - } - - // hooke are pre-ordered by kind, so keep order stable - sort.Stable(hookByWeight(executingHooks)) - - for _, h := range executingHooks { - // Set default delete policy to before-hook-creation - if h.DeletePolicies == nil || len(h.DeletePolicies) == 0 { - // TODO(jlegrone): Only apply before-hook-creation delete policy to run to completion - // resources. For all other resource types update in place if a - // resource with the same name already exists and is owned by the - // current release. - h.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation} - } - - if err := cfg.deleteHookByPolicy(h, release.HookBeforeHookCreation); err != nil { - return err - } - - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), true) - if err != nil { - return errors.Wrapf(err, "unable to build kubernetes object for %s hook %s", hook, h.Path) - } - - // Record the time at which the hook was applied to the cluster - h.LastRun = release.HookExecution{ - StartedAt: helmtime.Now(), - Phase: release.HookPhaseRunning, - } - cfg.recordRelease(rl) - - // As long as the implementation of WatchUntilReady does not panic, HookPhaseFailed or HookPhaseSucceeded - // should always be set by this function. If we fail to do that for any reason, then HookPhaseUnknown is - // the most appropriate value to surface. - h.LastRun.Phase = release.HookPhaseUnknown - - // Create hook resources - if _, err := cfg.KubeClient.Create(resources); err != nil { - h.LastRun.CompletedAt = helmtime.Now() - h.LastRun.Phase = release.HookPhaseFailed - return errors.Wrapf(err, "warning: Hook %s %s failed", hook, h.Path) - } - - // Watch hook resources until they have completed - err = cfg.KubeClient.WatchUntilReady(resources, timeout) - // Note the time of success/failure - h.LastRun.CompletedAt = helmtime.Now() - // Mark hook as succeeded or failed - if err != nil { - h.LastRun.Phase = release.HookPhaseFailed - // If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted - // under failed condition. If so, then clear the corresponding resource object in the hook - if err := cfg.deleteHookByPolicy(h, release.HookFailed); err != nil { - return err - } - return err - } - h.LastRun.Phase = release.HookPhaseSucceeded - } - - // If all hooks are successful, check the annotation of each hook to determine whether the hook should be deleted - // under succeeded condition. If so, then clear the corresponding resource object in each hook - for _, h := range executingHooks { - if err := cfg.deleteHookByPolicy(h, release.HookSucceeded); err != nil { - return err - } - } - - return nil -} - -// hookByWeight is a sorter for hooks -type hookByWeight []*release.Hook - -func (x hookByWeight) Len() int { return len(x) } -func (x hookByWeight) Swap(i, j int) { x[i], x[j] = x[j], x[i] } -func (x hookByWeight) Less(i, j int) bool { - if x[i].Weight == x[j].Weight { - return x[i].Name < x[j].Name - } - return x[i].Weight < x[j].Weight -} - -// deleteHookByPolicy deletes a hook if the hook policy instructs it to -func (cfg *Configuration) deleteHookByPolicy(h *release.Hook, policy release.HookDeletePolicy) error { - // Never delete CustomResourceDefinitions; this could cause lots of - // cascading garbage collection. - if h.Kind == "CustomResourceDefinition" { - return nil - } - if hookHasDeletePolicy(h, policy) { - resources, err := cfg.KubeClient.Build(bytes.NewBufferString(h.Manifest), false) - if err != nil { - return errors.Wrapf(err, "unable to build kubernetes object for deleting hook %s", h.Path) - } - _, errs := cfg.KubeClient.Delete(resources) - if len(errs) > 0 { - return errors.New(joinErrors(errs)) - } - } - return nil -} - -// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices -// supported by helm. If so, mark the hook as one should be deleted. -func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool { - for _, v := range h.DeletePolicies { - if policy == v { - return true - } - } - return false -} diff --git a/pkg/action/install.go b/pkg/action/install.go deleted file mode 100644 index fa5508234..000000000 --- a/pkg/action/install.go +++ /dev/null @@ -1,767 +0,0 @@ -/* -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 action - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "sync" - "text/template" - "time" - - "github.com/Masterminds/sprig/v3" - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/resource" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/kube" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/storage" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// NOTESFILE_SUFFIX that we want to treat special. It goes through the templating engine -// but it's not a yaml file (resource) hence can't have hooks, etc. And the user actually -// wants to see this file after rendering in the status command. However, it must be a suffix -// since there can be filepath in front of it. -const notesFileSuffix = "NOTES.txt" - -const defaultDirectoryPermission = 0755 - -// Install performs an installation operation. -type Install struct { - cfg *Configuration - - ChartPathOptions - - ClientOnly bool - CreateNamespace bool - DryRun bool - DisableHooks bool - Replace bool - Wait bool - WaitForJobs bool - Devel bool - DependencyUpdate bool - Timeout time.Duration - Namespace string - ReleaseName string - GenerateName bool - NameTemplate string - Description string - OutputDir string - Atomic bool - SkipCRDs bool - SubNotes bool - DisableOpenAPIValidation bool - IncludeCRDs bool - // KubeVersion allows specifying a custom kubernetes version to use and - // APIVersions allows a manual set of supported API Versions to be passed - // (for things like templating). These are ignored if ClientOnly is false - KubeVersion *chartutil.KubeVersion - APIVersions chartutil.VersionSet - // Used by helm template to render charts with .Release.IsUpgrade. Ignored if Dry-Run is false - IsUpgrade bool - // Used by helm template to add the release as part of OutputDir path - // OutputDir/ - UseReleaseName bool - PostRenderer postrender.PostRenderer - // Lock to control raceconditions when the process receives a SIGTERM - Lock sync.Mutex -} - -// ChartPathOptions captures common options used for controlling chart paths -type ChartPathOptions struct { - CaFile string // --ca-file - CertFile string // --cert-file - KeyFile string // --key-file - InsecureSkipTLSverify bool // --insecure-skip-verify - Keyring string // --keyring - Password string // --password - PassCredentialsAll bool // --pass-credentials - RepoURL string // --repo - Username string // --username - Verify bool // --verify - Version string // --version - - // registryClient provides a registry client but is not added with - // options from a flag - registryClient *registry.Client -} - -// NewInstall creates a new Install object with the given configuration. -func NewInstall(cfg *Configuration) *Install { - in := &Install{ - cfg: cfg, - } - in.ChartPathOptions.registryClient = cfg.RegistryClient - - return in -} - -func (i *Install) installCRDs(crds []chart.CRD) error { - // We do these one file at a time in the order they were read. - totalItems := []*resource.Info{} - for _, obj := range crds { - // Read in the resources - res, err := i.cfg.KubeClient.Build(bytes.NewBuffer(obj.File.Data), false) - if err != nil { - return errors.Wrapf(err, "failed to install CRD %s", obj.Name) - } - - // Send them to Kube - if _, err := i.cfg.KubeClient.Create(res); err != nil { - // If the error is CRD already exists, continue. - if apierrors.IsAlreadyExists(err) { - crdName := res[0].Name - i.cfg.Log("CRD %s is already present. Skipping.", crdName) - continue - } - return errors.Wrapf(err, "failed to install CRD %s", obj.Name) - } - totalItems = append(totalItems, res...) - } - if len(totalItems) > 0 { - // Invalidate the local cache, since it will not have the new CRDs - // present. - discoveryClient, err := i.cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } - i.cfg.Log("Clearing discovery cache") - discoveryClient.Invalidate() - // Give time for the CRD to be recognized. - - if err := i.cfg.KubeClient.Wait(totalItems, 60*time.Second); err != nil { - return err - } - - // Make sure to force a rebuild of the cache. - discoveryClient.ServerGroups() - } - return nil -} - -// Run executes the installation -// -// If DryRun is set to true, this will prepare the release, but not install it - -func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - ctx := context.Background() - return i.RunWithContext(ctx, chrt, vals) -} - -// Run executes the installation with Context -func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - // Check reachability of cluster unless in client-only mode (e.g. `helm template` without `--validate`) - if !i.ClientOnly { - if err := i.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - } - - if err := i.availableName(); err != nil { - return nil, err - } - - if err := chartutil.ProcessDependencies(chrt, vals); err != nil { - return nil, err - } - - // Pre-install anything in the crd/ directory. We do this before Helm - // contacts the upstream server and builds the capabilities object. - if crds := chrt.CRDObjects(); !i.ClientOnly && !i.SkipCRDs && len(crds) > 0 { - // On dry run, bail here - if i.DryRun { - i.cfg.Log("WARNING: This chart or one of its subcharts contains CRDs. Rendering may fail or contain inaccuracies.") - } else if err := i.installCRDs(crds); err != nil { - return nil, err - } - } - - if i.ClientOnly { - // Add mock objects in here so it doesn't use Kube API server - // NOTE(bacongobbler): used for `helm template` - i.cfg.Capabilities = chartutil.DefaultCapabilities.Copy() - if i.KubeVersion != nil { - i.cfg.Capabilities.KubeVersion = *i.KubeVersion - } - i.cfg.Capabilities.APIVersions = append(i.cfg.Capabilities.APIVersions, i.APIVersions...) - i.cfg.KubeClient = &kubefake.PrintingKubeClient{Out: ioutil.Discard} - - mem := driver.NewMemory() - mem.SetNamespace(i.Namespace) - i.cfg.Releases = storage.Init(mem) - } else if !i.ClientOnly && len(i.APIVersions) > 0 { - i.cfg.Log("API Version list given outside of client only mode, this list will be ignored") - } - - // Make sure if Atomic is set, that wait is set as well. This makes it so - // the user doesn't have to specify both - i.Wait = i.Wait || i.Atomic - - caps, err := i.cfg.getCapabilities() - if err != nil { - return nil, err - } - - // special case for helm template --is-upgrade - isUpgrade := i.IsUpgrade && i.DryRun - options := chartutil.ReleaseOptions{ - Name: i.ReleaseName, - Namespace: i.Namespace, - Revision: 1, - IsInstall: !isUpgrade, - IsUpgrade: isUpgrade, - } - valuesToRender, err := chartutil.ToRenderValues(chrt, vals, options, caps) - if err != nil { - return nil, err - } - - rel := i.createRelease(chrt, vals) - - var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, i.DryRun) - // Even for errors, attach this if available - if manifestDoc != nil { - rel.Manifest = manifestDoc.String() - } - // Check error from render - if err != nil { - rel.SetStatus(release.StatusFailed, fmt.Sprintf("failed to render resource: %s", err.Error())) - // Return a release with partial data so that the client can show debugging information. - return rel, err - } - - // Mark this release as in-progress - rel.SetStatus(release.StatusPendingInstall, "Initial install underway") - - var toBeAdopted kube.ResourceList - resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation) - if err != nil { - return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest") - } - - // It is safe to use "force" here because these are resources currently rendered by the chart. - err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true)) - if err != nil { - return nil, err - } - - // Install requires an extra validation step of checking that resources - // don't already exist before we actually create resources. If we continue - // forward and create the release object with resources that already exist, - // we'll end up in a state where we will delete those resources upon - // deleting the release because the manifest will be pointing at that - // resource - if !i.ClientOnly && !isUpgrade && len(resources) > 0 { - toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace) - if err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install") - } - } - - // Bail out here if it is a dry run - if i.DryRun { - rel.Info.Description = "Dry run complete" - return rel, nil - } - - if i.CreateNamespace { - ns := &v1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: i.Namespace, - Labels: map[string]string{ - "name": i.Namespace, - }, - }, - } - buf, err := yaml.Marshal(ns) - if err != nil { - return nil, err - } - resourceList, err := i.cfg.KubeClient.Build(bytes.NewBuffer(buf), true) - if err != nil { - return nil, err - } - if _, err := i.cfg.KubeClient.Create(resourceList); err != nil && !apierrors.IsAlreadyExists(err) { - return nil, err - } - } - - // If Replace is true, we need to supercede the last release. - if i.Replace { - if err := i.replaceRelease(rel); err != nil { - return nil, err - } - } - - // Store the release in history before continuing (new in Helm 3). We always know - // that this is a create operation. - if err := i.cfg.Releases.Create(rel); err != nil { - // We could try to recover gracefully here, but since nothing has been installed - // yet, this is probably safer than trying to continue when we know storage is - // not working. - return rel, err - } - rChan := make(chan resultMessage) - doneChan := make(chan struct{}) - defer close(doneChan) - go i.performInstall(rChan, rel, toBeAdopted, resources) - go i.handleContext(ctx, rChan, doneChan, rel) - result := <-rChan - //start preformInstall go routine - return result.r, result.e -} - -func (i *Install) performInstall(c chan<- resultMessage, rel *release.Release, toBeAdopted kube.ResourceList, resources kube.ResourceList) { - - // pre-install hooks - if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPreInstall, i.Timeout); err != nil { - i.reportToRun(c, rel, fmt.Errorf("failed pre-install: %s", err)) - return - } - } - - // At this point, we can do the install. Note that before we were detecting whether to - // do an update, but it's not clear whether we WANT to do an update if the re-use is set - // to true, since that is basically an upgrade operation. - if len(toBeAdopted) == 0 && len(resources) > 0 { - if _, err := i.cfg.KubeClient.Create(resources); err != nil { - i.reportToRun(c, rel, err) - return - } - } else if len(resources) > 0 { - if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil { - i.reportToRun(c, rel, err) - return - } - } - - if i.Wait { - if i.WaitForJobs { - if err := i.cfg.KubeClient.WaitWithJobs(resources, i.Timeout); err != nil { - i.reportToRun(c, rel, err) - return - } - } else { - if err := i.cfg.KubeClient.Wait(resources, i.Timeout); err != nil { - i.reportToRun(c, rel, err) - return - } - } - } - - if !i.DisableHooks { - if err := i.cfg.execHook(rel, release.HookPostInstall, i.Timeout); err != nil { - i.reportToRun(c, rel, fmt.Errorf("failed post-install: %s", err)) - return - } - } - - if len(i.Description) > 0 { - rel.SetStatus(release.StatusDeployed, i.Description) - } else { - rel.SetStatus(release.StatusDeployed, "Install complete") - } - - // This is a tricky case. The release has been created, but the result - // cannot be recorded. The truest thing to tell the user is that the - // release was created. However, the user will not be able to do anything - // further with this release. - // - // One possible strategy would be to do a timed retry to see if we can get - // this stored in the future. - if err := i.recordRelease(rel); err != nil { - i.cfg.Log("failed to record the release: %s", err) - } - - i.reportToRun(c, rel, nil) -} -func (i *Install) handleContext(ctx context.Context, c chan<- resultMessage, done chan struct{}, rel *release.Release) { - select { - case <-ctx.Done(): - err := ctx.Err() - i.reportToRun(c, rel, err) - case <-done: - return - } -} -func (i *Install) reportToRun(c chan<- resultMessage, rel *release.Release, err error) { - i.Lock.Lock() - if err != nil { - rel, err = i.failRelease(rel, err) - } - c <- resultMessage{r: rel, e: err} - i.Lock.Unlock() -} -func (i *Install) failRelease(rel *release.Release, err error) (*release.Release, error) { - rel.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", i.ReleaseName, err.Error())) - if i.Atomic { - i.cfg.Log("Install failed and atomic is set, uninstalling release") - uninstall := NewUninstall(i.cfg) - uninstall.DisableHooks = i.DisableHooks - uninstall.KeepHistory = false - uninstall.Timeout = i.Timeout - if _, uninstallErr := uninstall.Run(i.ReleaseName); uninstallErr != nil { - return rel, errors.Wrapf(uninstallErr, "an error occurred while uninstalling the release. original install error: %s", err) - } - return rel, errors.Wrapf(err, "release %s failed, and has been uninstalled due to atomic being set", i.ReleaseName) - } - i.recordRelease(rel) // Ignore the error, since we have another error to deal with. - return rel, err -} - -// availableName tests whether a name is available -// -// Roughly, this will return an error if name is -// -// - empty -// - too long -// - already in use, and not deleted -// - used by a deleted release, and i.Replace is false -func (i *Install) availableName() error { - start := i.ReleaseName - - if err := chartutil.ValidateReleaseName(start); err != nil { - return errors.Wrapf(err, "release name %q", start) - } - if i.DryRun { - return nil - } - - h, err := i.cfg.Releases.History(start) - if err != nil || len(h) < 1 { - return nil - } - releaseutil.Reverse(h, releaseutil.SortByRevision) - rel := h[0] - - if st := rel.Info.Status; i.Replace && (st == release.StatusUninstalled || st == release.StatusFailed) { - return nil - } - return errors.New("cannot re-use a name that is still in use") -} - -// createRelease creates a new release object -func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}) *release.Release { - ts := i.cfg.Now() - return &release.Release{ - Name: i.ReleaseName, - Namespace: i.Namespace, - Chart: chrt, - Config: rawVals, - Info: &release.Info{ - FirstDeployed: ts, - LastDeployed: ts, - Status: release.StatusUnknown, - }, - Version: 1, - } -} - -// recordRelease with an update operation in case reuse has been set. -func (i *Install) recordRelease(r *release.Release) error { - // This is a legacy function which has been reduced to a oneliner. Could probably - // refactor it out. - return i.cfg.Releases.Update(r) -} - -// replaceRelease replaces an older release with this one -// -// This allows us to re-use names by superseding an existing release with a new one -func (i *Install) replaceRelease(rel *release.Release) error { - hist, err := i.cfg.Releases.History(rel.Name) - if err != nil || len(hist) == 0 { - // No releases exist for this name, so we can return early - return nil - } - - releaseutil.Reverse(hist, releaseutil.SortByRevision) - last := hist[0] - - // Update version to the next available - rel.Version = last.Version + 1 - - // Do not change the status of a failed release. - if last.Info.Status == release.StatusFailed { - return nil - } - - // For any other status, mark it as superseded and store the old record - last.SetStatus(release.StatusSuperseded, "superseded by new release") - return i.recordRelease(last) -} - -// write the to /. controls if the file is created or content will be appended -func writeToFile(outputDir string, name string, data string, append bool) error { - outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) - - err := ensureDirectoryForFile(outfileName) - if err != nil { - return err - } - - f, err := createOrOpenFile(outfileName, append) - if err != nil { - return err - } - - defer f.Close() - - _, err = f.WriteString(fmt.Sprintf("---\n# Source: %s\n%s\n", name, data)) - - if err != nil { - return err - } - - fmt.Printf("wrote %s\n", outfileName) - return nil -} - -func createOrOpenFile(filename string, append bool) (*os.File, error) { - if append { - return os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0600) - } - return os.Create(filename) -} - -// check if the directory exists to create file. creates if don't exists -func ensureDirectoryForFile(file string) error { - baseDir := path.Dir(file) - _, err := os.Stat(baseDir) - if err != nil && !os.IsNotExist(err) { - return err - } - - return os.MkdirAll(baseDir, defaultDirectoryPermission) -} - -// NameAndChart returns the name and chart that should be used. -// -// This will read the flags and handle name generation if necessary. -func (i *Install) NameAndChart(args []string) (string, string, error) { - flagsNotSet := func() error { - if i.GenerateName { - return errors.New("cannot set --generate-name and also specify a name") - } - if i.NameTemplate != "" { - return errors.New("cannot set --name-template and also specify a name") - } - return nil - } - - if len(args) > 2 { - return args[0], args[1], errors.Errorf("expected at most two arguments, unexpected arguments: %v", strings.Join(args[2:], ", ")) - } - - if len(args) == 2 { - return args[0], args[1], flagsNotSet() - } - - if i.NameTemplate != "" { - name, err := TemplateName(i.NameTemplate) - return name, args[0], err - } - - if i.ReleaseName != "" { - return i.ReleaseName, args[0], nil - } - - if !i.GenerateName { - return "", args[0], errors.New("must either provide a name or specify --generate-name") - } - - base := filepath.Base(args[0]) - if base == "." || base == "" { - base = "chart" - } - // if present, strip out the file extension from the name - if idx := strings.Index(base, "."); idx != -1 { - base = base[0:idx] - } - - return fmt.Sprintf("%s-%d", base, time.Now().Unix()), args[0], nil -} - -// TemplateName renders a name template, returning the name or an error. -func TemplateName(nameTemplate string) (string, error) { - if nameTemplate == "" { - return "", nil - } - - t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) - if err != nil { - return "", err - } - var b bytes.Buffer - if err := t.Execute(&b, nil); err != nil { - return "", err - } - - return b.String(), nil -} - -// CheckDependencies checks the dependencies for a chart. -func CheckDependencies(ch *chart.Chart, reqs []*chart.Dependency) error { - var missing []string - -OUTER: - for _, r := range reqs { - for _, d := range ch.Dependencies() { - if d.Name() == r.Name { - continue OUTER - } - } - missing = append(missing, r.Name) - } - - if len(missing) > 0 { - return errors.Errorf("found in Chart.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) - } - return nil -} - -// LocateChart looks for a chart directory in known places, and returns either the full path or an error. -// -// This does not ensure that the chart is well-formed; only that the requested filename exists. -// -// Order of resolution: -// - relative to current working directory -// - if path is absolute or begins with '.', error out here -// - URL -// -// If 'verify' was set on ChartPathOptions, this will attempt to also verify the chart. -func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (string, error) { - // If there is no registry client and the name is in an OCI registry return - // an error and a lookup will not occur. - if registry.IsOCI(name) && c.registryClient == nil { - return "", fmt.Errorf("unable to lookup chart %q, missing registry client", name) - } - - name = strings.TrimSpace(name) - version := strings.TrimSpace(c.Version) - - if _, err := os.Stat(name); err == nil { - abs, err := filepath.Abs(name) - if err != nil { - return abs, err - } - if c.Verify { - if _, err := downloader.VerifyChart(abs, c.Keyring); err != nil { - return "", err - } - } - return abs, nil - } - if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { - return name, errors.Errorf("path %q not found", name) - } - - dl := downloader.ChartDownloader{ - Out: os.Stdout, - Keyring: c.Keyring, - Getters: getter.All(settings), - Options: []getter.Option{ - getter.WithPassCredentialsAll(c.PassCredentialsAll), - getter.WithTLSClientConfig(c.CertFile, c.KeyFile, c.CaFile), - getter.WithInsecureSkipVerifyTLS(c.InsecureSkipTLSverify), - }, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - RegistryClient: c.registryClient, - } - - if registry.IsOCI(name) { - dl.Options = append(dl.Options, getter.WithRegistryClient(c.registryClient)) - } - - if c.Verify { - dl.Verify = downloader.VerifyAlways - } - if c.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version, - c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings)) - if err != nil { - return "", err - } - name = chartURL - - // Only pass the user/pass on when the user has said to or when the - // location of the chart repo and the chart are the same domain. - u1, err := url.Parse(c.RepoURL) - if err != nil { - return "", err - } - u2, err := url.Parse(chartURL) - if err != nil { - return "", err - } - - // Host on URL (returned from url.Parse) contains the port if present. - // This check ensures credentials are not passed between different - // services on different ports. - if c.PassCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth("", "")) - } - } else { - dl.Options = append(dl.Options, getter.WithBasicAuth(c.Username, c.Password)) - } - - if err := os.MkdirAll(settings.RepositoryCache, 0755); err != nil { - return "", err - } - - filename, _, err := dl.DownloadTo(name, version, settings.RepositoryCache) - if err != nil { - return "", err - } - - lname, err := filepath.Abs(filename) - if err != nil { - return filename, err - } - return lname, nil -} diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go deleted file mode 100644 index 45e5a2670..000000000 --- a/pkg/action/install_test.go +++ /dev/null @@ -1,719 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "helm.sh/helm/v3/internal/test" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" - helmtime "helm.sh/helm/v3/pkg/time" -) - -type nameTemplateTestCase struct { - tpl string - expected string - expectedErrorStr string -} - -func installAction(t *testing.T) *Install { - config := actionConfigFixture(t) - instAction := NewInstall(config) - instAction.Namespace = "spaced" - instAction.ReleaseName = "test-install-release" - - return instAction -} - -func TestInstallRelease(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - instAction := installAction(t) - vals := map[string]interface{}{} - ctx, done := context.WithCancel(context.Background()) - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") - - // Detecting previous bug where context termination after successful release - // caused release to fail. - done() - time.Sleep(time.Millisecond * 100) - lastRelease, err := instAction.cfg.Releases.Last(rel.Name) - req.NoError(err) - is.Equal(lastRelease.Info.Status, release.StatusDeployed) -} - -func TestInstallReleaseWithValues(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - userVals := map[string]interface{}{ - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - }, - } - expectedUserValues := map[string]interface{}{ - "nestedKey": map[string]interface{}{ - "simpleKey": "simpleValue", - }, - } - res, err := instAction.Run(buildChart(withSampleValues()), userVals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - is.Equal(res.Name, "test-install-release", "Expected release name.") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal("Install complete", rel.Info.Description) - is.Equal(expectedUserValues, rel.Config) -} - -func TestInstallReleaseClientOnly(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ClientOnly = true - instAction.Run(buildChart(), nil) // disregard output - - is.Equal(instAction.cfg.Capabilities, chartutil.DefaultCapabilities) - is.Equal(instAction.cfg.KubeClient, &kubefake.PrintingKubeClient{Out: ioutil.Discard}) -} - -func TestInstallRelease_NoName(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "" - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(), vals) - if err == nil { - t.Fatal("expected failure when no name is specified") - } - assert.Contains(t, err.Error(), "no name provided") -} - -func TestInstallRelease_WithNotes(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("note here")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Equal(res.Name, "with-notes") - is.Equal(res.Namespace, "spaced") - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - is.Len(rel.Hooks, 1) - is.Equal(rel.Hooks[0].Manifest, manifestWithHook) - is.Equal(rel.Hooks[0].Events[0], release.HookPostInstall) - is.Equal(rel.Hooks[0].Events[1], release.HookPreDelete, "Expected event 0 is pre-delete") - is.NotEqual(len(res.Manifest), 0) - is.NotEqual(len(rel.Manifest), 0) - is.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Equal(rel.Info.Description, "Install complete") - - is.Equal(rel.Info.Notes, "note here") -} - -func TestInstallRelease_WithNotesRendered(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("got-{{.Release.Name}}")), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.NoError(err) - - expectedNotes := fmt.Sprintf("got-%s", res.Name) - is.Equal(expectedNotes, rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_WithChartAndDependencyParentNotes(t *testing.T) { - // Regression: Make sure that the child's notes don't override the parent's - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.Equal("with-notes", rel.Name) - is.NoError(err) - is.Equal("parent", rel.Info.Notes) - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_WithChartAndDependencyAllNotes(t *testing.T) { - // Regression: Make sure that the child's notes don't override the parent's - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "with-notes" - instAction.SubNotes = true - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withNotes("parent"), withDependency(withNotes("child"))), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - rel, err := instAction.cfg.Releases.Get(res.Name, res.Version) - is.Equal("with-notes", rel.Name) - is.NoError(err) - // test run can return as either 'parent\nchild' or 'child\nparent' - if !strings.Contains(rel.Info.Notes, "parent") && !strings.Contains(rel.Info.Notes, "child") { - t.Fatalf("Expected 'parent\nchild' or 'child\nparent', got '%s'", rel.Info.Notes) - } - is.Equal(rel.Info.Description, "Install complete") -} - -func TestInstallRelease_DryRun(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(withSampleTemplates()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Contains(res.Manifest, "---\n# Source: hello/templates/hello\nhello: world") - is.Contains(res.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") - is.Contains(res.Manifest, "hello: Earth") - is.NotContains(res.Manifest, "hello: {{ template \"_planet\" . }}") - is.NotContains(res.Manifest, "empty") - - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Len(res.Hooks, 1) - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "expect hook to not be marked as run") - is.Equal(res.Info.Description, "Dry run complete") -} - -// Regression test for #7955: Lookup must not connect to Kubernetes on a dry-run. -func TestInstallRelease_DryRun_Lookup(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - - mockChart := buildChart(withSampleTemplates()) - mockChart.Templates = append(mockChart.Templates, &chart.File{ - Name: "templates/lookup", - Data: []byte(`goodbye: {{ lookup "v1" "Namespace" "" "___" }}`), - }) - - res, err := instAction.Run(mockChart, vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.Contains(res.Manifest, "goodbye: map[]") -} - -func TestInstallReleaseIncorrectTemplate_DryRun(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DryRun = true - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withSampleIncludingIncorrectTemplates()), vals) - expectedErr := "\"hello/templates/incorrect\" at <.Values.bad.doh>: nil pointer evaluating interface {}.doh" - if err == nil { - t.Fatalf("Install should fail containing error: %s", expectedErr) - } - if err != nil { - is.Contains(err.Error(), expectedErr) - } -} - -func TestInstallRelease_NoHooks(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.DisableHooks = true - instAction.ReleaseName = "no-hooks" - instAction.cfg.Releases.Create(releaseStub()) - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - is.True(res.Hooks[0].LastRun.CompletedAt.IsZero(), "hooks should not run with no-hooks") -} - -func TestInstallRelease_FailedHooks(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "failed-hooks" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WatchUntilReadyError = fmt.Errorf("Failed watch") - instAction.cfg.KubeClient = failer - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "failed post-install") - is.Equal(release.StatusFailed, res.Info.Status) -} - -func TestInstallRelease_ReplaceRelease(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.Replace = true - - rel := releaseStub() - rel.Info.Status = release.StatusUninstalled - instAction.cfg.Releases.Create(rel) - instAction.ReleaseName = rel.Name - - vals := map[string]interface{}{} - res, err := instAction.Run(buildChart(), vals) - is.NoError(err) - - // This should have been auto-incremented - is.Equal(2, res.Version) - is.Equal(res.Name, rel.Name) - - getres, err := instAction.cfg.Releases.Get(rel.Name, res.Version) - is.NoError(err) - is.Equal(getres.Info.Status, release.StatusDeployed) -} - -func TestInstallRelease_KubeVersion(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - _, err := instAction.Run(buildChart(withKube(">=0.0.0")), vals) - is.NoError(err) - - // This should fail for a few hundred years - instAction.ReleaseName = "should-fail" - vals = map[string]interface{}{} - _, err = instAction.Run(buildChart(withKube(">=99.0.0")), vals) - is.Error(err) - is.Contains(err.Error(), "chart requires kubeVersion") -} - -func TestInstallRelease_Wait(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Wait = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} -func TestInstallRelease_Wait_Interrupted(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "interrupted-release" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - instAction.cfg.KubeClient = failer - instAction.Wait = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "Release \"interrupted-release\" failed: context canceled") - is.Equal(res.Info.Status, release.StatusFailed) -} -func TestInstallRelease_WaitForJobs(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Wait = true - instAction.WaitForJobs = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestInstallRelease_Atomic(t *testing.T) { - is := assert.New(t) - - t.Run("atomic uninstall succeeds", func(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - res, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "atomic") - - // Now make sure it isn't in storage any more - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Equal(err, driver.ErrReleaseNotFound) - }) - - t.Run("atomic uninstall fails", func(t *testing.T) { - instAction := installAction(t) - instAction.ReleaseName = "come-fail-away-with-me" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("uninstall fail") - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - _, err := instAction.Run(buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "I timed out") - is.Contains(err.Error(), "uninstall fail") - is.Contains(err.Error(), "an error occurred while uninstalling the release") - }) -} -func TestInstallRelease_Atomic_Interrupted(t *testing.T) { - - is := assert.New(t) - instAction := installAction(t) - instAction.ReleaseName = "interrupted-release" - failer := instAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - instAction.cfg.KubeClient = failer - instAction.Atomic = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := instAction.RunWithContext(ctx, buildChart(), vals) - is.Error(err) - is.Contains(err.Error(), "context canceled") - is.Contains(err.Error(), "atomic") - is.Contains(err.Error(), "uninstalled") - - // Now make sure it isn't in storage any more - _, err = instAction.cfg.Releases.Get(res.Name, res.Version) - is.Error(err) - is.Equal(err, driver.ErrReleaseNotFound) - -} -func TestNameTemplate(t *testing.T) { - testCases := []nameTemplateTestCase{ - // Just a straight up nop please - { - tpl: "foobar", - expected: "foobar", - expectedErrorStr: "", - }, - // Random numbers at the end for fun & profit - { - tpl: "foobar-{{randNumeric 6}}", - expected: "foobar-[0-9]{6}$", - expectedErrorStr: "", - }, - // Random numbers in the middle for fun & profit - { - tpl: "foobar-{{randNumeric 4}}-baz", - expected: "foobar-[0-9]{4}-baz$", - expectedErrorStr: "", - }, - // No such function - { - tpl: "foobar-{{randInteger}}", - expected: "", - expectedErrorStr: "function \"randInteger\" not defined", - }, - // Invalid template - { - tpl: "foobar-{{", - expected: "", - expectedErrorStr: "template: name-template:1: unclosed action", - }, - } - - for _, tc := range testCases { - - n, err := TemplateName(tc.tpl) - if err != nil { - if tc.expectedErrorStr == "" { - t.Errorf("Was not expecting error, but got: %v", err) - continue - } - re, compErr := regexp.Compile(tc.expectedErrorStr) - if compErr != nil { - t.Errorf("Expected error string failed to compile: %v", compErr) - continue - } - if !re.MatchString(err.Error()) { - t.Errorf("Error didn't match for %s expected %s but got %v", tc.tpl, tc.expectedErrorStr, err) - continue - } - } - if err == nil && tc.expectedErrorStr != "" { - t.Errorf("Was expecting error %s but didn't get an error back", tc.expectedErrorStr) - } - - if tc.expected != "" { - re, err := regexp.Compile(tc.expected) - if err != nil { - t.Errorf("Expected string failed to compile: %v", err) - continue - } - if !re.MatchString(n) { - t.Errorf("Returned name didn't match for %s expected %s but got %s", tc.tpl, tc.expected, n) - } - } - } -} - -func TestInstallReleaseOutputDir(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(dir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(dir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(dir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(dir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - -func TestInstallOutputDirWithReleaseName(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - vals := map[string]interface{}{} - - dir := t.TempDir() - - instAction.OutputDir = dir - instAction.UseReleaseName = true - instAction.ReleaseName = "madra" - - newDir := filepath.Join(dir, instAction.ReleaseName) - - _, err := instAction.Run(buildChart(withSampleTemplates(), withMultipleManifestTemplate()), vals) - if err != nil { - t.Fatalf("Failed install: %s", err) - } - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/goodbye")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/hello")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/with-partials")) - is.NoError(err) - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/rbac")) - is.NoError(err) - - test.AssertGoldenFile(t, filepath.Join(newDir, "hello/templates/rbac"), "rbac.txt") - - _, err = os.Stat(filepath.Join(newDir, "hello/templates/empty")) - is.True(os.IsNotExist(err)) -} - -func TestNameAndChart(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - chartName := "./foo" - - name, chrt, err := instAction.NameAndChart([]string{chartName}) - if err != nil { - t.Fatal(err) - } - is.Equal(instAction.ReleaseName, name) - is.Equal(chartName, chrt) - - instAction.GenerateName = true - _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --generate-name and also specify a name", err.Error()) - - instAction.GenerateName = false - instAction.NameTemplate = "{{ . }}" - _, _, err = instAction.NameAndChart([]string{"foo", chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("cannot set --name-template and also specify a name", err.Error()) - - instAction.NameTemplate = "" - instAction.ReleaseName = "" - _, _, err = instAction.NameAndChart([]string{chartName}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("must either provide a name or specify --generate-name", err.Error()) - - instAction.NameTemplate = "" - instAction.ReleaseName = "" - _, _, err = instAction.NameAndChart([]string{"foo", chartName, "bar"}) - if err == nil { - t.Fatal("expected an error") - } - is.Equal("expected at most two arguments, unexpected arguments: bar", err.Error()) -} - -func TestNameAndChartGenerateName(t *testing.T) { - is := assert.New(t) - instAction := installAction(t) - - instAction.ReleaseName = "" - instAction.GenerateName = true - - tests := []struct { - Name string - Chart string - ExpectedName string - }{ - { - "local filepath", - "./chart", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "dot filepath", - ".", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "empty filepath", - "", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart", - "chart.tgz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart with .tar.gz extension", - "chart.tar.gz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - { - "packaged chart with local extension", - "./chart.tgz", - fmt.Sprintf("chart-%d", helmtime.Now().Unix()), - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - - name, chrt, err := instAction.NameAndChart([]string{tc.Chart}) - if err != nil { - t.Fatal(err) - } - - is.Equal(tc.ExpectedName, name) - is.Equal(tc.Chart, chrt) - }) - } -} diff --git a/pkg/action/lazyclient.go b/pkg/action/lazyclient.go deleted file mode 100644 index 9037782bb..000000000 --- a/pkg/action/lazyclient.go +++ /dev/null @@ -1,197 +0,0 @@ -/* -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 action - -import ( - "context" - "sync" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/watch" - applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" - "k8s.io/client-go/kubernetes" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" -) - -// lazyClient is a workaround to deal with Kubernetes having an unstable client API. -// In Kubernetes v1.18 the defaults where removed which broke creating a -// client without an explicit configuration. ಠ_ಠ -type lazyClient struct { - // client caches an initialized kubernetes client - initClient sync.Once - client kubernetes.Interface - clientErr error - - // clientFn loads a kubernetes client - clientFn func() (*kubernetes.Clientset, error) - - // namespace passed to each client request - namespace string -} - -func (s *lazyClient) init() error { - s.initClient.Do(func() { - s.client, s.clientErr = s.clientFn() - }) - return s.clientErr -} - -// secretClient implements a corev1.SecretsInterface -type secretClient struct{ *lazyClient } - -var _ corev1.SecretInterface = (*secretClient)(nil) - -func newSecretClient(lc *lazyClient) *secretClient { - return &secretClient{lazyClient: lc} -} - -func (s *secretClient) Create(ctx context.Context, secret *v1.Secret, opts metav1.CreateOptions) (result *v1.Secret, err error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Create(ctx, secret, opts) -} - -func (s *secretClient) Update(ctx context.Context, secret *v1.Secret, opts metav1.UpdateOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Update(ctx, secret, opts) -} - -func (s *secretClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - if err := s.init(); err != nil { - return err - } - return s.client.CoreV1().Secrets(s.namespace).Delete(ctx, name, opts) -} - -func (s *secretClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - if err := s.init(); err != nil { - return err - } - return s.client.CoreV1().Secrets(s.namespace).DeleteCollection(ctx, opts, listOpts) -} - -func (s *secretClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Get(ctx, name, opts) -} - -func (s *secretClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).List(ctx, opts) -} - -func (s *secretClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Watch(ctx, opts) -} - -func (s *secretClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Patch(ctx, name, pt, data, opts, subresources...) -} - -func (s *secretClient) Apply(ctx context.Context, secretConfiguration *applycorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (*v1.Secret, error) { - if err := s.init(); err != nil { - return nil, err - } - return s.client.CoreV1().Secrets(s.namespace).Apply(ctx, secretConfiguration, opts) -} - -// configMapClient implements a corev1.ConfigMapInterface -type configMapClient struct{ *lazyClient } - -var _ corev1.ConfigMapInterface = (*configMapClient)(nil) - -func newConfigMapClient(lc *lazyClient) *configMapClient { - return &configMapClient{lazyClient: lc} -} - -func (c *configMapClient) Create(ctx context.Context, configMap *v1.ConfigMap, opts metav1.CreateOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Create(ctx, configMap, opts) -} - -func (c *configMapClient) Update(ctx context.Context, configMap *v1.ConfigMap, opts metav1.UpdateOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Update(ctx, configMap, opts) -} - -func (c *configMapClient) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - if err := c.init(); err != nil { - return err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Delete(ctx, name, opts) -} - -func (c *configMapClient) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - if err := c.init(); err != nil { - return err - } - return c.client.CoreV1().ConfigMaps(c.namespace).DeleteCollection(ctx, opts, listOpts) -} - -func (c *configMapClient) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Get(ctx, name, opts) -} - -func (c *configMapClient) List(ctx context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).List(ctx, opts) -} - -func (c *configMapClient) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Watch(ctx, opts) -} - -func (c *configMapClient) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Patch(ctx, name, pt, data, opts, subresources...) -} - -func (c *configMapClient) Apply(ctx context.Context, configMap *applycorev1.ConfigMapApplyConfiguration, opts metav1.ApplyOptions) (*v1.ConfigMap, error) { - if err := c.init(); err != nil { - return nil, err - } - return c.client.CoreV1().ConfigMaps(c.namespace).Apply(ctx, configMap, opts) -} diff --git a/pkg/action/lint.go b/pkg/action/lint.go deleted file mode 100644 index 5b566e9d3..000000000 --- a/pkg/action/lint.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -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 action - -import ( - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Lint is the action for checking that the semantics of a chart are well-formed. -// -// It provides the implementation of 'helm lint'. -type Lint struct { - Strict bool - Namespace string - WithSubcharts bool - Quiet bool -} - -// LintResult is the result of Lint -type LintResult struct { - TotalChartsLinted int - Messages []support.Message - Errors []error -} - -// NewLint creates a new Lint object with the given configuration. -func NewLint() *Lint { - return &Lint{} -} - -// Run executes 'helm Lint' against the given chart. -func (l *Lint) Run(paths []string, vals map[string]interface{}) *LintResult { - lowestTolerance := support.ErrorSev - if l.Strict { - lowestTolerance = support.WarningSev - } - result := &LintResult{} - for _, path := range paths { - linter, err := lintChart(path, vals, l.Namespace, l.Strict) - if err != nil { - result.Errors = append(result.Errors, err) - continue - } - - result.Messages = append(result.Messages, linter.Messages...) - result.TotalChartsLinted++ - for _, msg := range linter.Messages { - if msg.Severity >= lowestTolerance { - result.Errors = append(result.Errors, msg.Err) - } - } - } - return result -} - -// HasWaringsOrErrors checks is LintResult has any warnings or errors -func HasWarningsOrErrors(result *LintResult) bool { - for _, msg := range result.Messages { - if msg.Severity > support.InfoSev { - return true - } - } - return false -} - -func lintChart(path string, vals map[string]interface{}, namespace string, strict bool) (support.Linter, error) { - var chartPath string - linter := support.Linter{} - - if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { - tempDir, err := ioutil.TempDir("", "helm-lint") - if err != nil { - return linter, errors.Wrap(err, "unable to create temp dir to extract tarball") - } - defer os.RemoveAll(tempDir) - - file, err := os.Open(path) - if err != nil { - return linter, errors.Wrap(err, "unable to open tarball") - } - defer file.Close() - - if err = chartutil.Expand(tempDir, file); err != nil { - return linter, errors.Wrap(err, "unable to extract tarball") - } - - files, err := os.ReadDir(tempDir) - if err != nil { - return linter, errors.Wrapf(err, "unable to read temporary output directory %s", tempDir) - } - if !files[0].IsDir() { - return linter, errors.Errorf("unexpected file %s in temporary output directory %s", files[0].Name(), tempDir) - } - - chartPath = filepath.Join(tempDir, files[0].Name()) - } else { - chartPath = path - } - - // Guard: Error out if this is not a chart. - if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { - return linter, errors.Wrap(err, "unable to check Chart.yaml file in chart") - } - - return lint.All(chartPath, vals, namespace, strict), nil -} diff --git a/pkg/action/lint_test.go b/pkg/action/lint_test.go deleted file mode 100644 index 1828461f3..000000000 --- a/pkg/action/lint_test.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -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 action - -import ( - "testing" -) - -var ( - values = make(map[string]interface{}) - namespace = "testNamespace" - strict = false - chart1MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-1" - chart2MultipleChartLint = "testdata/charts/multiplecharts-lint-chart-2" - corruptedTgzChart = "testdata/charts/corrupted-compressed-chart.tgz" - chartWithNoTemplatesDir = "testdata/charts/chart-with-no-templates-dir" -) - -func TestLintChart(t *testing.T) { - tests := []struct { - name string - chartPath string - err bool - }{ - { - name: "decompressed-chart", - chartPath: "testdata/charts/decompressedchart/", - }, - { - name: "archived-chart-path", - chartPath: "testdata/charts/compressedchart-0.1.0.tgz", - }, - { - name: "archived-chart-path-with-hyphens", - chartPath: "testdata/charts/compressedchart-with-hyphens-0.1.0.tgz", - }, - { - name: "archived-tar-gz-chart-path", - chartPath: "testdata/charts/compressedchart-0.1.0.tar.gz", - }, - { - name: "invalid-archived-chart-path", - chartPath: "testdata/charts/invalidcompressedchart0.1.0.tgz", - err: true, - }, - { - name: "chart-missing-manifest", - chartPath: "testdata/charts/chart-missing-manifest", - err: true, - }, - { - name: "chart-with-schema", - chartPath: "testdata/charts/chart-with-schema", - }, - { - name: "chart-with-schema-negative", - chartPath: "testdata/charts/chart-with-schema-negative", - }, - { - name: "pre-release-chart", - chartPath: "testdata/charts/pre-release-chart-0.1.0-alpha.tgz", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := lintChart(tt.chartPath, map[string]interface{}{}, namespace, strict) - switch { - case err != nil && !tt.err: - t.Errorf("%s", err) - case err == nil && tt.err: - t.Errorf("Expected a chart parsing error") - } - }) - } -} - -func TestNonExistentChart(t *testing.T) { - t.Run("should error out for non existent tgz chart", func(t *testing.T) { - testCharts := []string{"non-existent-chart.tgz"} - expectedError := "unable to open tarball: open non-existent-chart.tgz: no such file or directory" - testLint := NewLint() - - result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - - actual := result.Errors[0].Error() - if actual != expectedError { - t.Errorf("expected '%s', but got '%s'", expectedError, actual) - } - }) - - t.Run("should error out for corrupted tgz chart", func(t *testing.T) { - testCharts := []string{corruptedTgzChart} - expectedEOFError := "unable to extract tarball: EOF" - testLint := NewLint() - - result := testLint.Run(testCharts, values) - if len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - - actual := result.Errors[0].Error() - if actual != expectedEOFError { - t.Errorf("expected '%s', but got '%s'", expectedEOFError, actual) - } - }) -} - -func TestLint_MultipleCharts(t *testing.T) { - testCharts := []string{chart2MultipleChartLint, chart1MultipleChartLint} - testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error(result.Errors) - } -} - -func TestLint_EmptyResultErrors(t *testing.T) { - testCharts := []string{chart2MultipleChartLint} - testLint := NewLint() - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } -} - -func TestLint_ChartWithWarnings(t *testing.T) { - t.Run("should pass when not strict", func(t *testing.T) { - testCharts := []string{chartWithNoTemplatesDir} - testLint := NewLint() - testLint.Strict = false - if result := testLint.Run(testCharts, values); len(result.Errors) > 0 { - t.Error("Expected no error, got more") - } - }) - - t.Run("should fail with errors when strict", func(t *testing.T) { - testCharts := []string{chartWithNoTemplatesDir} - testLint := NewLint() - testLint.Strict = true - if result := testLint.Run(testCharts, values); len(result.Errors) != 1 { - t.Error("expected one error, but got", len(result.Errors)) - } - }) -} diff --git a/pkg/action/list.go b/pkg/action/list.go deleted file mode 100644 index af0725c4a..000000000 --- a/pkg/action/list.go +++ /dev/null @@ -1,324 +0,0 @@ -/* -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 action - -import ( - "path" - "regexp" - - "k8s.io/apimachinery/pkg/labels" - - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" -) - -// ListStates represents zero or more status codes that a list item may have set -// -// Because this is used as a bitmask filter, more than one bit can be flipped -// in the ListStates. -type ListStates uint - -const ( - // ListDeployed filters on status "deployed" - ListDeployed ListStates = 1 << iota - // ListUninstalled filters on status "uninstalled" - ListUninstalled - // ListUninstalling filters on status "uninstalling" (uninstall in progress) - ListUninstalling - // ListPendingInstall filters on status "pending" (deployment in progress) - ListPendingInstall - // ListPendingUpgrade filters on status "pending_upgrade" (upgrade in progress) - ListPendingUpgrade - // ListPendingRollback filters on status "pending_rollback" (rollback in progress) - ListPendingRollback - // ListSuperseded filters on status "superseded" (historical release version that is no longer deployed) - ListSuperseded - // ListFailed filters on status "failed" (release version not deployed because of error) - ListFailed - // ListUnknown filters on an unknown status - ListUnknown -) - -// FromName takes a state name and returns a ListStates representation. -// -// Currently, there are only names for individual flipped bits, so the returned -// ListStates will only match one of the constants. However, it is possible that -// this behavior could change in the future. -func (s ListStates) FromName(str string) ListStates { - switch str { - case "deployed": - return ListDeployed - case "uninstalled": - return ListUninstalled - case "superseded": - return ListSuperseded - case "failed": - return ListFailed - case "uninstalling": - return ListUninstalling - case "pending-install": - return ListPendingInstall - case "pending-upgrade": - return ListPendingUpgrade - case "pending-rollback": - return ListPendingRollback - } - return ListUnknown -} - -// ListAll is a convenience for enabling all list filters -const ListAll = ListDeployed | ListUninstalled | ListUninstalling | ListPendingInstall | ListPendingRollback | ListPendingUpgrade | ListSuperseded | ListFailed - -// Sorter is a top-level sort -type Sorter uint - -const ( - // ByNameDesc sorts by descending lexicographic order - ByNameDesc Sorter = iota + 1 - // ByDateAsc sorts by ascending dates (oldest updated release first) - ByDateAsc - // ByDateDesc sorts by descending dates (latest updated release first) - ByDateDesc -) - -// List is the action for listing releases. -// -// It provides, for example, the implementation of 'helm list'. -// It returns no more than one revision of every release in one specific, or in -// all, namespaces. -// To list all the revisions of a specific release, see the History action. -type List struct { - cfg *Configuration - - // All ignores the limit/offset - All bool - // AllNamespaces searches across namespaces - AllNamespaces bool - // Sort indicates the sort to use - // - // see pkg/releaseutil for several useful sorters - Sort Sorter - // Overrides the default lexicographic sorting - ByDate bool - SortReverse bool - // StateMask accepts a bitmask of states for items to show. - // The default is ListDeployed - StateMask ListStates - // Limit is the number of items to return per Run() - Limit int - // Offset is the starting index for the Run() call - Offset int - // Filter is a filter that is applied to the results - Filter string - Short bool - NoHeaders bool - TimeFormat string - Uninstalled bool - Superseded bool - Uninstalling bool - Deployed bool - Failed bool - Pending bool - Selector string -} - -// NewList constructs a new *List -func NewList(cfg *Configuration) *List { - return &List{ - StateMask: ListDeployed | ListFailed, - cfg: cfg, - } -} - -// Run executes the list command, returning a set of matches. -func (l *List) Run() ([]*release.Release, error) { - if err := l.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - var filter *regexp.Regexp - if l.Filter != "" { - var err error - filter, err = regexp.Compile(l.Filter) - if err != nil { - return nil, err - } - } - - results, err := l.cfg.Releases.List(func(rel *release.Release) bool { - // Skip anything that doesn't match the filter. - if filter != nil && !filter.MatchString(rel.Name) { - return false - } - - return true - }) - - if err != nil { - return nil, err - } - - if results == nil { - return results, nil - } - - // by definition, superseded releases are never shown if - // only the latest releases are returned. so if requested statemask - // is _only_ ListSuperseded, skip the latest release filter - if l.StateMask != ListSuperseded { - results = filterLatestReleases(results) - } - - // State mask application must occur after filtering to - // latest releases, otherwise outdated entries can be returned - results = l.filterStateMask(results) - - // Skip anything that doesn't match the selector - selectorObj, err := labels.Parse(l.Selector) - if err != nil { - return nil, err - } - results = l.filterSelector(results, selectorObj) - - // Unfortunately, we have to sort before truncating, which can incur substantial overhead - l.sort(results) - - // Guard on offset - if l.Offset >= len(results) { - return []*release.Release{}, nil - } - - // Calculate the limit and offset, and then truncate results if necessary. - limit := len(results) - if l.Limit > 0 && l.Limit < limit { - limit = l.Limit - } - last := l.Offset + limit - if l := len(results); l < last { - last = l - } - results = results[l.Offset:last] - - return results, err -} - -// sort is an in-place sort where order is based on the value of a.Sort -func (l *List) sort(rels []*release.Release) { - if l.SortReverse { - l.Sort = ByNameDesc - } - - if l.ByDate { - l.Sort = ByDateDesc - if l.SortReverse { - l.Sort = ByDateAsc - } - } - - switch l.Sort { - case ByDateDesc: - releaseutil.SortByDate(rels) - case ByDateAsc: - releaseutil.Reverse(rels, releaseutil.SortByDate) - case ByNameDesc: - releaseutil.Reverse(rels, releaseutil.SortByName) - default: - releaseutil.SortByName(rels) - } -} - -// filterLatestReleases returns a list scrubbed of old releases. -func filterLatestReleases(releases []*release.Release) []*release.Release { - latestReleases := make(map[string]*release.Release) - - for _, rls := range releases { - name, namespace := rls.Name, rls.Namespace - key := path.Join(namespace, name) - if latestRelease, exists := latestReleases[key]; exists && latestRelease.Version > rls.Version { - continue - } - latestReleases[key] = rls - } - - var list = make([]*release.Release, 0, len(latestReleases)) - for _, rls := range latestReleases { - list = append(list, rls) - } - return list -} - -func (l *List) filterStateMask(releases []*release.Release) []*release.Release { - desiredStateReleases := make([]*release.Release, 0) - - for _, rls := range releases { - currentStatus := l.StateMask.FromName(rls.Info.Status.String()) - mask := l.StateMask & currentStatus - if mask == 0 { - continue - } - desiredStateReleases = append(desiredStateReleases, rls) - } - - return desiredStateReleases -} - -func (l *List) filterSelector(releases []*release.Release, selector labels.Selector) []*release.Release { - desiredStateReleases := make([]*release.Release, 0) - - for _, rls := range releases { - if selector.Matches(labels.Set(rls.Labels)) { - desiredStateReleases = append(desiredStateReleases, rls) - } - } - - return desiredStateReleases -} - -// SetStateMask calculates the state mask based on parameters. -func (l *List) SetStateMask() { - if l.All { - l.StateMask = ListAll - return - } - - state := ListStates(0) - if l.Deployed { - state |= ListDeployed - } - if l.Uninstalled { - state |= ListUninstalled - } - if l.Uninstalling { - state |= ListUninstalling - } - if l.Pending { - state |= ListPendingInstall | ListPendingRollback | ListPendingUpgrade - } - if l.Failed { - state |= ListFailed - } - if l.Superseded { - state |= ListSuperseded - } - - // Apply a default - if state == 0 { - state = ListDeployed | ListFailed - } - - l.StateMask = state -} diff --git a/pkg/action/list_test.go b/pkg/action/list_test.go deleted file mode 100644 index 73009d523..000000000 --- a/pkg/action/list_test.go +++ /dev/null @@ -1,368 +0,0 @@ -/* -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 action - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage" -) - -func TestListStates(t *testing.T) { - for input, expect := range map[string]ListStates{ - "deployed": ListDeployed, - "uninstalled": ListUninstalled, - "uninstalling": ListUninstalling, - "superseded": ListSuperseded, - "failed": ListFailed, - "pending-install": ListPendingInstall, - "pending-rollback": ListPendingRollback, - "pending-upgrade": ListPendingUpgrade, - "unknown": ListUnknown, - "totally made up key": ListUnknown, - } { - if expect != expect.FromName(input) { - t.Errorf("Expected %d for %s", expect, input) - } - // This is a cheap way to verify that ListAll actually allows everything but Unknown - if got := expect.FromName(input); got != ListUnknown && got&ListAll == 0 { - t.Errorf("Expected %s to match the ListAll filter", input) - } - } - - filter := ListDeployed | ListPendingRollback - if status := filter.FromName("deployed"); filter&status == 0 { - t.Errorf("Expected %d to match mask %d", status, filter) - } - if status := filter.FromName("failed"); filter&status != 0 { - t.Errorf("Expected %d to fail to match mask %d", status, filter) - } -} - -func TestList_Empty(t *testing.T) { - lister := NewList(actionConfigFixture(t)) - list, err := lister.Run() - assert.NoError(t, err) - assert.Len(t, list, 0) -} - -func newListFixture(t *testing.T) *List { - return NewList(actionConfigFixture(t)) -} - -func TestList_OneNamespace(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) -} - -func TestList_AllNamespaces(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - lister.AllNamespaces = true - lister.SetStateMask() - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) -} - -func TestList_Sort(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Sort = ByNameDesc // Other sorts are tested elsewhere - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) - is.Equal("two", list[0].Name) - is.Equal("three", list[1].Name) - is.Equal("one", list[2].Name) -} - -func TestList_Limit(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 2) - // Lex order means one, three, two - is.Equal("one", list[0].Name) - is.Equal("three", list[1].Name) -} - -func TestList_BigLimit(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 20 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 3) - - // Lex order means one, three, two - is.Equal("one", list[0].Name) - is.Equal("three", list[1].Name) - is.Equal("two", list[2].Name) -} - -func TestList_LimitOffset(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - lister.Offset = 1 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 2) - - // Lex order means one, three, two - is.Equal("three", list[0].Name) - is.Equal("two", list[1].Name) -} - -func TestList_LimitOffsetOutOfBounds(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Limit = 2 - lister.Offset = 3 // Last item is index 2 - makeMeSomeReleases(lister.cfg.Releases, t) - list, err := lister.Run() - is.NoError(err) - is.Len(list, 0) - - lister.Limit = 10 - lister.Offset = 1 - list, err = lister.Run() - is.NoError(err) - is.Len(list, 2) -} - -func TestList_StateMask(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - makeMeSomeReleases(lister.cfg.Releases, t) - one, err := lister.cfg.Releases.Get("one", 1) - is.NoError(err) - one.SetStatus(release.StatusUninstalled, "uninstalled") - err = lister.cfg.Releases.Update(one) - is.NoError(err) - - res, err := lister.Run() - is.NoError(err) - is.Len(res, 2) - is.Equal("three", res[0].Name) - is.Equal("two", res[1].Name) - - lister.StateMask = ListUninstalled - res, err = lister.Run() - is.NoError(err) - is.Len(res, 1) - is.Equal("one", res[0].Name) - - lister.StateMask |= ListDeployed - res, err = lister.Run() - is.NoError(err) - is.Len(res, 3) -} - -func TestList_StateMaskWithStaleRevisions(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.StateMask = ListFailed - - makeMeSomeReleasesWithStaleFailure(lister.cfg.Releases, t) - - res, err := lister.Run() - - is.NoError(err) - is.Len(res, 1) - - // "dirty" release should _not_ be present as most recent - // release is deployed despite failed release in past - is.Equal("failed", res[0].Name) -} - -func makeMeSomeReleasesWithStaleFailure(store *storage.Storage, t *testing.T) { - t.Helper() - one := namedReleaseStub("clean", release.StatusDeployed) - one.Namespace = "default" - one.Version = 1 - - two := namedReleaseStub("dirty", release.StatusDeployed) - two.Namespace = "default" - two.Version = 1 - - three := namedReleaseStub("dirty", release.StatusFailed) - three.Namespace = "default" - three.Version = 2 - - four := namedReleaseStub("dirty", release.StatusDeployed) - four.Namespace = "default" - four.Version = 3 - - five := namedReleaseStub("failed", release.StatusFailed) - five.Namespace = "default" - five.Version = 1 - - for _, rel := range []*release.Release{one, two, three, four, five} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } - } - - all, err := store.ListReleases() - assert.NoError(t, err) - assert.Len(t, all, 5, "sanity test: five items added") -} - -func TestList_Filter(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Filter = "th." - makeMeSomeReleases(lister.cfg.Releases, t) - - res, err := lister.Run() - is.NoError(err) - is.Len(res, 1) - is.Equal("three", res[0].Name) -} - -func TestList_FilterFailsCompile(t *testing.T) { - is := assert.New(t) - lister := newListFixture(t) - lister.Filter = "t[h.{{{" - makeMeSomeReleases(lister.cfg.Releases, t) - - _, err := lister.Run() - is.Error(err) -} - -func makeMeSomeReleases(store *storage.Storage, t *testing.T) { - t.Helper() - one := releaseStub() - one.Name = "one" - one.Namespace = "default" - one.Version = 1 - two := releaseStub() - two.Name = "two" - two.Namespace = "default" - two.Version = 2 - three := releaseStub() - three.Name = "three" - three.Namespace = "default" - three.Version = 3 - - for _, rel := range []*release.Release{one, two, three} { - if err := store.Create(rel); err != nil { - t.Fatal(err) - } - } - - all, err := store.ListReleases() - assert.NoError(t, err) - assert.Len(t, all, 3, "sanity test: three items added") -} - -func TestFilterLatestReleases(t *testing.T) { - t.Run("should filter old versions of the same release", func(t *testing.T) { - r1 := releaseStub() - r1.Name = "r" - r1.Version = 1 - r2 := releaseStub() - r2.Name = "r" - r2.Version = 2 - another := releaseStub() - another.Name = "another" - another.Version = 1 - - filteredList := filterLatestReleases([]*release.Release{r1, r2, another}) - expectedFilteredList := []*release.Release{r2, another} - - assert.ElementsMatch(t, expectedFilteredList, filteredList) - }) - - t.Run("should not filter out any version across namespaces", func(t *testing.T) { - r1 := releaseStub() - r1.Name = "r" - r1.Namespace = "default" - r1.Version = 1 - r2 := releaseStub() - r2.Name = "r" - r2.Namespace = "testing" - r2.Version = 2 - - filteredList := filterLatestReleases([]*release.Release{r1, r2}) - expectedFilteredList := []*release.Release{r1, r2} - - assert.ElementsMatch(t, expectedFilteredList, filteredList) - }) -} - -func TestSelectorList(t *testing.T) { - r1 := releaseStub() - r1.Name = "r1" - r1.Version = 1 - r1.Labels = map[string]string{"key": "value1"} - r2 := releaseStub() - r2.Name = "r2" - r2.Version = 1 - r2.Labels = map[string]string{"key": "value2"} - r3 := releaseStub() - r3.Name = "r3" - r3.Version = 1 - r3.Labels = map[string]string{} - - lister := newListFixture(t) - for _, rel := range []*release.Release{r1, r2, r3} { - if err := lister.cfg.Releases.Create(rel); err != nil { - t.Fatal(err) - } - } - - t.Run("should fail selector parsing", func(t *testing.T) { - is := assert.New(t) - lister.Selector = "a?=b" - - _, err := lister.Run() - is.Error(err) - }) - - t.Run("should select one release with matching label", func(t *testing.T) { - lister.Selector = "key==value1" - res, _ := lister.Run() - - expectedFilteredList := []*release.Release{r1} - assert.ElementsMatch(t, expectedFilteredList, res) - }) - - t.Run("should select two releases with non matching label", func(t *testing.T) { - lister.Selector = "key!=value1" - res, _ := lister.Run() - - expectedFilteredList := []*release.Release{r2, r3} - assert.ElementsMatch(t, expectedFilteredList, res) - }) -} diff --git a/pkg/action/package.go b/pkg/action/package.go deleted file mode 100644 index 52920956f..000000000 --- a/pkg/action/package.go +++ /dev/null @@ -1,182 +0,0 @@ -/* -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 action - -import ( - "bufio" - "fmt" - "io/ioutil" - "os" - "syscall" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "golang.org/x/term" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/provenance" -) - -// Package is the action for packaging a chart. -// -// It provides the implementation of 'helm package'. -type Package struct { - Sign bool - Key string - Keyring string - PassphraseFile string - Version string - AppVersion string - Destination string - DependencyUpdate bool - - RepositoryConfig string - RepositoryCache string -} - -// NewPackage creates a new Package object with the given configuration. -func NewPackage() *Package { - return &Package{} -} - -// Run executes 'helm package' against the given chart and returns the path to the packaged chart. -func (p *Package) Run(path string, vals map[string]interface{}) (string, error) { - ch, err := loader.LoadDir(path) - if err != nil { - return "", err - } - - // If version is set, modify the version. - if p.Version != "" { - ch.Metadata.Version = p.Version - } - - if err := validateVersion(ch.Metadata.Version); err != nil { - return "", err - } - - if p.AppVersion != "" { - ch.Metadata.AppVersion = p.AppVersion - } - - if reqs := ch.Metadata.Dependencies; reqs != nil { - if err := CheckDependencies(ch, reqs); err != nil { - return "", err - } - } - - var dest string - if p.Destination == "." { - // Save to the current working directory. - dest, err = os.Getwd() - if err != nil { - return "", err - } - } else { - // Otherwise save to set destination - dest = p.Destination - } - - name, err := chartutil.Save(ch, dest) - if err != nil { - return "", errors.Wrap(err, "failed to save") - } - - if p.Sign { - err = p.Clearsign(name) - } - - return name, err -} - -// validateVersion Verify that version is a Version, and error out if it is not. -func validateVersion(ver string) error { - if _, err := semver.NewVersion(ver); err != nil { - return err - } - return nil -} - -// Clearsign signs a chart -func (p *Package) Clearsign(filename string) error { - // Load keyring - signer, err := provenance.NewFromKeyring(p.Keyring, p.Key) - if err != nil { - return err - } - - passphraseFetcher := promptUser - if p.PassphraseFile != "" { - passphraseFetcher, err = passphraseFileFetcher(p.PassphraseFile, os.Stdin) - if err != nil { - return err - } - } - - if err := signer.DecryptKey(passphraseFetcher); err != nil { - return err - } - - sig, err := signer.ClearSign(filename) - if err != nil { - return err - } - - return ioutil.WriteFile(filename+".prov", []byte(sig), 0644) -} - -// promptUser implements provenance.PassphraseFetcher -func promptUser(name string) ([]byte, error) { - fmt.Printf("Password for key %q > ", name) - // syscall.Stdin is not an int in all environments and needs to be coerced - // into one there (e.g., Windows) - pw, err := term.ReadPassword(int(syscall.Stdin)) - fmt.Println() - return pw, err -} - -func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.PassphraseFetcher, error) { - file, err := openPassphraseFile(passphraseFile, stdin) - if err != nil { - return nil, err - } - defer file.Close() - - reader := bufio.NewReader(file) - passphrase, _, err := reader.ReadLine() - if err != nil { - return nil, err - } - return func(name string) ([]byte, error) { - return passphrase, nil - }, nil -} - -func openPassphraseFile(passphraseFile string, stdin *os.File) (*os.File, error) { - if passphraseFile == "-" { - stat, err := stdin.Stat() - if err != nil { - return nil, err - } - if (stat.Mode() & os.ModeNamedPipe) == 0 { - return nil, errors.New("specified reading passphrase from stdin, without input on stdin") - } - return stdin, nil - } - return os.Open(passphraseFile) -} diff --git a/pkg/action/package_test.go b/pkg/action/package_test.go deleted file mode 100644 index 5c5fed571..000000000 --- a/pkg/action/package_test.go +++ /dev/null @@ -1,125 +0,0 @@ -/* -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 action - -import ( - "io/ioutil" - "os" - "path" - "testing" - - "github.com/Masterminds/semver/v3" - - "helm.sh/helm/v3/internal/test/ensure" -) - -func TestPassphraseFileFetcher(t *testing.T) { - secret := "secret" - directory := ensure.TempFile(t, "passphrase-file", []byte(secret)) - defer os.RemoveAll(directory) - - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } - - passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } - - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } -} - -func TestPassphraseFileFetcher_WithLineBreak(t *testing.T) { - secret := "secret" - directory := ensure.TempFile(t, "passphrase-file", []byte(secret+"\n\n.")) - defer os.RemoveAll(directory) - - fetcher, err := passphraseFileFetcher(path.Join(directory, "passphrase-file"), nil) - if err != nil { - t.Fatal("Unable to create passphraseFileFetcher", err) - } - - passphrase, err := fetcher("key") - if err != nil { - t.Fatal("Unable to fetch passphrase") - } - - if string(passphrase) != secret { - t.Errorf("Expected %s got %s", secret, string(passphrase)) - } -} - -func TestPassphraseFileFetcher_WithInvalidStdin(t *testing.T) { - directory := ensure.TempDir(t) - defer os.RemoveAll(directory) - - stdin, err := ioutil.TempFile(directory, "non-existing") - if err != nil { - t.Fatal("Unable to create test file", err) - } - - if _, err := passphraseFileFetcher("-", stdin); err == nil { - t.Error("Expected passphraseFileFetcher returning an error") - } -} - -func TestValidateVersion(t *testing.T) { - type args struct { - ver string - } - tests := []struct { - name string - args args - wantErr error - }{ - { - "normal semver version", - args{ - ver: "1.1.3-23658", - }, - nil, - }, - { - "Pre version number starting with 0", - args{ - ver: "1.1.3-023658", - }, - semver.ErrSegmentStartsZero, - }, - { - "Invalid version number", - args{ - ver: "1.1.3.sd.023658", - }, - semver.ErrInvalidSemVer, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := validateVersion(tt.args.ver); err != nil { - if err != tt.wantErr { - t.Errorf("Expected {%v}, got {%v}", tt.wantErr, err) - } - - } - }) - } -} diff --git a/pkg/action/pull.go b/pkg/action/pull.go deleted file mode 100644 index b4018869e..000000000 --- a/pkg/action/pull.go +++ /dev/null @@ -1,166 +0,0 @@ -/* -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 action - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// Pull is the action for checking a given release's information. -// -// It provides the implementation of 'helm pull'. -type Pull struct { - ChartPathOptions - - Settings *cli.EnvSettings // TODO: refactor this out of pkg/action - - Devel bool - Untar bool - VerifyLater bool - UntarDir string - DestDir string - cfg *Configuration -} - -type PullOpt func(*Pull) - -func WithConfig(cfg *Configuration) PullOpt { - return func(p *Pull) { - p.cfg = cfg - } -} - -// NewPull creates a new Pull object. -func NewPull() *Pull { - return NewPullWithOpts() -} - -// NewPullWithOpts creates a new pull, with configuration options. -func NewPullWithOpts(opts ...PullOpt) *Pull { - p := &Pull{} - for _, fn := range opts { - fn(p) - } - - return p -} - -// Run executes 'helm pull' against the given release. -func (p *Pull) Run(chartRef string) (string, error) { - var out strings.Builder - - c := downloader.ChartDownloader{ - Out: &out, - Keyring: p.Keyring, - Verify: downloader.VerifyNever, - Getters: getter.All(p.Settings), - Options: []getter.Option{ - getter.WithBasicAuth(p.Username, p.Password), - getter.WithPassCredentialsAll(p.PassCredentialsAll), - getter.WithTLSClientConfig(p.CertFile, p.KeyFile, p.CaFile), - getter.WithInsecureSkipVerifyTLS(p.InsecureSkipTLSverify), - }, - RegistryClient: p.cfg.RegistryClient, - RepositoryConfig: p.Settings.RepositoryConfig, - RepositoryCache: p.Settings.RepositoryCache, - } - - if registry.IsOCI(chartRef) { - c.Options = append(c.Options, - getter.WithRegistryClient(p.cfg.RegistryClient)) - } - - if p.Verify { - c.Verify = downloader.VerifyAlways - } else if p.VerifyLater { - c.Verify = downloader.VerifyLater - } - - // If untar is set, we fetch to a tempdir, then untar and copy after - // verification. - dest := p.DestDir - if p.Untar { - var err error - dest, err = ioutil.TempDir("", "helm-") - if err != nil { - return out.String(), errors.Wrap(err, "failed to untar") - } - defer os.RemoveAll(dest) - } - - if p.RepoURL != "" { - chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings)) - if err != nil { - return out.String(), err - } - chartRef = chartURL - } - - saved, v, err := c.DownloadTo(chartRef, p.Version, dest) - if err != nil { - return out.String(), err - } - - if p.Verify { - for name := range v.SignedBy.Identities { - fmt.Fprintf(&out, "Signed by: %v\n", name) - } - fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", v.SignedBy.PrimaryKey.Fingerprint) - fmt.Fprintf(&out, "Chart Hash Verified: %s\n", v.FileHash) - } - - // After verification, untar the chart into the requested directory. - if p.Untar { - ud := p.UntarDir - if !filepath.IsAbs(ud) { - ud = filepath.Join(p.DestDir, ud) - } - // Let udCheck to check conflict file/dir without replacing ud when untarDir is the current directory(.). - udCheck := ud - if udCheck == "." { - _, udCheck = filepath.Split(chartRef) - } else { - _, chartName := filepath.Split(chartRef) - udCheck = filepath.Join(udCheck, chartName) - } - - if _, err := os.Stat(udCheck); err != nil { - if err := os.MkdirAll(udCheck, 0755); err != nil { - return out.String(), errors.Wrap(err, "failed to untar (mkdir)") - } - - } else { - return out.String(), errors.Errorf("failed to untar: a file or directory with the name %s already exists", udCheck) - } - - return out.String(), chartutil.ExpandFile(ud, saved) - } - return out.String(), nil -} diff --git a/pkg/action/push.go b/pkg/action/push.go deleted file mode 100644 index 99d1beadc..000000000 --- a/pkg/action/push.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -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 action - -import ( - "strings" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/uploader" -) - -// Push is the action for uploading a chart. -// -// It provides the implementation of 'helm push'. -type Push struct { - Settings *cli.EnvSettings - cfg *Configuration -} - -// PushOpt is a type of function that sets options for a push action. -type PushOpt func(*Push) - -// WithPushConfig sets the cfg field on the push configuration object. -func WithPushConfig(cfg *Configuration) PushOpt { - return func(p *Push) { - p.cfg = cfg - } -} - -// NewPushWithOpts creates a new push, with configuration options. -func NewPushWithOpts(opts ...PushOpt) *Push { - p := &Push{} - for _, fn := range opts { - fn(p) - } - return p -} - -// Run executes 'helm push' against the given chart archive. -func (p *Push) Run(chartRef string, remote string) (string, error) { - var out strings.Builder - - c := uploader.ChartUploader{ - Out: &out, - Pushers: pusher.All(p.Settings), - Options: []pusher.Option{}, - } - - if registry.IsOCI(remote) { - c.Options = append(c.Options, pusher.WithRegistryClient(p.cfg.RegistryClient)) - } - - return out.String(), c.UploadTo(chartRef, remote) -} diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go deleted file mode 100644 index 68bcc7442..000000000 --- a/pkg/action/registry_login.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 action - -import ( - "io" - - "helm.sh/helm/v3/pkg/registry" -) - -// RegistryLogin performs a registry login operation. -type RegistryLogin struct { - cfg *Configuration -} - -// NewRegistryLogin creates a new RegistryLogin object with the given configuration. -func NewRegistryLogin(cfg *Configuration) *RegistryLogin { - return &RegistryLogin{ - cfg: cfg, - } -} - -// Run executes the registry login operation -func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string, insecure bool) error { - return a.cfg.RegistryClient.Login( - hostname, - registry.LoginOptBasicAuth(username, password), - registry.LoginOptInsecure(insecure)) -} diff --git a/pkg/action/registry_logout.go b/pkg/action/registry_logout.go deleted file mode 100644 index 69add4163..000000000 --- a/pkg/action/registry_logout.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -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 action - -import ( - "io" -) - -// RegistryLogout performs a registry login operation. -type RegistryLogout struct { - cfg *Configuration -} - -// NewRegistryLogout creates a new RegistryLogout object with the given configuration. -func NewRegistryLogout(cfg *Configuration) *RegistryLogout { - return &RegistryLogout{ - cfg: cfg, - } -} - -// Run executes the registry logout operation -func (a *RegistryLogout) Run(out io.Writer, hostname string) error { - return a.cfg.RegistryClient.Logout(hostname) -} diff --git a/pkg/action/release_testing.go b/pkg/action/release_testing.go deleted file mode 100644 index ecaeaf59f..000000000 --- a/pkg/action/release_testing.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "io" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// ReleaseTesting is the action for testing a release. -// -// It provides the implementation of 'helm test'. -type ReleaseTesting struct { - cfg *Configuration - Timeout time.Duration - // Used for fetching logs from test pods - Namespace string - Filters map[string][]string -} - -// NewReleaseTesting creates a new ReleaseTesting object with the given configuration. -func NewReleaseTesting(cfg *Configuration) *ReleaseTesting { - return &ReleaseTesting{ - cfg: cfg, - Filters: map[string][]string{}, - } -} - -// Run executes 'helm test' against the given release. -func (r *ReleaseTesting) Run(name string) (*release.Release, error) { - if err := r.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("releaseTest: Release name is invalid: %s", name) - } - - // finds the non-deleted release with the given name - rel, err := r.cfg.Releases.Last(name) - if err != nil { - return rel, err - } - - skippedHooks := []*release.Hook{} - executingHooks := []*release.Hook{} - if len(r.Filters["!name"]) != 0 { - for _, h := range rel.Hooks { - if contains(r.Filters["!name"], h.Name) { - skippedHooks = append(skippedHooks, h) - } else { - executingHooks = append(executingHooks, h) - } - } - rel.Hooks = executingHooks - } - if len(r.Filters["name"]) != 0 { - executingHooks = nil - for _, h := range rel.Hooks { - if contains(r.Filters["name"], h.Name) { - executingHooks = append(executingHooks, h) - } else { - skippedHooks = append(skippedHooks, h) - } - } - rel.Hooks = executingHooks - } - - if err := r.cfg.execHook(rel, release.HookTest, r.Timeout); err != nil { - rel.Hooks = append(skippedHooks, rel.Hooks...) - r.cfg.Releases.Update(rel) - return rel, err - } - - rel.Hooks = append(skippedHooks, rel.Hooks...) - return rel, r.cfg.Releases.Update(rel) -} - -// GetPodLogs will write the logs for all test pods in the given release into -// the given writer. These can be immediately output to the user or captured for -// other uses -func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error { - client, err := r.cfg.KubernetesClientSet() - if err != nil { - return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs") - } - - for _, h := range rel.Hooks { - for _, e := range h.Events { - if e == release.HookTest { - req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) - logReader, err := req.Stream(context.Background()) - if err != nil { - return errors.Wrapf(err, "unable to get pod logs for %s", h.Name) - } - - fmt.Fprintf(out, "POD LOGS: %s\n", h.Name) - _, err = io.Copy(out, logReader) - fmt.Fprintln(out) - if err != nil { - return errors.Wrapf(err, "unable to write pod logs for %s", h.Name) - } - } - } - } - return nil -} - -func contains(arr []string, value string) bool { - for _, item := range arr { - if item == value { - return true - } - } - return false -} diff --git a/pkg/action/resource_policy.go b/pkg/action/resource_policy.go deleted file mode 100644 index 63e83f3d9..000000000 --- a/pkg/action/resource_policy.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -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 action - -import ( - "strings" - - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/releaseutil" -) - -func filterManifestsToKeep(manifests []releaseutil.Manifest) (keep, remaining []releaseutil.Manifest) { - for _, m := range manifests { - if m.Head.Metadata == nil || m.Head.Metadata.Annotations == nil || len(m.Head.Metadata.Annotations) == 0 { - remaining = append(remaining, m) - continue - } - - resourcePolicyType, ok := m.Head.Metadata.Annotations[kube.ResourcePolicyAnno] - if !ok { - remaining = append(remaining, m) - continue - } - - resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) - if resourcePolicyType == kube.KeepPolicy { - keep = append(keep, m) - } - - } - return keep, remaining -} diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go deleted file mode 100644 index dda8c700b..000000000 --- a/pkg/action/rollback.go +++ /dev/null @@ -1,246 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "strings" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// Rollback is the action for rolling back to a given release. -// -// It provides the implementation of 'helm rollback'. -type Rollback struct { - cfg *Configuration - - Version int - Timeout time.Duration - Wait bool - WaitForJobs bool - DisableHooks bool - DryRun bool - Recreate bool // will (if true) recreate pods after a rollback. - Force bool // will (if true) force resource upgrade through uninstall/recreate if needed - CleanupOnFail bool - MaxHistory int // MaxHistory limits the maximum number of revisions saved per release -} - -// NewRollback creates a new Rollback object with the given configuration. -func NewRollback(cfg *Configuration) *Rollback { - return &Rollback{ - cfg: cfg, - } -} - -// Run executes 'helm rollback' against the given release. -func (r *Rollback) Run(name string) error { - if err := r.cfg.KubeClient.IsReachable(); err != nil { - return err - } - - r.cfg.Releases.MaxHistory = r.MaxHistory - - r.cfg.Log("preparing rollback of %s", name) - currentRelease, targetRelease, err := r.prepareRollback(name) - if err != nil { - return err - } - - if !r.DryRun { - r.cfg.Log("creating rolled back release for %s", name) - if err := r.cfg.Releases.Create(targetRelease); err != nil { - return err - } - } - - r.cfg.Log("performing rollback of %s", name) - if _, err := r.performRollback(currentRelease, targetRelease); err != nil { - return err - } - - if !r.DryRun { - r.cfg.Log("updating status for rolled back release for %s", name) - if err := r.cfg.Releases.Update(targetRelease); err != nil { - return err - } - } - return nil -} - -// prepareRollback finds the previous release and prepares a new release object with -// the previous release's configuration -func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Release, error) { - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, nil, errors.Errorf("prepareRollback: Release name is invalid: %s", name) - } - - if r.Version < 0 { - return nil, nil, errInvalidRevision - } - - currentRelease, err := r.cfg.Releases.Last(name) - if err != nil { - return nil, nil, err - } - - previousVersion := r.Version - if r.Version == 0 { - previousVersion = currentRelease.Version - 1 - } - - r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) - - previousRelease, err := r.cfg.Releases.Get(name, previousVersion) - if err != nil { - return nil, nil, err - } - - // Store a new release object with previous release's configuration - targetRelease := &release.Release{ - Name: name, - Namespace: currentRelease.Namespace, - Chart: previousRelease.Chart, - Config: previousRelease.Config, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: helmtime.Now(), - Status: release.StatusPendingRollback, - Notes: previousRelease.Info.Notes, - // Because we lose the reference to previous version elsewhere, we set the - // message here, and only override it later if we experience failure. - Description: fmt.Sprintf("Rollback to %d", previousVersion), - }, - Version: currentRelease.Version + 1, - Manifest: previousRelease.Manifest, - Hooks: previousRelease.Hooks, - } - - return currentRelease, targetRelease, nil -} - -func (r *Rollback) performRollback(currentRelease, targetRelease *release.Release) (*release.Release, error) { - if r.DryRun { - r.cfg.Log("dry run for %s", targetRelease.Name) - return targetRelease, nil - } - - current, err := r.cfg.KubeClient.Build(bytes.NewBufferString(currentRelease.Manifest), false) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") - } - target, err := r.cfg.KubeClient.Build(bytes.NewBufferString(targetRelease.Manifest), false) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") - } - - // pre-rollback hooks - if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPreRollback, r.Timeout); err != nil { - return targetRelease, err - } - } else { - r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) - } - - // It is safe to use "force" here because these are resources currently rendered by the chart. - err = target.Visit(setMetadataVisitor(targetRelease.Name, targetRelease.Namespace, true)) - if err != nil { - return targetRelease, errors.Wrap(err, "unable to set metadata visitor from target release") - } - results, err := r.cfg.KubeClient.Update(current, target, r.Force) - - if err != nil { - msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) - r.cfg.Log("warning: %s", msg) - currentRelease.Info.Status = release.StatusSuperseded - targetRelease.Info.Status = release.StatusFailed - targetRelease.Info.Description = msg - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - if r.CleanupOnFail { - r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) - _, errs := r.cfg.KubeClient.Delete(results.Created) - if errs != nil { - var errorList []string - for _, e := range errs { - errorList = append(errorList, e.Error()) - } - return targetRelease, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original rollback error: %s", err) - } - r.cfg.Log("Resource cleanup complete") - } - return targetRelease, err - } - - if r.Recreate { - // NOTE: Because this is not critical for a release to succeed, we just - // log if an error occurs and continue onward. If we ever introduce log - // levels, we should make these error level logs so users are notified - // that they'll need to go do the cleanup on their own - if err := recreate(r.cfg, results.Updated); err != nil { - r.cfg.Log(err.Error()) - } - } - - if r.Wait { - if r.WaitForJobs { - if err := r.cfg.KubeClient.WaitWithJobs(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) - } - } else { - if err := r.cfg.KubeClient.Wait(target, r.Timeout); err != nil { - targetRelease.SetStatus(release.StatusFailed, fmt.Sprintf("Release %q failed: %s", targetRelease.Name, err.Error())) - r.cfg.recordRelease(currentRelease) - r.cfg.recordRelease(targetRelease) - return targetRelease, errors.Wrapf(err, "release %s failed", targetRelease.Name) - } - } - } - - // post-rollback hooks - if !r.DisableHooks { - if err := r.cfg.execHook(targetRelease, release.HookPostRollback, r.Timeout); err != nil { - return targetRelease, err - } - } - - deployed, err := r.cfg.Releases.DeployedAll(currentRelease.Name) - if err != nil && !strings.Contains(err.Error(), "has no deployed releases") { - return nil, err - } - // Supersede all previous deployments, see issue #2941. - for _, rel := range deployed { - r.cfg.Log("superseding previous deployment %d", rel.Version) - rel.Info.Status = release.StatusSuperseded - r.cfg.recordRelease(rel) - } - - targetRelease.Info.Status = release.StatusDeployed - - return targetRelease, nil -} diff --git a/pkg/action/show.go b/pkg/action/show.go deleted file mode 100644 index 9ba85234d..000000000 --- a/pkg/action/show.go +++ /dev/null @@ -1,156 +0,0 @@ -/* -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 action - -import ( - "bytes" - "fmt" - "strings" - - "github.com/pkg/errors" - "k8s.io/cli-runtime/pkg/printers" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" -) - -// ShowOutputFormat is the format of the output of `helm show` -type ShowOutputFormat string - -const ( - // ShowAll is the format which shows all the information of a chart - ShowAll ShowOutputFormat = "all" - // ShowChart is the format which only shows the chart's definition - ShowChart ShowOutputFormat = "chart" - // ShowValues is the format which only shows the chart's values - ShowValues ShowOutputFormat = "values" - // ShowReadme is the format which only shows the chart's README - ShowReadme ShowOutputFormat = "readme" - // ShowCRDs is the format which only shows the chart's CRDs - ShowCRDs ShowOutputFormat = "crds" -) - -var readmeFileNames = []string{"readme.md", "readme.txt", "readme"} - -func (o ShowOutputFormat) String() string { - return string(o) -} - -// Show is the action for checking a given release's information. -// -// It provides the implementation of 'helm show' and its respective subcommands. -type Show struct { - ChartPathOptions - Devel bool - OutputFormat ShowOutputFormat - JSONPathTemplate string - chart *chart.Chart // for testing -} - -// NewShow creates a new Show object with the given configuration. -// Deprecated: Use NewShowWithConfig -// TODO Helm 4: Fold NewShowWithConfig back into NewShow -func NewShow(output ShowOutputFormat) *Show { - return &Show{ - OutputFormat: output, - } -} - -// NewShowWithConfig creates a new Show object with the given configuration. -func NewShowWithConfig(output ShowOutputFormat, cfg *Configuration) *Show { - sh := &Show{ - OutputFormat: output, - } - sh.ChartPathOptions.registryClient = cfg.RegistryClient - - return sh -} - -// Run executes 'helm show' against the given release. -func (s *Show) Run(chartpath string) (string, error) { - if s.chart == nil { - chrt, err := loader.Load(chartpath) - if err != nil { - return "", err - } - s.chart = chrt - } - cf, err := yaml.Marshal(s.chart.Metadata) - if err != nil { - return "", err - } - - var out strings.Builder - if s.OutputFormat == ShowChart || s.OutputFormat == ShowAll { - fmt.Fprintf(&out, "%s\n", cf) - } - - if (s.OutputFormat == ShowValues || s.OutputFormat == ShowAll) && s.chart.Values != nil { - if s.OutputFormat == ShowAll { - fmt.Fprintln(&out, "---") - } - if s.JSONPathTemplate != "" { - printer, err := printers.NewJSONPathPrinter(s.JSONPathTemplate) - if err != nil { - return "", errors.Wrapf(err, "error parsing jsonpath %s", s.JSONPathTemplate) - } - printer.Execute(&out, s.chart.Values) - } else { - for _, f := range s.chart.Raw { - if f.Name == chartutil.ValuesfileName { - fmt.Fprintln(&out, string(f.Data)) - } - } - } - } - - if s.OutputFormat == ShowReadme || s.OutputFormat == ShowAll { - readme := findReadme(s.chart.Files) - if readme != nil { - if s.OutputFormat == ShowAll { - fmt.Fprintln(&out, "---") - } - fmt.Fprintf(&out, "%s\n", readme.Data) - } - } - - if s.OutputFormat == ShowCRDs || s.OutputFormat == ShowAll { - crds := s.chart.CRDObjects() - if len(crds) > 0 { - if s.OutputFormat == ShowAll && !bytes.HasPrefix(crds[0].File.Data, []byte("---")) { - fmt.Fprintln(&out, "---") - } - for _, crd := range crds { - fmt.Fprintf(&out, "%s\n", string(crd.File.Data)) - } - } - } - return out.String(), nil -} - -func findReadme(files []*chart.File) (file *chart.File) { - for _, file := range files { - for _, n := range readmeFileNames { - if strings.EqualFold(file.Name, n) { - return file - } - } - } - return nil -} diff --git a/pkg/action/show_test.go b/pkg/action/show_test.go deleted file mode 100644 index 8b617ea85..000000000 --- a/pkg/action/show_test.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -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 action - -import ( - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -func TestShow(t *testing.T) { - config := actionConfigFixture(t) - client := NewShowWithConfig(ShowAll, config) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "README.md", Data: []byte("README\n")}, - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - Raw: []*chart.File{ - {Name: "values.yaml", Data: []byte("VALUES\n")}, - }, - Values: map[string]interface{}{}, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `name: alpine - ---- -VALUES - ---- -README - ---- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowNoValues(t *testing.T) { - client := NewShow(ShowAll) - client.chart = new(chart.Chart) - - // Regression tests for missing values. See issue #1024. - client.OutputFormat = ShowValues - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - if len(output) != 0 { - t.Errorf("expected empty values buffer, got %s", output) - } -} - -func TestShowValuesByJsonPathFormat(t *testing.T) { - client := NewShow(ShowValues) - client.JSONPathTemplate = "{$.nestedKey.simpleKey}" - client.chart = buildChart(withSampleValues()) - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - expect := "simpleValue" - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowCRDs(t *testing.T) { - client := NewShow(ShowCRDs) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `--- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} - -func TestShowNoReadme(t *testing.T) { - client := NewShow(ShowAll) - client.chart = &chart.Chart{ - Metadata: &chart.Metadata{Name: "alpine"}, - Files: []*chart.File{ - {Name: "crds/ignoreme.txt", Data: []byte("error")}, - {Name: "crds/foo.yaml", Data: []byte("---\nfoo\n")}, - {Name: "crds/bar.json", Data: []byte("---\nbar\n")}, - }, - } - - output, err := client.Run("") - if err != nil { - t.Fatal(err) - } - - expect := `name: alpine - ---- -foo - ---- -bar - -` - if output != expect { - t.Errorf("Expected\n%q\nGot\n%q\n", expect, output) - } -} diff --git a/pkg/action/status.go b/pkg/action/status.go deleted file mode 100644 index 1c556e28d..000000000 --- a/pkg/action/status.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -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 action - -import ( - "helm.sh/helm/v3/pkg/release" -) - -// Status is the action for checking the deployment status of releases. -// -// It provides the implementation of 'helm status'. -type Status struct { - cfg *Configuration - - Version int - - // If true, display description to output format, - // only affect print type table. - // TODO Helm 4: Remove this flag and output the description by default. - ShowDescription bool -} - -// NewStatus creates a new Status object with the given configuration. -func NewStatus(cfg *Configuration) *Status { - return &Status{ - cfg: cfg, - } -} - -// Run executes 'helm status' against the given release. -func (s *Status) Run(name string) (*release.Release, error) { - if err := s.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - return s.cfg.releaseContent(name, s.Version) -} diff --git a/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml b/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml deleted file mode 100755 index ba10ee803..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/Chart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: chart-with-missing-deps -version: 2.1.8 diff --git a/pkg/action/testdata/charts/chart-missing-deps/requirements.lock b/pkg/action/testdata/charts/chart-missing-deps/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml b/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-missing-deps/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz b/pkg/action/testdata/charts/chart-with-compressed-dependencies-2.1.8.tgz deleted file mode 100644 index 7a22b1d827f593f101879554ae7831f2a40d2278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10962 zcmV;@DlOF?iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PKBhbKAzU;Q5SS(U&@BZRHB!OOJ&2`qWWuCDFzYT}g@Stk)$D zfgw3p00x;ENM3%sss7tJ>K**0+dtgz9v&VX z9QFRv>2*2>`+tGXJ;z(Cq+B5Om(GL7s!r|?QcyyE#f%dg58$Tjpd`uu-1olozI6i3 zeMS{yGNJ1pWltnP63`Qvl1Nn3m(g_C?2XKo=oCwT4XG$YHfT3lz0^K8@NA@n!e&Js-5&BO+oH5%mgjjswM_-_q`;ZIM;Y4U-NIo zn5k}Xgh)65pW282C;;jJ#7IukJab1>IQmI5k2##C%*SdJi-ZXg({!Y8V7j#bx&igC zF>~{2-Pkqjd0p>YXN~n=f&Xt%m}33_0kE9^yGMuJewF|G-NP;aKStTvf$ud{!CT6L zGf4nIA~eBZh|1a70fxspcS@?%6sU-kW#pMNr$7_lc6N3g)ziQMV0c2fV6y?3ri7}e z3FSmk`FySy2SA{Skwb2PM+C+0cMibL4qPPIC*v6)4eMDNhG#S+{!G7qfyXEf1qVUl z$#Hm#X8Z||v6+}a6CfO(gh*g6p6me*IR=h}s(w0tO@+h*j>Qw*T{iteyM3LGFpII2 zYJ|1}>hpHWF>@y=32<8tz}q_s!)}s>A&Dohex`bI0Fn&U0ThSQBfU|A7KPyqxSe7Q zh%qz+I?gA1fqjMr_Y5}lUl65Szz9Qx;#BqFGmHcdfW#oCSP3YeARl{`dGY#*3B<2? z+o$oEOj0=`8n~JCYs;DF$+-z5H%MUDu5#PXc1O+Ez&B7209=S>=ep+4c&1;zMiF)- zsr|w+lT(%O7A0~O+%?4G4vudKqj7{|;ocxd&?v-Ql4n*$j;e*VTucSXaXvUCh;v!j zsn8YzhT?!m(CxTVk|JMV1}IFXC{82Hh!0#axhaw{#8gNk`#dw*$v3$(1MEOgBZx?> zx315O!xOzdN(BA`e?UarF51mm2>2 zkWLp@uqBBMsm-xl-d_XaPH%VLnvM6NdCw z@p&dw%a_i4fLzaGmwN00S?IxW;3wz z@WFDay~87sxYhog9gxp|qFfBj?=#A>-*R2E=NYT#@01v`?->=;r{TbH7*^u!IZb0R zfUe`<4?U}tW1k0l*bCLm6u5oWS)xg5~%cMcu3UOVtgBIL#QTGgMU5Xaza zpnH8g#bT<7J-|swr5a;u0LSb`zv4&`KAu%KLY{#Q4F8rAh9xy~4=$-hkW?4!%H}oI z;!Fw8R!Es=*~YHMsL}%@h@l7z%%tvOR(b$}rFMM@R}#g}-B6gCGZg=l(=PO&NksSY zTq!7ka!@5;g+JAHJg=-YbD2SCdlb@9J3?Gw);>Que(~zWi-H}c7)j*}xppX5sc|%R z$8z6rSsGgwskO8zAMfnIsn%sIJxX3tnt)R82oCnLwZA1{2ooG*CKc0M zxhPPzfk-J<`8Kb{vCDwehF@s2Y20xv6zaqjE6#Q8ShdL80a-^2Ev^1l0Y8cL#w5)R zE^zG2RhK%`nSCPQR~n`fhUXz75nop+&w3W_jp~PI`l+6rUfu!#`oTQan|k@fZC&nJ7b0{jnnli>J5Apd$dt6+bWz#2%@ zm^aAxG3T#nfCq4nQSg=#f#1ZwT|G{I#{>BGN1}H3pEJ6d-_?Jh3?dQ}Nn3j*3Hq7_ zSnkN$aJ-#jrsNd7q$v0vg(&u=3B%xdm`dt8J(F926zs&um$O8whJ~7D2PwU`BRFb< z)W5mfph1&7{HNR@umJo)idiDUnOOm|SB6z3#+qSOVw%m}Sz#SQ+ zDhphp)CejKjjN$?Nm5vn6Vt;bL*p^=t#NKv&oOEdy@e@8p_qa{#r`!ns%k<>ELBw` z6x}OCCny!N}c z)t*1M1G(8Ct{$YlQ)sN7>^ipcnfzO>K8cASD13oKBu#i42YdkCUdK^_l+Yl5D4z)u zVVa8U;ei7%MkGubzML}5r!))(aOeQ=RIPd8Y1hdhTt0mN^Ne0zF}chno+#y%VME?x zii9?3ILv6O%p28_tS;N<3N4AbKw+qe*;7VQny5~HIe+P_=WNnYXh)m=J+KZTCB)^f zCI8jiSOIEZDk4VH(CS*Pc6K?%5E3q=Ng!2rXCTn6*G2)nz(B;wES#A z85@q(#4ZXlT%m@ zILKkK0zRwi(>TVyksV6p?(g^2C?e#Ab+CK3gea1d)RfaNzeH767V1KX}l`6A>l^5xoQikQ`d^+t_goQ7dWtO@0u z6iL1S@g5{0)<%)-X`z0cLt&ZCjVC1jP|}PQTNVcJqM!nyGyy_Bw|aRS3#V6}Hpjk{ z!G@OP1}hu3G}cfmXoLjum8W;4j65}90ThEysG!r1=4+vh+$K$%qV0S?v?B*IfRU5C2KTdQGoJPJx3v@ zIl&8R`;s_|P^=C-syL>pP*Ti_uPF{A?Yq!DpJf~Zncb%#Qc{`3W6C1CbL=dVyGnFu zlTQi<^Roblc2KWx{yo_E+u3m{#$JtkBbVGOntAJ+c$+ovHfh>z%B)*z(i#6iohg@* z_*icfW`|%nb8!(aBvig63B8y2NV`36%WlV*mbt>1DTxgKmSUyQ#8Rw8M65~6K8=!89kZEu0Y%E$U;3O?kD(=h zgd=sfdeH5?BI=2sq*71r=wlgaXF`ZZIMlXBLIXgYlZkSX%Npkm#zp};X&ky7huD{r zL4hV8U8pCN4dCM^eGWlGqj%nSP@l#=PDF0xsNUMr!VxZt16O22C@}Ir`2gO%*CyR~ zENzI{W1=ytJB@D#D8=T_7Ar4ACFPaLgZs<=wkL|2yk1zXjy^(J*gd%NlY2O-xy8&-|cs+ z@n5}z&X)flqj(b{oE>=c_Vvm61&m3k9Rs;z+|jQbsTir44ypVFsoLXVhN95X$HZ>_ z_#v^v+w!<&1^&-0!26ET$p5`c{Ac&5f3%JNe3Y`2hkZKErFE`?z}mbTQ+935sN99E zh{iMihnNbWiP5i7=xx=AKT=tN|4U&eA@#4<9ixf=`v=wizkAR<+VcNnl;TPc9P7z5 z0S=W2?Rj%p1-%OH85fQ>p>z^rln|5F0dSLNlk~l=6Oak!VgP)KdIyJtZ+obN5Bf*@ z{iDO~;dem~`{Scv|Dfl0zePP9eAhqd9E}fx?vdZ`f7eId{ez?NcjGY%j_`M*-l1co zWr6|pI^A#G&bMx-ciHO=I!A+puJ>K%pm)?gIy(GcTd7oE*5&{EsCG^K-#e)0e{~P~ z+x)LbDNX!e;NK74hYbh>pT+^v*<_U{Ql0N%JeP%}tt>)s%fp*5EAYQw(zXo>Tt6MO zf&aU`ey_s+y`z42%m0s2mgIkSJ6_l8@0T+`e?VO90mGp>Fxdm{qfq}saR3!k*gOv_F09J2uXkD%#m=e0>2nhr`>xn#pq_H0=|mQ z2;Cib01$^;9ES2=*#VA5I8aB^Bo-7RBxQ;Uyv6}=Iu^HxVGn*HlW8~ubc0A(3T!qJ zXT>a5DfO)QqHMBq{;Fjn5>4#yhTK&vid~3i{I}3?a}2tmA0&)MSWI!sH2Kx#W0XSv zuN8MHrE~SHUrQGc$@$IoO_lL$cAjm{@1G&(*ThAdfU-hFz73cZvqWkOLPf^sG40*Iw-b~Mo{c& zKD(fhmAHgogNCu!Dp$jR@ep7K0S^U&mzXHmDWW*7hU(gyAODHk+? zXy*CxSVg(ih&<(w&}ko|Hjee*nIf^*pXg;>zt>1@(qEdXhFN^U4qezZz3^B}jz1KvAOV$Zb6!>{**8qH2uk zd}hkTGo>A?fuz(HwYA$KO3dtKEu9e*C5t+j)pc|+`f#Ok-f(c0Ak}^M@RbIR#DR-~ zfML#`bv*T-28sjTB*aHSy|(CGwbc`1r{Im`g9M-!1Y)G?o2He$ND_)w6x2sR?wAfzt@9%um&7Nxo{a zXX8aE+M(LvWv>XB%PpPHF74{ItH~sl&*$?_`wAh|4A(jg)KB8ztC)ksLv&;OK3dh* zwQKd9M^`i!?0<8+*`x1J3TxYaYP(Nu_o*fORF!u$*KGHy?Ot`iy(+?j5uXp{Z$1FX z&My|7V3f}=5=J9KuWojhslY^&0o-)FZYEO}9|KfrY;Ir5&rMZgxX)06Z9aHS1??Jd zeb?>mx0aryu7CcwemYn~{73Jg*QuQU?eA~nzaFPJCe?d{Awfdj*mFB2p~)Fnx7iU6 zBP@lNjhS$qOOmMDyWS$1n(RxfV|h)(@&qk4T0(B&#HCc(^e!^vU`7(FHQjUKc&n_L{^`pDcJ6 zN>_$5cxqGoe8AyYLExwxVWn6@f->+K4sa|bwZ>k46E34vil)p_JAl7FEv}$B##(6s zS6BvcDf~C5ae~D3S?i-Gw@J^&$dnG;KDFRZsg;NO;JN|!X@GYf=aSkxRX|XE3q^MP z;Q(|!I8S3hD%n@v(_)jxmA%2G`mJ=Sf!22*BUhHjt4zS$F$J#6B5++HX5bQV^>HcG z_n_X{`LDzYuHhzdU5SIK1h%Jce=JPi*z)!S^{L#k+qcq&+0ACBwk&X`8)gx3Q+1uE zn?QjcgOL|oW?hX@9DsgxT3YKX&B~>5IBS~J0~xDvN(Juv((-)W@{s-iYKlW?$a^AL z8Vb~~|Mz>H!^-}D(C=;YKOUug{Mh~mZb&rH*V&K|i&=u7MG`Ik6c6B=wj%KMH_pk2 zM9Q7&?HW26%rW&`b4-&<63udM{#NPxo(>~cJGkX2S@X>eW{dqH!wA{Qi$#bq2DeZ5Z{4QBDhGh5|{Fb6nogV^95(T;Eyv4eARbR+4^BbP zQBlH0bst%8j;i%~pwv>1Vz|YF7l}syHWA%$r#!QAmuvld3tCre!_xl7ImaSpIK+=dFWI=@$Yq6pnC_7lv zg{yOhxC7>XF>}0@Bnx~pd3$sE5hXO1*0>#|m7ST01znhpG3&#)j0SVoG~B^&DHYgO zoMJ`#~^dZE5EJql*1^)ZN;Dk5ej|ykTg)q>?;8gM9c`6bWF? z?A76U!AVrcXU6&a@UpP*r zaWPOWdjVP6zzY>zb>UBOkje!*)maPr@yQ45r}@XOY;i%*gobo7`x(y$&??8E*l#Md zN^Q)VX7}b+UIIi&_|un_b4hh z>io@{%d4~Fi;K5!&R-OkO~o_)z-Wcw^zr%6cy^A*Rr09aUh58KEA6f&>DHc^XB8`C z5R5#P3Rbgjc3!_+oSavuV`y)#lh(psz9Pib;-o}_^WeWYzC8Z^_~N7y5pi{UTs1Ca z0a2Zty*zz>e0loj_0_94FY3mxZrjVcXkN=Ae2t4*?D{KO;9?fyDvO=iPnwlysV`i~ z$H*kpS)O>lV3o~wSPB)bTke`zQeE(eTv4*4n<%PxEk14SLCf{!|7ArDsm^sb(N|f> z!e48}Q|o=9@7hSsc_X$wnVLrBRjI^oHuYDkUhb(H@7dHrt@&xs&x1u;;QuZQ0l65_i*y6bhsvc6}}!K zr7ry^io*F3Or6|=)$J5J(RswLXd2haQE9Fg<`l_lXGly3P@4}@Y^oDtswWC{JuUXW zSECi?e4KNGsy7S9smoZ94cgiPSR#gzZe!jQkY9z!9JJ1{akdlb3d{B0@=5Z_dT;rZ z_g`NB&1D@t;nOF3@Wl1y|D}R=b@Aqx^XDg5ua94yJZs7JgL)bFkEhvZ%m-2p*lo8Y zEyvY6&7Q#fMkS-Nv~z04ciql@v%WADBKZ-EG9nXAvhzdOyL0B>EX&%^rS4mrx-8yu zJ}Y#~fa|U(=L`=l6m)Zf)7+des5h?H7css)QYqtT4L>U^H)NJe(gzM-?zSEog_O^- zpwp;S$(@(>Y{BMUR=u52x205Fp!aaCtmDJ97+dT(UBmON%gX*|UA^cOorUusK8blER#qW^WVv=y{w3s4Xocvxh>t?bl=J?LFdy0QkacwKM-z`qD z?&^>t%Y_^-uih}QflKn%*NAv;=4d4zweRnR~g{3img^k*Ile$7QdTa9}7}A zV%CSjH939qg&sCDDds(vT7zDa5eO!GR~!_Bq4gVEtWj(s!zVjM>O z>Xj~_+-)40p*T1WlPP+gM$qldk5@1>7IY(fv3G2?jmnR4Jgr_*Zvm~>oXIXe zd0wCD3yqf&{z5SBwM6yZE#ia1{ku)rVeaV2ezpLUsveVHg({UQwQ#m3{csHt3A9rzPLO& zzxwIT#pQhCb#Sww;cj7b=^HI_re-t{~Dz?s*fAsPY`7baRE=%JWv!y#gQ~ZCw zGZ+8YJ=n_sM=7__RHx^tv?w z)r)b_WztSsx6dmlyqQ9&T8tTr@GWK6R<$&apjHs1Qv9H>Jwk#HI^Mq5v30D+qoAa= zMu<;~2P`euFaCK~bYo!wF8)w>vI9O%W*|pWABdqe?;$WKDBFJW*?IH z^l32nTA)e6lzrGKdcj#<#FPA=a-9qLeOc=lY*>(X5OK*LLDo#;cfx%9Iy~2}?V#@eO8MyYNBfq5FSx_Fr@S z|9-dn{nvv|ZyW#rDCME|`!8W=+fGa3?6dO!kZ+-NiB)Q0mK;O4aTJnKh+FW{NRf5E zQrUDo3ND5xd`fS#?}GCGc%sJn^Lm%o=YLP?YD7g+t~f>$|93hE75?vc`&<40QHnPq z;!jKb+j0;7KR-Es@#@5jf|bWu&j0;>@1Xkq*Iw`FaLfOXQFiPG>^SetulG-x;bzDAS_NNAFVDF4j1N_oMI1JU0wN z8en)!u1SIeg7)ACDabDV;V@!)cbLFvr`ZnVawr)(fXA*b0qemuQU zqyNu~H?Lugkw_Wlp5yH7z~#&TcyaDH!{JbU{rj&Vw__kN7bpxRqDCRkB726zp=xv* z3r2&~my>Xu%PHaJ+x{a;1s4n@a+>o5Fd<@^jy#`6Sx}Gp*ERR8nvr&)aB=|V>)8-3l~>^ARh9% zwaYE(%zh(YF96SojOy7GkYe#|=4n4YtgZQ!>60KuyYEWR-ap+b{k;nuEAoPnaKZKG zPZ2{A7Lq~L_x61h!w6@6;{XDciDM_I2Xmai!#CetlH{9j2Jn(_0dnqU#@rCbP$5PR zr3w6(m@Ha@)V^v5IK%?0*F}UE$aEg}rcGXb9?1+x^hQbnv(QEC+M@^#h;!-_Byf<^ zfF=pOP@!s2pbSmW&x=Ubx<0f8I0c$CwXeb{&}5Y{ zVEFOZlk1q0`v|p66}-ltO%#M32Z5xF$st8G@zJ6Oa-7IsOv#cBaU$Z z2@Uve15S7-^JO&K={m$Wi zK`a0|ck$BVh0pG+k?O75MP=im(!m!4dc{y2P<5q_+<1^LcM=pPQxvBWX2b`sz9@dR zBAlh$eQJQcgCArKV;M-}!O$y$>?WPMR>Lt0`Er_SC<;!K(AeD19mwe@u}Q}e<6DhK zLBVGji3N0O8R;R#!r8RqcEq`L&wyRlYSndDf-A$CGR(###=%tvr^L}J_*AhR5=IGM zW~ePk=$%vruWaS};Z@V;eB@MpZ@Hi^noKr)X7TKXf~5~4&l*6NQffV*4eJht5$FnB zup*#KNp%UJOF4c+lBwp?hl^KU8CwYrKvs9{wFb*Jdbyd<@$yZisTAR?33DG-M?8tO z36WPdb{B%-nSCgJ&Ew*So1xG}a25hahDA7YSU>{<3Yubo$@~~F{CIDjt^v+swDfQ( zoEN$#=)mgQF<|&WD3{P9A9pbP_D8Y-0b2UF$FlzJjP(v<65?$0w6e;`0_`#_!+BM<$gOk6*yl48g{sS| z#iF|aXO{o4M9@D!`=$K~N0iO7bcrlQq8*Zm2(IAQT|!I)ym3CNP1PQj;k-;)tXzq4 z0CYUg^1UFDjo_*7YPT}G&B~?5H0 z!&%KA9_sy_#3V{1@M+A|?Ls%$<|bpFNq{nPKQElsyyV4jnwpEiR3_&p;jGW2FF7XW z_&sa0gg`gV@68$VRfexZSXI^znAYXTw_8)|qi@>U9>w8`tZa%(zwT zxSHkPFu!lwjLRV2m>t)Af!fM`n@8aHW;5>V;P+-T?(5+9W;5wtA~qw z@B?KK$!(Uza`)0__K6N_R`>T?Fg7rnA%cMVX@p~8I0GU~ zkPQcvhu+BP1?2!6&vA}Jfmw{?<`vHN0Kq?xU%k|}wF(p0@mj=0LmSgM+uU+eHR0qY z3nPHrsqCT>2d-kB@4X)GxMZx?p|udK{!f}Jpi+)Y!SwG6eY}5K_+!^(4=7cX{Eh*^ zSnl^4qS(iJ)!u!t$r>J#pP^X*g(1D=Fr%qlR0-&erVRX)3mSor*;YYwWHC@4oW?*| zpji~F&uHigoA!J1j~PTM7jl6@>MNC*#8Mq71ip`HC_5R-rRQw}FpdSAC6vU%l`sS& zFBk%+y30I+fTs~Q**RIZvS)}~&JhQIKL{M_JR8I?3_U&_?#X|C3x~=QQ9r%O??aP2 zl6$N+j(097!)0cZETO9t8YIH?N@fWm<|W~V^#db0gP8=|x9_v88WKy5VSZT22-o2a_+vVQ(&6SvMMh&JVa_YW)ipZ(6k_WO^IQ|z9l zt}L2jR^AXiKHUQ;C4n+9(o*l#Rw7HuYPNtZolDB6F=1S%7g~v>wRXpOWtBeb4u4lK zI`1KEcd}iJ#1j{}F}`)}uB9>*jWOnZX{>L{wrtC`eEIVK0{{U3{|~x%-T)i{0A)$2 AQ~&?~ diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml b/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml deleted file mode 100755 index 1d16590b6..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/Chart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -name: chart-with-compressed-dependencies -version: 2.1.8 diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz b/pkg/action/testdata/charts/chart-with-compressed-dependencies/charts/mariadb-4.3.1.tgz deleted file mode 100644 index 5b38fa1c399527b1e3dc452bd90e5e8312c7051a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8401 zcmV;?ATHk@iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PKDLa@#o4;QY;}=#zIZaXyohf5h3)n%F;Rh*_B1Oqo9M6o9s@NuhMx)W)=mxsMh;T}RVfV#^ zaOq7+6n?k%((CnlhX)7xXRp_*{@mT$-}`QN|L~yq?C{ys-rje;-MzzJ?>p$NAC>u% z3Q72Py*u|+9o+Avkc9q%Tu>JG;cC|*Ns|A$+w*q4{R1aJ;d7cu{qP4Oi z1Hvbv`HjQ@AQ4lL3{ru>5{zBJQtqRwPgHvnLO>)L5`hkYm;icmeI94|jig+%T-6Bc0~CQ>G%-|bq_ z@faUF)K|pG-VCXXNkl!LMO`8UO3@XP_?IGU)1}ck``y*A+`YZtr@j3HFNw#_B~Gsy z4@BQ_!PZj0O~uF1ac2~C!8}Mfi%?E56;4EGED4Q~>&6%n8ur0w*~5PnP;&rcq6TE2 zxkDx$^CaukOrHjojcrWjBpvFZ*>>4aH(>rHa(9MtO`936T|J)Q0zV~;OXSW~#&`$b zzPIaqYsvU?l#C&-NSLDdIt##J{l9zg?BJlH|Mz=O_cr?fKFZb>d~d`zS9dfbV|2E* zfa7SU)06-k0oQWObLr1GNRqMWI$K+gZr^tRIF6~1eA)-wkTU%=VS-Ago>yQv0FsRJ zmB$TmNJ;Ge-2vFzg0lpDI+_A87@nkIc)~*JPtEI>I3j5%g<5|e9fj9qDjoqHnaT>T zT38;1M52(7b^t?xAW-V+uSaj0JmDxXmX8d$eDM8l_c9$Kk5QuVDC-8y7u{4KcgHCW zu&X-|-K~V9o1|e#-RmLUi2PTQQ6kVUiBRWpih*;p}>ql3O z#0<_&IK3hg-E5Y-*=V=)crAW?{Qy8MU#F(#PdGI%-;xL&MPH`5n6>!zi6SfGvJd=L=snigxBH!Tlp;)t7BPOg^Tx@k)-3st5s!&}>?de#D^TFMimUK)B+ zGi0TdY>$edMljbtmbzhBD{gvGu(p5CY*G*kJ0q`RZK4-mD2%B9)GvhAeTG~tR%+DN z6zX@&Y@R|?_pk#jR;Is!9TBBsz_Ok!NfQ=31zA!R zF-qk0CFK^1{!tYf!v}Nl&Lrc?WP1@3AqrRoIHEBIJ0MI2{r;`ibpNCH=)VeWtbx~Y zJ9W)EogKKIP=5je6-I3YQ^n)H;)5%y+))~j3-Hp|VgZkpf(eYxxebhGUBzh_?m*1u zy3?!)G#?SD7CYci7}L{RfH&(Mxd-rgoa_OM;po>hH3||3ga^Rl8KfV7dbOio!+(e4 z1miPF{LAek>9sWWBw6?6R-IY;dN3Uw>>M}~2fUD}dPDDOkkT7J|7+fSogWo-TW00e9;F2KV zD8%!gr~etGXrO7+f=J*cG|&w~Hg=J7)__aM#@SAi7cwKN{)BMiOXRLV=?cB8`25}Z z4}UXnS!gyRvp1<6{q4f2?(o8=j+rDsq!`N&cc-MCLbrsKLC3NKiOCrG8oFAExK)9% zR*zysyJ!0`MInW@#6ucUx$;WmvnWZWVQsyUm@hQ>lRkX@;sAv73S$)FgtMVtdg@k- zTocDsQWDbNa>be1*}k^aB)LQoEJ9E+qgzqaVu_n_Sno_s+HaOgrk_SFvy;d2n|z?h3EU4AS9rz6sRA>(+4pIM}*|c z?)w;1+tjYLbMD>HXjuQ|R(pkQ+N@Ix)~TxO7^&H;RhzZyc579Hl2c!+ z_#AWNp#$XS7fmM^i-rgZkyLMXPH2c`|8zpQ-u)V)QeIiigyWpkq^}h7uSEJ2yK|9mmI_H^ zD(t3}2E(F=#6cgF2KHqLW0a7vK!AGM!P~ z+#XMPO4Jk*pFg`mNBQgG&a_oAs+L9XOxwy(Mo%wlUk^EqH3rU65YbqvH6j@YpJRZr z($t1}@9Y6gUmr_3Fx#l@!(Sd3XV4yFEj58lOar(K{A;OPD-RFAbp!NSfZL99&Xffkqp$bjrYz;NP#Fhc*MrkE2BJ6* zi^FN#pzbJGtwSnE*SFT^tCsoo|6+ona?d@PtSJm63s|EIgp4ma!neU#6i zyHDVXMtyyLp`0=~P4Ic7$l_114^O(9!MjhKSDzB4ce0=oV56Cl;5oLQYmaGWn#72l zJA13`ea{3O>lHkcC{^?L3OT0%!j1AIVjm9o^{+HKOGhL62|Df!n36fysFdy#=Y>9- z1(Nrn1bzx1QxfX)Lm)|_G54Hb(LmSrrRqbCLfJV!QLBM=#|kcq9`A@^7ziL7jf;ta z(n)ec1w0;3H7#DgJyUgckVk_4_5!lZv{5#izvy22&zGlB)-vHVmLur=Rk(i@or-;A zz^+KnBEDJ?&ZFC+!k5A=qlRmG=`4q4D3%<027XDnaWGGc5-qCh$ZBKMtye>(mUFbg zHFsWQ8uPo2=!P4;Gpkm)&VO{EbFnfm-6zf|%9NYPGILaJ`ZbcrSnAW{#Eo99m_aY5zxTbLLsve zu{?!J4OXI5wPh>C$_>6I<-YYQ_~$M2_8-mmD{T~y-gAvea{Jiu7W=Pv*xN1Je|x>B zdj}i)?>;6>qU(9B4mSagb|rhJ*tUk|BoK@mi8J z?8(g9+tZIIVX^YYvtBy+nTc!wVHd_6hTSsi&w0~u10Pc+F{?O1P9+&vYT8YuVr4^$ z&!6G3_X7=4ct3`02pwfMi>{4}!&vfZ!e}ftA%dY7bf0v#y(3}B3O7LG0AmSz55QrZ zmw!X;b;x~I+U5UQ#s52ew(s9l=p6x%|od4WQDatoV64AZd9ik+eq@P(XI77KM zS?{z6`|48Nq#4#P?IJ;64aM$3+c4sGI&|j3qS7QF63-+j630_R9+Tp9%5)CdJ6*wn z9JHxZoCO_sQXub&Bwj?i68Snchi(NBE6h|zKm9xUDW`J!g2fU)$=MKi8XpOr%mur> z-fsSUB!1*9O-fJg`h_2>Dz_MR2Av7vK_^23fMoG#IqZZ5N5w$3`~~D`121)O)tNuR zAXO9e+GH)5$FDx2pJw?fv*Lne2@BbH`V&t3&?(2DOqh^*Qc7*?oStEbzGS>#c?l2^ z=}&&H%=@gY$N|NHE-~Nv4%`w{7i=VNOZ9}~2>GqZ?!q(Oet{z)MWr(mkh#$sMp*9g#2PFMZ8@x3`ZeSP%u`-{`#RZlkE)cf_Q#10B~_|Er1yQCQr_P50?Al1p zc_+3!n3_iAS*gTsw!te^FGH%vd$xfp*Du4Gj}mQzR&7y+Hy>Tv2Cv$p2(N}W>lv#Y zf?LN~bwRohmbEIwxjS!F0N#nYDzNU#T@`S5XRivt75QrxdF&{y%l=KGaJ~myXSRd2 z+baSwdBks68rSJj<*pXS6sc+_L{9opn-5ZKY7%0qCkl0tns?u;(F${WoO7M(Hx2#N zWh|%$U9$mLVuqnN%GQmZ~2t>4{!g@bR9erlSe!7 z$o19#%D}rgJAQZi;?>35qc^XfcU1d+y^j0GV;vZ%+`_&x1Gc*z#mjN^PP0ewq1DK! zOzoVR@%P={LA$*$kuv!aWtotPVcGdEvb%HU-z>`7Fr}(6pDwZFd{yigA-B7vp0hI0 zsOaVtr@c9$U+-M6u44S`NTp1p74}(Su_LoslisoWGT3@z6jDCRiq4QyCwHD_rx020 zWp%d`>Xwx17U(UuR#w@=%qZ5pak|3JvnfmOKbz{sUDTSCRjA#oshNgC%Q8N$*|e#_ zb+H9eB!J1ttLS`_gj;9D6!8-Dx0+G6vsE$2cdp%I{HWvFmTbRW9Ae$oAw`i31um}M zGTtKIwd0=K$txHV)!y^%G9@HMrp!{Mb&lEZIA(oQ)>oR(f3N&?-3Hnc|FOG&P>uh1 zdbq#2|M^}@ZG4Aac-l{V8%*-s9{E1V0FPySwNkonvw2yl)({I)IAS)3!8JL3>V@ex zcPZv~ER6-dAR`cThB6Gs+0iRYNcgNln%|-z#}bZV&7WK9&DM{ z|H57~yS}TS+w#Ak?d@0f|K9E<{^wpwW*o;%&W?cWUAkWRGRAEksn)Pys0#PKUVPJa z!RY7XgnUdfj{>Y7_GOgJuZq;Y)b3DKj^XB7-oWti0At?{zZi#6KYOJMD7RaC<|Gb| z!em0;rV;G+=KCu+8Vz6+lA!W_C{NW6s&6#ZT$@A)5Uue9L@f*>& z)fCl(Ym$S){kv`A&`8gwshj<}lK!t*|3h+x>kR*IkN-cYe*fq2aCejcb1%ipF^PkP zGM{@9Isc{3GOQyzz+~D!02^U(azKfgjsD=*o&P5JUt{=xYyAJ-)2jSG+uMD% zk^lQB%dPk8%Mw^tcGPao!X=h+;i%{Es_B$t8f*MJU%<0Z@da^ z8UUXhpVk2G?C%iSE zLcR{tt>feCfZZWFz7E{oaxXGR?2nOpQ9GJDbJbcBFKR*Ek+0U5c2Ns&omm%I2lth9 zQOo9=^oUJ*#3nsrlOFLk(j!*ZFw3VWH0&?KeAx6HK7Zf@g^E}BAoCOEC*eV)C(NU@ zRCYp5vsIE47B~NDxd|;;-+yYtCQIQfm&Wto^@abp#{VD8$A2Ci>}}3}@1v}K{<~_H zz@Om6catLUP*VizroH~?>z46zzxOh)|0N3P@-&W-uW1Eni~ry6&Bg!q_BQ(eUP`TI zKVd?OfZZ*G3ibiyX^EqL$OkApbJc9OffV5?)S5fl&c8D88*3A{?`gjba@(kVv_HO|L(Wumf!+4?kX3Qo^d3>aU4zyUuj*1l?t0W z?i~ENEt;h%;buVGN`Do%P`mPyaGCMWTf9QP@eA)%>ev5uX8*Ov{~zqmegF0F*(Uz~ zUdmnX_g^5;Sv#YN^UuovL%oI0IqKBHJUNC6yHQAoA$H)i)gr5WrLygK6kLQyV#2QT z?}Cc|e5CvN^X@KX^6x1NjOzHvwfE5`|GnPRiu~{QdK>%yUWzxS@=weBo07}_>8qoc zZ(ey(@D=)4EdTradrzz1f8EDoW?QGcAQwQ*_Ybcb4+9Tg%m4R({;zW14?$_ z2jb(9u}cl@N5;k>dX7`DHnN@rAfgBx1RKd~!cn)>-v#Fw>fH*3Z#GmaA>r(b1}K2U zpzx-#L_Q)u!ie$Anu*7{jR)rw1Z`nUxH1-(8M4KQgo5Sc`0@3b?)`tB9lwPU5}9%o zp5tt7!THbs`SR3p27`hA`uATzE!;q3AxRi2LJdRABYOsefo}9VmYfBtuLj{b=MyUI zxBZ8VNg+8&)G+56U`*vC9eO^C@}M63)3x`l+Mc$daB=|l>)8-s60&K8u>=;Y4rZU! zHGTBE-BPy1cHT7AlvAwo{+}2|GnDL3(KaN+za(RXh{R-!+)(F)qfWUIRHASkxA+m? zUGLyO;F`(_e2QIyqN%z zGZ{0ZDG(*2w;gBp>0#r|r`((bY2JNbdiLS*R_X6;5UAM;LMo&%pFbs>L@1Sj>hJCQ zB!(g8urYu@XX0c7G@Uulf5DR{=QMfpqz^w+AwiAZj#ya67-+(%t_*{}i)qswq}i)( zfFVlMuZsjPu<1PQZJSy3c_MR+*p<=(cA|^a&5j}j5FwaPiNs*W1BNBcM1{IRfinz4 zzbF#f^gCi<$gcH3wF;aOJf9KuxIlfYvokK3bt@-Gc|Pe^W*^RRZc1BICmA59D>t=I zU!u7TR58TztqRQzMhnZ0bK@>wR;hc}Ruve|4GhW~se=Whnt8tb5Kz$j#o zvIW4~=sCkP+tY0XHpBCE{(0ji`hY!o6@} zZi;%?w$caYiyA3BUzio~^~s&)y?B=1f~PThGZeMe(cT9P_V;^-w?(ml=-kCi%@d#B zS)U8})}gkmvIwG@q~O=xU~a|3EPT5Yls#CUD!C>i)1iEQB0OpxwU zGCbR6JnM08-g9IZwOV%Gwc^UDrX8~pjWM{$@l-TghMqc>L*c0K%N@1F1iewF;6+yX zc62rTIUhMy4{fICi!M_ZKGQt9f#m6(*s})Fg`8TCXv@4qp$E2v6fBA8LRMXX=t7BK zlV+;<^kMVNYiBEA0jTP3cCEppg zPTqCjV8r+|PnXD3B)TDus1zE10}^5u;M(P=HdT92#`8SovEE9I17M?3p6>;TY6OoB zsO`$=)~lCV)366Op#Ov+ju^)(l2q6{Er~%9;~N<4?)BEkp9}K42N`Al$*zHoB*w~0 zN|28jkIdHHLVE7r5zPg7mNV1_w#Q#57#HwTl?1R<)?+xs^R5cC8P96|@W8C^G^SA+ zfzM*0Zx_14EH@cNt^vx#{knKo^OBqKv^AU1)Gp^b@vP6IFKtZB$$Q>r0fVkv-rFlVygyx|94*Sc&*bPK$os;y{sBA~d2hKeVDM1nz2zc;Mm+CL-pltFF5LQl zi9%nR9H-F`Ij|Av#`(R7^{v)K&-rWU8o=JbnZ{}*H*S4?Tw}I!74$du<7yImYu&hS z?Z+)ndTsHK?$M81m>}Io&wKRa>NBQmcWS?>A6LAEpk<4Gkso)~prO|0$5pO_X-0K< zKh9`X?Z&NJj@sS0CHy!IYP&kRwd$pIHx5?u<7ODI&5vunrfZN>rp>Nvpyzt*y7o)J za(}bguB*dvhUXgB_jWgKRlClj{#Eg;bK_RD>oN+j-L7kMIgFsG(guUT;UX13BRoDGeu~auOP*k;ds<|0iS0Y~x zy%vR3%^|u}Fx3qVu6o{X&%1;6sX=s6Ow}BsOGQ)NG{;EY4$)Sdy?|$fz21Q58e%|n zEGZeXt5T?FL=v5}K0(0v3a*rsGYm10%1}Pc`$`C39?QLaxVQ&DFbI`L7=aK=@dL zQ-}_Vf!YHzI{ZYUB*dK+;<^8q17P~(jLAIe;kk*9%l_O+1#&NoS$R&9BoAQG5TG$G z+)Q^CxGd1d4QH;bjpri1&(c`Vf+y4f%P0&yI0^!4k|M$>P-%l~1dv(ijT&B14zTeY z=O~oOW1<$XaJmBo{(bc3XX9JzFmV&FMQt>+b)ECYZN{oLoZMz%1aLi3AS!X-I@bBt z^Wlz4&UzhL3&HCDW~oLh6<7+Ue_w#{;c?-QZJRxyO;P$A0)dfQ?+r$=k7m~1e{a|t z?vkHjM1X`LyB08IshU)U=#-@#{8UI5fr;7HL36YjDGy%9z<6Lp6!m8`%z$nC9rec) zqEtvVK_T*CLFudGLB_oQzD_! zDCJQIAkyfb>NUCpeIsZV%b#ECVFzs}s&8`8sXTK#Yabq$ze^G@Uv!lsamOhQu$%AK zN!Q-<(A~B-J{Xj!Ff*AagxHr=HM| nYd2f9bcUjJ#=LJ$^-bB7P1%%hU;cjp00960B}eC=0O9}utS^%_ diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock b/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml b/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-with-compressed-dependencies/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml b/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml deleted file mode 100644 index d3458f6a2..000000000 --- a/pkg/action/testdata/charts/chart-with-no-templates-dir/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: chart-with-no-templates-dir -description: an example chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml deleted file mode 100644 index 395d24f6a..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Empty testing chart -home: https://k8s.io/helm -name: empty -sources: -- https://github.com/kubernetes/helm -version: 0.1.0 diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml deleted file mode 100644 index c80812f6e..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/templates/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -# This file is intentionally blank diff --git a/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json b/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$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/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml b/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml deleted file mode 100644 index 5a1250bff..000000000 --- a/pkg/action/testdata/charts/chart-with-schema-negative/values.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/action/testdata/charts/chart-with-schema/Chart.yaml b/pkg/action/testdata/charts/chart-with-schema/Chart.yaml deleted file mode 100644 index 395d24f6a..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Empty testing chart -home: https://k8s.io/helm -name: empty -sources: -- https://github.com/kubernetes/helm -version: 0.1.0 diff --git a/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml b/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml deleted file mode 100644 index 76c290c4f..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/extra-values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -age: -5 -employmentInfo: null diff --git a/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml b/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml deleted file mode 100644 index c80812f6e..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/templates/empty.yaml +++ /dev/null @@ -1 +0,0 @@ -# This file is intentionally blank diff --git a/pkg/action/testdata/charts/chart-with-schema/values.schema.json b/pkg/action/testdata/charts/chart-with-schema/values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$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/pkg/action/testdata/charts/chart-with-schema/values.yaml b/pkg/action/testdata/charts/chart-with-schema/values.yaml deleted file mode 100644 index 042dea664..000000000 --- a/pkg/action/testdata/charts/chart-with-schema/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -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/pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies-2.1.8.tgz deleted file mode 100644 index ad9e681795cf96f3c632c8d6ba6c406be3b96b45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10953 zcmV;)DmK+0iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PKD1a@$6i;P)F(afbS4ZDj=Trc1&ZPkoB5B-;3*k(9W;^}56+ z&?I{Vppou|WX5vUzQ(@ZKFL@91yc{^5T2@bKWU zd-S(Xue0Cl{tY_!8g0#za)H?2Iu9PJI=MecK?(U4GfrqcfSay^k|g_c-}}z{)(J58 z8A*hC^cIgGNk<{!QxZ=g36U667QvV@7^Nf(h=@@{)GNd}7QD>`@~?S2Yt|;wB>&4*uiaj+ z^P$(@_mX(xT;rL1%?FMPSv|)EKa&5KFgmUpP`<|_#|4xmA@Px%spCR2O`HgkSRfK( zrnb>6fojgdIt0 zzi`atR3*GciChJD4dJ+h;~T^{(+D%-0~bsxMKXq%3Q1(2XC^zjk~=fN4)ipFh{RfT zeP$eH96h`w=_gzb{4WZGOIaHdhR5W?XNE!zb*4rdz-@A+#=p=4b~wX)sF)`V>8;}P zOs2@kBMed{OR*)*5*URjzRoz1e+I}EASY2GW*O_i408?itIIQxgbe`>$qi;Ru=4Q1 za;d$;Bayh({+u0<&wrv^49xE{%Cg^bU9;yItLN{O7_;vg71XEUz;PH>;_Nw1V=;iP zuOvcVe6LOYISO$Mz6QG2 zw^J;pn%D!JgjAX_rUr1#ZuBdT1mWXZRT1(GY+(4elrSu*nR{?aC4!{6U{^M;sTOBS zc(y{yJUiE*Y34G6(Do>#qjrS2z^r|Ka{S`ei5CSs$}p0~8FKAVt}^3j?2e`0 za9J8#BStS=#lO2M+Nrg)DIf3bz^S%nEImqIQI>!*?+6a|vbDb@VF(i(ViR#5kN&gN%81sM+p25WYVE=8l=xE!G(&O-W^y%s zNEp2#(uX7Qq|{>?8%W@(q|HE^w7Wo=9V|}6a1UZSH=rV1Acc8>Y_kXclv1^9g&uPV zF!KPOPLdwb7>?gw$eExxKr8?yF0#IU{P|>0R)GHjZxS3|2;^VyW)y6r$LdE)0X`VJfNX^h~M%DcFgRFK3A~4GT4m1}VL_BREPy>fc-` zXwW1N{~t@5^1u~J zjiA!dxEdOlB!wk8F+E%|G#(S*I_GBf9Ag&ITbN=LiYfS0>|b+ZswR}g(o{u4(Y->1 zLZG8-tI9uV^cck_)n2A~@dv>W&_t_tY5GVjGnST*)PxM9fT+I`Mn`zw`d!;=&!5|Y z+-wk657OQ#G*(Y`9b5TK{w-IZ#6%DjzQ7@pEtqlvAHM&2MlY|JTxJqalySEZjW+#zU^j%65SLm@{;Snk z0cu|wBF56t=31?Fb~(im5-y}mAWe3)e`E;Xr#F~cA5BTda`$G7=@sPyLUN76nXaXb zoDqiqPG!Hb_73)7lnT2`g>R(Xh$z$Vv;z`j97x5fN8;J8nDHyr#9d>dJ2S zc2SVw3N<7K$^va}8ysD}ypS5IyvTlTe8Dkr5@5jNG4_S_l?t;`bRZj#kbjNiAcw^Y z_^hf=;~4u!b|{g%zu#A*h>#c7!Rl-YS?*^^`1cg4sZGBC`=LbBtsI}L9W_0W)RUTK zukozyVc4+UB0}qBkFCqIpnz_GzcZRX zVb-RT6zzt1LN)LT##4iw zdys@!J4Lpqh4yg{g=IE3o{;!M$ud@KSs1{Jf(nGv1qk`v>g8=LoL+g_9Q#rR8(NYI zRyJ&Dtf5rU2nph=o!*gh^3;F@Pz*Yuf=)Y{udOFFrTUr`#7hBL*vl<0ud?*i`QMDD z4CrkP1!~K&E)6BigeVB~{!gKzq$M+k9^x9$w6|KIT|T9080dW>ErDEFzCZc#^ff#` zIlnyp;q>|O<%xO<)Oe7EI_DS1aCUzB>+$6Y{Cx6H^(OOJc9a~kgM=i;Qbem^P|hl9 zEypQSu6&wQ@i9m4R&u4Wm1T$nF>I=h8HEhPy^=j8hmf(XWSvGV3Q(S^=P2YfCwM_^ zUlL~#iq(Nf6~{CcN{U(WHN|11_bznLXBme;=JzRxlvF11n6k)fj-5qvSBVbo@=4)f zeiq=+8`SH&e-C#4c6OYKvsbgdkxT9s-MsZ(yv@3In{@3q<<>2A>CFB>ohz4-_*kn6 zqahg1wzvov5-MMkgw`cK(%T-{QI|bNI{WpxCMn&g-t5xt6-9yF3Rd8hUJaBqbJZx$ za_>@mmfenVEpvl0GZGp8Eyc>9iKSSHh**=BeHtaHI%YG`0*aKgzw|k)9z#q12uJE{ z^`P5(Mbr~NNu{0K(Z@2<&x8<-aHxHcga&{(Clj?rE^C}OI2#4%q;u$U9AaNe1_hdY zbfKP5Hh_tlnGe#5tcl+JB_^)n% z%m0s2ya^G`4!n8$`sDlq#w64m1Np|dqhC4FFj6rc()bHfrQ>0SqR`RD#BToh{*l7l z^4Mi1{?B~C`;F14|BovAzk77Ff3)TQ$0$2_*r(%M+RarESi4sd^{@AozhZVEXksjB z6mIQ>KS5cE|4V@;A@#4<8>5N;`v+D1-|HOqw*3DXrMS)m$L{2r0EbG1_Pjf+f?fsp z%odI}p>z^rln|5F0dSLNlk~l=6Oak!VgP)KdIyJtZ+obN5Bf*@{iDO~;dem~`{Scv z|Dfl0zePP9eAhqd9E}fx?vdZ`f7eId{ez?NcjGY%j_`M*-l1coWr6|pI^A#G&bMx- zciHO=I!A+puJ>K%pm)?gIy(GcTUk|K*608Ajk_lP?;Xtf|DB_){(qFx#Qz2U{osAr zfI#qR91xvNR*53j`5tEHvXHcuMd)pLc=Kf?{@07zwqb$mrh_){f4A4~RrtT(JKEp! z|6`OT`Jdg6*Y*1QS&t8fz^;ubON!B1o|4QGIE5D80x%_ickn8hlko)uq|O;*ld zwJb!UiT&M>yJ|(T3(<`K7CLT@K^OFcgwY6#DNgyOGFI)N_ex65A+BkY!`fE!39j&m zgffAdQ!y34G-v!Z%6;Vjy6M2n<$rhoXn(&d{}1*LxAOlnN+JK(iaV9kxq8;GrHY5- z{O0zi%J?;!XPfi;SIGG_agipVtPqiJ117~Rk=BAxk@0y_IRx5#RG^$9%UGnPNgU3t=*vgj9AHWxmduQjCxhRMwXV zms6wy$Bb96jjSgs1T0M^3(tnEKCnN*Sq^QghX3kfmdCX!DFfoXKnSc*|olgr;+-PNcj0| zN=XMRq)*-_d2-XKlbcPP++5mZL(*iHE?l25S-Su_D7KJBQ0!?wyP&R%W1al;iUy@@ zjXZuf+vc55(yZ-}liy1{<$0Xup~)xDqHbT!F8=GK4chlpE@%YN%=6>1igKwDd1^mG zr+tjtIM#n>mar6Y>WOxiqnRI%+dC#E#--!(H4bhzhTifXlgR+?Tm|eqz47s0&*PdT zu2k15s5iXPle9^hSB4F^@9djYjK96o zqC33uiOzO03EdJ@8XQ9oDoKNFV5qf_TV)ty^(>86UsKc6#H$;Y$Ya*w z<0l7TF1@r~TjYsoD*KcCo(WECXZy-h6X+TPrx8|}pQ_c8eAQylW*4F8hwAMvdqu!p zZs~k>X;-gZO(v;)KA(5mR|u(QxYl8yei8>?#T*KEp^DjSRiI z*;%Fn6HNwi)A72QOj&#kP^Gasy_BDus>E=gp#cLii3g5hWAAX6D%O10SEUMTsJ$Rf{!1;JI4wj?{)U{ z(kQmi3C8TbqbIF^!HV=uo6mr*K3Q?^k%fWJR2uAn)_T4@1SSO#z@{5Pj@g2eP$ z>!T;tq-SGfN(XMAT5zY#%ENtd-2nSEz`KrfN$s5~AgI2DB0K(Y0JcV>M2xz+GQjp08UTrvI;|IFzouCz2(hKn?o8-|HM!^#8%( z(Ki3%QOd`U?Qh_QL<4=D4GFQBCHPq+(c(|>0KRD}0&jogoP0>6+^OEKp@_qCOg+~e z)8vvwvz(j1RrbE8gNT&|w;UyFzPZ7SkpRP;^dw>*4*TjSi7wLdn0$bin|CI8&ebBN z{f(2~C2Piz3_pY4QWUE5LqJKQAorZNSof~#3ps|Ig|u^gB(;HZ#|kcqzL%KbFwnP= zDHjt5>MExx;qY{%@9BN<`a;%KK^_VAcbA|gmqvCre^;Xno~@1}+cIG!7Gr4rD|i2u zw<`9L?siRd7U5Nba30(S5xx>^78|Y}rL`KEAzyLow)++Pje~hqlyFhiBkRpkwO$XD zTFOxjw|MX((dgeMq8ski&aBjOt$%Al>uPOS+TS?mSforxmg%E%6JMvU)LuyJ#>~km z-TKw94aH}aq$+y$b+LhUxNVba3;RBABhX~NqI`GKnEglW-lmE8QTwwIO3Ja}P5FO^ol5?1 zzuW6|w)Wp+luG{3HHm|P*=K)+5+}j}Nhf-c1r=7j#fovD{9s8HuFe_a4w(DJ%<)>1 zEZCFD+ndvmD50_R#_cGr?94O7i>+^5M@Y62P3~hWxLCPN$Op)$eo;x92~PQi}YIl7zQ!x+5%*<@5`)vKLrv zjMrNY%)UNXFOm$K=XRc8f1qx`RLQXI{UbctD1#a(Ho z>KE#dwoY-7W4~hQ=}nzNre4hh>io@{%d4~Fi;K5! z&R-OkO~o_)z-Wcw^zr%6cy^A*Rr07+uXP8rm3G&XbZgJdvx*fm2u7Yt1*=&%JFj0Z zPR^^-F|;?=No!#*UlC$zaZ;kedGKExUmkydd~wo7@1@`%M;HRtg=yu zrBKnj<*tb()dhdZ4JA9eiK2Sf;?ve1v|L~QUslwR>RfjdeU*hQ{JmB@wcZ!{u8q{3 zcVf$vscBSRl}hYpQ-7uE<({hXo=shq>zDhQj}mR_t=gj8-+XjwQ-9SK#r|q&v!1ZZ zA-HvvRTrfD)sj|aIuECe7FrD4ZX`)X8_S zx}9PtI*<4jP2)N_D&5t>oFZB642kIgYV$#gO?5&{^+chrr^UPP)o6veJ*H4^&swtmpkBuP<7xI8^MN!2cH1pU%W?HivnTMr(a5MQ z?VOqMUAME}Y%ffONPfhkjL1Zj?EDb+?wt8I%d$3fsr#0uE{j#pXN7JVaNQN3>0nRPQ+6?1&&+C9a;skpWk+wT^qSa)?uk>x^;msf9? zZxQa=dC%?S6%2{$=-GCeWRfCL+Ei(kWA+D*S>Kk;mCc<0-uTOI18s=^==S%k@gIl% z-uC|IM=7=O9Y*mqpZG2?$?tpQ`&9;btm3Pc(sdV`m&Na9*T;esj+pgfa7|91e4&TU zT#ES}OKm|f$p{3Mq0A;&Xg5PQU1hR$o=Z}8RZ1P3Y9%$sONV9~ppk|%u`kVLUu}7q z{5K!!Znz4%DgXPZ*RSS(9d)+xKaWzZaU4^T9|4)WbiMLrjJqmQZGpm&6&_t*d{Moi z<@1{a{g&b^3b1O>Xj~_+-)40p*T1W zlPP+gM$qldk5_Os7IY(fv3G2?jniZw}94b&SV##Jg-mng~m$>e<2w6 zTB7>y7V$yh{@o^aScuO>OPBv#Oa4Dl|A*)XH--N<$N%qFzyEX8?{4#d9;FyQMscu| z=QA%tWx#GKJ)rJhhD~G#=uF$Mz(!b}98f~$LVxh{uKyab8w5ttJb+4l<+IA3A*Kmn&KY{+Cid|k-{8rR$0 zfk(|9s6+ZTbzqx1uuUEKxZdmsm-CI+!Oen> z&)%HZblmFqJBN)xEg%+2i;Zo+A`+?Roo!Oa(xi;dXJBZf;?I$QQ8$+d#K_lmcfSbv zx}NSGA79tm1ES;Wx_emeg>}UK9H|$zvw1L8Z6xucwyOuy)#lPJYWv${)`cD5v63!o z$$Xn0u}zQIrblekBR)rZ#M%;O_4I@V`^zvNHa!Q=pEyCG;uU_C`3duj@KvNI%!9R3 zc0x_Fb&?a7H~(z82@Oy`erm!tOW`w@jhz235C3nB|KFdB|2*vW54Y#Pk5Se?|6Mmr z;4g6EyG;@JT2ln-mc9Py>z?s*fAsP&`7baRE=%JWvn3j!DgM9TsmA{t9(1?z|4~Y< zWIv-pja0AazDgpvqKIlV?E&0DNN=-qi@C2Gwx&3YJU(qtDZOqDK=op_=rU<1z1!!N z6W+|AR4v8~MfjGoYinAXji6Q#q*DB#usuS84?5ny*Rgf%jz_^rZH*A077tiju3!A~ zuDFeb1-SS_;mHp8G?{@MNqr!O(vU-7zEEt&Ou#*dcFI{>?Ub}bGQuCQ--foTuE1=U zeChH&pIh2=Q*9~^sC`yF6VF7m*#?q`t6*#HWIOxH#HUZ~nwQy!BtCr_%)J(9QgCG- zc8XruEHC0o{!eY43;BN~lz5w^vB#&`j`$ybYi23C1Q2y^v)Hr|L-KG2RzbCCVqGBmm8l#E-JDr0H|M&Ngw)X#{6mLSrUzYf{{tctIPc7__fMxnBz(}e3+_=i*_8^K z#@Sb2BgzFNG)Y60e`Z^yykN$G=ZaA%>Le@%%6iFuWz#B*6he zd+-DD@rcrE)!UDhPD1QCPQlu+BL{$Zv2WmXEN&6Qs-^laIK!dZt&s5AsS*JRqcmGorp#J1@kF)p;BtyVSTY#>4*r`bGyJiSn(|IdpzuVIXl zNEzmyAJsK@;4n)_DGNV`xtIRNwZYzS}?(piLK0W_8a zX~fw|+j z#gF)Qz5Ra!De2&QED#)@opLa{){YBjzv}gr3xgT(G*R2&hLY1bz$~0em`FTkhzpkb za#aj^74|K}>Z_~jd-Qf$vhO78@#UXpJ6eBQco@xG^I$kwxVZWQ@sQW8U2aKd_8akf z0eD7aRL`b>6pL>&Py6X%?ail5p9Cq|eOG$+{^?HX?_JeDlpENxu1J051s_Am?so%ne}-6=LL2n!taF$)Yt# z?W=ZxLoBd*T|{_+Oy_ZL+T_*ek<4&JZ=@733thynJ&NFfIHx{A0tYz_Xp+zi6{-dW z%FqP;yoh8y?wE!ly;T!cA}~jIwj%Oz0sBT~TPnzh6;n)Dw&*u{AI@>^N?TKBwj-!3 zcePJnV0{^=B#6~p6`E^^mX~*Dt$P2Ff4DR4wuX-_lVVJgR{NY<+y;M z7RVlT{>oF*M_5ukT)kzk*UXGJ*8Jzz`Wj7d0zrvb$rVk!UyL0u1m8gYyRNNB)!D{vN1 zxR)->T~Q93R_effStAMObFC3i&mOey#k2GVIJMDhK~z>p^B6Gf_dAF81+f6=+{H_a z7e2eQMw+)ui^|4BWrHsU^opT4pz2B;sd$htH3nwGvWhRUlhMu5zf->J~hDJ z!4I;A@eHK%VCWS=c9TwBtKk@hd^t@u6a}YAXl(B14&-!{*ra2K@vX+Apx`r%!~#0C zjP#IV;cQxQJL25BXTUCNwd%Sn!Ifc6Ic8%L{!^JDFoUMcgAgjCfT7zYZUMdqhUamx%N)gVQF!y0~#FJRN5P4POcOe*_ z*@xoSJT88?846tlXCYu@ScEf&1vD_ApeY8J%#Q)XkN39e8sIEOOAnXAd7*294y>*n z1BMTTatS{3$od9+9;=$5gYXx@`R*K};4LEpzlnYP{+=fNf)4@A_g0Hf@_U2(aRRz|zqDWBh_YFhE|H~3v_ldR!4>?vONeQJH_k`3soKLboR=w!)mCC003DCBd@o33 zBY3L2+O5oPvvR314SRS8>Q5NqF=e<4B;h7cOW>dg@f{4iz0T(7b4h;p&|>B%dJA+c za4fB)1hX+^k>0vniqGx?!CV4oIYVt|MtnQPaRDw_i33eVJ%l-&4;7$`;jHEl54CV6gyU-1`xyhJk5}=IS&kJWYFL^PXrsg6rmCLzFIP3H1OB)k&{GPR0LZF-G z_vQ@wD#KTytmk;S?gty>qh*=*mXE#$eqWl_KZLJ_-y1Fr7=A7M-f$7YLO36d-^=$G zF5UWmfw{UgIZmSyWAOZ#!llU^JAqet}PmL^Cy;qwvwxcZFg+MU`j>ci1?hZe6?1fc{zGtaIbmwCgN}H*VK8xpAxbaW%`oVSeAV z8<#=6F+Z;P0=1R*Z61N&o87ptgWsFoxUYlXo87oC&F_^=|7CW_g-{V@z)65zA2A72 zgvAu69PH&^^Q|^?0$;wdZH164=_pyC)(N_TYTVVGGdFl+997-$E5%aX!BAq=+^ObT zw62A|8hkAashR_HrC_Q%7~XWeZpV9o^r-=KSxnU&pesdF-7Suhx*wp8HhTfi1@`&^ zI5!XjqGCzWh~AV!MI)4`r1dEV9N)l=baF-^&Z07;5A&`P!k5KzuO2S$!4H%{B-Jd5 zrS{Ti_K6N_R`>T?Fg7rnA%cMVX@p~8I0GU~kPQcvhu+BP z1?2!6&vA}Jfmw{C@(O2rfZ(6UuU=~3T7`-0cr9Y0p^fXDDYu+dO*py9!U*7YD!Zt} zfvZ^Od#{H(E;;LUXe|V*|C6Q)sFdSUF#WqiAMc+Q{@69y1IiR7zhgi!mik^p6#H1O z+Pm*HS;Ir}Gc*gJFr>E}W;B(HDgm9*l!2deK_k#H+bU>|EC$Mh(-c;O84W#Q z(|%9>F@q@OLM~89ePuF}SegTc!1pl?WhXZdpPeQ0t=@*S&<bvui!fq%}s>O%5uRXKrWh{nPSyNdoHgwiG1p zBqafEXZv-+HTOKUcg>9t8YIH?N@fWm<|W~V^#db0gP8=|x9_v88WKl@)@= zr+Xl!Bv1xMT53&gC9;&PMg?T)Tv9%b3FA7w&`K **Tip**: List all releases using `helm list` - -## Uninstalling the Chart - -To uninstall/delete the `my-release` deployment: - -```bash -$ helm delete my-release -``` - -The command removes all the Kubernetes components associated with the chart and deletes the release. - -## Configuration - -The following table lists the configurable parameters of the MariaDB chart and their default values. - -| Parameter | Description | Default | -|-------------------------------------------|-----------------------------------------------------|-------------------------------------------------------------------| -| `image.registry` | MariaDB image registry | `docker.io` | -| `image.repository` | MariaDB Image name | `bitnami/mariadb` | -| `image.tag` | MariaDB Image tag | `{VERSION}` | -| `image.pullPolicy` | MariaDB image pull policy | `Always` if `imageTag` is `latest`, else `IfNotPresent` | -| `image.pullSecrets` | Specify image pull secrets | `nil` (does not add image pull secrets to deployed pods) | -| `service.type` | Kubernetes service type | `ClusterIP` | -| `service.port` | MySQL service port | `3306` | -| `rootUser.password` | Password for the `root` user | _random 10 character alphanumeric string_ | -| `rootUser.forcePassword` | Force users to specify a password | `false` | -| `db.user` | Username of new user to create | `nil` | -| `db.password` | Password for the new user | _random 10 character alphanumeric string if `db.user` is defined_ | -| `db.name` | Name for new database to create | `my_database` | -| `replication.enabled` | MariaDB replication enabled | `true` | -| `replication.user` | MariaDB replication user | `replicator` | -| `replication.password` | MariaDB replication user password | _random 10 character alphanumeric string_ | -| `master.antiAffinity` | Master pod anti-affinity policy | `soft` | -| `master.persistence.enabled` | Enable persistence using a `PersistentVolumeClaim` | `true` | -| `master.persistence.annotations` | Persistent Volume Claim annotations | `{}` | -| `master.persistence.storageClass` | Persistent Volume Storage Class | `` | -| `master.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `master.persistence.size` | Persistent Volume Size | `8Gi` | -| `master.config` | Config file for the MariaDB Master server | `_default values in the values.yaml file_` | -| `master.resources` | CPU/Memory resource requests/limits for master node | `{}` | -| `master.livenessProbe.enabled` | Turn on and off liveness probe (master) | `true` | -| `master.livenessProbe.initialDelaySeconds`| Delay before liveness probe is initiated (master) | `120` | -| `master.livenessProbe.periodSeconds` | How often to perform the probe (master) | `10` | -| `master.livenessProbe.timeoutSeconds` | When the probe times out (master) | `1` | -| `master.livenessProbe.successThreshold` | Minimum consecutive successes for the probe (master)| `1` | -| `master.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe (master) | `3` | -| `master.readinessProbe.enabled` | Turn on and off readiness probe (master) | `true` | -| `master.readinessProbe.initialDelaySeconds`| Delay before readiness probe is initiated (master) | `15` | -| `master.readinessProbe.periodSeconds` | How often to perform the probe (master) | `10` | -| `master.readinessProbe.timeoutSeconds` | When the probe times out (master) | `1` | -| `master.readinessProbe.successThreshold` | Minimum consecutive successes for the probe (master)| `1` | -| `master.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe (master) | `3` | -| `slave.replicas` | Desired number of slave replicas | `1` | -| `slave.antiAffinity` | Slave pod anti-affinity policy | `soft` | -| `slave.persistence.enabled` | Enable persistence using a `PersistentVolumeClaim` | `true` | -| `slave.persistence.annotations` | Persistent Volume Claim annotations | `{}` | -| `slave.persistence.storageClass` | Persistent Volume Storage Class | `` | -| `slave.persistence.accessModes` | Persistent Volume Access Modes | `[ReadWriteOnce]` | -| `slave.persistence.size` | Persistent Volume Size | `8Gi` | -| `slave.config` | Config file for the MariaDB Slave replicas | `_default values in the values.yaml file_` | -| `slave.resources` | CPU/Memory resource requests/limits for slave node | `{}` | -| `slave.livenessProbe.enabled` | Turn on and off liveness probe (slave) | `true` | -| `slave.livenessProbe.initialDelaySeconds` | Delay before liveness probe is initiated (slave) | `120` | -| `slave.livenessProbe.periodSeconds` | How often to perform the probe (slave) | `10` | -| `slave.livenessProbe.timeoutSeconds` | When the probe times out (slave) | `1` | -| `slave.livenessProbe.successThreshold` | Minimum consecutive successes for the probe (slave) | `1` | -| `slave.livenessProbe.failureThreshold` | Minimum consecutive failures for the probe (slave) | `3` | -| `slave.readinessProbe.enabled` | Turn on and off readiness probe (slave) | `true` | -| `slave.readinessProbe.initialDelaySeconds`| Delay before readiness probe is initiated (slave) | `15` | -| `slave.readinessProbe.periodSeconds` | How often to perform the probe (slave) | `10` | -| `slave.readinessProbe.timeoutSeconds` | When the probe times out (slave) | `1` | -| `slave.readinessProbe.successThreshold` | Minimum consecutive successes for the probe (slave) | `1` | -| `slave.readinessProbe.failureThreshold` | Minimum consecutive failures for the probe (slave) | `3` | -| `metrics.enabled` | Start a side-car prometheus exporter | `false` | -| `metrics.image.registry` | Exporter image registry | `docker.io` | -`metrics.image.repository` | Exporter image name | `prom/mysqld-exporter` | -| `metrics.image.tag` | Exporter image tag | `v0.10.0` | -| `metrics.image.pullPolicy` | Exporter image pull policy | `IfNotPresent` | -| `metrics.resources` | Exporter resource requests/limit | `nil` | - -The above parameters map to the env variables defined in [bitnami/mariadb](http://github.com/bitnami/bitnami-docker-mariadb). For more information please refer to the [bitnami/mariadb](http://github.com/bitnami/bitnami-docker-mariadb) image documentation. - -Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, - -```bash -$ helm install --name my-release \ - --set root.password=secretpassword,user.database=app_database \ - stable/mariadb -``` - -The above command sets the MariaDB `root` account password to `secretpassword`. Additionally it creates a database named `my_database`. - -Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, - -```bash -$ helm install --name my-release -f values.yaml stable/mariadb -``` - -> **Tip**: You can use the default [values.yaml](values.yaml) - -## Initialize a fresh instance - -The [Bitnami MariaDB](https://github.com/bitnami/bitnami-docker-mariadb) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, they must be located inside the chart folder `files/docker-entrypoint-initdb.d` so they can be consumed as a ConfigMap. - -The allowed extensions are `.sh`, `.sql` and `.sql.gz`. - -## Persistence - -The [Bitnami MariaDB](https://github.com/bitnami/bitnami-docker-mariadb) image stores the MariaDB data and configurations at the `/bitnami/mariadb` path of the container. - -The chart mounts a [Persistent Volume](kubernetes.io/docs/user-guide/persistent-volumes/) volume at this location. The volume is created using dynamic volume provisioning, by default. An existing PersistentVolumeClaim can be defined. diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md deleted file mode 100755 index aaddde303..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/files/docker-entrypoint-initdb.d/README.md +++ /dev/null @@ -1,3 +0,0 @@ -You can copy here your custom .sh, .sql or .sql.gz file so they are executed during the first boot of the image. - -More info in the [bitnami-docker-mariadb](https://github.com/bitnami/bitnami-docker-mariadb#initializing-a-new-instance) repository. \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt deleted file mode 100755 index 4ba3b668a..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/NOTES.txt +++ /dev/null @@ -1,35 +0,0 @@ - -Please be patient while the chart is being deployed - -Tip: - - Watch the deployment status using the command: kubectl get pods -w --namespace {{ .Release.Namespace }} -l release={{ .Release.Name }} - -Services: - - echo Master: {{ template "mariadb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} -{{- if .Values.replication.enabled }} - echo Slave: {{ template "slave.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.port }} -{{- end }} - -Administrator credentials: - - Username: root - Password : $(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "mariadb.fullname" . }} -o jsonpath="{.data.mariadb-root-password}" | base64 --decode) - -To connect to your database - - 1. Run a pod that you can use as a client: - - kubectl run {{ template "mariadb.fullname" . }}-client --rm --tty -i --image {{ template "mariadb.image" . }} --namespace {{ .Release.Namespace }} --command -- bash - - 2. To connect to master service (read/write): - - mysql -h {{ template "mariadb.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -uroot -p {{ .Values.db.name }} - -{{- if .Values.replication.enabled }} - - 3. To connect to slave service (read-only): - - mysql -h {{ template "slave.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local -uroot -p {{ .Values.db.name }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl deleted file mode 100755 index 5afe380ff..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/_helpers.tpl +++ /dev/null @@ -1,53 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "mariadb.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). -*/}} -{{- define "mariadb.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "master.fullname" -}} -{{- if .Values.replication.enabled -}} -{{- printf "%s-%s" .Release.Name "mariadb-master" | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name "mariadb" | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - - -{{- define "slave.fullname" -}} -{{- printf "%s-%s" .Release.Name "mariadb-slave" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{- define "mariadb.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Return the proper image name -*/}} -{{- define "mariadb.image" -}} -{{- $registryName := .Values.image.registry -}} -{{- $repositoryName := .Values.image.repository -}} -{{- $tag := .Values.image.tag | toString -}} -{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} - -{{/* -Return the proper image name -*/}} -{{- define "metrics.image" -}} -{{- $registryName := .Values.metrics.image.registry -}} -{{- $repositoryName := .Values.metrics.image.repository -}} -{{- $tag := .Values.metrics.image.tag | toString -}} -{{- printf "%s/%s:%s" $registryName $repositoryName $tag -}} -{{- end -}} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml deleted file mode 100755 index 7bb969627..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/initialization-configmap.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "master.fullname" . }}-init-scripts - labels: - app: {{ template "mariadb.name" . }} - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: -{{ (.Files.Glob "files/docker-entrypoint-initdb.d/*").AsConfig | indent 2 }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml deleted file mode 100755 index 880a10198..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-configmap.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if .Values.master.config }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "master.fullname" . }} - labels: - app: {{ template "mariadb.name" . }} - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: - my.cnf: |- -{{ .Values.master.config | indent 4 }} -{{- end -}} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml deleted file mode 100755 index 0d74f01ff..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-statefulset.yaml +++ /dev/null @@ -1,187 +0,0 @@ -apiVersion: apps/v1beta1 -kind: StatefulSet -metadata: - name: {{ template "master.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "master" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -spec: - serviceName: "{{ template "master.fullname" . }}" - replicas: 1 - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - app: "{{ template "mariadb.name" . }}" - component: "master" - release: "{{ .Release.Name }}" - chart: {{ template "mariadb.chart" . }} - spec: - securityContext: - runAsUser: 1001 - fsGroup: 1001 - {{- if eq .Values.master.antiAffinity "hard" }} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: "kubernetes.io/hostname" - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- else if eq .Values.master.antiAffinity "soft" }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- end }} - {{- if .Values.image.pullSecrets }} - imagePullSecrets: - {{- range .Values.image.pullSecrets }} - - name: {{ . }} - {{- end}} - {{- end }} - containers: - - name: "mariadb" - image: {{ template "mariadb.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - {{- if .Values.db.user }} - - name: MARIADB_USER - value: "{{ .Values.db.user }}" - - name: MARIADB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- end }} - - name: MARIADB_DATABASE - value: "{{ .Values.db.name }}" - {{- if .Values.replication.enabled }} - - name: MARIADB_REPLICATION_MODE - value: "master" - - name: MARIADB_REPLICATION_USER - value: "{{ .Values.replication.user }}" - - name: MARIADB_REPLICATION_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-replication-password - {{- end }} - ports: - - name: mysql - containerPort: 3306 - {{- if .Values.master.livenessProbe.enabled }} - livenessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.master.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.master.livenessProbe.failureThreshold }} - {{- end }} - {{- if .Values.master.readinessProbe.enabled }} - readinessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.master.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.master.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.master.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.master.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.master.readinessProbe.failureThreshold }} - {{- end }} - resources: -{{ toYaml .Values.master.resources | indent 10 }} - volumeMounts: - - name: data - mountPath: /bitnami/mariadb - - name: custom-init-scripts - mountPath: /docker-entrypoint-initdb.d -{{- if .Values.master.config }} - - name: config - mountPath: /opt/bitnami/mariadb/conf/my.cnf - subPath: my.cnf -{{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ] - ports: - - name: metrics - containerPort: 9104 - livenessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 15 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 5 - timeoutSeconds: 1 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - volumes: - {{- if .Values.master.config }} - - name: config - configMap: - name: {{ template "master.fullname" . }} - {{- end }} - - name: custom-init-scripts - configMap: - name: {{ template "master.fullname" . }}-init-scripts -{{- if .Values.master.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "master" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} - spec: - accessModes: - {{- range .Values.master.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.master.persistence.size | quote }} - {{- if .Values.master.persistence.storageClass }} - {{- if (eq "-" .Values.master.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.master.persistence.storageClass | quote }} - {{- end }} - {{- end }} -{{- else }} - - name: "data" - emptyDir: {} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml deleted file mode 100755 index 460ec328e..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/master-svc.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ template "mariadb.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - component: "master" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -{{- if .Values.metrics.enabled }} - annotations: -{{ toYaml .Values.metrics.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.service.type }} - ports: - - name: mysql - port: {{ .Values.service.port }} - targetPort: mysql -{{- if .Values.metrics.enabled }} - - name: metrics - port: 9104 - targetPort: metrics -{{- end }} - selector: - app: "{{ template "mariadb.name" . }}" - component: "master" - release: "{{ .Release.Name }}" diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml deleted file mode 100755 index 17999d609..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/secrets.yaml +++ /dev/null @@ -1,38 +0,0 @@ -{{- if (not .Values.rootUser.existingSecret) -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ template "mariadb.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -type: Opaque -data: - {{- if .Values.rootUser.password }} - mariadb-root-password: "{{ .Values.rootUser.password | b64enc }}" - {{- else if (not .Values.rootUser.forcePassword) }} - mariadb-root-password: "{{ randAlphaNum 10 | b64enc }}" - {{ else }} - mariadb-root-password: {{ required "A MariaDB Root Password is required!" .Values.rootUser.password }} - {{- end }} - {{- if .Values.db.user }} - {{- if .Values.db.password }} - mariadb-password: "{{ .Values.db.password | b64enc }}" - {{- else if (not .Values.db.forcePassword) }} - mariadb-password: "{{ randAlphaNum 10 | b64enc }}" - {{- else }} - mariadb-password: {{ required "A MariaDB Database Password is required!" .Values.db.password }} - {{- end }} - {{- end }} - {{- if .Values.replication.enabled }} - {{- if .Values.replication.password }} - mariadb-replication-password: "{{ .Values.replication.password | b64enc }}" - {{- else if (not .Values.replication.forcePassword) }} - mariadb-replication-password: "{{ randAlphaNum 10 | b64enc }}" - {{- else }} - mariadb-replication-password: {{ required "A MariaDB Replication Password is required!" .Values.replication.password }} - {{- end }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml deleted file mode 100755 index 056cf5c07..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-configmap.yaml +++ /dev/null @@ -1,15 +0,0 @@ -{{- if and .Values.replication.enabled .Values.slave.config }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: {{ template "mariadb.name" . }} - component: "slave" - chart: {{ template "mariadb.chart" . }} - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -data: - my.cnf: |- -{{ .Values.slave.config | indent 4 }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml deleted file mode 100755 index aa67d4a70..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-statefulset.yaml +++ /dev/null @@ -1,193 +0,0 @@ -{{- if .Values.replication.enabled }} -apiVersion: apps/v1beta1 -kind: StatefulSet -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -spec: - serviceName: "{{ template "slave.fullname" . }}" - replicas: {{ .Values.slave.replicas }} - updateStrategy: - type: RollingUpdate - template: - metadata: - labels: - app: "{{ template "mariadb.name" . }}" - component: "slave" - release: "{{ .Release.Name }}" - chart: {{ template "mariadb.chart" . }} - spec: - securityContext: - runAsUser: 1001 - fsGroup: 1001 - {{- if eq .Values.slave.antiAffinity "hard" }} - affinity: - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - topologyKey: "kubernetes.io/hostname" - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- else if eq .Values.slave.antiAffinity "soft" }} - affinity: - podAntiAffinity: - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 1 - podAffinityTerm: - topologyKey: kubernetes.io/hostname - labelSelector: - matchLabels: - app: "{{ template "mariadb.name" . }}" - release: "{{ .Release.Name }}" - {{- end }} - {{- if .Values.image.pullSecrets }} - imagePullSecrets: - {{- range .Values.image.pullSecrets }} - - name: {{ . }} - {{- end}} - {{- end }} - containers: - - name: "mariadb" - image: {{ template "mariadb.image" . }} - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - {{- if .Values.db.user }} - - name: MARIADB_USER - value: "{{ .Values.db.user }}" - - name: MARIADB_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-password - {{- end }} - - name: MARIADB_DATABASE - value: "{{ .Values.db.name }}" - - name: MARIADB_REPLICATION_MODE - value: "slave" - - name: MARIADB_MASTER_HOST - value: {{ template "mariadb.fullname" . }} - - name: MARIADB_MASTER_PORT - value: "3306" - - name: MARIADB_MASTER_USER - value: "root" - - name: MARIADB_MASTER_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - - name: MARIADB_REPLICATION_USER - value: "{{ .Values.replication.user }}" - - name: MARIADB_REPLICATION_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-replication-password - ports: - - name: mysql - containerPort: 3306 - {{- if .Values.slave.livenessProbe.enabled }} - livenessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.slave.livenessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.livenessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.livenessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.livenessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.livenessProbe.failureThreshold }} - {{- end }} - {{- if .Values.slave.readinessProbe.enabled }} - readinessProbe: - exec: - command: ["sh", "-c", "exec mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] - initialDelaySeconds: {{ .Values.slave.readinessProbe.initialDelaySeconds }} - periodSeconds: {{ .Values.slave.readinessProbe.periodSeconds }} - timeoutSeconds: {{ .Values.slave.readinessProbe.timeoutSeconds }} - successThreshold: {{ .Values.slave.readinessProbe.successThreshold }} - failureThreshold: {{ .Values.slave.readinessProbe.failureThreshold }} - {{- end }} - resources: -{{ toYaml .Values.slave.resources | indent 10 }} - volumeMounts: - - name: data - mountPath: /bitnami/mariadb -{{- if .Values.slave.config }} - - name: config - mountPath: /opt/bitnami/mariadb/conf/my.cnf - subPath: my.cnf -{{- end }} -{{- if .Values.metrics.enabled }} - - name: metrics - image: {{ template "metrics.image" . }} - imagePullPolicy: {{ .Values.metrics.image.pullPolicy | quote }} - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - command: [ 'sh', '-c', 'DATA_SOURCE_NAME="root:$MARIADB_ROOT_PASSWORD@(localhost:3306)/" /bin/mysqld_exporter' ] - ports: - - name: metrics - containerPort: 9104 - livenessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 15 - timeoutSeconds: 5 - readinessProbe: - httpGet: - path: /metrics - port: metrics - initialDelaySeconds: 5 - timeoutSeconds: 1 - resources: -{{ toYaml .Values.metrics.resources | indent 10 }} -{{- end }} - volumes: - {{- if .Values.slave.config }} - - name: config - configMap: - name: {{ template "slave.fullname" . }} - {{- end }} -{{- if .Values.slave.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} - spec: - accessModes: - {{- range .Values.slave.persistence.accessModes }} - - {{ . | quote }} - {{- end }} - resources: - requests: - storage: {{ .Values.slave.persistence.size | quote }} - {{- if .Values.slave.persistence.storageClass }} - {{- if (eq "-" .Values.slave.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.slave.persistence.storageClass | quote }} - {{- end }} - {{- end }} -{{- else }} - - name: "data" - emptyDir: {} -{{- end }} -{{- end }} diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml deleted file mode 100755 index fa551371f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/slave-svc.yaml +++ /dev/null @@ -1,31 +0,0 @@ -{{- if .Values.replication.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "slave.fullname" . }} - labels: - app: "{{ template "mariadb.name" . }}" - chart: {{ template "mariadb.chart" . }} - component: "slave" - release: {{ .Release.Name | quote }} - heritage: {{ .Release.Service | quote }} -{{- if .Values.metrics.enabled }} - annotations: -{{ toYaml .Values.metrics.annotations | indent 4 }} -{{- end }} -spec: - type: {{ .Values.service.type }} - ports: - - name: mysql - port: {{ .Values.service.port }} - targetPort: mysql -{{- if .Values.metrics.enabled }} - - name: metrics - port: 9104 - targetPort: metrics -{{- end }} - selector: - app: "{{ template "mariadb.name" . }}" - component: "slave" - release: "{{ .Release.Name }}" -{{- end }} \ No newline at end of file diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml deleted file mode 100755 index 99a85d4aa..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/test-runner.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: "{{ template "mariadb.fullname" . }}-test-{{ randAlphaNum 5 | lower }}" - annotations: - "helm.sh/hook": test-success -spec: - initContainers: - - name: "test-framework" - image: "dduportal/bats:0.4.0" - command: - - "bash" - - "-c" - - | - set -ex - # copy bats to tools dir - cp -R /usr/local/libexec/ /tools/bats/ - volumeMounts: - - mountPath: /tools - name: tools - containers: - - name: mariadb-test - image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy | quote }} - command: ["/tools/bats/bats", "-t", "/tests/run.sh"] - env: - - name: MARIADB_ROOT_PASSWORD - valueFrom: - secretKeyRef: - name: {{ template "mariadb.fullname" . }} - key: mariadb-root-password - volumeMounts: - - mountPath: /tests - name: tests - readOnly: true - - mountPath: /tools - name: tools - volumes: - - name: tests - configMap: - name: {{ template "mariadb.fullname" . }}-tests - - name: tools - emptyDir: {} - restartPolicy: Never diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml deleted file mode 100755 index 957f3fd1e..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/templates/tests.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ template "mariadb.fullname" . }}-tests -data: - run.sh: |- - @test "Testing MariaDB is accessible" { - mysql -h {{ template "mariadb.fullname" . }} -uroot -p$MARIADB_ROOT_PASSWORD -e 'show databases;' - } diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml deleted file mode 100755 index ce2414e9f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/charts/mariadb/values.yaml +++ /dev/null @@ -1,233 +0,0 @@ -## Bitnami MariaDB image -## ref: https://hub.docker.com/r/bitnami/mariadb/tags/ -## -image: - registry: docker.io - repository: bitnami/mariadb - tag: 10.1.34-debian-9 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistrKeySecretName - -service: - ## Kubernetes service type - type: ClusterIP - port: 3306 - -rootUser: - ## MariaDB admin password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-the-root-password-on-first-run - ## - password: - ## Use existing secret (ignores root, db and replication passwords) - # existingSecret: - ## - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -db: - ## MariaDB username and password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#creating-a-database-user-on-first-run - ## - user: - password: - ## Password is ignored if existingSecret is specified. - ## Database to create - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#creating-a-database-on-first-run - ## - name: my_database - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -replication: - ## Enable replication. This enables the creation of replicas of MariaDB. If false, only a - ## master deployment would be created - enabled: true - ## - ## MariaDB replication user - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-up-a-replication-cluster - ## - user: replicator - ## MariaDB replication user password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb#setting-up-a-replication-cluster - ## - password: - ## Password is ignored if existingSecret is specified. - ## - ## Option to force users to specify a password. That is required for 'helm upgrade' to work properly. - ## If it is not force, a random password will be generated. - forcePassword: false - -master: - antiAffinity: soft - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - persistence: - ## If true, use a Persistent Volume Claim, If false, use emptyDir - ## - enabled: true - ## Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - ## Persistent Volume Claim annotations - ## - annotations: - ## Persistent Volume Access Mode - ## - accessModes: - - ReadWriteOnce - ## Persistent Volume size - ## - size: 8Gi - ## - - ## Configure MySQL with a custom my.cnf file - ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file - ## - config: |- - [mysqld] - skip-name-resolve - explicit_defaults_for_timestamp - basedir=/opt/bitnami/mariadb - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - tmpdir=/opt/bitnami/mariadb/tmp - max_allowed_packet=16M - bind-address=0.0.0.0 - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - log-error=/opt/bitnami/mariadb/logs/mysqld.log - character-set-server=UTF8 - collation-server=utf8_general_ci - - [client] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - default-character-set=UTF8 - - [manager] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - - ## Configure master resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: {} - livenessProbe: - enabled: true - ## - ## Initializing the database could take some time - initialDelaySeconds: 120 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - enabled: true - initialDelaySeconds: 15 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - -slave: - replicas: 1 - antiAffinity: soft - persistence: - ## If true, use a Persistent Volume Claim, If false, use emptyDir - ## - enabled: true - # storageClass: "-" - annotations: - accessModes: - - ReadWriteOnce - ## Persistent Volume size - ## - size: 8Gi - ## - - ## Configure MySQL slave with a custom my.cnf file - ## ref: https://mysql.com/kb/en/mysql/configuring-mysql-with-mycnf/#example-of-configuration-file - ## - config: |- - [mysqld] - skip-name-resolve - explicit_defaults_for_timestamp - basedir=/opt/bitnami/mariadb - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - tmpdir=/opt/bitnami/mariadb/tmp - max_allowed_packet=16M - bind-address=0.0.0.0 - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - log-error=/opt/bitnami/mariadb/logs/mysqld.log - character-set-server=UTF8 - collation-server=utf8_general_ci - - [client] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - default-character-set=UTF8 - - [manager] - port=3306 - socket=/opt/bitnami/mariadb/tmp/mysql.sock - pid-file=/opt/bitnami/mariadb/tmp/mysqld.pid - - ## - ## Configure slave resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - ## - resources: {} - livenessProbe: - enabled: true - ## - ## Initializing the database could take some time - initialDelaySeconds: 120 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - readinessProbe: - enabled: true - initialDelaySeconds: 15 - ## - ## Default Kubernetes values - periodSeconds: 10 - timeoutSeconds: 1 - successThreshold: 1 - failureThreshold: 3 - -metrics: - enabled: false - image: - registry: docker.io - repository: prom/mysqld-exporter - tag: v0.10.0 - pullPolicy: IfNotPresent - resources: {} - annotations: - prometheus.io/scrape: "true" - prometheus.io/port: "9104" diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock deleted file mode 100755 index dcda2b142..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: mariadb - repository: https://charts.helm.sh/stable/ - version: 4.3.1 -digest: sha256:82a0e5374376169d2ecf7d452c18a2ed93507f5d17c3393a1457f9ffad7e9b26 -generated: 2018-08-02T22:07:51.905271776Z diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml deleted file mode 100755 index fef7d0b7f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -dependencies: -- name: mariadb - version: 4.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - wordpress-database diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt deleted file mode 100755 index 75ed9b64f..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -Placeholder. diff --git a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml b/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml deleted file mode 100755 index 3cb66dafd..000000000 --- a/pkg/action/testdata/charts/chart-with-uncompressed-dependencies/values.yaml +++ /dev/null @@ -1,254 +0,0 @@ -## Bitnami WordPress image version -## ref: https://hub.docker.com/r/bitnami/wordpress/tags/ -## -image: - registry: docker.io - repository: bitnami/wordpress - tag: 4.9.8-debian-9 - ## Specify a imagePullPolicy - ## Defaults to 'Always' if image tag is 'latest', else set to 'IfNotPresent' - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - ## - pullPolicy: IfNotPresent - ## Optionally specify an array of imagePullSecrets. - ## Secrets must be manually created in the namespace. - ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ - ## - # pullSecrets: - # - myRegistrKeySecretName - -## User of the application -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressUsername: user - -## Application password -## Defaults to a random 10-character alphanumeric string if not set -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -# wordpressPassword: - -## Admin email -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressEmail: user@example.com - -## First name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressFirstName: FirstName - -## Last name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressLastName: LastName - -## Blog name -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressBlogName: User's Blog! - -## Table prefix -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -## -wordpressTablePrefix: wp_ - -## Set to `yes` to allow the container to be started with blank passwords -## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables -allowEmptyPassword: yes - -## SMTP mail delivery configuration -## ref: https://github.com/bitnami/bitnami-docker-wordpress/#smtp-configuration -## -# smtpHost: -# smtpPort: -# smtpUser: -# smtpPassword: -# smtpUsername: -# smtpProtocol: - -replicaCount: 1 - -externalDatabase: -## All of these values are only used when mariadb.enabled is set to false - ## Database host - host: localhost - - ## non-root Username for Wordpress Database - user: bn_wordpress - - ## Database password - password: "" - - ## Database name - database: bitnami_wordpress - - ## Database port number - port: 3306 - -## -## MariaDB chart configuration -## -mariadb: - ## Whether to deploy a mariadb server to satisfy the applications database requirements. To use an external database set this to false and configure the externalDatabase parameters - enabled: true - ## Disable MariaDB replication - replication: - enabled: false - - ## Create a database and a database user - ## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#creating-a-database-user-on-first-run - ## - db: - name: bitnami_wordpress - user: bn_wordpress - ## If the password is not specified, mariadb will generates a random password - ## - # password: - - ## MariaDB admin password - ## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#setting-the-root-password-on-first-run - ## - # rootUser: - # password: - - ## Enable persistence using Persistent Volume Claims - ## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ - ## - master: - persistence: - enabled: true - ## mariadb data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - accessMode: ReadWriteOnce - size: 8Gi - -## Kubernetes configuration -## For minikube, set this to NodePort, elsewhere use LoadBalancer or ClusterIP -## -serviceType: LoadBalancer -## -## serviceType: NodePort -## nodePorts: -## http: -## https: -nodePorts: - http: "" - https: "" -## Enable client source IP preservation -## ref http://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip -## -serviceExternalTrafficPolicy: Cluster - -## Allow health checks to be pointed at the https port -healthcheckHttps: false - -## Configure extra options for liveness and readiness probes -## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) -livenessProbe: - initialDelaySeconds: 120 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 -readinessProbe: - initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 6 - successThreshold: 1 - -## Configure the ingress resource that allows you to access the -## Wordpress installation. Set up the URL -## ref: http://kubernetes.io/docs/user-guide/ingress/ -## -ingress: - ## Set to true to enable ingress record generation - enabled: false - - ## The list of hostnames to be covered with this ingress record. - ## Most likely this will be just one host, but in the event more hosts are needed, this is an array - hosts: - - name: wordpress.local - - ## Set this to true in order to enable TLS on the ingress record - ## A side effect of this will be that the backend wordpress service will be connected at port 443 - tls: false - - ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS - tlsSecret: wordpress.local-tls - - ## Ingress annotations done as key:value pairs - ## If you're using kube-lego, you will want to add: - ## kubernetes.io/tls-acme: true - ## - ## For a full list of possible ingress annotations, please see - ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/annotations.md - ## - ## If tls is set to true, annotation ingress.kubernetes.io/secure-backends: "true" will automatically be set - annotations: - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: true - - secrets: - ## If you're providing your own certificates, please use this to add the certificates as secrets - ## key and certificate should start with -----BEGIN CERTIFICATE----- or - ## -----BEGIN RSA PRIVATE KEY----- - ## - ## name should line up with a tlsSecret set further up - ## If you're using kube-lego, this is unneeded, as it will create the secret for you if it is not set - ## - ## It is also possible to create and manage the certificates outside of this helm chart - ## Please see README.md for more information - # - name: wordpress.local-tls - # key: - # certificate: - -## Enable persistence using Persistent Volume Claims -## ref: http://kubernetes.io/docs/user-guide/persistent-volumes/ -## -persistence: - enabled: true - ## wordpress data Persistent Volume Storage Class - ## If defined, storageClassName: - ## If set to "-", storageClassName: "", which disables dynamic provisioning - ## If undefined (the default) or set to null, no storageClassName spec is - ## set, choosing the default provisioner. (gp2 on AWS, standard on - ## GKE, AWS & OpenStack) - ## - # storageClass: "-" - ## - ## If you want to reuse an existing claim, you can pass the name of the PVC using - ## the existingClaim variable - # existingClaim: your-claim - accessMode: ReadWriteOnce - size: 10Gi - -## Configure resource requests and limits -## ref: http://kubernetes.io/docs/user-guide/compute-resources/ -## -resources: - requests: - memory: 512Mi - cpu: 300m - -## Node labels for pod assignment -## Ref: https://kubernetes.io/docs/user-guide/node-selection/ -## -nodeSelector: {} - -## Tolerations for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ -## -tolerations: [] - -## Affinity for pod assignment -## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity -## -affinity: {} diff --git a/pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz b/pkg/action/testdata/charts/compressedchart-0.1.0.tar.gz deleted file mode 100644 index 3c9c24d76063d6a904405b2d6a7a84cb087f1ce4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 477 zcmV<30V4h%iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL4vi_<_5!26s}F{UjHhD>x0Xejw~|-egB1QU=&JEdrHEIt>CEtv%dd}n=<=92zSKn;o(8OM@#S> zYFc5(0#_R!xxRXQ%zfa$rtiOMiLGgzk94**j`?3M7Jt0|r`i8O7{f;tq39Bbhr@%1 zO-l}zo#EQJ1_D-Jv7w}jF??!Gg4BiJqa;WzF+;Dc zVQyr3R8em|NM&qo0PL4vi_<_5!26s}F{UjHhD>x0Xejw~|-egB1QU=&JEdrHEIt>CEtv%dd}n=<=92zSKn;o(8OM@#S> zYFc5(0#_R!xxRXQ%zfa$rtiOMiLGgzk94**j`?3M7Jt0|r`i8O7{f;tq39Bbhr@%1 zO-l}zo#EQJ1_D-Jv7w}jF??!Gg4BiJqa;WzF+;Dc zVQyr3R8em|NM&qo0PL4vi_<_5!26s}F)eG5__n?E62T&$9nR zaPZh}uYVQ7^}*#!N0u3azW+itFbbuoJtg79R&dn+S>OM~O_}{4ggavP@bIACqb2wb zHLb8?fvb&=Twgst=05OW)AwJs#MU&^XY$NVoBi$C7~)9n8sjNv1SP;?2z!{Nch zrX>f<&Tws90|BeA*icf%7(TToLFz*AQ4*wspoCYeko Tb2>i)00960YK^g$02BZKS`FmP diff --git a/pkg/action/testdata/charts/compressedchart-0.3.0.tgz b/pkg/action/testdata/charts/compressedchart-0.3.0.tgz deleted file mode 100644 index 051bd6fd9a030a3f4327e8002a8912c1f5f68050..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 477 zcmV<30V4h%iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL4vi_<_5!26s}Fj!(#55d%N%6r{|dRMblL`R$bVgxL;q*9Y4-md z>^-)d>tBU%y?6Q2k!8-o(0?Ht7=_Z)o|156D>&%?bm;&5rp*4Ig*#&Q@bIACqb2wb z4K1-}fvdHT++00A=05OWGxT3|#MZRVM>^RWhx{++^FQAIRrdcZjNv1SP;?2z!~Vg; zx+4dR-f&}F3jyn|*iur(7(R6-LFz;BQ4*w%n4x9A0E<$0#EPK51s@!5z`Na*+mIko1U8OTq2AtqxfdU)P_4<|CYeko Tb38u+00960Q~zm}02BZK0h8&p diff --git a/pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz b/pkg/action/testdata/charts/compressedchart-with-hyphens-0.1.0.tgz deleted file mode 100644 index 379210a92c1795429d2c4baae389f1e971f26824..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 548 zcmV+<0^9u`iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL4fi`y^|#dG$jc->rDXjD0A64=|)92WW)wwIoYVoz*QSrU?* zG;H^~7dcDXrjTqgP3fZFMMBsbi zPWu0B_M89nr2n%p#E0nPPIpGV%QZGNX)If*N~tSYQG5|qH0t|nfN!leE_nEwltQJ< z5{(E&Ep_!Aj+6*;9W6i9KdlR0WlY0RR8Xa#gbc6aWCh@%&~0 diff --git a/pkg/action/testdata/charts/corrupted-compressed-chart.tgz b/pkg/action/testdata/charts/corrupted-compressed-chart.tgz deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/action/testdata/charts/decompressedchart/Chart.yaml b/pkg/action/testdata/charts/decompressedchart/Chart.yaml deleted file mode 100644 index 92ba4d88f..000000000 --- a/pkg/action/testdata/charts/decompressedchart/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: decompressedchart -version: 0.1.0 diff --git a/pkg/action/testdata/charts/decompressedchart/values.yaml b/pkg/action/testdata/charts/decompressedchart/values.yaml deleted file mode 100644 index a940d1fd9..000000000 --- a/pkg/action/testdata/charts/decompressedchart/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for decompressedchart. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. - name: my-decompressed-chart diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml deleted file mode 100644 index e33c97e8c..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -name: multiplecharts-lint-chart-1 -version: "1" -icon: "" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml deleted file mode 100644 index 88ebf2468..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/templates/configmap.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -metadata: - name: multicharttest-chart1-configmap -data: - dat: | - {{ .Values.config | indent 4 }} diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml deleted file mode 100644 index aafb09e4b..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-1/values.yaml +++ /dev/null @@ -1 +0,0 @@ -config: "Test" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml deleted file mode 100644 index b27de2754..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -name: multiplecharts-lint-chart-2 -version: "1" -icon: "" \ No newline at end of file diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml deleted file mode 100644 index 8484bfe6a..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/templates/configmap.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -metadata: - name: multicharttest-chart2-configmap -data: - {{ toYaml .Values.config | indent 4 }} diff --git a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml b/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml deleted file mode 100644 index 9139f486e..000000000 --- a/pkg/action/testdata/charts/multiplecharts-lint-chart-2/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -config: - test: "Test" \ No newline at end of file diff --git a/pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz b/pkg/action/testdata/charts/pre-release-chart-0.1.0-alpha.tgz deleted file mode 100644 index 5d5770fed227de5f776eb9080eb377aad6293c1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 355 zcmV-p0i6CHiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK{zYV$x4g;nb*23$-3NWU)N&csC^NtY(&SQ_DlU9Ff|8T|G^ za%V#lh=@thS7=o1ZFbK&gK#2jnUs^}ND}@%OyBfO&PEG?h*+29ToLiQVwP7?_P@yL zI1Ma8b~7i_FmV`{SsQ%M$8b5@3*jnN45@T9YE&=p2h=9&w(}W z$?+C$9uN+r2y|ofk(Ta0{KWJPp`$V@VjMq`0UE1~Q@$ zJRGL~X*n=`@No8{Kwvjm3an`imvnLG 0 { - rel.Info.Description = u.Description - } else { - rel.Info.Description = "Uninstallation complete" - } - - if !u.KeepHistory { - u.cfg.Log("purge requested for %s", name) - err := u.purgeReleases(rels...) - if err != nil { - errs = append(errs, errors.Wrap(err, "uninstall: Failed to purge the release")) - } - - // Return the errors that occurred while deleting the release, if any - if len(errs) > 0 { - return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) - } - - return res, nil - } - - if err := u.cfg.Releases.Update(rel); err != nil { - u.cfg.Log("uninstall: Failed to store updated release: %s", err) - } - - if len(errs) > 0 { - return res, errors.Errorf("uninstallation completed with %d error(s): %s", len(errs), joinErrors(errs)) - } - return res, nil -} - -func (u *Uninstall) purgeReleases(rels ...*release.Release) error { - for _, rel := range rels { - if _, err := u.cfg.Releases.Delete(rel.Name, rel.Version); err != nil { - return err - } - } - return nil -} - -func joinErrors(errs []error) string { - es := make([]string, 0, len(errs)) - for _, e := range errs { - es = append(es, e.Error()) - } - return strings.Join(es, "; ") -} - -// deleteRelease deletes the release and returns list of delete resources and manifests that were kept in the deletion process -func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, string, []error) { - var errs []error - caps, err := u.cfg.getCapabilities() - if err != nil { - return nil, rel.Manifest, []error{errors.Wrap(err, "could not get apiVersions from Kubernetes")} - } - - manifests := releaseutil.SplitManifests(rel.Manifest) - _, files, err := releaseutil.SortManifests(manifests, caps.APIVersions, releaseutil.UninstallOrder) - if err != nil { - // We could instead just delete everything in no particular order. - // FIXME: One way to delete at this point would be to try a label-based - // deletion. The problem with this is that we could get a false positive - // and delete something that was not legitimately part of this release. - return nil, rel.Manifest, []error{errors.Wrap(err, "corrupted release record. You must manually delete the resources")} - } - - filesToKeep, filesToDelete := filterManifestsToKeep(files) - var kept string - for _, f := range filesToKeep { - kept += "[" + f.Head.Kind + "] " + f.Head.Metadata.Name + "\n" - } - - var builder strings.Builder - for _, file := range filesToDelete { - builder.WriteString("\n---\n" + file.Content) - } - - resources, err := u.cfg.KubeClient.Build(strings.NewReader(builder.String()), false) - if err != nil { - return nil, "", []error{errors.Wrap(err, "unable to build kubernetes objects for delete")} - } - if len(resources) > 0 { - _, errs = u.cfg.KubeClient.Delete(resources) - } - return resources, kept, errs -} diff --git a/pkg/action/uninstall_test.go b/pkg/action/uninstall_test.go deleted file mode 100644 index 9cc75520b..000000000 --- a/pkg/action/uninstall_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -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 action - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" -) - -func uninstallAction(t *testing.T) *Uninstall { - config := actionConfigFixture(t) - unAction := NewUninstall(config) - return unAction -} - -func TestUninstallRelease_deleteRelease(t *testing.T) { - is := assert.New(t) - - unAction := uninstallAction(t) - unAction.DisableHooks = true - unAction.DryRun = false - unAction.KeepHistory = true - - rel := releaseStub() - rel.Name = "keep-secret" - rel.Manifest = `{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "secret", - "annotations": { - "helm.sh/resource-policy": "keep" - } - }, - "type": "Opaque", - "data": { - "password": "password" - } - }` - unAction.cfg.Releases.Create(rel) - res, err := unAction.Run(rel.Name) - is.NoError(err) - expected := `These resources were kept due to the resource policy: -[Secret] secret -` - is.Contains(res.Info, expected) -} - -func TestUninstallRelease_Wait(t *testing.T) { - is := assert.New(t) - - unAction := uninstallAction(t) - unAction.DisableHooks = true - unAction.DryRun = false - unAction.Wait = true - - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Manifest = `{ - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "secret" - }, - "type": "Opaque", - "data": { - "password": "password" - } - }` - unAction.cfg.Releases.Create(rel) - failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("U timed out") - unAction.cfg.KubeClient = failer - res, err := unAction.Run(rel.Name) - is.Error(err) - is.Contains(err.Error(), "U timed out") - is.Equal(res.Release.Info.Status, release.StatusUninstalled) -} diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go deleted file mode 100644 index 690397d4a..000000000 --- a/pkg/action/upgrade.go +++ /dev/null @@ -1,574 +0,0 @@ -/* -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 action - -import ( - "bytes" - "context" - "fmt" - "strings" - "sync" - "time" - - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/kube" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// Upgrade is the action for upgrading releases. -// -// It provides the implementation of 'helm upgrade'. -type Upgrade struct { - cfg *Configuration - - ChartPathOptions - - // Install is a purely informative flag that indicates whether this upgrade was done in "install" mode. - // - // Applications may use this to determine whether this Upgrade operation was done as part of a - // pure upgrade (Upgrade.Install == false) or as part of an install-or-upgrade operation - // (Upgrade.Install == true). - // - // Setting this to `true` will NOT cause `Upgrade` to perform an install if the release does not exist. - // That process must be handled by creating an Install action directly. See cmd/upgrade.go for an - // example of how this flag is used. - Install bool - // Devel indicates that the operation is done in devel mode. - Devel bool - // Namespace is the namespace in which this operation should be performed. - Namespace string - // SkipCRDs skips installing CRDs when install flag is enabled during upgrade - SkipCRDs bool - // Timeout is the timeout for this operation - Timeout time.Duration - // Wait determines whether the wait operation should be performed after the upgrade is requested. - Wait bool - // WaitForJobs determines whether the wait operation for the Jobs should be performed after the upgrade is requested. - WaitForJobs bool - // DisableHooks disables hook processing if set to true. - DisableHooks bool - // DryRun controls whether the operation is prepared, but not executed. - // If `true`, the upgrade is prepared but not performed. - DryRun bool - // Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway. - // - // This should be used with caution. - Force bool - // ResetValues will reset the values to the chart's built-ins rather than merging with existing. - ResetValues bool - // ReuseValues will re-use the user's last supplied values. - ReuseValues bool - // Recreate will (if true) recreate pods after a rollback. - Recreate bool - // MaxHistory limits the maximum number of revisions saved per release - MaxHistory int - // Atomic, if true, will roll back on failure. - Atomic bool - // CleanupOnFail will, if true, cause the upgrade to delete newly-created resources on a failed update. - CleanupOnFail bool - // SubNotes determines whether sub-notes are rendered in the chart. - SubNotes bool - // Description is the description of this operation - Description string - // PostRender is an optional post-renderer - // - // If this is non-nil, then after templates are rendered, they will be sent to the - // post renderer before sending to the Kubernetes API server. - PostRenderer postrender.PostRenderer - // DisableOpenAPIValidation controls whether OpenAPI validation is enforced. - DisableOpenAPIValidation bool - // Get missing dependencies - DependencyUpdate bool - // Lock to control raceconditions when the process receives a SIGTERM - Lock sync.Mutex -} - -type resultMessage struct { - r *release.Release - e error -} - -// NewUpgrade creates a new Upgrade object with the given configuration. -func NewUpgrade(cfg *Configuration) *Upgrade { - up := &Upgrade{ - cfg: cfg, - } - up.ChartPathOptions.registryClient = cfg.RegistryClient - - return up -} - -// Run executes the upgrade on the given release. -func (u *Upgrade) Run(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - ctx := context.Background() - return u.RunWithContext(ctx, name, chart, vals) -} - -// RunWithContext executes the upgrade on the given release with context. -func (u *Upgrade) RunWithContext(ctx context.Context, name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, error) { - if err := u.cfg.KubeClient.IsReachable(); err != nil { - return nil, err - } - - // Make sure if Atomic is set, that wait is set as well. This makes it so - // the user doesn't have to specify both - u.Wait = u.Wait || u.Atomic - - if err := chartutil.ValidateReleaseName(name); err != nil { - return nil, errors.Errorf("release name is invalid: %s", name) - } - u.cfg.Log("preparing upgrade for %s", name) - currentRelease, upgradedRelease, err := u.prepareUpgrade(name, chart, vals) - if err != nil { - return nil, err - } - - u.cfg.Releases.MaxHistory = u.MaxHistory - - u.cfg.Log("performing update for %s", name) - res, err := u.performUpgrade(ctx, currentRelease, upgradedRelease) - if err != nil { - return res, err - } - - if !u.DryRun { - u.cfg.Log("updating status for upgraded release for %s", name) - if err := u.cfg.Releases.Update(upgradedRelease); err != nil { - return res, err - } - } - - return res, nil -} - -// prepareUpgrade builds an upgraded release for an upgrade operation. -func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[string]interface{}) (*release.Release, *release.Release, error) { - if chart == nil { - return nil, nil, errMissingChart - } - - // finds the last non-deleted release with the given name - lastRelease, err := u.cfg.Releases.Last(name) - if err != nil { - // to keep existing behavior of returning the "%q has no deployed releases" error when an existing release does not exist - if errors.Is(err, driver.ErrReleaseNotFound) { - return nil, nil, driver.NewErrNoDeployedReleases(name) - } - return nil, nil, err - } - - // Concurrent `helm upgrade`s will either fail here with `errPending` or when creating the release with "already exists". This should act as a pessimistic lock. - if lastRelease.Info.Status.IsPending() { - return nil, nil, errPending - } - - var currentRelease *release.Release - if lastRelease.Info.Status == release.StatusDeployed { - // no need to retrieve the last deployed release from storage as the last release is deployed - currentRelease = lastRelease - } else { - // finds the deployed release with the given name - currentRelease, err = u.cfg.Releases.Deployed(name) - if err != nil { - if errors.Is(err, driver.ErrNoDeployedReleases) && - (lastRelease.Info.Status == release.StatusFailed || lastRelease.Info.Status == release.StatusSuperseded) { - currentRelease = lastRelease - } else { - return nil, nil, err - } - } - } - - // determine if values will be reused - vals, err = u.reuseValues(chart, currentRelease, vals) - if err != nil { - return nil, nil, err - } - - if err := chartutil.ProcessDependencies(chart, vals); err != nil { - return nil, nil, err - } - - // Increment revision count. This is passed to templates, and also stored on - // the release object. - revision := lastRelease.Version + 1 - - options := chartutil.ReleaseOptions{ - Name: name, - Namespace: currentRelease.Namespace, - Revision: revision, - IsUpgrade: true, - } - - caps, err := u.cfg.getCapabilities() - if err != nil { - return nil, nil, err - } - valuesToRender, err := chartutil.ToRenderValues(chart, vals, options, caps) - if err != nil { - return nil, nil, err - } - - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, u.DryRun) - if err != nil { - return nil, nil, err - } - - // Store an upgraded release. - upgradedRelease := &release.Release{ - Name: name, - Namespace: currentRelease.Namespace, - Chart: chart, - Config: vals, - Info: &release.Info{ - FirstDeployed: currentRelease.Info.FirstDeployed, - LastDeployed: Timestamper(), - Status: release.StatusPendingUpgrade, - Description: "Preparing upgrade", // This should be overwritten later. - }, - Version: revision, - Manifest: manifestDoc.String(), - Hooks: hooks, - } - - if len(notesTxt) > 0 { - upgradedRelease.Info.Notes = notesTxt - } - err = validateManifest(u.cfg.KubeClient, manifestDoc.Bytes(), !u.DisableOpenAPIValidation) - return currentRelease, upgradedRelease, err -} - -func (u *Upgrade) performUpgrade(ctx context.Context, originalRelease, upgradedRelease *release.Release) (*release.Release, error) { - current, err := u.cfg.KubeClient.Build(bytes.NewBufferString(originalRelease.Manifest), false) - if err != nil { - // Checking for removed Kubernetes API error so can provide a more informative error message to the user - // Ref: https://github.com/helm/helm/issues/7219 - if strings.Contains(err.Error(), "unable to recognize \"\": no matches for kind") { - return upgradedRelease, errors.Wrap(err, "current release manifest contains removed kubernetes api(s) for this "+ - "kubernetes version and it is therefore unable to build the kubernetes "+ - "objects for performing the diff. error from kubernetes") - } - return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from current release manifest") - } - target, err := u.cfg.KubeClient.Build(bytes.NewBufferString(upgradedRelease.Manifest), !u.DisableOpenAPIValidation) - if err != nil { - return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest") - } - - // It is safe to use force only on target because these are resources currently rendered by the chart. - err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true)) - if err != nil { - return upgradedRelease, err - } - - // Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist - existingResources := make(map[string]bool) - for _, r := range current { - existingResources[objectKey(r)] = true - } - - var toBeCreated kube.ResourceList - for _, r := range target { - if !existingResources[objectKey(r)] { - toBeCreated = append(toBeCreated, r) - } - } - - toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace) - if err != nil { - return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update") - } - - toBeUpdated.Visit(func(r *resource.Info, err error) error { - if err != nil { - return err - } - current.Append(r) - return nil - }) - - if u.DryRun { - u.cfg.Log("dry run for %s", upgradedRelease.Name) - if len(u.Description) > 0 { - upgradedRelease.Info.Description = u.Description - } else { - upgradedRelease.Info.Description = "Dry run complete" - } - return upgradedRelease, nil - } - - u.cfg.Log("creating upgraded release for %s", upgradedRelease.Name) - if err := u.cfg.Releases.Create(upgradedRelease); err != nil { - return nil, err - } - rChan := make(chan resultMessage) - ctxChan := make(chan resultMessage) - doneChan := make(chan interface{}) - defer close(doneChan) - go u.releasingUpgrade(rChan, upgradedRelease, current, target, originalRelease) - go u.handleContext(ctx, doneChan, ctxChan, upgradedRelease) - select { - case result := <-rChan: - return result.r, result.e - case result := <-ctxChan: - return result.r, result.e - } -} - -// Function used to lock the Mutex, this is important for the case when the atomic flag is set. -// In that case the upgrade will finish before the rollback is finished so it is necessary to wait for the rollback to finish. -// The rollback will be trigger by the function failRelease -func (u *Upgrade) reportToPerformUpgrade(c chan<- resultMessage, rel *release.Release, created kube.ResourceList, err error) { - u.Lock.Lock() - if err != nil { - rel, err = u.failRelease(rel, created, err) - } - c <- resultMessage{r: rel, e: err} - u.Lock.Unlock() -} - -// Setup listener for SIGINT and SIGTERM -func (u *Upgrade) handleContext(ctx context.Context, done chan interface{}, c chan<- resultMessage, upgradedRelease *release.Release) { - select { - case <-ctx.Done(): - err := ctx.Err() - - // when the atomic flag is set the ongoing release finish first and doesn't give time for the rollback happens. - u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, err) - case <-done: - return - } -} -func (u *Upgrade) releasingUpgrade(c chan<- resultMessage, upgradedRelease *release.Release, current kube.ResourceList, target kube.ResourceList, originalRelease *release.Release) { - // pre-upgrade hooks - - if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPreUpgrade, u.Timeout); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, kube.ResourceList{}, fmt.Errorf("pre-upgrade hooks failed: %s", err)) - return - } - } else { - u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) - } - - results, err := u.cfg.KubeClient.Update(current, target, u.Force) - if err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - - if u.Recreate { - // NOTE: Because this is not critical for a release to succeed, we just - // log if an error occurs and continue onward. If we ever introduce log - // levels, we should make these error level logs so users are notified - // that they'll need to go do the cleanup on their own - if err := recreate(u.cfg, results.Updated); err != nil { - u.cfg.Log(err.Error()) - } - } - - if u.Wait { - u.cfg.Log( - "waiting for release %s resources (created: %d updated: %d deleted: %d)", - upgradedRelease.Name, len(results.Created), len(results.Updated), len(results.Deleted)) - if u.WaitForJobs { - if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - } else { - if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { - u.cfg.recordRelease(originalRelease) - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, err) - return - } - } - } - - // post-upgrade hooks - if !u.DisableHooks { - if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { - u.reportToPerformUpgrade(c, upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) - return - } - } - - originalRelease.Info.Status = release.StatusSuperseded - u.cfg.recordRelease(originalRelease) - - upgradedRelease.Info.Status = release.StatusDeployed - if len(u.Description) > 0 { - upgradedRelease.Info.Description = u.Description - } else { - upgradedRelease.Info.Description = "Upgrade complete" - } - u.reportToPerformUpgrade(c, upgradedRelease, nil, nil) -} - -func (u *Upgrade) failRelease(rel *release.Release, created kube.ResourceList, err error) (*release.Release, error) { - msg := fmt.Sprintf("Upgrade %q failed: %s", rel.Name, err) - u.cfg.Log("warning: %s", msg) - - rel.Info.Status = release.StatusFailed - rel.Info.Description = msg - u.cfg.recordRelease(rel) - if u.CleanupOnFail && len(created) > 0 { - u.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(created)) - _, errs := u.cfg.KubeClient.Delete(created) - if errs != nil { - var errorList []string - for _, e := range errs { - errorList = append(errorList, e.Error()) - } - return rel, errors.Wrapf(fmt.Errorf("unable to cleanup resources: %s", strings.Join(errorList, ", ")), "an error occurred while cleaning up resources. original upgrade error: %s", err) - } - u.cfg.Log("Resource cleanup complete") - } - if u.Atomic { - u.cfg.Log("Upgrade failed and atomic is set, rolling back to last successful release") - - // As a protection, get the last successful release before rollback. - // If there are no successful releases, bail out - hist := NewHistory(u.cfg) - fullHistory, herr := hist.Run(rel.Name) - if herr != nil { - return rel, errors.Wrapf(herr, "an error occurred while finding last successful release. original upgrade error: %s", err) - } - - // There isn't a way to tell if a previous release was successful, but - // generally failed releases do not get superseded unless the next - // release is successful, so this should be relatively safe - filteredHistory := releaseutil.FilterFunc(func(r *release.Release) bool { - return r.Info.Status == release.StatusSuperseded || r.Info.Status == release.StatusDeployed - }).Filter(fullHistory) - if len(filteredHistory) == 0 { - return rel, errors.Wrap(err, "unable to find a previously successful release when attempting to rollback. original upgrade error") - } - - releaseutil.Reverse(filteredHistory, releaseutil.SortByRevision) - - rollin := NewRollback(u.cfg) - rollin.Version = filteredHistory[0].Version - rollin.Wait = true - rollin.WaitForJobs = u.WaitForJobs - rollin.DisableHooks = u.DisableHooks - rollin.Recreate = u.Recreate - rollin.Force = u.Force - rollin.Timeout = u.Timeout - if rollErr := rollin.Run(rel.Name); rollErr != nil { - return rel, errors.Wrapf(rollErr, "an error occurred while rolling back the release. original upgrade error: %s", err) - } - return rel, errors.Wrapf(err, "release %s failed, and has been rolled back due to atomic being set", rel.Name) - } - - return rel, err -} - -// reuseValues copies values from the current release to a new release if the -// new release does not have any values. -// -// If the request already has values, or if there are no values in the current -// release, this does nothing. -// -// This is skipped if the u.ResetValues flag is set, in which case the -// request values are not altered. -func (u *Upgrade) reuseValues(chart *chart.Chart, current *release.Release, newVals map[string]interface{}) (map[string]interface{}, error) { - if u.ResetValues { - // If ResetValues is set, we completely ignore current.Config. - u.cfg.Log("resetting values to the chart's original version") - return newVals, nil - } - - // If the ReuseValues flag is set, we always copy the old values over the new config's values. - if u.ReuseValues { - u.cfg.Log("reusing the old release's values") - - // We have to regenerate the old coalesced values: - oldVals, err := chartutil.CoalesceValues(current.Chart, current.Config) - if err != nil { - return nil, errors.Wrap(err, "failed to rebuild old values") - } - - newVals = chartutil.CoalesceTables(newVals, current.Config) - - chart.Values = oldVals - - return newVals, nil - } - - if len(newVals) == 0 && len(current.Config) > 0 { - u.cfg.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) - newVals = current.Config - } - return newVals, nil -} - -func validateManifest(c kube.Interface, manifest []byte, openAPIValidation bool) error { - _, err := c.Build(bytes.NewReader(manifest), openAPIValidation) - return err -} - -// recreate captures all the logic for recreating pods for both upgrade and -// rollback. If we end up refactoring rollback to use upgrade, this can just be -// made an unexported method on the upgrade action. -func recreate(cfg *Configuration, resources kube.ResourceList) error { - for _, res := range resources { - versioned := kube.AsVersioned(res) - selector, err := kube.SelectorsForObject(versioned) - if err != nil { - // If no selector is returned, it means this object is - // definitely not a pod, so continue onward - continue - } - - client, err := cfg.KubernetesClientSet() - if err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - - pods, err := client.CoreV1().Pods(res.Namespace).List(context.Background(), metav1.ListOptions{ - LabelSelector: selector.String(), - }) - if err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - - // Restart pods - for _, pod := range pods.Items { - // Delete each pod for get them restarted with changed spec. - if err := client.CoreV1().Pods(pod.Namespace).Delete(context.Background(), pod.Name, *metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { - return errors.Wrapf(err, "unable to recreate pods for object %s/%s because an error occurred", res.Namespace, res.Name) - } - } - } - return nil -} - -func objectKey(r *resource.Info) string { - gvk := r.Object.GetObjectKind().GroupVersionKind() - return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) -} diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go deleted file mode 100644 index 62922b373..000000000 --- a/pkg/action/upgrade_test.go +++ /dev/null @@ -1,390 +0,0 @@ -/* -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 action - -import ( - "context" - "fmt" - "testing" - "time" - - "helm.sh/helm/v3/pkg/chart" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - kubefake "helm.sh/helm/v3/pkg/kube/fake" - "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -func upgradeAction(t *testing.T) *Upgrade { - config := actionConfigFixture(t) - upAction := NewUpgrade(config) - upAction.Namespace = "spaced" - - return upAction -} - -func TestUpgradeRelease_Success(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "previous-release" - rel.Info.Status = release.StatusDeployed - req.NoError(upAction.cfg.Releases.Create(rel)) - - upAction.Wait = true - vals := map[string]interface{}{} - - ctx, done := context.WithCancel(context.Background()) - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - done() - req.NoError(err) - is.Equal(res.Info.Status, release.StatusDeployed) - - // Detecting previous bug where context termination after successful release - // caused release to fail. - time.Sleep(time.Millisecond * 100) - lastRelease, err := upAction.cfg.Releases.Last(rel.Name) - req.NoError(err) - is.Equal(lastRelease.Info.Status, release.StatusDeployed) -} - -func TestUpgradeRelease_Wait(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - upAction.cfg.KubeClient = failer - upAction.Wait = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_WaitForJobs(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - upAction.cfg.KubeClient = failer - upAction.Wait = true - upAction.WaitForJobs = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_CleanupOnFail(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitError = fmt.Errorf("I timed out") - failer.DeleteError = fmt.Errorf("I tried to delete nil") - upAction.cfg.KubeClient = failer - upAction.Wait = true - upAction.CleanupOnFail = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.NotContains(err.Error(), "unable to cleanup resources") - is.Contains(res.Info.Description, "I timed out") - is.Equal(res.Info.Status, release.StatusFailed) -} - -func TestUpgradeRelease_Atomic(t *testing.T) { - is := assert.New(t) - req := require.New(t) - - t.Run("atomic rollback succeeds", func(t *testing.T) { - upAction := upgradeAction(t) - - rel := releaseStub() - rel.Name = "nuketown" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - // We can't make Update error because then the rollback won't work - failer.WatchUntilReadyError = fmt.Errorf("arming key removed") - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - res, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(err.Error(), "arming key removed") - is.Contains(err.Error(), "atomic") - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) - // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, release.StatusDeployed) - }) - - t.Run("atomic uninstall fails", func(t *testing.T) { - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "fallout" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.UpdateError = fmt.Errorf("update fail") - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - _, err := upAction.Run(rel.Name, buildChart(), vals) - req.Error(err) - is.Contains(err.Error(), "update fail") - is.Contains(err.Error(), "an error occurred while rolling back the release") - }) -} - -func TestUpgradeRelease_ReuseValues(t *testing.T) { - is := assert.New(t) - - t.Run("reuse values should work with values", func(t *testing.T) { - upAction := upgradeAction(t) - - existingValues := map[string]interface{}{ - "name": "value", - "maxHeapSize": "128m", - "replicas": 2, - } - newValues := map[string]interface{}{ - "name": "newValue", - "maxHeapSize": "512m", - "cpu": "12m", - } - expectedValues := map[string]interface{}{ - "name": "newValue", - "maxHeapSize": "512m", - "cpu": "12m", - "replicas": 2, - } - - rel := releaseStub() - rel.Name = "nuketown" - rel.Info.Status = release.StatusDeployed - rel.Config = existingValues - - err := upAction.cfg.Releases.Create(rel) - is.NoError(err) - - upAction.ReuseValues = true - // setting newValues and upgrading - res, err := upAction.Run(rel.Name, buildChart(), newValues) - is.NoError(err) - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) - - if updatedRes == nil { - is.Fail("Updated Release is nil") - return - } - is.Equal(release.StatusDeployed, updatedRes.Info.Status) - is.Equal(expectedValues, updatedRes.Config) - }) - - t.Run("reuse values should not install disabled charts", func(t *testing.T) { - upAction := upgradeAction(t) - chartDefaultValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": true, - }, - } - dependency := chart.Dependency{ - Name: "subchart", - Version: "0.1.0", - Repository: "http://some-repo.com", - Condition: "subchart.enabled", - } - sampleChart := buildChart( - withName("sample"), - withValues(chartDefaultValues), - withMetadataDependency(dependency), - ) - now := helmtime.Now() - existingValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": false, - }, - } - rel := &release.Release{ - Name: "nuketown", - Info: &release.Info{ - FirstDeployed: now, - LastDeployed: now, - Status: release.StatusDeployed, - Description: "Named Release Stub", - }, - Chart: sampleChart, - Config: existingValues, - Version: 1, - } - err := upAction.cfg.Releases.Create(rel) - is.NoError(err) - - upAction.ReuseValues = true - sampleChartWithSubChart := buildChart( - withName(sampleChart.Name()), - withValues(sampleChart.Values), - withDependency(withName("subchart")), - withMetadataDependency(dependency), - ) - // reusing values and upgrading - res, err := upAction.Run(rel.Name, sampleChartWithSubChart, map[string]interface{}{}) - is.NoError(err) - - // Now get the upgraded release - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2) - is.NoError(err) - - if updatedRes == nil { - is.Fail("Updated Release is nil") - return - } - is.Equal(release.StatusDeployed, updatedRes.Info.Status) - is.Equal(0, len(updatedRes.Chart.Dependencies()), "expected 0 dependencies") - - expectedValues := map[string]interface{}{ - "subchart": map[string]interface{}{ - "enabled": false, - }, - } - is.Equal(expectedValues, updatedRes.Config) - }) -} - -func TestUpgradeRelease_Pending(t *testing.T) { - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "come-fail-away" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - rel2 := releaseStub() - rel2.Name = "come-fail-away" - rel2.Info.Status = release.StatusPendingUpgrade - rel2.Version = 2 - upAction.cfg.Releases.Create(rel2) - - vals := map[string]interface{}{} - - _, err := upAction.Run(rel.Name, buildChart(), vals) - req.Contains(err.Error(), "progress", err) -} - -func TestUpgradeRelease_Interrupted_Wait(t *testing.T) { - - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "interrupted-release" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 10 * time.Second - upAction.cfg.KubeClient = failer - upAction.Wait = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - - req.Error(err) - is.Contains(res.Info.Description, "Upgrade \"interrupted-release\" failed: context canceled") - is.Equal(res.Info.Status, release.StatusFailed) - -} - -func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) { - - is := assert.New(t) - req := require.New(t) - - upAction := upgradeAction(t) - rel := releaseStub() - rel.Name = "interrupted-release" - rel.Info.Status = release.StatusDeployed - upAction.cfg.Releases.Create(rel) - - failer := upAction.cfg.KubeClient.(*kubefake.FailingKubeClient) - failer.WaitDuration = 5 * time.Second - upAction.cfg.KubeClient = failer - upAction.Atomic = true - vals := map[string]interface{}{} - - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - time.AfterFunc(time.Second, cancel) - - res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(), vals) - - req.Error(err) - is.Contains(err.Error(), "release interrupted-release failed, and has been rolled back due to atomic being set: context canceled") - - // Now make sure it is actually upgraded - updatedRes, err := upAction.cfg.Releases.Get(res.Name, 3) - is.NoError(err) - // Should have rolled back to the previous - is.Equal(updatedRes.Info.Status, release.StatusDeployed) - -} diff --git a/pkg/action/validate.go b/pkg/action/validate.go deleted file mode 100644 index 73eb1937b..000000000 --- a/pkg/action/validate.go +++ /dev/null @@ -1,184 +0,0 @@ -/* -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 action - -import ( - "fmt" - - "github.com/pkg/errors" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/resource" - - "helm.sh/helm/v3/pkg/kube" -) - -var accessor = meta.NewAccessor() - -const ( - appManagedByLabel = "app.kubernetes.io/managed-by" - appManagedByHelm = "Helm" - helmReleaseNameAnnotation = "meta.helm.sh/release-name" - helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace" -) - -func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) { - var requireUpdate kube.ResourceList - - err := resources.Visit(func(info *resource.Info, err error) error { - if err != nil { - return err - } - - helper := resource.NewHelper(info.Client, info.Mapping) - existing, err := helper.Get(info.Namespace, info.Name) - if err != nil { - if apierrors.IsNotFound(err) { - return nil - } - return errors.Wrapf(err, "could not get information about the resource %s", resourceString(info)) - } - - // Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace. - if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err) - } - - requireUpdate.Append(info) - return nil - }) - - return requireUpdate, err -} - -func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error { - lbls, err := accessor.Labels(obj) - if err != nil { - return err - } - annos, err := accessor.Annotations(obj) - if err != nil { - return err - } - - var errs []error - if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil { - errs = append(errs, fmt.Errorf("label validation error: %s", err)) - } - if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) - } - if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil { - errs = append(errs, fmt.Errorf("annotation validation error: %s", err)) - } - - if len(errs) > 0 { - err := errors.New("invalid ownership metadata") - for _, e := range errs { - err = fmt.Errorf("%w; %s", err, e) - } - return err - } - - return nil -} - -func requireValue(meta map[string]string, k, v string) error { - actual, ok := meta[k] - if !ok { - return fmt.Errorf("missing key %q: must be set to %q", k, v) - } - if actual != v { - return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual) - } - return nil -} - -// setMetadataVisitor adds release tracking metadata to all resources. If force is enabled, existing -// ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an -// existing and conflicting value for the managed by label or Helm release/namespace annotations. -func setMetadataVisitor(releaseName, releaseNamespace string, force bool) resource.VisitorFunc { - return func(info *resource.Info, err error) error { - if err != nil { - return err - } - - if !force { - if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil { - return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err) - } - } - - if err := mergeLabels(info.Object, map[string]string{ - appManagedByLabel: appManagedByHelm, - }); err != nil { - return fmt.Errorf( - "%s labels could not be updated: %s", - resourceString(info), err, - ) - } - - if err := mergeAnnotations(info.Object, map[string]string{ - helmReleaseNameAnnotation: releaseName, - helmReleaseNamespaceAnnotation: releaseNamespace, - }); err != nil { - return fmt.Errorf( - "%s annotations could not be updated: %s", - resourceString(info), err, - ) - } - - return nil - } -} - -func resourceString(info *resource.Info) string { - _, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind() - return fmt.Sprintf( - "%s %q in namespace %q", - k, info.Name, info.Namespace, - ) -} - -func mergeLabels(obj runtime.Object, labels map[string]string) error { - current, err := accessor.Labels(obj) - if err != nil { - return err - } - return accessor.SetLabels(obj, mergeStrStrMaps(current, labels)) -} - -func mergeAnnotations(obj runtime.Object, annotations map[string]string) error { - current, err := accessor.Annotations(obj) - if err != nil { - return err - } - return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations)) -} - -// merge two maps, always taking the value on the right -func mergeStrStrMaps(current, desired map[string]string) map[string]string { - result := make(map[string]string) - for k, v := range current { - result[k] = v - } - for k, desiredVal := range desired { - result[k] = desiredVal - } - return result -} diff --git a/pkg/action/validate_test.go b/pkg/action/validate_test.go deleted file mode 100644 index a9c1cb49c..000000000 --- a/pkg/action/validate_test.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -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 action - -import ( - "testing" - - "helm.sh/helm/v3/pkg/kube" - - appsv1 "k8s.io/api/apps/v1" - - "github.com/stretchr/testify/assert" - "k8s.io/apimachinery/pkg/api/meta" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" -) - -func newDeploymentResource(name, namespace string) *resource.Info { - return &resource.Info{ - Name: name, - Mapping: &meta.RESTMapping{ - Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"}, - GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}, - }, - Object: &appsv1.Deployment{ - ObjectMeta: v1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - }, - } -} - -func TestCheckOwnership(t *testing.T) { - deployFoo := newDeploymentResource("foo", "ns-a") - - // Verify that a resource that lacks labels/annotations is not owned - err := checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set managed by label and verify annotation error message - _ = accessor.SetLabels(deployFoo.Object, map[string]string{ - appManagedByLabel: appManagedByHelm, - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set only the release name annotation and verify missing release namespace error message - _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ - helmReleaseNameAnnotation: "rel-a", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`) - - // Set both release name and namespace annotations and verify no ownership errors - _ = accessor.SetAnnotations(deployFoo.Object, map[string]string{ - helmReleaseNameAnnotation: "rel-a", - helmReleaseNamespaceAnnotation: "ns-a", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.NoError(t, err) - - // Verify ownership error for wrong release name - err = checkOwnership(deployFoo.Object, "rel-b", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`) - - // Verify ownership error for wrong release namespace - err = checkOwnership(deployFoo.Object, "rel-a", "ns-b") - assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`) - - // Verify ownership error for wrong manager label - _ = accessor.SetLabels(deployFoo.Object, map[string]string{ - appManagedByLabel: "helm", - }) - err = checkOwnership(deployFoo.Object, "rel-a", "ns-a") - assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`) -} - -func TestSetMetadataVisitor(t *testing.T) { - var ( - err error - deployFoo = newDeploymentResource("foo", "ns-a") - deployBar = newDeploymentResource("bar", "ns-a-system") - resources = kube.ResourceList{deployFoo, deployBar} - ) - - // Set release tracking metadata and verify no error - err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true)) - assert.NoError(t, err) - - // Verify that release "b" cannot take ownership of "a" - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) - - // Force release "b" to take ownership - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true)) - assert.NoError(t, err) - - // Check that there is now no ownership error when setting metadata without force - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.NoError(t, err) - - // Add a new resource that is missing ownership metadata and verify error - resources.Append(newDeploymentResource("baz", "default")) - err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false)) - assert.Error(t, err) - assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`) -} diff --git a/pkg/action/verify.go b/pkg/action/verify.go deleted file mode 100644 index f36239496..000000000 --- a/pkg/action/verify.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 action - -import ( - "fmt" - "strings" - - "helm.sh/helm/v3/pkg/downloader" -) - -// Verify is the action for building a given chart's Verify tree. -// -// It provides the implementation of 'helm verify'. -type Verify struct { - Keyring string - Out string -} - -// NewVerify creates a new Verify object with the given configuration. -func NewVerify() *Verify { - return &Verify{} -} - -// Run executes 'helm verify'. -func (v *Verify) Run(chartfile string) error { - var out strings.Builder - p, err := downloader.VerifyChart(chartfile, v.Keyring) - if err != nil { - return err - } - - for name := range p.SignedBy.Identities { - fmt.Fprintf(&out, "Signed by: %v\n", name) - } - fmt.Fprintf(&out, "Using Key With Fingerprint: %X\n", p.SignedBy.PrimaryKey.Fingerprint) - fmt.Fprintf(&out, "Chart Hash Verified: %s\n", p.FileHash) - - // TODO(mattfarina): The output is set as a property rather than returned - // to maintain the Go API. In Helm v4 this function should return the out - // and the property on the struct can be removed. - v.Out = out.String() - - return nil -} diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go deleted file mode 100644 index a3bed63a3..000000000 --- a/pkg/chart/chart.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -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 chart - -import ( - "path/filepath" - "regexp" - "strings" -) - -// APIVersionV1 is the API version number for version 1. -const APIVersionV1 = "v1" - -// APIVersionV2 is the API version number for version 2. -const APIVersionV2 = "v2" - -// 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. -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/pkg/chart/chart_test.go b/pkg/chart/chart_test.go deleted file mode 100644 index ef8cec3ad..000000000 --- a/pkg/chart/chart_test.go +++ /dev/null @@ -1,211 +0,0 @@ -/* -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 chart - -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: "v2", - 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/pkg/chart/dependency.go b/pkg/chart/dependency.go deleted file mode 100644 index d9d4ee981..000000000 --- a/pkg/chart/dependency.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -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 chart - -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"` - // 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"` - // 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"` - // A yaml path that resolves to a boolean, used for enabling/disabling charts (e.g. subchart1.enabled ) - Condition string `json:"condition,omitempty"` - // Tags can be used to group charts for enabling/disabling together - Tags []string `json:"tags,omitempty"` - // Enabled bool determines if chart should be loaded - Enabled bool `json:"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"` - // Alias usable alias to be used for the chart - Alias string `json:"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("dependency cannot be an empty list") - } - 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/pkg/chart/dependency_test.go b/pkg/chart/dependency_test.go deleted file mode 100644 index 99c45b4b5..000000000 --- a/pkg/chart/dependency_test.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -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 chart - -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/pkg/chart/errors.go b/pkg/chart/errors.go deleted file mode 100644 index 2fad5f370..000000000 --- a/pkg/chart/errors.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 chart - -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/pkg/chart/file.go b/pkg/chart/file.go deleted file mode 100644 index 9dd7c08d5..000000000 --- a/pkg/chart/file.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -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 chart - -// 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/pkg/chart/loader/archive.go b/pkg/chart/loader/archive.go deleted file mode 100644 index 8b38cb89f..000000000 --- a/pkg/chart/loader/archive.go +++ /dev/null @@ -1,196 +0,0 @@ -/* -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" - "fmt" - "io" - "net/http" - "os" - "path" - "regexp" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" -) - -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) - } - if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" { - // 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 -} - -// 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) - 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, errors.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 _, err := io.Copy(b, tr); err != nil { - return nil, err - } - - 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/pkg/chart/loader/archive_test.go b/pkg/chart/loader/archive_test.go deleted file mode 100644 index 41b0af1aa..000000000 --- a/pkg/chart/loader/archive_test.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -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(w *tar.Writer) {}, - check: func(t *testing.T, files []*BufferedFile, err error) { - 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) { - 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/pkg/chart/loader/directory.go b/pkg/chart/loader/directory.go deleted file mode 100644 index bbe543870..000000000 --- a/pkg/chart/loader/directory.go +++ /dev/null @@ -1,120 +0,0 @@ -/* -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" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/ignore" - "helm.sh/helm/v3/internal/sympath" - "helm.sh/helm/v3/pkg/chart" -) - -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) - } - - data, err := ioutil.ReadFile(name) - if err != nil { - return errors.Wrapf(err, "error reading %s", n) - } - - 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/pkg/chart/loader/load.go b/pkg/chart/loader/load.go deleted file mode 100644 index 7cc8878a8..000000000 --- a/pkg/chart/loader/load.go +++ /dev/null @@ -1,200 +0,0 @@ -/* -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" - "log" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// 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, errors.Wrap(err, "cannot load Chart.yaml") - } - // NOTE(bacongobbler): while the chart specification says that APIVersion must be set, - // Helm 2 accepted charts that did not provide an APIVersion in their chart metadata. - // Because of that, if APIVersion is unset, we should assume we're loading a v1 chart. - if c.Metadata.APIVersion == "" { - c.Metadata.APIVersion = chart.APIVersionV1 - } - } - } - 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, errors.Wrap(err, "cannot load Chart.lock") - } - case f.Name == "values.yaml": - c.Values = make(map[string]interface{}) - if err := yaml.Unmarshal(f.Data, &c.Values); err != nil { - return c, errors.Wrap(err, "cannot load values.yaml") - } - case f.Name == "values.schema.json": - c.Schema = f.Data - - // Deprecated: requirements.yaml is deprecated use Chart.yaml. - // We will handle it for you because we are nice people - case f.Name == "requirements.yaml": - if c.Metadata == nil { - c.Metadata = new(chart.Metadata) - } - if c.Metadata.APIVersion != chart.APIVersionV1 { - log.Printf("Warning: Dependencies are handled in Chart.yaml since apiVersion \"v2\". We recommend migrating dependencies to Chart.yaml.") - } - if err := yaml.Unmarshal(f.Data, c.Metadata); err != nil { - return c, errors.Wrap(err, "cannot load requirements.yaml") - } - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Files = append(c.Files, &chart.File{Name: f.Name, Data: f.Data}) - } - // Deprecated: requirements.lock is deprecated use Chart.lock. - case f.Name == "requirements.lock": - c.Lock = new(chart.Lock) - if err := yaml.Unmarshal(f.Data, &c.Lock); err != nil { - return c, errors.Wrap(err, "cannot load requirements.lock") - } - if c.Metadata == nil { - c.Metadata = new(chart.Metadata) - } - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Files = append(c.Files, &chart.File{Name: f.Name, Data: 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") - } - - 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, errors.Errorf("error unpacking 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, errors.Wrapf(err, "error unpacking %s in %s", n, c.Name()) - } - c.AddDependency(sc) - } - - return c, nil -} diff --git a/pkg/chart/loader/load_test.go b/pkg/chart/loader/load_test.go deleted file mode 100644 index a737098b4..000000000 --- a/pkg/chart/loader/load_test.go +++ /dev/null @@ -1,649 +0,0 @@ -/* -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" - "io/ioutil" - "log" - "os" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "helm.sh/helm/v3/pkg/chart" -) - -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 := ioutil.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 := ioutil.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 TestLoadV1(t *testing.T) { - l, err := Loader("testdata/frobnitz.v1") - 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 TestLoadFileV1(t *testing.T) { - l, err := Loader("testdata/frobnitz.v1.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) - } - verifyDependencies(t, c) - verifyDependenciesLock(t, c) -} - -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_BadCases(t *testing.T) { - for _, tt := range []struct { - name string - bufferedFiles []*BufferedFile - expectError string - }{ - { - name: "These files contain only requirements.lock", - bufferedFiles: []*BufferedFile{ - { - Name: "requirements.lock", - Data: []byte(""), - }, - }, - expectError: "validation: chart.metadata.apiVersion is required"}, - } { - _, err := LoadFiles(tt.bufferedFiles) - if err == nil { - t.Fatal("expected error when load 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.name) - } - } -} - -func TestLoadFiles(t *testing.T) { - goodFiles := []*BufferedFile{ - { - Name: "Chart.yaml", - Data: []byte(`apiVersion: v1 -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: v1 -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 TestLoadV2WithReqs(t *testing.T) { - l, err := Loader("testdata/frobnitz.v2.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 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) { - 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) { - 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) { - verifyChartFileAndTemplate(t, c, "frobnitz") -} - -func verifyChartFileAndTemplate(t *testing.T, c *chart.Chart, name string) { - 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) { - 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/pkg/chart/loader/testdata/LICENSE b/pkg/chart/loader/testdata/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/albatross/Chart.yaml b/pkg/chart/loader/testdata/albatross/Chart.yaml deleted file mode 100644 index eeef737ff..000000000 --- a/pkg/chart/loader/testdata/albatross/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -name: albatross -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/albatross/values.yaml b/pkg/chart/loader/testdata/albatross/values.yaml deleted file mode 100644 index 3121cd7ce..000000000 --- a/pkg/chart/loader/testdata/albatross/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -albatross: "true" - -global: - author: Coleridge diff --git a/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz-1.2.3.tgz deleted file mode 100644 index b2b76a83c768f144f8b436a0deeb9da2c9a57bd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3482 zcmV;L4Q28liwFR+9h6)E1MOT1ToYFs$J#1Hw`!{ub+^6^xK)J7F-a&C5DF+N;sK&) z6^7(Nrc5$%W&)(B*y1X!*Vdz#t=Qdq{AjDyR_jqkd#y!_-Bwy@t-6X=z1Di67PD_A zAwWdq0a4rc|A{l1_r3RK=9}+(-!+rPv6*ICcuwKda!@Ljp;|4FwmO}(RjM_PtrI{+ zYBXeMs7j;M0;P&X@B!KuJSP{dyg+g2R45KY9$udgk!5AwU>xUlxVHBRvg-JsI50Lo zDLK9+=0NvDr_tPi|7xup|1~73^#RJ3I2m{R!TIlGJFqa69vWH3069W-w@(VgYjDYb ztdZgb!bX{xW}m}@{Hs(dwVV8F)mjqEzc)Zx=v2t@G;0nAxhk2NGQn_A_fW_Tkk@mx zMUa|Pj5H6>Kg!ik$a3rY%2kA#Amy?gXvKpA8v ziZ%=A4~^pJ;Fv$f2tfj5OdthPCUhJCh$xwGU_=L_VLoNDFp$u*rUreISrcT<5X3V) zY}m!jX0irbqfR_)<@C@wqfrnne7Hi8D%!J0wvx$X(pI2a1^>b;f}prim{64Ng*O5pgL*+C&!Ra*5Ax9na$uAGcMh*n){m}3`&l%@M9R%nu1&6JJO&TZ z;09?Bl~x)><6#sI8pJ^XTI53Q9h`5%~+oD!3mNC^2tbI;+?{|~*^{#UB7{Cfi~ zquvZ?GrC5GL8iIhYJz5gknC_hL!*6UCE9?2<}D1`3aAeb68t{kl7GbYma+bBC;qEZ z>#+Uj6}b3Mh`u$QZDzR}+=SbMNBysI=f75?)nNVa4LCTiz}ox|3YYwkh>wXI5>J>6 z%|C}H`B#%Fcm1zZ>ahHK0|B6aU5r-Nz!r4I_ka`?B4o0F05FoLvSCw11Q;j)BgF$V z3#=lMgFX)CLWZ@7mZVJ9pXGqTaR742(#&Fm0S_TECD9*i31P&x29ZW=UU~^``5%VW z=QR5qp5&iYy34;-tx;n6_Xh5}G=qW8hP)7tbVx46&<3n0?hG#Z7a+RRlmJ`63A}ax zPfL<2?EiTKF8)hdN4`Z006gh`mF61#uT`nB{`Up~Kn!rOUJO(tgO8OG^1Qf{MQ2+% zDT9qHI)@gQi)nU{g!bh!84vYRWdKs&&Q`GjX=XqHXPX@B=X)DX=>NJsLa;QCD)1Qp z(YVL|5gCsEdj$!QVOUU5KuDrH5jkmDl$8;HJdfoVgM88?EWp2OG>-qbOc2PU{A;gW z|4AJG@e1mT`WjRKgDf#z<4glO4S>6Sq8Kd#_xdk~-`~7G;LZAfTBS;-B1QSvDRtQY z^9tPgpYBKqrJI^D3+U1RQ@YoG>2wQK%`k@t`B#N1LtXk`r&Ejm zAKJ(A?+x^v;p?;dFY}-AX?^n59AoDJf8Me(VQgA)NR)5o(ryabply3kojmxz?vFnG zQJciTxZwx-R*XB$4Zb+xt+$5El7}S3%o_9g^5Qp&&&5`Dc(!NBgAZxPbey^J%j%** zZ)bx3t13T~t13<&njQGh;R|OJpFNn7bvny#Uw^6Jzo(2QJ6*7;i-B*e_*SQum!9ff zbEtZEhmO6{QXd@N<=8VFdks&Ci2ka2>4+zF&%b7VD03=v?toQD3;gwwuv&XSWs#Ck7OG&=eqn!-=`97W0iZhdmXM4SqzbPfU z{p)MCwa?l1Fz3^G$~zA%pANRZV*2j1&z9aAw~6X^r{o|ApDt=;%jrjmmj%lCiybz-;t%HI@&OM`-fW)-Gg7#wV$)27drN8>*G z)3>85rnLK{w022!^!$fzP{q@lY*>;uzVtCmweI<}ko2>;X4Up4chbfX_` zb4-~3P05LFpPb4WKfA{V8{>w?bbT3s-7)X^zyHszLw05!tIbHTmu!Bm%esp}i*~(#$sRs!Mq%3dWvA^yZ?(&e z_Nz(zZBCo@v-FW=)tZotGmotwLoIqFs@5{HVEFNAss#lV3{&nG5>ZoaPPJ8ibpEI3 zwtTiOS$k<&Y}XGT-!V3iRIlo?uWHX~_{FIeUG)8`CQlq&S+QhPP>{_0WF*~rrM3$9deTX}W5HuU>MlWpS4k83_&`_z)3&kPKED*Qrq@XKZUXV+Ra zdr9k_O()tG?%H{L@`ke2%Pec>^-SM*{w#U%hgl_ShP@GXY0Scp*yQcA_8i(b{^-VC ztInTeu3ibS*Swil*!4)s6Z1B_ba`^kFUP+6e)i_27gi`1uGg0>Oeop>c0@t>$={Z2 zJ^RFp<0X^M{%3#kWp;achum=|heeWmXD{$?-==75hO%xmGxT7$rz>CF;?w&M=J@B5k(j0}E@Y ze-ltj5;u5tPR!$qwS@$4l(yJdyq9nt`ES;@!EVL>*JxFu{YPqa*#GwiuEYOk4Fr4A z|JrNne-Qt%|L+w@{(oJyUmnGSY>3=HNJlQvp7LuO-zPco?1M=x};TIBAeyJ!?Fy=DTbigBvU2>xKf)kSsH5v`UY>k z2n}z9yGJ|K7lL_}{G6K%Vr! z(!KsijhgZIf4qVK=_Nl=AB(1cU6qf?7UFmiaQ)8j93J(*+ZuX1b9l7>NYYLINko9< z-y3lA|8r=wAsh@t{yZ|kC<7%>_0@d^1;hx*K#GS_vCyPRA?S!BP~`ZHC>bgfGEyNq z07l5s0+rpkXENk+X}yCOIMPQ+b)&nDlQt`O211KGSRRHR0QtE4Lv!BP|2I**pbn8DI(Lu@@+ALcsHWckCpAd^Ra#vC z=NJx_O5^Z7HOfpqzNY>J0lEZXEQ&V+AMlsK#huxMkoQ)K} z&E<1q*Q(M_m7Uw{uduxo5p%j^&sf>d1o6Vgg7*dlYfJ;beWCa?(O+0zRGiXj=f`jA zV&}F#GDjPAw500$b^kau^Zj{#KGBQdst5c>^{y?boqc@JyTL;Pm#*6}`gG9*zeQ0w zd-hCMj$0FWsuzFp#>8W##OLj_g(n_1Y|2s_vdD8!g_ip@Gllc2j-a(W3ABpXa z(?2ne4>C&6g%n^LY|Vt686p><>C}%LyN>q*Zbts^G#KPb{?#gX|4*&ehGO~m2E_OD zuL+(@Y$s#^4LPx# diff --git a/pkg/chart/loader/testdata/frobnitz.v1.tgz b/pkg/chart/loader/testdata/frobnitz.v1.tgz deleted file mode 100644 index 6282f9b73fb2877b76328adeaccdc8d94b1097c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3525 zcmV;$4Lb54iwFQ;V5eOG1MOT1ToYFs$J!!9w`!{ub+^6^xK)J70)n!TW{3^|kOvu-q|K2)ieqvNmkI|NXrWn>qZwNyFmjxQjg-k?0b#OG z5Yy8pnche+9P84diC|ENj9ZT?Oo=IOs>i$i!0t1+%75~p_{5adM6ZwGN&az#+D-n| zxLPgBzc1kAIj~TK9vW%N02xe_u|E!6<)6$k(+u>!8F=&dpIWKZi1wc^;L87aeunj? z;pc=W{jX8F+kYB#RMh{z0AV518?3QBxx`_sHM|rO17!6KY2k$CG$Y9Z^p9w4$0SXb zoE%eNN~t6l+VW|};It(hJK}$^7KYa6LQeQ$pbdmUVj@U0NAPxGGvhPO2tg90Odt&s zCc!v_h>}8fAvzfAVidZZlS-Q)ZH6F`Vxj9PW?DxZY|Tz$X)B{|Fn3!sHM412rXv`M zk>-2mDVh^VNIiegt(~e-6=+nL#hfD{%x0P+_(vy<1PeHZKu;&qv6n|sR{lB(L}Y}F z(BRy6H2H}CuJZ4&u0Kt5^UjL}ksS|des0GVUtoIglIw3LG^Cyha8Me);BOrk>@ z1O9D}k_m_SHU{cBfjpaJ5IrbBJxGB~`0MOmg*WxCLHpSxMMX=^M4nA5Yu*P7QRfDs z6S-RGMdhIv1{(NIqWA|Aa}POKuJS)9B{eNBIT_;$IIoW3(f`*f+~r@Z5aWM7L8DP; z2BaC?BSj(8OmF35z?fi%=_wNJ0~28b29mW?#_RWT&oiGzc1j7`H9;64+>ZLADI}JFf1LTf!;6%_RjanwLI8%lJP<>vO$3h9~(~ z;w|J~s}bY>KEdy<{=-1#K$eR{IwX&vNQ0;+ZV#^V&q4IC2@ZPO3A}m#PlK!FqW|X$ zxbj~}JbAV9&jC;RU#@DA|5YdzqW<>3qCsIHCW!7N;# z0X_PExO@GVR;dyFKc7I*|E!U=QU;(yzMH^$v}+SW^At^>=^zZ~td0s0p2DoBtOi6P zxOvX}z}>zJ5x0s~+b)_wW9y$6ZD!9;J5lO^Pll+ zd-By>W0!${-m)@jTt;blOhEP0?lQ^XZF^6hJovvJk3Rj8e{x8|hy(qq#vf*eT%7pU zTSI3_!;|7>js1Li=^Lfz;;TD8+pFxshty*`&0P6qZOP!bbzs1%>JO#zs*{Iihx~KI z!WpG!4`yYb&bHgvU+VwwDPxqKFW3~NAfQcRn^VinPxYxgRJ*%lr`{Rq502=1?3qrz zN2Eo?epS14^6;7pY0~O;0l^guj@Wjd?5O`yH`VmDqGa)e+GOKma5P`C;;|ayy)R0y62a4W0fVoQ$pa2HF0Gf-=8)hdElhoib;KYSw}sQIjG+`$tT;|PdH^N zJGilO|A${EcQ2^EDjQNB8X7vQIOD>QF#8<;zE>Vi`0P*Lj;Wf`{*&_hC9$#dAL{+G zoou(|ME@S|oXG5GZCCR;={Ieqy>j@KAunE=Q*GZduifFr0UYL2o8^$Vo~`f(c!Kae56Ox^R+(6D&fH*aSg zsaiL6?guHuc8A*sWww~B-Ho;lqup`!9Yu_`F&=eFTH zb;s(nlI&%hU+cQ=V(6k>?_aVqUb!LqQ|^I*}HsoUqgGJZu> zj3slSE&Xy`!e7g*>z=aj+ca_2rn+5+_x3n{Vtv(@RaJ1`q4U>{n$AX-uU~MbcH7Ep z)79GVlTEfsD?hILclsz$T z!%LSZ*Zp$rtM6xTUV34LY~gx+#lobr&2L8)R-U}NWb4@{Rva%YI{Tmfsh8>Pl^yfO zpBx^o+&g%W}I-)5`75}<{mZOG))5iF?(aRLcQCe79YV4)37E5U>$vCGmaK99>67GkVX*owyD zy@cz_zxP&y-L(I&K!jrbuTOA&{(CPF>`DKtH0oCQFY5n055fO$sP@YzSdase??-UR z!MC!cIY$Ts=xLLQFdIM?ufKtwfvDX%Nn~MsNgxDSP07P7U?BOGQZ!?0N+%P1(T@$V zq%aVgFaQG`>u^Cn$r%A5(bHDGiOvQVfaeevsUNz7y z=zk5a7WKa`aDD!JuNugc{>R@jTWPlL{f+L!$`w9!Ok&uD}3x#5# zqM~qg#1SQO{6>@%p@WoANDcsDv0%AY9c0YNmSZuJmLVgO$bp7%ovm4KsgTJd^^Oi& zJFRhex4{5th${$hF49|WG)))iyvL#8B0NEk%mbl z(90qnfA_SkNB`eMu$&@X27{M&kOU zp_*h{gj%Oe#|^1!a%^gZW>|WPV8D${wdzYe}9$jrKq^m zWqZa+e#WpDHWt1&Fid3{^z93!r?COt@{-cD&O1MTQxiY8?U6a^n4@Ji->>_}shRK3 z3-pU!1Xn!}G`dfHVg2mmgWnAs7P55Rjxnc8CI&8w$=$POIzE0)$dPx~_N`s~#T%23 zm1CdVizAl(Soy2!&&+p`>yM7E?IEd6)Q5Fdq|6DzdV*1XKi@b`*YU|C#kh6YQ z18z|MZz~w&N&Xdb_x!I?q1B4=?+ftj^jiea1)gJ)V3$vRX2cCm4o7V!g5z@od<>o^ zNPv2ZV4&a!IbITAAxMS=9L*OCS_qaEN(IqR8*OD81Mn<=GJg_?22$ZXQC-{>wDSFD z6ZG;I;5^2ETh@Q8G~)UX5h6s05FtW@2oWMgh!7z{ga{ELZX5pxA)=pu0C)fZ1FjDw diff --git a/pkg/chart/loader/testdata/frobnitz.v1/.helmignore b/pkg/chart/loader/testdata/frobnitz.v1/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock b/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml deleted file mode 100644 index 134cd1109..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/LICENSE b/pkg/chart/loader/testdata/frobnitz.v1/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/README.md b/pkg/chart/loader/testdata/frobnitz.v1/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz.v1/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md b/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz.v1/icon.svg b/pkg/chart/loader/testdata/frobnitz.v1/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz.v1/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml b/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml deleted file mode 100644 index 5eb0bc98b..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz.v1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v1/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v1/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore b/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml deleted file mode 100644 index f3ab30291..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/Chart.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v2 -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/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE b/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz.v2.reqs/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md b/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg b/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz.v2.reqs/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml deleted file mode 100644 index 5eb0bc98b..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/requirements.yaml +++ /dev/null @@ -1,7 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml b/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz.v2.reqs/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz/.helmignore b/pkg/chart/loader/testdata/frobnitz/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz/Chart.lock b/pkg/chart/loader/testdata/frobnitz/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz/LICENSE b/pkg/chart/loader/testdata/frobnitz/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz/README.md b/pkg/chart/loader/testdata/frobnitz/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz/docs/README.md b/pkg/chart/loader/testdata/frobnitz/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz/icon.svg b/pkg/chart/loader/testdata/frobnitz/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz/values.yaml b/pkg/chart/loader/testdata/frobnitz/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chart/loader/testdata/frobnitz_backslash-1.2.3.tgz deleted file mode 100644 index a9d4c11d82bf1f3b49851053e6f0d9b6c1e9a2d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3490 zcmV;T4PEjdiwFR+9h6)E1MOT1ToYFs$J!!9w`!{ub+^79aH|NDJ1G1m$Ym{oGACP(P7H*C|IRBl@1{R9YLnBQYAcITp_DzZK z8QjQ!ypdoy+(wuvFOA_r{^fFX&|Lnt3av_%e_w#Gkm-7fg;Ta3vW}F|d&m0Kyvxg#*^Y(E41+2?qw+ zKo}$@f;4mJ5B1`>z;P#OMhKE1WddoCFro7RKtxGJ0wX#Z3kwL7g@U-AHZ{2>l{P`z z3_&8r!lsv)X&r5_HM@wVt&AQzM>KMrg^iR-n-EB|XSYQd*&&c6G%us|IF?# z)u;+ID%@hukq~Ax%@O>o6Gnms97CYD6Y1E?BPc6>p9CT@LPltC?mLB>GIPM7KlLpQhjVaU`49)yxj*)X7kcMcCgDi)|psS*I zBPAx$F(Co|Gmer9r}#Do>N$Ztn`96@C_p`6z$X0f>|P~n>|KNQvq_4I#>_;X4U;zS zgN3MbgV2dwE%c)D&kKPV+NEiO43=L$IQjNwuLYZW)x|8lJ; z|Gq$jDQ^a(89gIKA%k3RH9<3n3)Z)uBGEpw4Q;?cvK9($IdqK}B=~*6jr=42d(-;6 zo%pXtp%v{vpTG_Oao)EkbIdezi`UT_c;vs_mH%3mRw3$tU%xJf+*SW;6k`0>CkO@u>Las~Cbpn6st1Hv5H67f2ZK>8kptZk;b0I4 zj06kJG_dkS4*CR`2PxXZTapsV0Ga^?#|g+FOEa4e11yBdlth2DC7j~hn#31H^U_DS zY5s?z^|@Xe!;}0gWv=qCMb5n_|GvO|ms~KAIgsTdkq*ftDAFM6i93TE`R5>d=L846 z<^;Ch|I?x)(f{)W-0)w>Ii_yZoU`1C zD)6ZPRW9*=gu>o@P-fAQvXja6n@Fj z{ErAl|Ia75IsY>qsi92M9cKYO`hPN)`Y(k{qY?c-pFq(6tdX`-2B1T}o4|9lYZF5A z6iuM%APnfNjtUT-!mOvP21FvbdCvU6-F^xYw~L$0zcXMPE`(@tF6X)19>atD%e69X zqyE<@c>hnO5##?pL9dwserx`+;2FQRr(Vr9b{Y8Rt*es8Wt4=+1XL~SE|m=4zVGy@ zL;vgX=+hthCx;}AIM}ap{1IlzrHOC7HFP!>o)kBG?B^>=-Y7XAU)Ay1UZoE{q#oO8 z)~YXSiU+@~0|Qo9eTc~`PaU2U^3M^AW|o{gl$CuZ+iu@*x&ObXj!|~LXj7DcfHsM3 zPOm6C-KX|&&7O{(dS|3RIHK$EXFByBkroyERn4-IPimfj&HRvV8g>4lmCFeE^^u4= zdvNrY;GnvT(%DZZ*ZLce1+OYzJR@|6bX|GK!)wd2q&4jVg3A{kwe339QU9ZEn(1pr z@sbHO$;Kt%SiWTCW7WiaUzA+U4eS%YvCXG}1KV_NUwkG zcM3MA#ddgo?e-42+aG59x=elNffX~rwpUEwo$=e+C%d}GYD;{lguoZ8<4QZeKYc** zz)5=)llu0uj(Q?F9F_PPFj zuRfaa*`K~0Q#rN$CuMa@V`CRQ)ca*S*>3B}{yp9~nc30WuKIP-Z~7{G#qg^`Uc5fH z%D!`cyCX{iIPQUYj~__C`02N6=9MM39~^q*v9r%SKBh|*72Cg4+N)^`x_#5tKjgux z7fJ^7<2D+8AVYqcw)dr>Ve!&$-p)8$xqjNb4^oEh3AYc*Y%5lzXi|h6>0DI!z3oMwtc$b|E_}7Z1KVUoO?@2Ng z|My_&-==Omd!}1LdBwpZRZ!5+ZNqozj@M-+*-N*))^+`*(8asozif}3KC>v}!tyir z(6`#_VgqY4uFdt|Fk2s8UZV=XH0$_=vBcs>V(KiT3P+rnE?-z!Nl_Jn;Ze0U=5$;2 zM;Cs2ZtG|3Q`MK3$9Mbi@tx!Hm5S9}_gC*-1HU-Eva7y-^^{5Dsw$U`4(%D=|Im^7 zMJX%F%-64gn*W<#; z4V7P3R>J*gV`o#Q=FI|~Z`^)jKzMr#Y*~OL8MH}?xi;_yWyd70o zaq8OAZReg?d7^akx&ItUy+ZG(=$JSD)bMEKzBvnnI`|iF%aYY^W@!&~f4b_$t$uy( zK>j`2|6W)P=+XYuC|&G-g-opf@D1Fq|8pjPo2~gvfEJ3jA(Kx>u%sTu2_ztyG+PUR zg*G&<{}YnLO%}}Yd0f7t5J#UDzZH$edkG%p-wUh3TI&Bdtp8HTm3;lD=>Pxr@W_8J z1cE*BU+r4|gZMA{|Gt6X|JPUh}@VHPlu{7NaBu{EZX3BKsZ23S%U2u&D(fsWPJAfM!nfRN~EE8j$C z0}H{RX^2A;N`2?fk_3)6QCTi|YX1J}lUDVKY?|RuOR^ld7>-60445p?kkFJ!GH5H% zH(B+RgZ~`B!RH4e>vy+s-L#mn_n;M}RmY%K|I+}VGeeAZh z5p*>Fi(Gyucdst{RMy4+K&9=asJJtwd&fzB#_<<66}~qxOl2DM?F%Jm@B!S4;*zw^ zyFPwX6F;x*(YflFW2M#Kum8vCS?|ve^ov~#S3eLmx=&qU-JBDH-whiUvTXg%F=vV= z1}=`t-Me>&Z2a1gqwlWkTeIYgHzpk~!#}qdMJ)fZ;#bw5neQSu92;HJLsFBd59_K( znHz-n1f%+XzG6aQ*p3*X(`2zFhS8y64YT1nKu53qlWSKUBOs z)33u_e)0DRS|k7R+jIe2>i-Gxe-$tP3YA=p|M>*1o^NPv0@y*a^Wa(o@YLXZp#IGQgO zv=A&SlnSCl8*OD80`M$(^b diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore b/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore deleted file mode 100755 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock deleted file mode 100755 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml deleted file mode 100755 index b1dd40a5d..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt deleted file mode 100755 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE b/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE deleted file mode 100755 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/README.md deleted file mode 100755 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me deleted file mode 100755 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml deleted file mode 100755 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md deleted file mode 100755 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100755 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml deleted file mode 100755 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100755 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml deleted file mode 100755 index 0ac5ca6a8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml deleted file mode 100755 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz deleted file mode 100755 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md b/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md deleted file mode 100755 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg b/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg deleted file mode 100755 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_backslash/ignore/me.txt deleted file mode 100755 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl deleted file mode 100755 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml b/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml deleted file mode 100755 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_backslash/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom.tgz deleted file mode 100644 index be0cd027db34d392a875cf8151642f9922016dbd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3523 zcmV;!4LtH6iwFpsexY6f17>n>Vs2@4dS7>GbZB2l` znyB~YxdYx5VfMWn1PB3&iueFgG=d@A)?MdMF5{FG{)R-w{q^;#sSRI4>wKcMw~Q(D5t@dOKi zA7!-`+)B<<`fn83E%ZMmB{eNBIT_~*crVotZ@BcY(W;T0Qmaxb{D8ts z_1_YIaQ)Y8DN8d^96jbvO|I-O_N{vddll(tl;3oR_>LlRN{?j%e|Fv3;H2(Vp zjr~7>TstE)F*Jz==xkxQcDaEBPcas0JpMhRCI6F$#3!btCVH_BkNj6Fn%jSBwZwm) z0P)|Y@W$+yxc)~$Oa8};_P(7k)84AX1OJsuWy|=lP9^c*7eM?ctW-K=IW)ybf;^?n zLYQGBaQ6!t2{|K6S$Q$J!BlXk z1%gDHgN?IT7z0Dvn`Gh`8*7BF8cjTJNEv94Q{pM$OwytaT-rzO|XDx3G{X%omY7TZ4=r_AR!ZE#RS*2Gl_&&Xd$7+NWmuW zhM=wJeTh%l@bVS75?5g2%?2O(#tjrbdahA{W`Y$I(5yrauEtfZD)CNtW(loE%kkf- zOK(O1+Tj1|)GEpU_XTdM|4iqyK&JWjSAjg)|HuGnHvc1OY5w;K-2OjjVr(=C3=lZ= zjxOzDXqILO+5!cF<_vI)XPD*)$no^E>` zp6q|ULSH}rYjnc+uh;05690XHKC=V-*8OGC6MpT_yp(I|Hu%rm*Cb8IC=QPas9e!g zE*rY@z}YiL{@3fl$KUr)4oMhwctFL(W9+cYPrd&7@VQucQrz6}pR6i=wfI7OW#=dR zl-zrtc6^sPYd)(k8v2F-3|d?H9;U1~b97$FKSwQ@U3~sXR(4Ic!?Eegz<*C4r|x>m zt||rr?GoFaT~&IvU+vNAeVx1X%}Bp@RQHolbm==PEh_f&>J_6O)jjjFpBDkmn}YS-*cw3 z@jJr|^B1b3Ws|CtP0PTEeA()Us))BgExwi;*e`x_yN?0~x9i%m=t@ce@nBbyyV$?0 zN`7t{{$$@b3$~`kc6w#~&Q7^I?`QqGO@H&ARkOg37tLSS_-*f(UDa!?HNHzi;B!@R zC7s`yIVgGXlzplx{rlL)Jd!zNzy;ZdJ3CG~Yc4smrTox)UnKV|sQg(ztTZ$r5rFF|=V;9}m_XP*lVf*QUz1}>X+1b{i>J`dw<{C%& z$ZNx%yFS0tv3p^MW6J_~{+iw6zhH_h*eQ|FG7EE2b$H52uYt`~^ zvE2*zC6Pt{JzVm)={wHV^hhWxKU}B@3i_da_#VT_x~wEe$+nlfZ@e73bniP?9Fa3; z7iL^sS>p(Oy`v#Eur}l8`Tm>c8l%gqHQ|@%oZK{?So%OropsFQQKx1q7f-IB>GHtv zsM=~vy1nZCi{C%J{o{?P+AAyLd%XAX?g{y7)!Oa{tM;#hpPpUa-8isn+LQ^E70btl z_KqKTsP_buvhZn)N3<#&3|#?>WUa^<`R4Q)!Kx=me@8v<~X?ZskK{c z_Z~aY>*DE66`xg9z=KCGUO!d>@wMumYp&1I>c2@g+o!Dgp!S0ek1hY<+>nUJ zA}>{ky-;>&UY$*IKyBN<^>l~Ay?ahg+g!G8rFFx?KABrCo>yP~c5cb~k*`Kv8NcLx zCUw``{YMW@I(_!EwXbCq_BdYh$imIfU!7L_Qqd(H~ z@yh46`}MmG{@--`cRHC~aRXX!|Lc?r$^Q2VJn7#X3xS^Oe~rF*{9muuO8dWj0=NC| z-tCc3a3BXF!;j#Rfp6m|OOEIU7#Xvfu#g~2h`#|N3sJf&5S4`sTS6en;vp&m-RI<4 zfTUPx6aq#lrx@Q8f`)kzedj2#A}d1z7CPf_KO|tr&sHw z{a-%8FVw%8;CN+QdqJ!9uTp4~YDxdTK&$lMMq|L^{IAwGoBvg6wG{vN32rw3iwbW# z{l_^Mc=o?qQBeGD*n5Cu+|O$p7^g)Hn;y(T4DZ|`2Xue zJpYl<(s>pYj?01@ZTZQF8=f4F(#!-e#0CT(To6crMw(!uxRu1&Ly-4QvB)iFgvCND z!ExeJA^K`Z-hRCU&Iy@?OduLyVm@iO@I6|=e^t0RxVL^S&=ddF`uhFHN1(&}Vi)H}S&J-){hsG+JRy8m>#WM`#V|bOo8JrN*a5=tiWc>xPe|pCl}#J#7>d zE!Y8mr}wTc{YcTx|4@bf`KY*>lKm58Kj8SYTPD9fI80+6^3}7&HTWQYRZ($T*F7J+ zri)+D{`h=t%!!h!Z#Mqp?3{NN2KvP=g=_B#8r!dKa^1XBL*EJ;5wc?A?r}9mPX#WG z$=$zymSW=ikmGM{=wH3;(^sdQEX6-@6h^H4uKXv>pV_Y?H=P(;-Ah)TXbkJFN|_&o z_XcD7f3jtQq4T513l+8z(@HxWnsoh{B|kg*e{rSo?{&|dFAp*vJQ0K*)N!O}Z>C?T zJN)AB5_sZ&Lrk~rb+p0%Qwsc-{J&oxzg+*#BI&KD!_)Y$Q>vQTe``h8bg=`*WbQW-Q`ECnKu76Z6TBGW30h*i3loto`e&nGC}wS$^6qbEcy zaS|p{jYl{)QM;DVBEbYqZlEl3j)u^Rg<%nh0@%bmWczyJe;ZxCCh)|6g+|kS{YRyh z`0o>l{-3bGDy+u}UD*}?!OZq>XBUvXvfndW@?SXEf^&H}-mAkC|FsS4zpmeZLzmL| zPoE%aV1b!-ANq;Lgfm0~V`NcC{{SUexJYyrjk(T0<>%+)`D&bDbL1+dH#thfoMN{W z5obSVX$3T8$-R-VG9n^EE~a9Qk0Ci*1oA18H$`JAtqwyIK9ytQU0*0N8bc-mkHw%| zQ9`&nCegKlL+Hm*a`BdsCTvF$#j`0E$%6t^0tW2jUsv%OMMLR2be&DnbTnom^6Z$r zX&D?ug_B|-O08Im#zQGAB!xnvgclNX7mcVb`R@&nuPIu;{)?;tjpYCN0=LqC{Z=X7 zdRu3KJ==fH&;ROmdMW-za0w`V9Evj@UhfyrdZ5E3^B z)JKRgaS#y?2D;KD|M70d&3^xbW{kPss>9>@pP~i-RT{m-e_sIc|2OwD6C{;`T)s#& x%AjG3BBk*~f&>W?BuJ1TL4pJc5+q2FAVGoz2@)hokRYKE{tus-sa61Z0036V1Lgn# diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore deleted file mode 100644 index 7a4b92da2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock deleted file mode 100644 index ed43b227f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml deleted file mode 100644 index 21b21f0b5..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt deleted file mode 100644 index 77c4e724a..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE b/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE deleted file mode 100644 index c27b00bf2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/README.md deleted file mode 100644 index e9c40031b..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me deleted file mode 100644 index a7e3a38b7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml deleted file mode 100644 index adb9853c6..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md deleted file mode 100644 index ea7526bee..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1ad84b346..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index f690d53c4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index f3e662a28..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml deleted file mode 100644 index 6b7cb2596..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_bom/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md deleted file mode 100644 index 816c3e431..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_bom/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl deleted file mode 100644 index bb29c5491..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml deleted file mode 100644 index c24ceadf9..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_bom/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE b/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_dev_null/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_dev_null/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/null b/pkg/chart/loader/testdata/frobnitz_with_dev_null/null deleted file mode 120000 index dc1dc0cde..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/null +++ /dev/null @@ -1 +0,0 @@ -/dev/null \ No newline at end of file diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_dev_null/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore b/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock b/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt b/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 21ae20aad..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz b/pkg/chart/loader/testdata/frobnitz_with_symlink/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md b/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg b/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt b/pkg/chart/loader/testdata/frobnitz_with_symlink/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl b/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml b/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chart/loader/testdata/frobnitz_with_symlink/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chart/loader/testdata/genfrob.sh b/pkg/chart/loader/testdata/genfrob.sh deleted file mode 100755 index 35fdd59f2..000000000 --- a/pkg/chart/loader/testdata/genfrob.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/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/pkg/chart/loader/testdata/mariner/Chart.yaml b/pkg/chart/loader/testdata/mariner/Chart.yaml deleted file mode 100644 index 92dc4b390..000000000 --- a/pkg/chart/loader/testdata/mariner/Chart.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -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/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz b/pkg/chart/loader/testdata/mariner/charts/albatross-0.1.0.tgz deleted file mode 100644 index 128ef82f7a07c2efe1208bc21264c578661d95b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 306 zcmV-20nPp&iwFR+9h6)E1MSpJZo)7S1z^@b#fSq?o5YEf9b!|d`|b@)NF;-i?I88` zby6Cns?ZJeM}6OB6vcAvjGv8Nndn^z50kr*QkGiNP>W)ya-P4{6H-~8YpDt~S0vMe z))UhAThdtnrn|_A%B!!i@c2Hpa)}piu2RJ#{Pg zdyX8$@qH3GQ!^. -# 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/pkg/chart/metadata.go b/pkg/chart/metadata.go deleted file mode 100644 index 7d16ecd1b..000000000 --- a/pkg/chart/metadata.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -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 chart - -import ( - "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("maintainer cannot be an empty list") - } - 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.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. - for _, dependency := range md.Dependencies { - if err := dependency.Validate(); err != nil { - return err - } - } - 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/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go deleted file mode 100644 index 98354d13f..000000000 --- a/pkg/chart/metadata_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 chart - -import ( - "testing" -) - -func TestValidate(t *testing.T) { - tests := []struct { - md *Metadata - err error - }{ - { - nil, - ValidationError("chart.metadata is required"), - }, - { - &Metadata{Name: "test", Version: "1.0"}, - ValidationError("chart.metadata.apiVersion is required"), - }, - { - &Metadata{APIVersion: "v2", Version: "1.0"}, - ValidationError("chart.metadata.name is required"), - }, - { - &Metadata{Name: "test", APIVersion: "v2"}, - ValidationError("chart.metadata.version is required"), - }, - { - &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "test"}, - ValidationError("chart.metadata.type must be application or library"), - }, - { - &Metadata{Name: "test", APIVersion: "v2", Version: "1.0", Type: "application"}, - nil, - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - {Name: "dependency", Alias: "legal-alias"}, - }, - }, - nil, - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - {Name: "bad", Alias: "illegal alias"}, - }, - }, - ValidationError("dependency \"bad\" has disallowed characters in the alias"), - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Dependencies: []*Dependency{ - nil, - }, - }, - ValidationError("dependency cannot be an empty list"), - }, - { - &Metadata{ - Name: "test", - APIVersion: "v2", - Version: "1.0", - Type: "application", - Maintainers: []*Maintainer{ - nil, - }, - }, - ValidationError("maintainer cannot be an empty list"), - }, - { - &Metadata{APIVersion: "v2", 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 '%s', got '%s'", tt.err, result) - } - } -} - -func TestValidate_sanitize(t *testing.T) { - md := &Metadata{APIVersion: "v2", 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/pkg/chartutil/capabilities.go b/pkg/chartutil/capabilities.go deleted file mode 100644 index 5f57e11a5..000000000 --- a/pkg/chartutil/capabilities.go +++ /dev/null @@ -1,126 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "fmt" - "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/v3/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 { - for _, x := range v { - if x == apiVersion { - return true - } - } - return false -} - -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/pkg/chartutil/capabilities_test.go b/pkg/chartutil/capabilities_test.go deleted file mode 100644 index ffd8d76da..000000000 --- a/pkg/chartutil/capabilities_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "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 != "v3.10" { - t.Errorf("Expected default HelmVersion to be v3.10, 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/pkg/chartutil/chartfile.go b/pkg/chartutil/chartfile.go deleted file mode 100644 index 808a902b1..000000000 --- a/pkg/chartutil/chartfile.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// LoadChartfile loads a Chart.yaml file into a *chart.Metadata. -func LoadChartfile(filename string) (*chart.Metadata, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - y := new(chart.Metadata) - err = yaml.Unmarshal(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 { - // Pull out the dependencies of a v1 Chart, since there's no way - // to tell the serializer to skip a field for just this use case - savedDependencies := cf.Dependencies - if cf.APIVersion == chart.APIVersionV1 { - cf.Dependencies = nil - } - out, err := yaml.Marshal(cf) - if cf.APIVersion == chart.APIVersionV1 { - cf.Dependencies = savedDependencies - } - if err != nil { - return err - } - return ioutil.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, errors.Errorf("%q is not a directory", dirName) - } - - chartYaml := filepath.Join(dirName, ChartfileName) - if _, err := os.Stat(chartYaml); os.IsNotExist(err) { - return false, errors.Errorf("no %s exists in directory %q", ChartfileName, dirName) - } - - chartYamlContent, err := ioutil.ReadFile(chartYaml) - if err != nil { - return false, errors.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, errors.Errorf("chart metadata (%s) missing", ChartfileName) - } - if chartContent.Name == "" { - return false, errors.Errorf("invalid chart (%s): name must not be empty", ChartfileName) - } - - return true, nil -} diff --git a/pkg/chartutil/chartfile_test.go b/pkg/chartutil/chartfile_test.go deleted file mode 100644 index fb5f15376..000000000 --- a/pkg/chartutil/chartfile_test.go +++ /dev/null @@ -1,121 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -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) { - - if f == nil { - t.Fatal("Failed verifyChartfile because f is nil") - } - - if f.APIVersion != chart.APIVersionV1 { - t.Errorf("Expected API Version %q, got %q", chart.APIVersionV1, f.APIVersion) - } - - 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/pkg/chartutil/coalesce.go b/pkg/chartutil/coalesce.go deleted file mode 100644 index f634d6425..000000000 --- a/pkg/chartutil/coalesce.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "fmt" - "log" - - "github.com/mitchellh/copystructure" - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" -) - -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) { - 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 coalesce(log.Printf, chrt, valsCopy, "") -} - -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. -func coalesce(printf printFn, ch *chart.Chart, dest map[string]interface{}, prefix string) (map[string]interface{}, error) { - coalesceValues(printf, ch, dest, prefix) - return coalesceDeps(printf, ch, dest, prefix) -} - -// coalesceDeps coalesces the dependencies of the given chart. -func coalesceDeps(printf printFn, chrt *chart.Chart, dest map[string]interface{}, prefix string) (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, errors.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) - - // Now coalesce the rest of the values. - var err error - dest[subchart.Name()], err = coalesce(printf, subchart, dvmap, subPrefix) - 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) { - 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) - coalesceTablesFullKey(printf, vv, destvmap, subPrefix) - 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)) - for k, v := range src { - m[k] = v - } - 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) { - subPrefix := concatPrefix(prefix, c.Metadata.Name) - for key, val := range c.Values { - if value, ok := v[key]; ok { - if value == nil { - // When the YAML value is null, we remove the value's key. - // This allows Helm's various sources of values (value files or --set) to - // remove incompatible keys from any previous chart, file, or set values. - delete(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 { - // Because v has higher precedence than nv, dest values override src - // values. - coalesceTablesFullKey(printf, dest, src, concatPrefix(subPrefix, key)) - } - } - } else { - // If the key is not in v, copy it from nv. - v[key] = val - } - } -} - -// 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, "") -} - -// coalesceTablesFullKey merges a source map into a destination map. -// -// dest is considered authoritative. -func coalesceTablesFullKey(printf printFn, dst, src map[string]interface{}, prefix string) 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 - } - // 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 && 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) - } 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/pkg/chartutil/coalesce_test.go b/pkg/chartutil/coalesce_test.go deleted file mode 100644 index 3fe93f5ff..000000000 --- a/pkg/chartutil/coalesce_test.go +++ /dev/null @@ -1,409 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/pkg/chart" -) - -// 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: - global: - name: Stinky - harpooner: Tashtego - nested: - boat: false - sail: true - ahab: - scope: whale - boat: null - nested: - foo: true - bar: 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"}, - }, - }, - }, - 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 CoalesceValues as argument, so that we can - // use it for asserting later - valsCopy := make(Values, len(vals)) - for key, value := range vals { - valsCopy[key] = value - } - - 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.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{})["ahab"].(map[string]interface{}) - if _, ok := subchart["boat"]; ok { - t.Error("Expected subchart boat key to be removed, still present") - } - - if _, ok := subchart["nested"].(map[string]interface{})["bar"]; ok { - t.Error("Expected subchart nested bar key to be removed, still present") - } - - // 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 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, "") - 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/pkg/chartutil/compatible.go b/pkg/chartutil/compatible.go deleted file mode 100644 index f4656c913..000000000 --- a/pkg/chartutil/compatible.go +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import "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/pkg/chartutil/compatible_test.go b/pkg/chartutil/compatible_test.go deleted file mode 100644 index df7be6161..000000000 --- a/pkg/chartutil/compatible_test.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -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 chartutil - -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/pkg/chartutil/create.go b/pkg/chartutil/create.go deleted file mode 100644 index 3a8f3cc5a..000000000 --- a/pkg/chartutil/create.go +++ /dev/null @@ -1,687 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/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" - // 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: v2 -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. - -replicaCount: 1 - -image: - repository: nginx - pullPolicy: IfNotPresent - # Overrides the image tag whose default is the chart appVersion. - tag: "" - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: 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: "" - -podAnnotations: {} - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - -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 - -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 - -autoscaling: - enabled: false - minReplicas: 1 - maxReplicas: 100 - targetCPUUtilizationPercentage: 80 - # targetMemoryUtilizationPercentage: 80 - -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 -}} -{{- $fullName := include ".fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include ".labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- 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 }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- 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 ".selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include ".serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: {{ .Values.service.port }} - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- 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 }} -{{- end }} -` - -const defaultHorizontalPodAutoscaler = `{{- if .Values.autoscaling.enabled }} -apiVersion: autoscaling/v2beta1 -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 - targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} - {{- end }} - {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} - - type: Resource - resource: - name: memory - targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} - {{- end }} -{{- end }} -` - -const defaultNotes = `1. Get the application URL by running these commands: -{{- 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 the status of 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 errors.Wrapf(err, "could not load %s", src) - } - - 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 errors.Wrap(err, "reading values file") - } - - var m map[string]interface{} - if err := yaml.Unmarshal(transform(string(b), schart.Name()), &m); err != nil { - return errors.Wrap(err, "transforming values file") - } - 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, errors.Errorf("no such directory %s", path) - } - - cdir := filepath.Join(path, name) - if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { - return cdir, errors.Errorf("file %s already exists and is not a directory", cdir) - } - - 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), - }, - { - // 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 ioutil.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/pkg/chartutil/create_test.go b/pkg/chartutil/create_test.go deleted file mode 100644 index f123a37cd..000000000 --- a/pkg/chartutil/create_test.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/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.APIVersionV1, - 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 := ioutil.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 := ioutil.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/pkg/chartutil/dependencies.go b/pkg/chartutil/dependencies.go deleted file mode 100644 index e01b95bf7..000000000 --- a/pkg/chartutil/dependencies.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "log" - "strings" - - "helm.sh/helm/v3/pkg/chart" -) - -// ProcessDependencies checks through this chart's dependencies, processing accordingly. -func ProcessDependencies(c *chart.Chart, v Values) error { - if err := processDependencyEnabled(c, v, ""); err != nil { - return err - } - return processDependencyImportValues(c) -} - -// processDependencyConditions disables charts based on condition path value in values -func processDependencyConditions(reqs []*chart.Dependency, cvals Values, cpath string) { - if reqs == nil { - return - } - for _, r := range reqs { - for _, c := range strings.Split(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 - } else { - log.Printf("Warning: Condition path '%s' for chart %s returned non-bool value", c, r.Name) - } - } else if _, ok := err.(ErrNoValue); !ok { - // this is a real error - log.Printf("Warning: PathValue returned error %v", 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 { - log.Printf("Warning: Tag '%s' for chart %s returned non-bool value", k, r.Name) - } - } - } - if !hasTrue && hasFalse { - r.Enabled = false - } else if hasTrue || !hasTrue && !hasFalse { - r.Enabled = true - } - } -} - -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 - md := *c.Metadata - out.Metadata = &md - - if dep.Alias != "" { - md.Name = dep.Alias - } - return &out - } - return nil -} - -// 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 anyways processed from Chart.yaml - -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 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) error { - if c.Metadata.Dependencies == nil { - return nil - } - // combine chart values and empty config to get Values - cvals, err := CoalesceValues(c, nil) - if err != nil { - return err - } - b := make(map[string]interface{}) - // import values from each dependency if specified in import-values - for _, r := range c.Metadata.Dependencies { - var outiv []interface{} - for _, riv := range r.ImportValues { - switch iv := riv.(type) { - case map[string]interface{}: - child := iv["child"].(string) - parent := iv["parent"].(string) - - outiv = append(outiv, map[string]string{ - "child": child, - "parent": parent, - }) - - // get child table - vv, err := cvals.Table(r.Name + "." + child) - if err != nil { - log.Printf("Warning: ImportValues missing table from chart %s: %v", r.Name, err) - continue - } - // create value map from child to be merged into parent - b = CoalesceTables(cvals, pathToMap(parent, vv.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 { - log.Printf("Warning: ImportValues missing table: %v", err) - continue - } - b = CoalesceTables(b, vm.AsMap()) - } - } - // set our formatted import values - r.ImportValues = outiv - } - - // set the new values - c.Values = CoalesceTables(cvals, b) - - return nil -} - -// processDependencyImportValues imports specified chart values from child to parent. -func processDependencyImportValues(c *chart.Chart) error { - for _, d := range c.Dependencies() { - // recurse - if err := processDependencyImportValues(d); err != nil { - return err - } - } - return processImportValues(c) -} diff --git a/pkg/chartutil/dependencies_test.go b/pkg/chartutil/dependencies_test.go deleted file mode 100644 index 7f5e74956..000000000 --- a/pkg/chartutil/dependencies_test.go +++ /dev/null @@ -1,457 +0,0 @@ -/* -Copyright The Helm Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ -package chartutil - -import ( - "os" - "path/filepath" - "sort" - "strconv" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/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) - } - } - }) - } -} - -// extractCharts recursively searches chart dependencies returning all charts found -func extractChartNames(c *chart.Chart) []string { - var out []string - var fn func(c *chart.Chart) - fn = func(c *chart.Chart) { - out = append(out, c.ChartPath()) - for _, d := range c.Dependencies() { - fn(d) - } - } - fn(c) - sort.Strings(out) - return out -} - -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" - - 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" - - 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); 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) - } - case bool: - if b := strconv.FormatBool(pv); b != vv { - t.Errorf("failed to match imported bool value %v with expected %v", b, vv) - } - default: - if pv != vv { - t.Errorf("failed to match imported string value %q with expected %q", pv, vv) - } - } - } -} - -func TestProcessDependencyImportValuesMultiLevelPrecedence(t *testing.T) { - c := loadChart(t, "testdata/three-level-dependent-chart/umbrella") - - e := make(map[string]string) - - e["app1.service.port"] = "3456" - e["app2.service.port"] = "8080" - - if err := processDependencyImportValues(c); 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); 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 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)) - } -} diff --git a/pkg/chartutil/doc.go b/pkg/chartutil/doc.go deleted file mode 100644 index 8f06bcc9a..000000000 --- a/pkg/chartutil/doc.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/*Package chartutil 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 chartutil // import "helm.sh/helm/v3/pkg/chartutil" diff --git a/pkg/chartutil/errors.go b/pkg/chartutil/errors.go deleted file mode 100644 index fcdcc27ea..000000000 --- a/pkg/chartutil/errors.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "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) } diff --git a/pkg/chartutil/errors_test.go b/pkg/chartutil/errors_test.go deleted file mode 100644 index 3f63e3733..000000000 --- a/pkg/chartutil/errors_test.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "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/pkg/chartutil/expand.go b/pkg/chartutil/expand.go deleted file mode 100644 index 6ad09e417..000000000 --- a/pkg/chartutil/expand.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "io" - "io/ioutil" - "os" - "path/filepath" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/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 errors.Wrap(err, "cannot load Chart.yaml") - } - chartName = ch.Name - } - } - if chartName == "" { - return errors.New("chart name not specified") - } - - // Find the base directory - 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 := ioutil.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/pkg/chartutil/expand_test.go b/pkg/chartutil/expand_test.go deleted file mode 100644 index f31a3d290..000000000 --- a/pkg/chartutil/expand_test.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "os" - "path/filepath" - "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 alwaty 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 alwaty 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/pkg/chartutil/jsonschema.go b/pkg/chartutil/jsonschema.go deleted file mode 100644 index 753dc98c1..000000000 --- a/pkg/chartutil/jsonschema.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "bytes" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/xeipuuv/gojsonschema" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// 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 { - err := ValidateAgainstSingleSchema(values, chrt.Schema) - if err != nil { - sb.WriteString(fmt.Sprintf("%s:\n", chrt.Name())) - sb.WriteString(err.Error()) - } - } - - // 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) error { - valuesData, err := yaml.Marshal(values) - if err != nil { - return err - } - valuesJSON, err := yaml.YAMLToJSON(valuesData) - if err != nil { - return err - } - if bytes.Equal(valuesJSON, []byte("null")) { - valuesJSON = []byte("{}") - } - schemaLoader := gojsonschema.NewBytesLoader(schemaJSON) - valuesLoader := gojsonschema.NewBytesLoader(valuesJSON) - - result, err := gojsonschema.Validate(schemaLoader, valuesLoader) - if err != nil { - return err - } - - if !result.Valid() { - var sb strings.Builder - for _, desc := range result.Errors() { - sb.WriteString(fmt.Sprintf("- %s\n", desc)) - } - return errors.New(sb.String()) - } - - return nil -} diff --git a/pkg/chartutil/jsonschema_test.go b/pkg/chartutil/jsonschema_test.go deleted file mode 100644 index a0acd5a7f..000000000 --- a/pkg/chartutil/jsonschema_test.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "io/ioutil" - "testing" - - "helm.sh/helm/v3/pkg/chart" -) - -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 := ioutil.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 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 := ioutil.ReadFile("./testdata/test-values.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 := `- (root): employmentInfo is required -- age: Must be greater than or equal to 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" - ] -} -` - -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: -- (root): age is required -` - if errString != expectedErrString { - t.Errorf("Error string :\n`%s`\ndoes not match expected\n`%s`", errString, expectedErrString) - } -} diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go deleted file mode 100644 index 2ce4eddaf..000000000 --- a/pkg/chartutil/save.go +++ /dev/null @@ -1,244 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "archive/tar" - "compress/gzip" - "encoding/json" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -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 - outdir := filepath.Join(dest, c.Name()) - if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { - return errors.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 errors.Wrapf(err, "saving %s", dep.ChartFullPath()) - } - } - 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 "", errors.Wrap(err, "chart validation") - } - - 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 os.IsNotExist(err) { - if err2 := os.MkdirAll(dir, 0755); err2 != nil { - return "", err2 - } - } else { - return "", errors.Wrapf(err, "stat %s", dir) - } - } else if !stat.IsDir() { - return "", errors.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.Header.Extra = headerBytes - zipper.Header.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 { - base := filepath.Join(prefix, c.Name()) - - // Pull out the dependencies of a v1 Chart, since there's no way - // to tell the serializer to skip a field for just this use case - savedDependencies := c.Metadata.Dependencies - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Metadata.Dependencies = nil - } - // Save Chart.yaml - cdata, err := yaml.Marshal(c.Metadata) - if c.Metadata.APIVersion == chart.APIVersionV1 { - c.Metadata.Dependencies = savedDependencies - } - if err != nil { - return err - } - if err := writeToTar(out, filepath.Join(base, ChartfileName), cdata); err != nil { - return err - } - - // Save Chart.lock - // TODO: remove the APIVersion check when APIVersionV1 is not used anymore - if c.Metadata.APIVersion == chart.APIVersionV2 { - 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 -} diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go deleted file mode 100644 index 6914cd200..000000000 --- a/pkg/chartutil/save_test.go +++ /dev/null @@ -1,237 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "testing" - "time" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -func TestSave(t *testing.T) { - tmp := ensure.TempDir(t) - defer os.RemoveAll(tmp) - - for _, dest := range []string{tmp, path.Join(tmp, "newdir")} { - t.Run("outDir="+dest, func(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: chart.APIVersionV1, - 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 c2.Lock != nil { - t.Fatal("Expected v1 chart archive not to contain Chart.lock file") - } - - 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.APIVersionV2 - 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 v2 chart archive to contain a Chart.lock file") - } - if c2.Lock.Digest != c.Lock.Digest { - t.Fatal("Chart.lock data did not match") - } - }) - } -} - -// 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.APIVersionV1, - 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.APIVersionV1, - Name: "ahab", - Version: "1.2.3", - }, - Files: []*chart.File{ - {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, - }, - Templates: []*chart.File{ - {Name: filepath.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 != filepath.Join(TemplatesDir, "nested", "dir", "thing.yaml") { - t.Fatal("Templates data did not match") - } - - if len(c2.Files) != 1 || c2.Files[0].Name != "scheherazade/shahryar.txt" { - t.Fatal("Files data did not match") - } -} diff --git a/pkg/chartutil/testdata/chartfiletest.yaml b/pkg/chartutil/testdata/chartfiletest.yaml deleted file mode 100644 index 134cd1109..000000000 --- a/pkg/chartutil/testdata/chartfiletest.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/coleridge.yaml b/pkg/chartutil/testdata/coleridge.yaml deleted file mode 100644 index b6579628b..000000000 --- a/pkg/chartutil/testdata/coleridge.yaml +++ /dev/null @@ -1,12 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-alias/.helmignore b/pkg/chartutil/testdata/dependent-chart-alias/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock b/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml deleted file mode 100644 index 751a3aa67..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml +++ /dev/null @@ -1,29 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/pkg/chartutil/testdata/dependent-chart-alias/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/README.md b/pkg/chartutil/testdata/dependent-chart-alias/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md b/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg b/pkg/chartutil/testdata/dependent-chart-alias/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml b/pkg/chartutil/testdata/dependent-chart-alias/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-alias/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore b/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore deleted file mode 100644 index 8a71bc82e..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore +++ /dev/null @@ -1,2 +0,0 @@ -ignore/ -.* diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml deleted file mode 100644 index 7c071c27b..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml b/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml deleted file mode 100644 index 7c071c27b..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml deleted file mode 100644 index fe7a99681..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml deleted file mode 100644 index 7fc39e28d..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz deleted file mode 100644 index 3190136b050e62c628b3c817fd963ac9dc4a9e25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml b/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/frobnitz-1.2.3.tgz b/pkg/chartutil/testdata/frobnitz-1.2.3.tgz deleted file mode 100644 index 8731dce02cc9603e7813a07670a7d057129a8b20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/frobnitz/.helmignore b/pkg/chartutil/testdata/frobnitz/.helmignore deleted file mode 100644 index 9973a57b8..000000000 --- a/pkg/chartutil/testdata/frobnitz/.helmignore +++ /dev/null @@ -1 +0,0 @@ -ignore/ diff --git a/pkg/chartutil/testdata/frobnitz/Chart.lock b/pkg/chartutil/testdata/frobnitz/Chart.lock deleted file mode 100644 index 6fcc2ed9f..000000000 --- a/pkg/chartutil/testdata/frobnitz/Chart.lock +++ /dev/null @@ -1,8 +0,0 @@ -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/pkg/chartutil/testdata/frobnitz/Chart.yaml b/pkg/chartutil/testdata/frobnitz/Chart.yaml deleted file mode 100644 index fcd4a4a37..000000000 --- a/pkg/chartutil/testdata/frobnitz/Chart.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/frobnitz/INSTALL.txt b/pkg/chartutil/testdata/frobnitz/INSTALL.txt deleted file mode 100644 index 2010438c2..000000000 --- a/pkg/chartutil/testdata/frobnitz/INSTALL.txt +++ /dev/null @@ -1 +0,0 @@ -This is an install document. The client may display this. diff --git a/pkg/chartutil/testdata/frobnitz/LICENSE b/pkg/chartutil/testdata/frobnitz/LICENSE deleted file mode 100644 index 6121943b1..000000000 --- a/pkg/chartutil/testdata/frobnitz/LICENSE +++ /dev/null @@ -1 +0,0 @@ -LICENSE placeholder. diff --git a/pkg/chartutil/testdata/frobnitz/README.md b/pkg/chartutil/testdata/frobnitz/README.md deleted file mode 100644 index 8cf4cc3d7..000000000 --- a/pkg/chartutil/testdata/frobnitz/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# 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/pkg/chartutil/testdata/frobnitz/charts/_ignore_me b/pkg/chartutil/testdata/frobnitz/charts/_ignore_me deleted file mode 100644 index 2cecca682..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/_ignore_me +++ /dev/null @@ -1 +0,0 @@ -This should be ignored by the loader, but may be included in a chart. diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml deleted file mode 100644 index 79e0d65db..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: alpine -description: Deploy a basic Alpine Linux pod -version: 0.1.0 -home: https://helm.sh/helm diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md b/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md deleted file mode 100644 index b30b949dd..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml deleted file mode 100644 index 1c9dd5fa4..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: mast1 -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml deleted file mode 100644 index 42c39c262..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz b/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz deleted file mode 100644 index 61cb62051110b55f3d08213dc81dcf0b1c2d8e53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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<` diff --git a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml deleted file mode 100644 index 6c2aab7ba..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: "my-alpine" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml deleted file mode 100644 index 92dc4b390..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/Chart.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml deleted file mode 100644 index b5188fde0..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: albatross -description: A Helm chart for Kubernetes -version: 0.1.0 -home: "" diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml deleted file mode 100644 index 3121cd7ce..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/charts/albatross/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -albatross: "true" - -global: - author: Coleridge diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl b/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl deleted file mode 100644 index 29c11843a..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/templates/placeholder.tpl +++ /dev/null @@ -1 +0,0 @@ -# This is a placeholder. diff --git a/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml b/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml deleted file mode 100644 index b0ccb0086..000000000 --- a/pkg/chartutil/testdata/frobnitz/charts/mariner/values.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# 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/pkg/chartutil/testdata/frobnitz/docs/README.md b/pkg/chartutil/testdata/frobnitz/docs/README.md deleted file mode 100644 index d40747caf..000000000 --- a/pkg/chartutil/testdata/frobnitz/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -This is a placeholder for documentation. diff --git a/pkg/chartutil/testdata/frobnitz/icon.svg b/pkg/chartutil/testdata/frobnitz/icon.svg deleted file mode 100644 index 892130606..000000000 --- a/pkg/chartutil/testdata/frobnitz/icon.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - Example icon - - - diff --git a/pkg/chartutil/testdata/frobnitz/ignore/me.txt b/pkg/chartutil/testdata/frobnitz/ignore/me.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/frobnitz/templates/template.tpl b/pkg/chartutil/testdata/frobnitz/templates/template.tpl deleted file mode 100644 index c651ee6a0..000000000 --- a/pkg/chartutil/testdata/frobnitz/templates/template.tpl +++ /dev/null @@ -1 +0,0 @@ -Hello {{.Name | default "world"}} diff --git a/pkg/chartutil/testdata/frobnitz/values.yaml b/pkg/chartutil/testdata/frobnitz/values.yaml deleted file mode 100644 index 61f501258..000000000 --- a/pkg/chartutil/testdata/frobnitz/values.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# A values file contains configuration. - -name: "Some Name" - -section: - name: "Name in a section" diff --git a/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz b/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz deleted file mode 100644 index 6929659514d35d2a2fe1098c1c097194378cc3b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/genfrob.sh b/pkg/chartutil/testdata/genfrob.sh deleted file mode 100755 index 35fdd59f2..000000000 --- a/pkg/chartutil/testdata/genfrob.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/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/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock deleted file mode 100644 index b2f17fb39..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.lock +++ /dev/null @@ -1,9 +0,0 @@ -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/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml deleted file mode 100644 index 24b26d9e5..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/Chart.yaml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: v2 -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/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/charts/dev-v0.1.0.tgz deleted file mode 100644 index d28e1621c86a56affb0617a912930d982ee5d09c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml deleted file mode 100644 index 80a52f538..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: dev -version: v0.1.0 -appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml deleted file mode 100644 index 38f03484d..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/dev/values.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Dev values parent-chart -nameOverride: parent-chart-dev -exports: - data: - resources: - autoscaler: - minReplicas: 1 - maxReplicas: 3 - targetCPUUtilizationPercentage: 80 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml deleted file mode 100644 index bda4be458..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v2 -name: prod -version: v0.1.0 -appVersion: v0.1.0 \ No newline at end of file diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml deleted file mode 100644 index 10cc756b2..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/envs/prod/values.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# Prod values parent-chart -nameOverride: parent-chart-prod -exports: - data: - resources: - autoscaler: - minReplicas: 2 - maxReplicas: 5 - targetCPUUtilizationPercentage: 90 diff --git a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml deleted file mode 100644 index 976e5a8f1..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/templates/autoscaler.yaml +++ /dev/null @@ -1,16 +0,0 @@ -################################################################################################### -# 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/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml b/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml deleted file mode 100644 index b812f0a33..000000000 --- a/pkg/chartutil/testdata/import-values-from-enabled-subchart/parent-chart/values.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# 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/pkg/chartutil/testdata/joonix/Chart.yaml b/pkg/chartutil/testdata/joonix/Chart.yaml deleted file mode 100644 index c3464c56e..000000000 --- a/pkg/chartutil/testdata/joonix/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: joonix -version: 1.2.3 diff --git a/pkg/chartutil/testdata/joonix/charts/.gitkeep b/pkg/chartutil/testdata/joonix/charts/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/chartutil/testdata/subpop/Chart.yaml b/pkg/chartutil/testdata/subpop/Chart.yaml deleted file mode 100644 index 27118672a..000000000 --- a/pkg/chartutil/testdata/subpop/Chart.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/subpop/README.md b/pkg/chartutil/testdata/subpop/README.md deleted file mode 100644 index e43fbfe9c..000000000 --- a/pkg/chartutil/testdata/subpop/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## 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/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml deleted file mode 100644 index 9d8c03ee1..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml +++ /dev/null @@ -1,36 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml deleted file mode 100644 index be3edcefb..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subcharta -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml deleted file mode 100644 index f0381ae6a..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml deleted file mode 100644 index c3c6bbaf0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartb -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml deleted file mode 100644 index 774fdd75c..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml deleted file mode 100644 index fca77fd4b..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/crds/crdA.yaml +++ /dev/null @@ -1,13 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt deleted file mode 100644 index 4bdf443f6..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml deleted file mode 100644 index fee94dced..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml +++ /dev/null @@ -1,22 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml deleted file mode 100644 index 91b954e5f..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/role.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ .Chart.Name }}-role -rules: -- resources: ["*"] - verbs: ["get","list","watch"] diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml deleted file mode 100644 index 5d193f1a6..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/rolebinding.yaml +++ /dev/null @@ -1,12 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml deleted file mode 100644 index 7126c7d89..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/templates/subdir/serviceaccount.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ .Chart.Name }}-sa diff --git a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml deleted file mode 100644 index a974e316a..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml deleted file mode 100644 index f936528a7..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -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/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml deleted file mode 100644 index c3c6bbaf0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartb -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml deleted file mode 100644 index 3f168bdbf..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: subchart2-{{ .Chart.Name }} - labels: - helm.sh/hart: "{{ .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/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml deleted file mode 100644 index dcc45c088..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: subchartc -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml b/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml deleted file mode 100644 index 5e5b21065..000000000 --- a/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml b/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml deleted file mode 100644 index bbb0941c3..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: parentchart -version: 0.1.0 diff --git a/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml b/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml deleted file mode 100644 index 27501e1e0..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -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/pkg/chartutil/testdata/subpop/noreqs/values.yaml b/pkg/chartutil/testdata/subpop/noreqs/values.yaml deleted file mode 100644 index 4ed3b7ad3..000000000 --- a/pkg/chartutil/testdata/subpop/noreqs/values.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# 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/pkg/chartutil/testdata/subpop/values.yaml b/pkg/chartutil/testdata/subpop/values.yaml deleted file mode 100644 index d611d6a89..000000000 --- a/pkg/chartutil/testdata/subpop/values.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# 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 diff --git a/pkg/chartutil/testdata/test-values-negative.yaml b/pkg/chartutil/testdata/test-values-negative.yaml deleted file mode 100644 index 5a1250bff..000000000 --- a/pkg/chartutil/testdata/test-values-negative.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/chartutil/testdata/test-values.schema.json b/pkg/chartutil/testdata/test-values.schema.json deleted file mode 100644 index 4df89bbe8..000000000 --- a/pkg/chartutil/testdata/test-values.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$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/pkg/chartutil/testdata/test-values.yaml b/pkg/chartutil/testdata/test-values.yaml deleted file mode 100644 index 042dea664..000000000 --- a/pkg/chartutil/testdata/test-values.yaml +++ /dev/null @@ -1,17 +0,0 @@ -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/pkg/chartutil/testdata/three-level-dependent-chart/README.md b/pkg/chartutil/testdata/three-level-dependent-chart/README.md deleted file mode 100644 index a5fed642d..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# 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 dependecy, 2x: app1/app2) -- Umbrella Chart (Has all the app charts as dependencies) - -The precendence is as follows: `library < app < umbrella` - -Catches two use-cases: - -- app overwriting library (app2) -- umbrella overwriting app and library (app1) diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml deleted file mode 100644 index 7552e07cd..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/Chart.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v2 -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 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml deleted file mode 100644 index 388245e31..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v2 -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/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml deleted file mode 100644 index f2f8a90d9..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v2 -name: library -description: A Helm chart for Kubernetes -type: library -version: 0.1.0 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml deleted file mode 100644 index 3fd398b53..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/templates/service.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Service -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml deleted file mode 100644 index 0c08b6cd2..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/charts/library/values.yaml +++ /dev/null @@ -1,5 +0,0 @@ -exports: - defaults: - service: - type: ClusterIP - port: 9090 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml deleted file mode 100644 index 8ed8ddf1f..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/templates/service.yaml +++ /dev/null @@ -1 +0,0 @@ -{{- include "library.service" . }} diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml deleted file mode 100644 index 3728aa930..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app1/values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -service: - type: ClusterIP - port: 1234 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml deleted file mode 100644 index fea2768c7..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v2 -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/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml deleted file mode 100644 index f2f8a90d9..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v2 -name: library -description: A Helm chart for Kubernetes -type: library -version: 0.1.0 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml deleted file mode 100644 index 3fd398b53..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/templates/service.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Service -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml deleted file mode 100644 index 0c08b6cd2..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/charts/library/values.yaml +++ /dev/null @@ -1,5 +0,0 @@ -exports: - defaults: - service: - type: ClusterIP - port: 9090 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml deleted file mode 100644 index 8ed8ddf1f..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/templates/service.yaml +++ /dev/null @@ -1 +0,0 @@ -{{- include "library.service" . }} diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml deleted file mode 100644 index 98bd6d24b..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/charts/app2/values.yaml +++ /dev/null @@ -1,3 +0,0 @@ -service: - type: ClusterIP - port: 8080 diff --git a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml b/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml deleted file mode 100644 index 94ee31855..000000000 --- a/pkg/chartutil/testdata/three-level-dependent-chart/umbrella/values.yaml +++ /dev/null @@ -1,8 +0,0 @@ -app1: - enabled: true - service: - type: ClusterIP - port: 3456 - -app2: - enabled: true diff --git a/pkg/chartutil/validate_name.go b/pkg/chartutil/validate_name.go deleted file mode 100644 index 05c090cb6..000000000 --- a/pkg/chartutil/validate_name.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "fmt" - "regexp" - - "github.com/pkg/errors" -) - -// 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/pkg/chartutil/validate_name_test.go b/pkg/chartutil/validate_name_test.go deleted file mode 100644 index 5f0792f94..000000000 --- a/pkg/chartutil/validate_name_test.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import "testing" - -// TestValidateName 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/pkg/chartutil/values.go b/pkg/chartutil/values.go deleted file mode 100644 index 97bf44217..000000000 --- a/pkg/chartutil/values.go +++ /dev/null @@ -1,212 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "fmt" - "io" - "io/ioutil" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" -) - -// 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 := ioutil.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) { - 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 err := ValidateAgainstSchema(chrt, vals); err != nil { - errFmt := "values don't meet the specifications of the schema(s) in the following chart(s):\n%s" - return top, fmt.Errorf(errFmt, err.Error()) - } - - 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/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go deleted file mode 100644 index c95fa503a..000000000 --- a/pkg/chartutil/values_test.go +++ /dev/null @@ -1,292 +0,0 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package chartutil - -import ( - "bytes" - "fmt" - "testing" - "text/template" - - "helm.sh/helm/v3/pkg/chart" -) - -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 := ToRenderValues(c, overrideValues, o, nil) - 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{}) { - 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") - } - } -} diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go deleted file mode 100644 index ac3093629..000000000 --- a/pkg/cli/environment.go +++ /dev/null @@ -1,230 +0,0 @@ -/* -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 cli describes the operating environment for the Helm CLI. - -Helm's environment encapsulates all of the service dependencies Helm has. -These dependencies are expressed as interfaces so that alternate implementations -(mocks, etc.) can be easily generated. -*/ -package cli - -import ( - "fmt" - "os" - "strconv" - "strings" - - "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/helmpath" -) - -// defaultMaxHistory sets the maximum number of releases to 0: unlimited -const defaultMaxHistory = 10 - -// defaultBurstLimit sets the default client-side throttling limit -const defaultBurstLimit = 100 - -// EnvSettings describes all of the environment settings. -type EnvSettings struct { - namespace string - config *genericclioptions.ConfigFlags - - // KubeConfig is the path to the kubeconfig file - KubeConfig string - // KubeContext is the name of the kubeconfig context. - KubeContext string - // Bearer KubeToken used for authentication - KubeToken string - // Username to impersonate for the operation - KubeAsUser string - // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list - KubeAsGroups []string - // Kubernetes API Server Endpoint for authentication - KubeAPIServer string - // Custom certificate authority file. - KubeCaFile string - // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. - // This makes the HTTPS connections insecure - KubeInsecureSkipTLSVerify bool - // KubeTLSServerName overrides the name to use for server certificate validation. - // If it is not provided, the hostname used to contact the server is used - KubeTLSServerName string - // Debug indicates whether or not Helm is running in Debug mode. - Debug bool - // RegistryConfig is the path to the registry config file. - RegistryConfig string - // RepositoryConfig is the path to the repositories file. - RepositoryConfig string - // RepositoryCache is the path to the repository cache directory. - RepositoryCache string - // PluginsDirectory is the path to the plugins directory. - PluginsDirectory string - // MaxHistory is the max release history maintained. - MaxHistory int - // BurstLimit is the default client-side throttling limit. - BurstLimit int -} - -func New() *EnvSettings { - env := &EnvSettings{ - namespace: os.Getenv("HELM_NAMESPACE"), - MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), - KubeContext: os.Getenv("HELM_KUBECONTEXT"), - KubeToken: os.Getenv("HELM_KUBETOKEN"), - KubeAsUser: os.Getenv("HELM_KUBEASUSER"), - KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), - KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), - KubeCaFile: os.Getenv("HELM_KUBECAFILE"), - KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), - KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), - PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), - RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), - RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), - RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), - BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), - } - env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) - - // bind to kubernetes config flags - env.config = &genericclioptions.ConfigFlags{ - Namespace: &env.namespace, - Context: &env.KubeContext, - BearerToken: &env.KubeToken, - APIServer: &env.KubeAPIServer, - CAFile: &env.KubeCaFile, - KubeConfig: &env.KubeConfig, - Impersonate: &env.KubeAsUser, - Insecure: &env.KubeInsecureSkipTLSVerify, - TLSServerName: &env.KubeTLSServerName, - ImpersonateGroup: &env.KubeAsGroups, - WrapConfigFn: func(config *rest.Config) *rest.Config { - config.Burst = env.BurstLimit - return config - }, - } - return env -} - -// AddFlags binds flags to the given flagset. -func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") - fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") - fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") - fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") - fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") - fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") - fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") - fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") - fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") - fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") - fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") - fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") - fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") - fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the file containing cached repository indexes") - fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") -} - -func envOr(name, def string) string { - if v, ok := os.LookupEnv(name); ok { - return v - } - return def -} - -func envBoolOr(name string, def bool) bool { - if name == "" { - return def - } - envVal := envOr(name, strconv.FormatBool(def)) - ret, err := strconv.ParseBool(envVal) - if err != nil { - return def - } - return ret -} - -func envIntOr(name string, def int) int { - if name == "" { - return def - } - envVal := envOr(name, strconv.Itoa(def)) - ret, err := strconv.Atoi(envVal) - if err != nil { - return def - } - return ret -} - -func envCSV(name string) (ls []string) { - trimmed := strings.Trim(os.Getenv(name), ", ") - if trimmed != "" { - ls = strings.Split(trimmed, ",") - } - return -} - -func (s *EnvSettings) EnvVars() map[string]string { - envvars := map[string]string{ - "HELM_BIN": os.Args[0], - "HELM_CACHE_HOME": helmpath.CachePath(""), - "HELM_CONFIG_HOME": helmpath.ConfigPath(""), - "HELM_DATA_HOME": helmpath.DataPath(""), - "HELM_DEBUG": fmt.Sprint(s.Debug), - "HELM_PLUGINS": s.PluginsDirectory, - "HELM_REGISTRY_CONFIG": s.RegistryConfig, - "HELM_REPOSITORY_CACHE": s.RepositoryCache, - "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, - "HELM_NAMESPACE": s.Namespace(), - "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), - "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), - - // broken, these are populated from helm flags and not kubeconfig. - "HELM_KUBECONTEXT": s.KubeContext, - "HELM_KUBETOKEN": s.KubeToken, - "HELM_KUBEASUSER": s.KubeAsUser, - "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), - "HELM_KUBEAPISERVER": s.KubeAPIServer, - "HELM_KUBECAFILE": s.KubeCaFile, - "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), - "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, - } - if s.KubeConfig != "" { - envvars["KUBECONFIG"] = s.KubeConfig - } - return envvars -} - -// Namespace gets the namespace from the configuration -func (s *EnvSettings) Namespace() string { - if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { - return ns - } - return "default" -} - -// SetNamespace sets the namespace in the configuration -func (s *EnvSettings) SetNamespace(namespace string) { - s.namespace = namespace -} - -// RESTClientGetter gets the kubeconfig from EnvSettings -func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { - return s.config -} diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go deleted file mode 100644 index dbf056e3a..000000000 --- a/pkg/cli/environment_test.go +++ /dev/null @@ -1,248 +0,0 @@ -/* -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 cli - -import ( - "os" - "reflect" - "strings" - "testing" - - "github.com/spf13/pflag" -) - -func TestSetNamespace(t *testing.T) { - settings := New() - - if settings.namespace != "" { - t.Errorf("Expected empty namespace, got %s", settings.namespace) - } - - settings.SetNamespace("testns") - if settings.namespace != "testns" { - t.Errorf("Expected namespace testns, got %s", settings.namespace) - } - -} - -func TestEnvSettings(t *testing.T) { - tests := []struct { - name string - - // input - args string - envvars map[string]string - - // expected values - ns, kcontext string - debug bool - maxhistory int - kubeAsUser string - kubeAsGroups []string - kubeCaFile string - kubeInsecure bool - kubeTLSServer string - burstLimit int - }{ - { - name: "defaults", - ns: "default", - maxhistory: defaultMaxHistory, - burstLimit: defaultBurstLimit, - }, - { - name: "with flags set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - ns: "myns", - debug: true, - maxhistory: defaultMaxHistory, - burstLimit: 100, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - { - name: "with envvars set", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, - ns: "yourns", - maxhistory: 5, - burstLimit: 150, - debug: true, - kubeAsUser: "pikachu", - kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, - kubeCaFile: "/tmp/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - { - name: "with flags and envvars set", - args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", - envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org"}, - ns: "myns", - debug: true, - maxhistory: 5, - burstLimit: 175, - kubeAsUser: "poro", - kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, - kubeCaFile: "/my/ca.crt", - kubeTLSServer: "example.org", - kubeInsecure: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer resetEnv()() - - for k, v := range tt.envvars { - os.Setenv(k, v) - } - - flags := pflag.NewFlagSet("testing", pflag.ContinueOnError) - - settings := New() - settings.AddFlags(flags) - flags.Parse(strings.Split(tt.args, " ")) - - if settings.Debug != tt.debug { - t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) - } - if settings.Namespace() != tt.ns { - t.Errorf("expected namespace %q, got %q", tt.ns, settings.Namespace()) - } - if settings.KubeContext != tt.kcontext { - t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) - } - if settings.MaxHistory != tt.maxhistory { - t.Errorf("expected maxHistory %d, got %d", tt.maxhistory, settings.MaxHistory) - } - if tt.kubeAsUser != settings.KubeAsUser { - t.Errorf("expected kAsUser %q, got %q", tt.kubeAsUser, settings.KubeAsUser) - } - if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) { - t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups)) - } - if tt.kubeCaFile != settings.KubeCaFile { - t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) - } - if tt.burstLimit != settings.BurstLimit { - t.Errorf("expected BurstLimit %d, got %d", tt.burstLimit, settings.BurstLimit) - } - if tt.kubeInsecure != settings.KubeInsecureSkipTLSVerify { - t.Errorf("expected kubeInsecure %t, got %t", tt.kubeInsecure, settings.KubeInsecureSkipTLSVerify) - } - if tt.kubeTLSServer != settings.KubeTLSServerName { - t.Errorf("expected kubeTLSServer %q, got %q", tt.kubeTLSServer, settings.KubeTLSServerName) - } - }) - } -} - -func TestEnvOrBool(t *testing.T) { - const envName = "TEST_ENV_OR_BOOL" - tests := []struct { - name string - env string - val string - def bool - expected bool - }{ - { - name: "unset with default false", - def: false, - expected: false, - }, - { - name: "unset with default true", - def: true, - expected: true, - }, - { - name: "blank env with default false", - env: envName, - def: false, - expected: false, - }, - { - name: "blank env with default true", - env: envName, - def: true, - expected: true, - }, - { - name: "env true with default false", - env: envName, - val: "true", - def: false, - expected: true, - }, - { - name: "env false with default true", - env: envName, - val: "false", - def: true, - expected: false, - }, - { - name: "env fails parsing with default true", - env: envName, - val: "NOT_A_BOOL", - def: true, - expected: true, - }, - { - name: "env fails parsing with default false", - env: envName, - val: "NOT_A_BOOL", - def: false, - expected: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.env != "" { - t.Cleanup(func() { - os.Unsetenv(tt.env) - }) - os.Setenv(tt.env, tt.val) - } - actual := envBoolOr(tt.env, tt.def) - if actual != tt.expected { - t.Errorf("expected result %t, got %t", tt.expected, actual) - } - }) - } -} - -func resetEnv() func() { - origEnv := os.Environ() - - // ensure any local envvars do not hose us - for e := range New().EnvVars() { - os.Unsetenv(e) - } - - return func() { - for _, pair := range origEnv { - kv := strings.SplitN(pair, "=", 2) - os.Setenv(kv[0], kv[1]) - } - } -} diff --git a/pkg/cli/output/output.go b/pkg/cli/output/output.go deleted file mode 100644 index a46c977ad..000000000 --- a/pkg/cli/output/output.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -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 output - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/gosuri/uitable" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" -) - -// Format is a type for capturing supported output formats -type Format string - -const ( - Table Format = "table" - JSON Format = "json" - YAML Format = "yaml" -) - -// Formats returns a list of the string representation of the supported formats -func Formats() []string { - return []string{Table.String(), JSON.String(), YAML.String()} -} - -// FormatsWithDesc returns a list of the string representation of the supported formats -// including a description -func FormatsWithDesc() map[string]string { - return map[string]string{ - Table.String(): "Output result in human-readable format", - JSON.String(): "Output result in JSON format", - YAML.String(): "Output result in YAML format", - } -} - -// ErrInvalidFormatType is returned when an unsupported format type is used -var ErrInvalidFormatType = fmt.Errorf("invalid format type") - -// String returns the string representation of the Format -func (o Format) String() string { - return string(o) -} - -// Write the output in the given format to the io.Writer. Unsupported formats -// will return an error -func (o Format) Write(out io.Writer, w Writer) error { - switch o { - case Table: - return w.WriteTable(out) - case JSON: - return w.WriteJSON(out) - case YAML: - return w.WriteYAML(out) - } - return ErrInvalidFormatType -} - -// ParseFormat takes a raw string and returns the matching Format. -// If the format does not exists, ErrInvalidFormatType is returned -func ParseFormat(s string) (out Format, err error) { - switch s { - case Table.String(): - out, err = Table, nil - case JSON.String(): - out, err = JSON, nil - case YAML.String(): - out, err = YAML, nil - default: - out, err = "", ErrInvalidFormatType - } - return -} - -// Writer is an interface that any type can implement to write supported formats -type Writer interface { - // WriteTable will write tabular output into the given io.Writer, returning - // an error if any occur - WriteTable(out io.Writer) error - // WriteJSON will write JSON formatted output into the given io.Writer, - // returning an error if any occur - WriteJSON(out io.Writer) error - // WriteYAML will write YAML formatted output into the given io.Writer, - // returning an error if any occur - WriteYAML(out io.Writer) error -} - -// EncodeJSON is a helper function to decorate any error message with a bit more -// context and avoid writing the same code over and over for printers. -func EncodeJSON(out io.Writer, obj interface{}) error { - enc := json.NewEncoder(out) - err := enc.Encode(obj) - if err != nil { - return errors.Wrap(err, "unable to write JSON output") - } - return nil -} - -// EncodeYAML is a helper function to decorate any error message with a bit more -// context and avoid writing the same code over and over for printers -func EncodeYAML(out io.Writer, obj interface{}) error { - raw, err := yaml.Marshal(obj) - if err != nil { - return errors.Wrap(err, "unable to write YAML output") - } - - _, err = out.Write(raw) - if err != nil { - return errors.Wrap(err, "unable to write YAML output") - } - return nil -} - -// EncodeTable is a helper function to decorate any error message with a bit -// more context and avoid writing the same code over and over for printers -func EncodeTable(out io.Writer, table *uitable.Table) error { - raw := table.Bytes() - raw = append(raw, []byte("\n")...) - _, err := out.Write(raw) - if err != nil { - return errors.Wrap(err, "unable to write table output") - } - return nil -} diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go deleted file mode 100644 index b895211d5..000000000 --- a/pkg/cli/values/options.go +++ /dev/null @@ -1,138 +0,0 @@ -/* -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 values - -import ( - "io/ioutil" - "net/url" - "os" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/strvals" -) - -type Options struct { - ValueFiles []string - StringValues []string - Values []string - FileValues []string - JSONValues []string -} - -// MergeValues merges values from files specified via -f/--values and directly -// via --set, --set-string, or --set-file, marshaling them to YAML -func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range opts.ValueFiles { - currentMap := map[string]interface{}{} - - bytes, err := readFile(filePath, p) - if err != nil { - return nil, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return nil, errors.Wrapf(err, "failed to parse %s", filePath) - } - // Merge with the previous map - base = mergeMaps(base, currentMap) - } - - // User specified a value via --set-json - for _, value := range opts.JSONValues { - if err := strvals.ParseJSON(value, base); err != nil { - return nil, errors.Errorf("failed parsing --set-json data %s", value) - } - } - - // User specified a value via --set - for _, value := range opts.Values { - if err := strvals.ParseInto(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set data") - } - } - - // User specified a value via --set-string - for _, value := range opts.StringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-string data") - } - } - - // User specified a value via --set-file - for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { - bytes, err := readFile(string(rs), p) - if err != nil { - return nil, err - } - return string(bytes), err - } - if err := strvals.ParseIntoFile(value, base, reader); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-file data") - } - } - - return base, nil -} - -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) - for k, v := range a { - out[k] = v - } - 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 -} - -// readFile load a file from stdin, the local directory, or a remote file with a url. -func readFile(filePath string, p getter.Providers) ([]byte, error) { - if strings.TrimSpace(filePath) == "-" { - return ioutil.ReadAll(os.Stdin) - } - u, err := url.Parse(filePath) - if err != nil { - return nil, err - } - - // FIXME: maybe someone handle other protocols like ftp. - g, err := p.ByScheme(u.Scheme) - if err != nil { - return ioutil.ReadFile(filePath) - } - data, err := g.Get(filePath, getter.WithURL(filePath)) - if err != nil { - return nil, err - } - return data.Bytes(), err -} diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go deleted file mode 100644 index 54124c0fa..000000000 --- a/pkg/cli/values/options_test.go +++ /dev/null @@ -1,88 +0,0 @@ -/* -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 values - -import ( - "reflect" - "testing" - - "helm.sh/helm/v3/pkg/getter" -) - -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 TestReadFile(t *testing.T) { - var p getter.Providers - filePath := "%a.txt" - _, err := readFile(filePath, p) - if err == nil { - t.Errorf("Expected error when has special strings") - } -} diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go deleted file mode 100644 index c532759b5..000000000 --- a/pkg/downloader/chart_downloader.go +++ /dev/null @@ -1,425 +0,0 @@ -/* -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 downloader - -import ( - "fmt" - "io" - "net/url" - "os" - "path/filepath" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/fileutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// VerificationStrategy describes a strategy for determining whether to verify a chart. -type VerificationStrategy int - -const ( - // VerifyNever will skip all verification of a chart. - VerifyNever VerificationStrategy = iota - // VerifyIfPossible will attempt a verification, it will not error if verification - // data is missing. But it will not stop processing if verification fails. - VerifyIfPossible - // VerifyAlways will always attempt a verification, and will fail if the - // verification fails. - VerifyAlways - // VerifyLater will fetch verification data, but not do any verification. - // This is to accommodate the case where another step of the process will - // perform verification. - VerifyLater -) - -// ErrNoOwnerRepo indicates that a given chart URL can't be found in any repos. -var ErrNoOwnerRepo = errors.New("could not find a repo containing the given URL") - -// ChartDownloader handles downloading a chart. -// -// It is capable of performing verifications on charts as well. -type ChartDownloader struct { - // Out is the location to write warning and info messages. - Out io.Writer - // Verify indicates what verification strategy to use. - Verify VerificationStrategy - // Keyring is the keyring file used for verification. - Keyring string - // Getter collection for the operation - Getters getter.Providers - // Options provide parameters to be passed along to the Getter being initialized. - Options []getter.Option - RegistryClient *registry.Client - RepositoryConfig string - RepositoryCache string -} - -// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file. -// -// If Verify is set to VerifyNever, the verification will be nil. -// If Verify is set to VerifyIfPossible, this will return a verification (or nil on failure), and print a warning on failure. -// If Verify is set to VerifyAlways, this will return a verification or an error if the verification fails. -// If Verify is set to VerifyLater, this will download the prov file (if it exists), but not verify it. -// -// For VerifyNever and VerifyIfPossible, the Verification may be empty. -// -// Returns a string path to the location where the file was downloaded and a verification -// (if provenance was verified), or an error if something bad happened. -func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *provenance.Verification, error) { - u, err := c.ResolveChartVersion(ref, version) - if err != nil { - return "", nil, err - } - - g, err := c.Getters.ByScheme(u.Scheme) - if err != nil { - return "", nil, err - } - - data, err := g.Get(u.String(), c.Options...) - if err != nil { - return "", nil, err - } - - name := filepath.Base(u.Path) - if u.Scheme == registry.OCIScheme { - idx := strings.LastIndexByte(name, ':') - name = fmt.Sprintf("%s-%s.tgz", name[:idx], name[idx+1:]) - } - - destfile := filepath.Join(dest, name) - if err := fileutil.AtomicWriteFile(destfile, data, 0644); err != nil { - return destfile, nil, err - } - - // If provenance is requested, verify it. - ver := &provenance.Verification{} - if c.Verify > VerifyNever { - body, err := g.Get(u.String() + ".prov") - if err != nil { - if c.Verify == VerifyAlways { - return destfile, ver, errors.Errorf("failed to fetch provenance %q", u.String()+".prov") - } - fmt.Fprintf(c.Out, "WARNING: Verification not found for %s: %s\n", ref, err) - return destfile, ver, nil - } - provfile := destfile + ".prov" - if err := fileutil.AtomicWriteFile(provfile, body, 0644); err != nil { - return destfile, nil, err - } - - if c.Verify != VerifyLater { - ver, err = VerifyChart(destfile, c.Keyring) - if err != nil { - // Fail always in this case, since it means the verification step - // failed. - return destfile, ver, err - } - } - } - return destfile, ver, nil -} - -func (c *ChartDownloader) getOciURI(ref, version string, u *url.URL) (*url.URL, error) { - var tag string - var err error - - // Evaluate whether an explicit version has been provided. Otherwise, determine version to use - _, errSemVer := semver.NewVersion(version) - if errSemVer == nil { - tag = version - } else { - // Retrieve list of repository tags - tags, err := c.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme))) - if err != nil { - return nil, err - } - if len(tags) == 0 { - return nil, errors.Errorf("Unable to locate any tags in provided repository: %s", ref) - } - - // Determine if version provided - // If empty, try to get the highest available tag - // If exact version, try to find it - // If semver constraint string, try to find a match - tag, err = registry.GetTagMatchingVersionOrConstraint(tags, version) - if err != nil { - return nil, err - } - } - - u.Path = fmt.Sprintf("%s:%s", u.Path, tag) - - return u, err -} - -// ResolveChartVersion resolves a chart reference to a URL. -// -// It returns the URL and sets the ChartDownloader's Options that can fetch -// the URL using the appropriate Getter. -// -// A reference may be an HTTP URL, an oci reference URL, a 'reponame/chartname' -// reference, or a local path. -// -// A version is a SemVer string (1.2.3-beta.1+f334a6789). -// -// - For fully qualified URLs, the version will be ignored (since URLs aren't versioned) -// - For a chart reference -// * If version is non-empty, this will return the URL for that version -// * If version is empty, this will return the URL for the latest version -// * If no version can be found, an error is returned -func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, error) { - u, err := url.Parse(ref) - if err != nil { - return nil, errors.Errorf("invalid chart URL format: %s", ref) - } - - if registry.IsOCI(u.String()) { - return c.getOciURI(ref, version, u) - } - - rf, err := loadRepoConfig(c.RepositoryConfig) - if err != nil { - return u, err - } - - if u.IsAbs() && len(u.Host) > 0 && len(u.Path) > 0 { - // In this case, we have to find the parent repo that contains this chart - // URL. And this is an unfortunate problem, as it requires actually going - // through each repo cache file and finding a matching URL. But basically - // we want to find the repo in case we have special SSL cert config - // for that repo. - - rc, err := c.scanReposForURL(ref, rf) - if err != nil { - // If there is no special config, return the default HTTP client and - // swallow the error. - if err == ErrNoOwnerRepo { - // Make sure to add the ref URL as the URL for the getter - c.Options = append(c.Options, getter.WithURL(ref)) - return u, nil - } - return u, err - } - - // If we get here, we don't need to go through the next phase of looking - // up the URL. We have it already. So we just set the parameters and return. - c.Options = append( - c.Options, - getter.WithURL(rc.URL), - ) - if rc.CertFile != "" || rc.KeyFile != "" || rc.CAFile != "" { - c.Options = append(c.Options, getter.WithTLSClientConfig(rc.CertFile, rc.KeyFile, rc.CAFile)) - } - if rc.Username != "" && rc.Password != "" { - c.Options = append( - c.Options, - getter.WithBasicAuth(rc.Username, rc.Password), - getter.WithPassCredentialsAll(rc.PassCredentialsAll), - ) - } - return u, nil - } - - // See if it's of the form: repo/path_to_chart - p := strings.SplitN(u.Path, "/", 2) - if len(p) < 2 { - return u, errors.Errorf("non-absolute URLs should be in form of repo_name/path_to_chart, got: %s", u) - } - - repoName := p[0] - chartName := p[1] - rc, err := pickChartRepositoryConfigByName(repoName, rf.Repositories) - - if err != nil { - return u, err - } - - // Now that we have the chart repository information we can use that URL - // to set the URL for the getter. - c.Options = append(c.Options, getter.WithURL(rc.URL)) - - r, err := repo.NewChartRepository(rc, c.Getters) - if err != nil { - return u, err - } - - if r != nil && r.Config != nil { - if r.Config.CertFile != "" || r.Config.KeyFile != "" || r.Config.CAFile != "" { - c.Options = append(c.Options, getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile)) - } - if r.Config.Username != "" && r.Config.Password != "" { - c.Options = append(c.Options, - getter.WithBasicAuth(r.Config.Username, r.Config.Password), - getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), - ) - } - } - - // Next, we need to load the index, and actually look up the chart. - idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) - i, err := repo.LoadIndexFile(idxFile) - if err != nil { - return u, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") - } - - cv, err := i.Get(chartName, version) - if err != nil { - return u, errors.Wrapf(err, "chart %q matching %s not found in %s index. (try 'helm repo update')", chartName, version, r.Config.Name) - } - - if len(cv.URLs) == 0 { - return u, errors.Errorf("chart %q has no downloadable URLs", ref) - } - - // TODO: Seems that picking first URL is not fully correct - u, err = url.Parse(cv.URLs[0]) - if err != nil { - return u, errors.Errorf("invalid chart URL format: %s", ref) - } - - // If the URL is relative (no scheme), prepend the chart repo's base URL - if !u.IsAbs() { - repoURL, err := url.Parse(rc.URL) - if err != nil { - return repoURL, err - } - q := repoURL.Query() - // We need a trailing slash for ResolveReference to work, but make sure there isn't already one - repoURL.RawPath = strings.TrimSuffix(repoURL.RawPath, "/") + "/" - repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" - u = repoURL.ResolveReference(u) - u.RawQuery = q.Encode() - // TODO add user-agent - if _, err := getter.NewHTTPGetter(getter.WithURL(rc.URL)); err != nil { - return repoURL, err - } - return u, err - } - - // TODO add user-agent - return u, nil -} - -// VerifyChart takes a path to a chart archive and a keyring, and verifies the chart. -// -// It assumes that a chart archive file is accompanied by a provenance file whose -// name is the archive file name plus the ".prov" extension. -func VerifyChart(path, keyring string) (*provenance.Verification, error) { - // For now, error out if it's not a tar file. - switch fi, err := os.Stat(path); { - case err != nil: - return nil, err - case fi.IsDir(): - return nil, errors.New("unpacked charts cannot be verified") - case !isTar(path): - return nil, errors.New("chart must be a tgz file") - } - - provfile := path + ".prov" - if _, err := os.Stat(provfile); err != nil { - return nil, errors.Wrapf(err, "could not load provenance file %s", provfile) - } - - sig, err := provenance.NewFromKeyring(keyring, "") - if err != nil { - return nil, errors.Wrap(err, "failed to load keyring") - } - return sig.Verify(path, provfile) -} - -// isTar tests whether the given file is a tar file. -// -// Currently, this simply checks extension, since a subsequent function will -// untar the file and validate its binary format. -func isTar(filename string) bool { - return strings.EqualFold(filepath.Ext(filename), ".tgz") -} - -func pickChartRepositoryConfigByName(name string, cfgs []*repo.Entry) (*repo.Entry, error) { - for _, rc := range cfgs { - if rc.Name == name { - if rc.URL == "" { - return nil, errors.Errorf("no URL found for repository %s", name) - } - return rc, nil - } - } - return nil, errors.Errorf("repo %s not found", name) -} - -// scanReposForURL scans all repos to find which repo contains the given URL. -// -// This will attempt to find the given URL in all of the known repositories files. -// -// If the URL is found, this will return the repo entry that contained that URL. -// -// If all of the repos are checked, but the URL is not found, an ErrNoOwnerRepo -// error is returned. -// -// Other errors may be returned when repositories cannot be loaded or searched. -// -// Technically, the fact that a URL is not found in a repo is not a failure indication. -// Charts are not required to be included in an index before they are valid. So -// be mindful of this case. -// -// The same URL can technically exist in two or more repositories. This algorithm -// will return the first one it finds. Order is determined by the order of repositories -// in the repositories.yaml file. -func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry, error) { - // FIXME: This is far from optimal. Larger installations and index files will - // incur a performance hit for this type of scanning. - for _, rc := range rf.Repositories { - r, err := repo.NewChartRepository(rc, c.Getters) - if err != nil { - return nil, err - } - - idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) - i, err := repo.LoadIndexFile(idxFile) - if err != nil { - return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") - } - - for _, entry := range i.Entries { - for _, ver := range entry { - for _, dl := range ver.URLs { - if urlutil.Equal(u, dl) { - return rc, nil - } - } - } - } - } - // This means that there is no repo file for the given URL. - return nil, ErrNoOwnerRepo -} - -func loadRepoConfig(file string) (*repo.File, error) { - r, err := repo.LoadFile(file) - if err != nil && !os.IsNotExist(errors.Cause(err)) { - return nil, err - } - return r, nil -} diff --git a/pkg/downloader/chart_downloader_test.go b/pkg/downloader/chart_downloader_test.go deleted file mode 100644 index 8ff780daf..000000000 --- a/pkg/downloader/chart_downloader_test.go +++ /dev/null @@ -1,347 +0,0 @@ -/* -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 downloader - -import ( - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo" - "helm.sh/helm/v3/pkg/repo/repotest" -) - -const ( - repoConfig = "testdata/repositories.yaml" - repoCache = "testdata/repository" -) - -func TestResolveChartRef(t *testing.T) { - tests := []struct { - name, ref, expect, version string - fail bool - }{ - {name: "full URL", ref: "http://example.com/foo-1.2.3.tgz", expect: "http://example.com/foo-1.2.3.tgz"}, - {name: "full URL, HTTPS", ref: "https://example.com/foo-1.2.3.tgz", expect: "https://example.com/foo-1.2.3.tgz"}, - {name: "full URL, with authentication", ref: "http://username:password@example.com/foo-1.2.3.tgz", expect: "http://username:password@example.com/foo-1.2.3.tgz"}, - {name: "reference, testing repo", ref: "testing/alpine", expect: "http://example.com/alpine-1.2.3.tgz"}, - {name: "reference, version, testing repo", ref: "testing/alpine", version: "0.2.0", expect: "http://example.com/alpine-0.2.0.tgz"}, - {name: "reference, version, malformed repo", ref: "malformed/alpine", version: "1.2.3", expect: "http://dl.example.com/alpine-1.2.3.tgz"}, - {name: "reference, querystring repo", ref: "testing-querystring/alpine", expect: "http://example.com/alpine-1.2.3.tgz?key=value"}, - {name: "reference, testing-relative repo", ref: "testing-relative/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, - {name: "reference, testing-relative repo", ref: "testing-relative/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, - {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/foo", expect: "http://example.com/helm/charts/foo-1.2.3.tgz"}, - {name: "reference, testing-relative-trailing-slash repo", ref: "testing-relative-trailing-slash/bar", expect: "http://example.com/helm/bar-1.2.3.tgz"}, - {name: "encoded URL", ref: "encoded-url/foobar", expect: "http://example.com/with%2Fslash/charts/foobar-4.2.1.tgz"}, - {name: "full URL, HTTPS, irrelevant version", ref: "https://example.com/foo-1.2.3.tgz", version: "0.1.0", expect: "https://example.com/foo-1.2.3.tgz", fail: true}, - {name: "full URL, file", ref: "file:///foo-1.2.3.tgz", fail: true}, - {name: "invalid", ref: "invalid-1.2.3", fail: true}, - {name: "not found", ref: "nosuchthing/invalid-1.2.3", fail: true}, - } - - c := ChartDownloader{ - Out: os.Stderr, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - for _, tt := range tests { - u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - if tt.fail { - continue - } - t.Errorf("%s: failed with error %q", tt.name, err) - continue - } - if got := u.String(); got != tt.expect { - t.Errorf("%s: expected %s, got %s", tt.name, tt.expect, got) - } - } -} - -func TestResolveChartOpts(t *testing.T) { - tests := []struct { - name, ref, version string - expect []getter.Option - }{ - { - name: "repo with CA-file", - ref: "testing-ca-file/foo", - expect: []getter.Option{ - getter.WithURL("https://example.com/foo-1.2.3.tgz"), - getter.WithTLSClientConfig("cert", "key", "ca"), - }, - }, - } - - c := ChartDownloader{ - Out: os.Stderr, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - // snapshot options - snapshotOpts := c.Options - - for _, tt := range tests { - // reset chart downloader options for each test case - c.Options = snapshotOpts - - expect, err := getter.NewHTTPGetter(tt.expect...) - if err != nil { - t.Errorf("%s: failed to setup http client: %s", tt.name, err) - continue - } - - u, err := c.ResolveChartVersion(tt.ref, tt.version) - if err != nil { - t.Errorf("%s: failed with error %s", tt.name, err) - continue - } - - got, err := getter.NewHTTPGetter( - append( - c.Options, - getter.WithURL(u.String()), - )..., - ) - if err != nil { - t.Errorf("%s: failed to create http client: %s", tt.name, err) - continue - } - - if *(got.(*getter.HTTPGetter)) != *(expect.(*getter.HTTPGetter)) { - t.Errorf("%s: expected %s, got %s", tt.name, expect, got) - } - } -} - -func TestVerifyChart(t *testing.T) { - v, err := VerifyChart("testdata/signtest-0.1.0.tgz", "testdata/helm-test-key.pub") - if err != nil { - t.Fatal(err) - } - // The verification is tested at length in the provenance package. Here, - // we just want a quick sanity check that the v is not empty. - if len(v.FileHash) == 0 { - t.Error("Digest missing") - } -} - -func TestIsTar(t *testing.T) { - tests := map[string]bool{ - "foo.tgz": true, - "foo/bar/baz.tgz": true, - "foo-1.2.3.4.5.tgz": true, - "foo.tar.gz": false, // for our purposes - "foo.tgz.1": false, - "footgz": false, - } - - for src, expect := range tests { - if isTar(src) != expect { - t.Errorf("%q should be %t", src, expect) - } - } -} - -func TestDownloadTo(t *testing.T) { - srv := repotest.NewTempServerWithCleanupAndBasicAuth(t, "testdata/*.tgz*") - defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyAlways, - Keyring: "testdata/helm-test-key.pub", - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - Options: []getter.Option{ - getter.WithBasicAuth("username", "password"), - getter.WithPassCredentialsAll(false), - }, - } - cname := "/signtest-0.1.0.tgz" - dest := srv.Root() - where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } - - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } - - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Error(err) - } -} - -func TestDownloadTo_TLS(t *testing.T) { - // Set up mock server w/ tls enabled - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - srv.Stop() - if err != nil { - t.Fatal(err) - } - srv.StartTLS() - defer srv.Stop() - if err := srv.CreateIndex(); err != nil { - t.Fatal(err) - } - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - repoConfig := filepath.Join(srv.Root(), "repositories.yaml") - repoCache := srv.Root() - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyAlways, - Keyring: "testdata/helm-test-key.pub", - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - Options: []getter.Option{}, - } - cname := "test/signtest" - dest := srv.Root() - where, v, err := c.DownloadTo(cname, "", dest) - if err != nil { - t.Fatal(err) - } - - target := filepath.Join(dest, "signtest-0.1.0.tgz") - if expect := target; where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if v.FileHash == "" { - t.Error("File hash was empty, but verification is required.") - } - - if _, err := os.Stat(target); err != nil { - t.Error(err) - } -} - -func TestDownloadTo_VerifyLater(t *testing.T) { - defer ensure.HelmHome(t)() - - dest := ensure.TempDir(t) - defer os.RemoveAll(dest) - - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyLater, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - cname := "/signtest-0.1.0.tgz" - where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) - if err != nil { - t.Fatal(err) - } - - if expect := filepath.Join(dest, cname); where != expect { - t.Errorf("Expected download to %s, got %s", expect, where) - } - - if _, err := os.Stat(filepath.Join(dest, cname)); err != nil { - t.Fatal(err) - } - if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { - t.Fatal(err) - } -} - -func TestScanReposForURL(t *testing.T) { - c := ChartDownloader{ - Out: os.Stderr, - Verify: VerifyLater, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - Getters: getter.All(&cli.EnvSettings{ - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - }), - } - - u := "http://example.com/alpine-0.2.0.tgz" - rf, err := repo.LoadFile(repoConfig) - if err != nil { - t.Fatal(err) - } - - entry, err := c.scanReposForURL(u, rf) - if err != nil { - t.Fatal(err) - } - - if entry.Name != "testing" { - t.Errorf("Unexpected repo %q for URL %q", entry.Name, u) - } - - // A lookup failure should produce an ErrNoOwnerRepo - u = "https://no.such.repo/foo/bar-1.23.4.tgz" - if _, err = c.scanReposForURL(u, rf); err != ErrNoOwnerRepo { - t.Fatalf("expected ErrNoOwnerRepo, got %v", err) - } -} diff --git a/pkg/downloader/doc.go b/pkg/downloader/doc.go deleted file mode 100644 index 9588a7dfe..000000000 --- a/pkg/downloader/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -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 downloader provides a library for downloading charts. - -This package contains various tools for downloading charts from repository -servers, and then storing them in Helm-specific directory structures. This -library contains many functions that depend on a specific -filesystem layout. -*/ -package downloader diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go deleted file mode 100644 index 18b28dde1..000000000 --- a/pkg/downloader/manager.go +++ /dev/null @@ -1,898 +0,0 @@ -/* -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 downloader - -import ( - "crypto" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "log" - "net/url" - "os" - "path" - "path/filepath" - "regexp" - "strings" - "sync" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/resolver" - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// ErrRepoNotFound indicates that chart repositories can't be found in local repo cache. -// The value of Repos is missing repos. -type ErrRepoNotFound struct { - Repos []string -} - -// Error implements the error interface. -func (e ErrRepoNotFound) Error() string { - return fmt.Sprintf("no repository definition for %s", strings.Join(e.Repos, ", ")) -} - -// Manager handles the lifecycle of fetching, resolving, and storing dependencies. -type Manager struct { - // Out is used to print warnings and notifications. - Out io.Writer - // ChartPath is the path to the unpacked base chart upon which this operates. - ChartPath string - // Verification indicates whether the chart should be verified. - Verify VerificationStrategy - // Debug is the global "--debug" flag - Debug bool - // Keyring is the key ring file. - Keyring string - // SkipUpdate indicates that the repository should not be updated first. - SkipUpdate bool - // Getter collection for the operation - Getters []getter.Provider - RegistryClient *registry.Client - RepositoryConfig string - RepositoryCache string -} - -// Build rebuilds a local charts directory from a lockfile. -// -// If the lockfile is not present, this will run a Manager.Update() -// -// If SkipUpdate is set, this will not update the repository. -func (m *Manager) Build() error { - c, err := m.loadChartDir() - if err != nil { - return err - } - - // If a lock file is found, run a build from that. Otherwise, just do - // an update. - lock := c.Lock - if lock == nil { - return m.Update() - } - - // Check that all of the repos we're dependent on actually exist. - req := c.Metadata.Dependencies - - // If using apiVersion v1, calculate the hash before resolve repo names - // because resolveRepoNames will change req if req uses repo alias - // and Helm 2 calculate the digest from the original req - // Fix for: https://github.com/helm/helm/issues/7619 - var v2Sum string - if c.Metadata.APIVersion == chart.APIVersionV1 { - v2Sum, err = resolver.HashV2Req(req) - if err != nil { - return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") - } - } - - if _, err := m.resolveRepoNames(req); err != nil { - return err - } - - if sum, err := resolver.HashReq(req, lock.Dependencies); err != nil || sum != lock.Digest { - // If lock digest differs and chart is apiVersion v1, it maybe because the lock was built - // with Helm 2 and therefore should be checked with Helm v2 hash - // Fix for: https://github.com/helm/helm/issues/7233 - if c.Metadata.APIVersion == chart.APIVersionV1 { - log.Println("warning: a valid Helm v3 hash was not found. Checking against Helm v2 hash...") - if v2Sum != lock.Digest { - return errors.New("the lock file (requirements.lock) is out of sync with the dependencies file (requirements.yaml). Please update the dependencies") - } - } else { - return errors.New("the lock file (Chart.lock) is out of sync with the dependencies file (Chart.yaml). Please update the dependencies") - } - } - - // Check that all of the repos we're dependent on actually exist. - if err := m.hasAllRepos(lock.Dependencies); err != nil { - return err - } - - if !m.SkipUpdate { - // For each repo in the file, update the cached copy of that repo - if err := m.UpdateRepositories(); err != nil { - return err - } - } - - // Now we need to fetch every package here into charts/ - return m.downloadAll(lock.Dependencies) -} - -// Update updates a local charts directory. -// -// It first reads the Chart.yaml file, and then attempts to -// negotiate versions based on that. It will download the versions -// from remote chart repositories unless SkipUpdate is true. -func (m *Manager) Update() error { - c, err := m.loadChartDir() - if err != nil { - return err - } - - // If no dependencies are found, we consider this a successful - // completion. - req := c.Metadata.Dependencies - if req == nil { - return nil - } - - // Get the names of the repositories the dependencies need that Helm is - // configured to know about. - repoNames, err := m.resolveRepoNames(req) - if err != nil { - return err - } - - // For the repositories Helm is not configured to know about, ensure Helm - // has some information about them and, when possible, the index files - // locally. - // TODO(mattfarina): Repositories should be explicitly added by end users - // rather than automattic. In Helm v4 require users to add repositories. They - // should have to add them in order to make sure they are aware of the - // repositories and opt-in to any locations, for security. - repoNames, err = m.ensureMissingRepos(repoNames, req) - if err != nil { - return err - } - - // For each of the repositories Helm is configured to know about, update - // the index information locally. - if !m.SkipUpdate { - if err := m.UpdateRepositories(); err != nil { - return err - } - } - - // Now we need to find out which version of a chart best satisfies the - // dependencies in the Chart.yaml - lock, err := m.resolve(req, repoNames) - if err != nil { - return err - } - - // Now we need to fetch every package here into charts/ - if err := m.downloadAll(lock.Dependencies); err != nil { - return err - } - - // downloadAll might overwrite dependency version, recalculate lock digest - newDigest, err := resolver.HashReq(req, lock.Dependencies) - if err != nil { - return err - } - lock.Digest = newDigest - - // If the lock file hasn't changed, don't write a new one. - oldLock := c.Lock - if oldLock != nil && oldLock.Digest == lock.Digest { - return nil - } - - // Finally, we need to write the lockfile. - return writeLock(m.ChartPath, lock, c.Metadata.APIVersion == chart.APIVersionV1) -} - -func (m *Manager) loadChartDir() (*chart.Chart, error) { - if fi, err := os.Stat(m.ChartPath); err != nil { - return nil, errors.Wrapf(err, "could not find %s", m.ChartPath) - } else if !fi.IsDir() { - return nil, errors.New("only unpacked charts can be updated") - } - return loader.LoadDir(m.ChartPath) -} - -// resolve takes a list of dependencies and translates them into an exact version to download. -// -// This returns a lock file, which has all of the dependencies normalized to a specific version. -func (m *Manager) resolve(req []*chart.Dependency, repoNames map[string]string) (*chart.Lock, error) { - res := resolver.New(m.ChartPath, m.RepositoryCache, m.RegistryClient) - return res.Resolve(req, repoNames) -} - -// downloadAll takes a list of dependencies and downloads them into charts/ -// -// It will delete versions of the chart that exist on disk and might cause -// a conflict. -func (m *Manager) downloadAll(deps []*chart.Dependency) error { - repos, err := m.loadChartRepositories() - if err != nil { - return err - } - - destPath := filepath.Join(m.ChartPath, "charts") - tmpPath := filepath.Join(m.ChartPath, "tmpcharts") - - // Check if 'charts' directory is not actally a directory. If it does not exist, create it. - if fi, err := os.Stat(destPath); err == nil { - if !fi.IsDir() { - return errors.Errorf("%q is not a directory", destPath) - } - } else if os.IsNotExist(err) { - if err := os.MkdirAll(destPath, 0755); err != nil { - return err - } - } else { - return fmt.Errorf("unable to retrieve file info for '%s': %v", destPath, err) - } - - // Prepare tmpPath - if err := os.MkdirAll(tmpPath, 0755); err != nil { - return err - } - defer os.RemoveAll(tmpPath) - - fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps)) - var saveError error - churls := make(map[string]struct{}) - for _, dep := range deps { - // No repository means the chart is in charts directory - if dep.Repository == "" { - fmt.Fprintf(m.Out, "Dependency %s did not declare a repository. Assuming it exists in the charts directory\n", dep.Name) - // NOTE: we are only validating the local dependency conforms to the constraints. No copying to tmpPath is necessary. - chartPath := filepath.Join(destPath, dep.Name) - ch, err := loader.LoadDir(chartPath) - if err != nil { - return fmt.Errorf("unable to load chart '%s': %v", chartPath, err) - } - - constraint, err := semver.NewConstraint(dep.Version) - if err != nil { - return fmt.Errorf("dependency %s has an invalid version/constraint format: %s", dep.Name, err) - } - - v, err := semver.NewVersion(ch.Metadata.Version) - if err != nil { - return fmt.Errorf("invalid version %s for dependency %s: %s", dep.Version, dep.Name, err) - } - - if !constraint.Check(v) { - saveError = fmt.Errorf("dependency %s at version %s does not satisfy the constraint %s", dep.Name, ch.Metadata.Version, dep.Version) - break - } - continue - } - if strings.HasPrefix(dep.Repository, "file://") { - if m.Debug { - fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository) - } - ver, err := tarFromLocalDir(m.ChartPath, dep.Name, dep.Repository, dep.Version, tmpPath) - if err != nil { - saveError = err - break - } - dep.Version = ver - continue - } - - // Any failure to resolve/download a chart should fail: - // https://github.com/helm/helm/issues/1439 - churl, username, password, insecureskiptlsverify, passcredentialsall, caFile, certFile, keyFile, err := m.findChartURL(dep.Name, dep.Version, dep.Repository, repos) - if err != nil { - saveError = errors.Wrapf(err, "could not find %s", churl) - break - } - - if _, ok := churls[churl]; ok { - fmt.Fprintf(m.Out, "Already downloaded %s from repo %s\n", dep.Name, dep.Repository) - continue - } - - fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) - - dl := ChartDownloader{ - Out: m.Out, - Verify: m.Verify, - Keyring: m.Keyring, - RepositoryConfig: m.RepositoryConfig, - RepositoryCache: m.RepositoryCache, - RegistryClient: m.RegistryClient, - Getters: m.Getters, - Options: []getter.Option{ - getter.WithBasicAuth(username, password), - getter.WithPassCredentialsAll(passcredentialsall), - getter.WithInsecureSkipVerifyTLS(insecureskiptlsverify), - getter.WithTLSClientConfig(certFile, keyFile, caFile), - }, - } - - version := "" - if registry.IsOCI(churl) { - churl, version, err = parseOCIRef(churl) - if err != nil { - return errors.Wrapf(err, "could not parse OCI reference") - } - dl.Options = append(dl.Options, - getter.WithRegistryClient(m.RegistryClient), - getter.WithTagName(version)) - } - - if _, _, err = dl.DownloadTo(churl, version, tmpPath); err != nil { - saveError = errors.Wrapf(err, "could not download %s", churl) - break - } - - churls[churl] = struct{}{} - } - - // TODO: this should probably be refactored to be a []error, so we can capture and provide more information rather than "last error wins". - if saveError == nil { - // now we can move all downloaded charts to destPath and delete outdated dependencies - if err := m.safeMoveDeps(deps, tmpPath, destPath); err != nil { - return err - } - } else { - fmt.Fprintln(m.Out, "Save error occurred: ", saveError) - return saveError - } - return nil -} - -func parseOCIRef(chartRef string) (string, string, error) { - refTagRegexp := regexp.MustCompile(`^(oci://[^:]+(:[0-9]{1,5})?[^:]+):(.*)$`) - caps := refTagRegexp.FindStringSubmatch(chartRef) - if len(caps) != 4 { - return "", "", errors.Errorf("improperly formatted oci chart reference: %s", chartRef) - } - chartRef = caps[1] - tag := caps[3] - - return chartRef, tag, nil -} - -// safeMoveDep moves all dependencies in the source and moves them into dest. -// -// It does this by first matching the file name to an expected pattern, then loading -// the file to verify that it is a chart. -// -// Any charts in dest that do not exist in source are removed (barring local dependencies) -// -// Because it requires tar file introspection, it is more intensive than a basic move. -// -// This will only return errors that should stop processing entirely. Other errors -// will emit log messages or be ignored. -func (m *Manager) safeMoveDeps(deps []*chart.Dependency, source, dest string) error { - existsInSourceDirectory := map[string]bool{} - isLocalDependency := map[string]bool{} - sourceFiles, err := os.ReadDir(source) - if err != nil { - return err - } - // attempt to read destFiles; fail fast if we can't - destFiles, err := os.ReadDir(dest) - if err != nil { - return err - } - - for _, dep := range deps { - if dep.Repository == "" { - isLocalDependency[dep.Name] = true - } - } - - for _, file := range sourceFiles { - if file.IsDir() { - continue - } - filename := file.Name() - sourcefile := filepath.Join(source, filename) - destfile := filepath.Join(dest, filename) - existsInSourceDirectory[filename] = true - if _, err := loader.LoadFile(sourcefile); err != nil { - fmt.Fprintf(m.Out, "Could not verify %s for moving: %s (Skipping)", sourcefile, err) - continue - } - // NOTE: no need to delete the dest; os.Rename replaces it. - if err := fs.RenameWithFallback(sourcefile, destfile); err != nil { - fmt.Fprintf(m.Out, "Unable to move %s to charts dir %s (Skipping)", sourcefile, err) - continue - } - } - - fmt.Fprintln(m.Out, "Deleting outdated charts") - // find all files that exist in dest that do not exist in source; delete them (outdated dependencies) - for _, file := range destFiles { - if !file.IsDir() && !existsInSourceDirectory[file.Name()] { - fname := filepath.Join(dest, file.Name()) - ch, err := loader.LoadFile(fname) - if err != nil { - fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)\n", fname, err) - continue - } - // local dependency - skip - if isLocalDependency[ch.Name()] { - continue - } - if err := os.Remove(fname); err != nil { - fmt.Fprintf(m.Out, "Could not delete %s: %s (Skipping)", fname, err) - continue - } - } - } - - return nil -} - -// hasAllRepos ensures that all of the referenced deps are in the local repo cache. -func (m *Manager) hasAllRepos(deps []*chart.Dependency) error { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return err - } - repos := rf.Repositories - - // Verify that all repositories referenced in the deps are actually known - // by Helm. - missing := []string{} -Loop: - for _, dd := range deps { - // If repo is from local path or OCI, continue - if strings.HasPrefix(dd.Repository, "file://") || registry.IsOCI(dd.Repository) { - continue - } - - if dd.Repository == "" { - continue - } - for _, repo := range repos { - if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { - continue Loop - } - } - missing = append(missing, dd.Repository) - } - if len(missing) > 0 { - return ErrRepoNotFound{missing} - } - return nil -} - -// ensureMissingRepos attempts to ensure the repository information for repos -// not managed by Helm is present. This takes in the repoNames Helm is configured -// to work with along with the chart dependencies. It will find the deps not -// in a known repo and attempt to ensure the data is present for steps like -// version resolution. -func (m *Manager) ensureMissingRepos(repoNames map[string]string, deps []*chart.Dependency) (map[string]string, error) { - - var ru []*repo.Entry - - for _, dd := range deps { - - // If the chart is in the local charts directory no repository needs - // to be specified. - if dd.Repository == "" { - continue - } - - // When the repoName for a dependency is known we can skip ensuring - if _, ok := repoNames[dd.Name]; ok { - continue - } - - // The generated repository name, which will result in an index being - // locally cached, has a name pattern of "helm-manager-" followed by a - // sha256 of the repo name. This assumes end users will never create - // repositories with these names pointing to other repositories. Using - // this method of naming allows the existing repository pulling and - // resolution code to do most of the work. - rn, err := key(dd.Repository) - if err != nil { - return repoNames, err - } - rn = managerKeyPrefix + rn - - repoNames[dd.Name] = rn - - // Assuming the repository is generally available. For Helm managed - // access controls the repository needs to be added through the user - // managed system. This path will work for public charts, like those - // supplied by Bitnami, but not for protected charts, like corp ones - // behind a username and pass. - ri := &repo.Entry{ - Name: rn, - URL: dd.Repository, - } - ru = append(ru, ri) - } - - // Calls to UpdateRepositories (a public function) will only update - // repositories configured by the user. Here we update repos found in - // the dependencies that are not known to the user if update skipping - // is not configured. - if !m.SkipUpdate && len(ru) > 0 { - fmt.Fprintln(m.Out, "Getting updates for unmanaged Helm repositories...") - if err := m.parallelRepoUpdate(ru); err != nil { - return repoNames, err - } - } - - return repoNames, nil -} - -// resolveRepoNames returns the repo names of the referenced deps which can be used to fetch the cached index file -// and replaces aliased repository URLs into resolved URLs in dependencies. -func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string, error) { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - if os.IsNotExist(err) { - return make(map[string]string), nil - } - return nil, err - } - repos := rf.Repositories - - reposMap := make(map[string]string) - - // Verify that all repositories referenced in the deps are actually known - // by Helm. - missing := []string{} - for _, dd := range deps { - // Don't map the repository, we don't need to download chart from charts directory - if dd.Repository == "" { - continue - } - // if dep chart is from local path, verify the path is valid - if strings.HasPrefix(dd.Repository, "file://") { - if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil { - return nil, err - } - - if m.Debug { - fmt.Fprintf(m.Out, "Repository from local path: %s\n", dd.Repository) - } - reposMap[dd.Name] = dd.Repository - continue - } - - if registry.IsOCI(dd.Repository) { - reposMap[dd.Name] = dd.Repository - continue - } - - found := false - - for _, repo := range repos { - if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) || - (strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) { - found = true - dd.Repository = repo.URL - reposMap[dd.Name] = repo.Name - break - } else if urlutil.Equal(repo.URL, dd.Repository) { - found = true - reposMap[dd.Name] = repo.Name - break - } - } - if !found { - repository := dd.Repository - // Add if URL - _, err := url.ParseRequestURI(repository) - if err == nil { - reposMap[repository] = repository - continue - } - missing = append(missing, repository) - } - } - if len(missing) > 0 { - errorMessage := fmt.Sprintf("no repository definition for %s. Please add them via 'helm repo add'", strings.Join(missing, ", ")) - // It is common for people to try to enter "stable" as a repository instead of the actual URL. - // For this case, let's give them a suggestion. - containsNonURL := false - for _, repo := range missing { - if !strings.Contains(repo, "//") && !strings.HasPrefix(repo, "@") && !strings.HasPrefix(repo, "alias:") { - containsNonURL = true - } - } - if containsNonURL { - errorMessage += ` -Note that repositories must be URLs or aliases. For example, to refer to the "example" -repository, use "https://charts.example.com/" or "@example" instead of -"example". Don't forget to add the repo, too ('helm repo add').` - } - return nil, errors.New(errorMessage) - } - return reposMap, nil -} - -// UpdateRepositories updates all of the local repos to the latest. -func (m *Manager) UpdateRepositories() error { - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return err - } - repos := rf.Repositories - if len(repos) > 0 { - fmt.Fprintln(m.Out, "Hang tight while we grab the latest from your chart repositories...") - // This prints warnings straight to out. - if err := m.parallelRepoUpdate(repos); err != nil { - return err - } - fmt.Fprintln(m.Out, "Update Complete. ⎈Happy Helming!⎈") - } - return nil -} - -func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { - - var wg sync.WaitGroup - for _, c := range repos { - r, err := repo.NewChartRepository(c, m.Getters) - if err != nil { - return err - } - wg.Add(1) - go func(r *repo.ChartRepository) { - if _, err := r.DownloadIndexFile(); err != nil { - // For those dependencies that are not known to helm and using a - // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { - fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository:\n\t%s\n", r.Config.URL, err) - } else { - fmt.Fprintf(m.Out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) - } - } else { - // For those dependencies that are not known to helm and using a - // generated key name we display the repo url. - if strings.HasPrefix(r.Config.Name, managerKeyPrefix) { - fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.URL) - } else { - fmt.Fprintf(m.Out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) - } - } - wg.Done() - }(r) - } - wg.Wait() - - return nil -} - -// findChartURL searches the cache of repo data for a chart that has the name and the repoURL specified. -// -// 'name' is the name of the chart. Version is an exact semver, or an empty string. If empty, the -// newest version will be returned. -// -// repoURL is the repository to search -// -// If it finds a URL that is "relative", it will prepend the repoURL. -func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, insecureskiptlsverify, passcredentialsall bool, caFile, certFile, keyFile string, err error) { - if registry.IsOCI(repoURL) { - return fmt.Sprintf("%s/%s:%s", repoURL, name, version), "", "", false, false, "", "", "", nil - } - - for _, cr := range repos { - - if urlutil.Equal(repoURL, cr.Config.URL) { - var entry repo.ChartVersions - entry, err = findEntryByName(name, cr) - if err != nil { - return - } - var ve *repo.ChartVersion - ve, err = findVersionedEntry(version, entry) - if err != nil { - return - } - url, err = normalizeURL(repoURL, ve.URLs[0]) - if err != nil { - return - } - username = cr.Config.Username - password = cr.Config.Password - passcredentialsall = cr.Config.PassCredentialsAll - insecureskiptlsverify = cr.Config.InsecureSkipTLSverify - caFile = cr.Config.CAFile - certFile = cr.Config.CertFile - keyFile = cr.Config.KeyFile - return - } - } - url, err = repo.FindChartInRepoURL(repoURL, name, version, certFile, keyFile, caFile, m.Getters) - if err == nil { - return url, username, password, false, false, "", "", "", err - } - err = errors.Errorf("chart %s not found in %s: %s", name, repoURL, err) - return url, username, password, false, false, "", "", "", err -} - -// findEntryByName finds an entry in the chart repository whose name matches the given name. -// -// It returns the ChartVersions for that entry. -func findEntryByName(name string, cr *repo.ChartRepository) (repo.ChartVersions, error) { - for ename, entry := range cr.IndexFile.Entries { - if ename == name { - return entry, nil - } - } - return nil, errors.New("entry not found") -} - -// findVersionedEntry takes a ChartVersions list and returns a single chart version that satisfies the version constraints. -// -// If version is empty, the first chart found is returned. -func findVersionedEntry(version string, vers repo.ChartVersions) (*repo.ChartVersion, error) { - for _, verEntry := range vers { - if len(verEntry.URLs) == 0 { - // Not a legit entry. - continue - } - - if version == "" || versionEquals(version, verEntry.Version) { - return verEntry, nil - } - } - return nil, errors.New("no matching version") -} - -func versionEquals(v1, v2 string) bool { - sv1, err := semver.NewVersion(v1) - if err != nil { - // Fallback to string comparison. - return v1 == v2 - } - sv2, err := semver.NewVersion(v2) - if err != nil { - return false - } - return sv1.Equal(sv2) -} - -func normalizeURL(baseURL, urlOrPath string) (string, error) { - u, err := url.Parse(urlOrPath) - if err != nil { - return urlOrPath, err - } - if u.IsAbs() { - return u.String(), nil - } - u2, err := url.Parse(baseURL) - if err != nil { - return urlOrPath, errors.Wrap(err, "base URL failed to parse") - } - - u2.RawPath = path.Join(u2.RawPath, urlOrPath) - u2.Path = path.Join(u2.Path, urlOrPath) - return u2.String(), nil -} - -// loadChartRepositories reads the repositories.yaml, and then builds a map of -// ChartRepositories. -// -// The key is the local name (which is only present in the repositories.yaml). -func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, error) { - indices := map[string]*repo.ChartRepository{} - - // Load repositories.yaml file - rf, err := loadRepoConfig(m.RepositoryConfig) - if err != nil { - return indices, errors.Wrapf(err, "failed to load %s", m.RepositoryConfig) - } - - for _, re := range rf.Repositories { - lname := re.Name - idxFile := filepath.Join(m.RepositoryCache, helmpath.CacheIndexFile(lname)) - index, err := repo.LoadIndexFile(idxFile) - if err != nil { - return indices, err - } - - // TODO: use constructor - cr := &repo.ChartRepository{ - Config: re, - IndexFile: index, - } - indices[lname] = cr - } - return indices, nil -} - -// writeLock writes a lockfile to disk -func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error { - data, err := yaml.Marshal(lock) - if err != nil { - return err - } - lockfileName := "Chart.lock" - if legacyLockfile { - lockfileName = "requirements.lock" - } - dest := filepath.Join(chartpath, lockfileName) - return ioutil.WriteFile(dest, data, 0644) -} - -// archive a dep chart from local directory and save it into destPath -func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) { - if !strings.HasPrefix(repo, "file://") { - return "", errors.Errorf("wrong format: chart %s repository %s", name, repo) - } - - origPath, err := resolver.GetLocalPath(repo, chartpath) - if err != nil { - return "", err - } - - ch, err := loader.LoadDir(origPath) - if err != nil { - return "", err - } - - constraint, err := semver.NewConstraint(version) - if err != nil { - return "", errors.Wrapf(err, "dependency %s has an invalid version/constraint format", name) - } - - v, err := semver.NewVersion(ch.Metadata.Version) - if err != nil { - return "", err - } - - if constraint.Check(v) { - _, err = chartutil.Save(ch, destPath) - return ch.Metadata.Version, err - } - - return "", errors.Errorf("can't get a valid version for dependency %s", name) -} - -// The prefix to use for cache keys created by the manager for repo names -const managerKeyPrefix = "helm-manager-" - -// key is used to turn a name, such as a repository url, into a filesystem -// safe name that is unique for querying. To accomplish this a unique hash of -// the string is used. -func key(name string) (string, error) { - in := strings.NewReader(name) - hash := crypto.SHA256.New() - if _, err := io.Copy(hash, in); err != nil { - return "", nil - } - return hex.EncodeToString(hash.Sum(nil)), nil -} diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go deleted file mode 100644 index f7ab1a568..000000000 --- a/pkg/downloader/manager_test.go +++ /dev/null @@ -1,574 +0,0 @@ -/* -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 downloader - -import ( - "bytes" - "os" - "path/filepath" - "reflect" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/repo/repotest" -) - -func TestVersionEquals(t *testing.T) { - tests := []struct { - name, v1, v2 string - expect bool - }{ - {name: "semver match", v1: "1.2.3-beta.11", v2: "1.2.3-beta.11", expect: true}, - {name: "semver match, build info", v1: "1.2.3-beta.11+a", v2: "1.2.3-beta.11+b", expect: true}, - {name: "string match", v1: "abcdef123", v2: "abcdef123", expect: true}, - {name: "semver mismatch", v1: "1.2.3-beta.11", v2: "1.2.3-beta.22", expect: false}, - {name: "semver mismatch, invalid semver", v1: "1.2.3-beta.11", v2: "stinkycheese", expect: false}, - } - - for _, tt := range tests { - if versionEquals(tt.v1, tt.v2) != tt.expect { - t.Errorf("%s: failed comparison of %q and %q (expect equal: %t)", tt.name, tt.v1, tt.v2, tt.expect) - } - } -} - -func TestNormalizeURL(t *testing.T) { - tests := []struct { - name, base, path, expect string - }{ - {name: "basic URL", base: "https://example.com", path: "http://helm.sh/foo", expect: "http://helm.sh/foo"}, - {name: "relative path", base: "https://helm.sh/charts", path: "foo", expect: "https://helm.sh/charts/foo"}, - {name: "Encoded path", base: "https://helm.sh/a%2Fb/charts", path: "foo", expect: "https://helm.sh/a%2Fb/charts/foo"}, - } - - for _, tt := range tests { - got, err := normalizeURL(tt.base, tt.path) - if err != nil { - t.Errorf("%s: error %s", tt.name, err) - continue - } else if got != tt.expect { - t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got) - } - } -} - -func TestFindChartURL(t *testing.T) { - var b bytes.Buffer - m := &Manager{ - Out: &b, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - } - repos, err := m.loadChartRepositories() - if err != nil { - t.Fatal(err) - } - - name := "alpine" - version := "0.1.0" - repoURL := "http://example.com/charts" - - churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err := m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } - - if churl != "https://charts.helm.sh/stable/alpine-0.1.0.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } - if insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } - - name = "tlsfoo" - version = "1.2.3" - repoURL = "https://example-https-insecureskiptlsverify.com" - - churl, username, password, insecureSkipTLSVerify, passcredentialsall, _, _, _, err = m.findChartURL(name, version, repoURL, repos) - if err != nil { - t.Fatal(err) - } - - if !insecureSkipTLSVerify { - t.Errorf("Unexpected insecureSkipTLSVerify %t", insecureSkipTLSVerify) - } - if churl != "https://example.com/tlsfoo-1.2.3.tgz" { - t.Errorf("Unexpected URL %q", churl) - } - if username != "" { - t.Errorf("Unexpected username %q", username) - } - if password != "" { - t.Errorf("Unexpected password %q", password) - } - if passcredentialsall != false { - t.Errorf("Unexpected passcredentialsall %t", passcredentialsall) - } -} - -func TestGetRepoNames(t *testing.T) { - b := bytes.NewBuffer(nil) - m := &Manager{ - Out: b, - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - } - tests := []struct { - name string - req []*chart.Dependency - expect map[string]string - err bool - }{ - { - name: "no repo definition, but references a url", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "http://example.com/test"}, - }, - expect: map[string]string{"http://example.com/test": "http://example.com/test"}, - }, - { - name: "no repo definition failure -- stable repo", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "stable"}, - }, - err: true, - }, - { - name: "no repo definition failure", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "http://example.com"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo from local path", - req: []*chart.Dependency{ - {Name: "local-dep", Repository: "file://./testdata/signtest"}, - }, - expect: map[string]string{"local-dep": "file://./testdata/signtest"}, - }, - { - name: "repo alias (alias:)", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "alias:testing"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo alias (@)", - req: []*chart.Dependency{ - {Name: "oedipus-rex", Repository: "@testing"}, - }, - expect: map[string]string{"oedipus-rex": "testing"}, - }, - { - name: "repo from local chart under charts path", - req: []*chart.Dependency{ - {Name: "local-subchart", Repository: ""}, - }, - expect: map[string]string{}, - }, - } - - for _, tt := range tests { - l, err := m.resolveRepoNames(tt.req) - if err != nil { - if tt.err { - continue - } - t.Fatal(err) - } - - if tt.err { - t.Fatalf("Expected error in test %q", tt.name) - } - - // m1 and m2 are the maps we want to compare - eq := reflect.DeepEqual(l, tt.expect) - if !eq { - t.Errorf("%s: expected map %v, got %v", tt.name, l, tt.name) - } - } -} - -func TestDownloadAll(t *testing.T) { - chartPath := t.TempDir() - m := &Manager{ - Out: new(bytes.Buffer), - RepositoryConfig: repoConfig, - RepositoryCache: repoCache, - ChartPath: chartPath, - } - signtest, err := loader.LoadDir(filepath.Join("testdata", "signtest")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(signtest, filepath.Join(chartPath, "testdata")); err != nil { - t.Fatal(err) - } - - local, err := loader.LoadDir(filepath.Join("testdata", "local-subchart")) - if err != nil { - t.Fatal(err) - } - if err := chartutil.SaveDir(local, filepath.Join(chartPath, "charts")); err != nil { - t.Fatal(err) - } - - signDep := &chart.Dependency{ - Name: signtest.Name(), - Repository: "file://./testdata/signtest", - Version: signtest.Metadata.Version, - } - localDep := &chart.Dependency{ - Name: local.Name(), - Repository: "", - Version: local.Metadata.Version, - } - - // create a 'tmpcharts' directory to test #5567 - if err := os.MkdirAll(filepath.Join(chartPath, "tmpcharts"), 0755); err != nil { - t.Fatal(err) - } - if err := m.downloadAll([]*chart.Dependency{signDep, localDep}); err != nil { - t.Error(err) - } - - if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) { - t.Error(err) - } -} - -func TestUpdateBeforeBuild(t *testing.T) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Save dep - d := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "dep-chart", - Version: "0.1.0", - APIVersion: "v1", - }, - } - if err := chartutil.SaveDir(d, dir()); err != nil { - t.Fatal(err) - } - // Save a chart - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "with-dependency", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{{ - Name: d.Metadata.Name, - Version: ">=0.1.0", - Repository: "file://../dep-chart", - }}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(c.Metadata.Name), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // Update before Build. see issue: https://github.com/helm/helm/issues/7101 - err = m.Update() - if err != nil { - t.Fatal(err) - } - - err = m.Build() - if err != nil { - t.Fatal(err) - } -} - -// TestUpdateWithNoRepo is for the case of a dependency that has no repo listed. -// This happens when the dependency is in the charts directory and does not need -// to be fetched. -func TestUpdateWithNoRepo(t *testing.T) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Setup the dependent chart - d := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "dep-chart", - Version: "0.1.0", - APIVersion: "v1", - }, - } - - // Save a chart with the dependency - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "with-dependency", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{{ - Name: d.Metadata.Name, - Version: "0.1.0", - }}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Save dependent chart into the parents charts directory. If the chart is - // not in the charts directory Helm will return an error that it is not - // found. - if err := chartutil.SaveDir(d, dir(c.Metadata.Name, "charts")); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(c.Metadata.Name), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // Test the update - err = m.Update() - if err != nil { - t.Fatal(err) - } -} - -// This function is the skeleton test code of failing tests for #6416 and #6871 and bugs due to #5874. -// -// This function is used by below tests that ensures success of build operation -// with optional fields, alias, condition, tags, and even with ranged version. -// Parent chart includes local-subchart 0.1.0 subchart from a fake repository, by default. -// If each of these main fields (name, version, repository) is not supplied by dep param, default value will be used. -func checkBuildWithOptionalFields(t *testing.T, chartName string, dep chart.Dependency) { - // Set up a fake repo - srv, err := repotest.NewTempServerWithCleanup(t, "testdata/*.tgz*") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - if err := srv.LinkIndices(); err != nil { - t.Fatal(err) - } - dir := func(p ...string) string { - return filepath.Join(append([]string{srv.Root()}, p...)...) - } - - // Set main fields if not exist - if dep.Name == "" { - dep.Name = "local-subchart" - } - if dep.Version == "" { - dep.Version = "0.1.0" - } - if dep.Repository == "" { - dep.Repository = srv.URL() - } - - // Save a chart - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: chartName, - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{&dep}, - }, - } - if err := chartutil.SaveDir(c, dir()); err != nil { - t.Fatal(err) - } - - // Set-up a manager - b := bytes.NewBuffer(nil) - g := getter.Providers{getter.Provider{ - Schemes: []string{"http", "https"}, - New: getter.NewHTTPGetter, - }} - m := &Manager{ - ChartPath: dir(chartName), - Out: b, - Getters: g, - RepositoryConfig: dir("repositories.yaml"), - RepositoryCache: dir(), - } - - // First build will update dependencies and create Chart.lock file. - err = m.Build() - if err != nil { - t.Fatal(err) - } - - // Second build should be passed. See PR #6655. - err = m.Build() - if err != nil { - t.Fatal(err) - } -} - -func TestBuild_WithoutOptionalFields(t *testing.T) { - // Dependency has main fields only (name/version/repository) - checkBuildWithOptionalFields(t, "without-optional-fields", chart.Dependency{}) -} - -func TestBuild_WithSemVerRange(t *testing.T) { - // Dependency version is the form of SemVer range - checkBuildWithOptionalFields(t, "with-semver-range", chart.Dependency{ - Version: ">=0.1.0", - }) -} - -func TestBuild_WithAlias(t *testing.T) { - // Dependency has an alias - checkBuildWithOptionalFields(t, "with-alias", chart.Dependency{ - Alias: "local-subchart-alias", - }) -} - -func TestBuild_WithCondition(t *testing.T) { - // Dependency has a condition - checkBuildWithOptionalFields(t, "with-condition", chart.Dependency{ - Condition: "some.condition", - }) -} - -func TestBuild_WithTags(t *testing.T) { - // Dependency has several tags - checkBuildWithOptionalFields(t, "with-tags", chart.Dependency{ - Tags: []string{"tag1", "tag2"}, - }) -} - -// Failing test for #6871 -func TestBuild_WithRepositoryAlias(t *testing.T) { - // Dependency repository is aliased in Chart.yaml - checkBuildWithOptionalFields(t, "with-repository-alias", chart.Dependency{ - Repository: "@test", - }) -} - -func TestErrRepoNotFound_Error(t *testing.T) { - type fields struct { - Repos []string - } - tests := []struct { - name string - fields fields - want string - }{ - { - name: "OK", - fields: fields{ - Repos: []string{"https://charts1.example.com", "https://charts2.example.com"}, - }, - want: "no repository definition for https://charts1.example.com, https://charts2.example.com", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - e := ErrRepoNotFound{ - Repos: tt.fields.Repos, - } - if got := e.Error(); got != tt.want { - t.Errorf("Error() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestKey(t *testing.T) { - tests := []struct { - name string - expect string - }{ - { - name: "file:////tmp", - expect: "afeed3459e92a874f6373aca264ce1459bfa91f9c1d6612f10ae3dc2ee955df3", - }, - { - name: "https://example.com/charts", - expect: "7065c57c94b2411ad774638d76823c7ccb56415441f5ab2f5ece2f3845728e5d", - }, - { - name: "foo/bar/baz", - expect: "15c46a4f8a189ae22f36f201048881d6c090c93583bedcf71f5443fdef224c82", - }, - } - - for _, tt := range tests { - o, err := key(tt.name) - if err != nil { - t.Fatalf("unable to generate key for %q with error: %s", tt.name, err) - } - if o != tt.expect { - t.Errorf("wrong key name generated for %q, expected %q but got %q", tt.name, tt.expect, o) - } - } -} diff --git a/pkg/downloader/testdata/helm-test-key.pub b/pkg/downloader/testdata/helm-test-key.pub deleted file mode 100644 index 38714f25adaf701b08e11fd559a587074bbde0e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1243 zcmV<11SI>J0SyFKmTjH^2mr{k15wFPQdpTAAEclY4oV^-7nTy5x$&xF;PC4il}o-Pk4@#$knlp#(|J0GE?qli_lr;7-o zyY8vBsN_GJe;#w<`JdR7riNL&RJlcS)FG+W=91;dYS6NZ2tY?kZ8Sw9{r=e4|L3E{ zRod|EPC!PWgW&pe&4qiqKQAijj;G~fyjcC^m%0p54Tn{h%YFKd0VC4n=#~SRc@BVd znj66*)Om%*SEQfX{1*Tb0RRECT}WkYZ6H)-b98BLXCNq4XlZjGYh`&Lb7*gMY-AvB zZftoVVr3w8b7f>8W^ZyJbY*jNX>MmOAVg0fPES-IR8mz_R4yqXJZNQXZ7p6uVakV zT7GGV$jaKjyjfI_a~N1!Hk?5C$0wa&4)R=i$v7t&ZMycW#RkavpF%A?>MTT2anNDzOQUm<++zEOykJ9-@&c2QXq3owqf7fek`=L@+7iF zv;IW2Q>Q&r+V@cWDF&hAUUsCKlDinerKgvJUJCl$5gjb7NhM{mBP%!M^mX-iS8xFf zuLB{@MDqvtZzF#Bxd9CXSC(y_0SExW>8~h=U8|!do4*OJj2u#!KDe3v+1T+aVzU5di=Ji2)x37y$|Z2?YXImTjH_8w>yn2@r%kznCAv zhhp0`2mpxVj5j%o&5i)?`r7iES|8dA@p2kk@+XS(tjBGN)6>tm^=gayCn`gTEC*K74Y~{I_PREk) z)PstIMx1RxB@cK8%Mey%;nVnKriAKUk2Ky?dBMG3uXItKL$3N(#3P^pQa*K$l)wUy F^>pMLK0g2e diff --git a/pkg/downloader/testdata/helm-test-key.secret b/pkg/downloader/testdata/helm-test-key.secret deleted file mode 100644 index a966aef93ed97d01d764f29940738df6df2d9d24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2545 zcmVclY4oV^-7nTy5x$&xF;PC4il}o-Pk4@#$knlp#(|J0GE?qli_lr;7-o zyY8vBsN_GJe;#w<`JdR7riNL&RJlcS)FG+W=91;dYS6NZ2tY?kZ8Sw9{r=e4|L3E{ zRod|EPC!PWgW&pe&4qiqKQAijj;G~fyjcC^m%0p54Tn{h%YFKd0VC4n=#~SRc@BVd znj66*)Om%*SEQfX{1*Tb0RRC22mUT~!#(ymA#eaSp1lpODzX${Vf^l{qDyu}xC-Z; zRnH<54GSVm<$?Ua1k#(+mu~3_*CIx=sPuoZB#9t`5)>)SncaZ0<~%)I$~BM-5aP3W z%`ewoaI;P40uHnDeE!9-_o2Lr{wDfL45jGGU-JZ36T9ToJqMX(TnRN-EvGi{o6aI#oT_2HU(J8=theYZsj5h?ml@F2 zqCpxqkdZi=~i+&Z}q^cR< zq>lNT5cnJ5X@K!3vOww0B>@Bg*7x*i59vbegj}$ELl?K2l`+`uY;jn;@-#}^!(c8$ z&Y`@LLxZ_Y>^#gGbxsy-2s=w7cVmR@z_%b#0_e^qDmIrpKw6U7N;6^TN}@&nxKj6i zje++&m}XQA&G8O8FX86?Frxrjmu5ktfDRyHBb|j&n&H#v>T!Mdmk8Y#1OV>P(*gow;}0v-BdsmdUSV3M9tIkRO0OTBw16eYCzxs>OEG!?i}$^8yY+hFlb3GJ~F z@#2Vimrfeb0(o3X?>!tSIROL!mGC1>cHXGVp;VD$oE{N!h=IF(C(PNLd6^nZO^!ix zHnE%@Y*d~bJl_M}WW0D1EM+&xdQI5#y67>-8{P4^*?j9-RL!3>e89fC4fbJFTGXY* zQA`Z&jZ*qV!0N>p>(<2RFPDhHj^h*B*O(i139Dwv{>MY%puY021Or@)I~ufINM&qo zAXH^@bZKs9AShI5X>%ZJWqBZTXm53FWFT*DY^`Z*m}XWpi|CZf7na zL{A`2PgEdOQdLt_E-4^9Xk~0|Ep%mbbZKs9Kxk!bZ7y?YK8XQ01QP)Y03iheSC(y_ z0viJb3ke7Z0|gZd2?z@X76JnS00JHX0vCV)3JDN|JHMD8!G~grMF;@2`RLQ-(ihS@ zk(ZDi8>PUMNBttVp^;f=(#~5ORUCP*V~o^Verbou%G$oXSyYd67+6|1oIv=;C!Jsp z@?3ezI42oxy7sHZ2FUrJLM=V)2rULvozb@g^vZ~+Ui10l{t^9T2DBYydv1DFI?mTjH^2mrz9 zuPBIJtD_~GzX`6498#D*yg_W@HI~u}LQvFZ zjHz2I7O5nm<}d0gU&SbRw}dGu2{gYWzK!Qb2tL4r=Ttf(&pz_gadeY}n}E@spby2h zn?Jq8s}cOpE&?`36cTnn-abrV^*hkY1rlNa6>W(?OAePZXMfE&?IzWku7z=T;E66)b)o_po?XSOFY;U!8IY8l|z)F~<`!sdiAt3+}0RRC2 z2mUKrERA52qzq^qU4-%uqeMA@h`$YTvMnKwO3MFdg819*{h|i5{tcC;Av-jm`%7`? zISDa>*_u$~x5)kpVt_aYB_e`#K)Xd5tcJ05BQ>ps?qeo`#OS{-ilRZ+9`nljqxsy1 zp;Lu#*--$l?6qncfhI%m^w(3lOt}ywL5?%+_Ov|T=-O)O#|1&>=}51a%Sb~KTR2_K z!};{n;NgPO;;v%0;n-j>b-Y|l)x=^&d84lKmr8o*+q*$Sul50u>9%n+e!b~90-}xc znpRXgsh*hBzGXpmnXaxdFnD1FEnbiC?537`DY#mL7&iHNEY4|+!A|s9dFssoYIy_z z+imR1K+cnVPeX&M1X~ed#U~gsS6HR0zgm1NR~u@{BN;*5Gvl)42%Kq{=4gSyFIAOo zw)ZGKn^3RZn+iXfb*zL1mnJJGsvTLnDB5DF8)!;+KX&@(mJ7k5LnTlXYxI(#)c`4{ zo6I4Djv|uTRI{JJ9glvUHq0WkzV7H91OVbY6u%#c1Z-!^cIjhIC)Ek7Hx7cRvtc6M zYLV(#kP^D1#2+7pDzLBFanZFqRw>On{`4qC48A)&{zk{n0CZEKY$SfN1Rk^!_V}?Oa05R<~;U7Vou+rQZcj7^Zr@2q2}K8g2gzsQ|Y$Hp^5`riTL^4T#Q?}_!b9ge@36zaVNe`|(D|D@%b z?q#ETmMPVDW6=SC^ zp>(H_BkTP!*5u$7;(xt$0Z3AJ%*#wE`2MxJYbiqwMV z55Sy@$Oto*a)E^@IG(B#1R_APzVCxJvjDnj!`b?U+KFs4f-?w1(lA6SB!RPKJ5Wx? zlJL}niiAd-Z9pXtcm~T5R%GGR_+_Sq>RpdC-c)(PyDc zVQyr3R8em|NM&qo0PNJuio!4y2H>vq6nTN^{F#IDc zVQyr3R8em|NM&qo0PI;!Z{s!-&Y8brUgz=_Sktr}$Ea^Xvp|8iX|P!oD2ie|mc|mX z6v>j56T{7aFGN{RptN<1*ba)-bCFFAIYWuhe96m92l8R?O^z<`H5TgZ&=5k1>0}bG zLWuTN3`du{-*J36nocjyKpfnXKSAjOx-;==UG2^NM}SuTM9xd2XRsQwlzif(4e|dK zd`qf;q&gX}G!DKi7vwYr@=RkvGiXi^TQzG4KIDSE^{zVnQ|$P^LRFGKiUZike<8*# z{*Onaj{hgY=CLE|my8|%0~JgTD{>4VF*=~sVa28w)d|0wGg8BYv-qqfgS&OPO6ZZHjWOhV=w{qp0jiKm`e}7wAQ%b!RMqDWXdd{ zz>wrpXYas~!XQ@!7DN7Q9CgahK~siRbpijkj+XL)Qn;5PhyQ)W;YY33V04^WnFN*` zD5;4vetq}pE*M9QXEJoI;9%JCzjnq)X#?!#|M*4xA6<+){+|MWSN~s=Rb~wc3-mI9 zt9U}-d#TF@uqI`>sRDZ*g7ve(po$;d=kdC257cLhc~iQC{EYQ?!kG+tx!{Q@qI^B6 zYa*N;ZT^3Fe|7!CdtRgm)Ul8M6ESSZ|H-uD|49%7Iz3=v6~R4v$VijJKq-{IivA&| zCNYP39{YigFwmCVbI#buoM8S`Kh7bQj*?*9x~T;`Agsu(!ON(~nzX7OqF<=vKeEJ> z)h)9Giw+A4i^A#e;`HZiQiyBkB|M$hS&LG{ht9ST#$-&KR`}ShFIx8n|ViWC6ihh>Q4(d&FZbS zwzqfY?IgA%@H_lgnotSGashYlS&90`8}00960pc^bi03-ka*J$OZ diff --git a/pkg/downloader/testdata/signtest-0.1.0.tgz.prov b/pkg/downloader/testdata/signtest-0.1.0.tgz.prov deleted file mode 100644 index d325bb266..000000000 --- a/pkg/downloader/testdata/signtest-0.1.0.tgz.prov +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA512 - -apiVersion: v1 -description: A Helm chart for Kubernetes -name: signtest -version: 0.1.0 - -... -files: - signtest-0.1.0.tgz: sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55 ------BEGIN PGP SIGNATURE----- - -wsBcBAEBCgAQBQJcoosfCRCEO7+YH8GHYgAA220IALAs8T8NPgkcLvHu+5109cAN -BOCNPSZDNsqLZW/2Dc9cKoBG7Jen4Qad+i5l9351kqn3D9Gm6eRfAWcjfggRobV/ -9daZ19h0nl4O1muQNAkjvdgZt8MOP3+PB3I3/Tu2QCYjI579SLUmuXlcZR5BCFPR -PJy+e3QpV2PcdeU2KZLG4tjtlrq+3QC9ZHHEJLs+BVN9d46Dwo6CxJdHJrrrAkTw -M8MhA92vbiTTPRSCZI9x5qDAwJYhoq0oxLflpuL2tIlo3qVoCsaTSURwMESEHO32 -XwYG7BaVDMELWhAorBAGBGBwWFbJ1677qQ2gd9CN0COiVhekWlFRcnn60800r84= -=k9Y9 ------END PGP SIGNATURE----- \ No newline at end of file diff --git a/pkg/downloader/testdata/signtest/.helmignore b/pkg/downloader/testdata/signtest/.helmignore deleted file mode 100644 index 435b756d8..000000000 --- a/pkg/downloader/testdata/signtest/.helmignore +++ /dev/null @@ -1,5 +0,0 @@ -# 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 -.git diff --git a/pkg/downloader/testdata/signtest/Chart.yaml b/pkg/downloader/testdata/signtest/Chart.yaml deleted file mode 100644 index f1f73723a..000000000 --- a/pkg/downloader/testdata/signtest/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: signtest -version: 0.1.0 diff --git a/pkg/downloader/testdata/signtest/alpine/Chart.yaml b/pkg/downloader/testdata/signtest/alpine/Chart.yaml deleted file mode 100644 index eec261220..000000000 --- a/pkg/downloader/testdata/signtest/alpine/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -description: Deploy a basic Alpine Linux pod -home: https://helm.sh/helm -name: alpine -sources: -- https://github.com/helm/helm -version: 0.1.0 diff --git a/pkg/downloader/testdata/signtest/alpine/README.md b/pkg/downloader/testdata/signtest/alpine/README.md deleted file mode 100644 index 28bebae07..000000000 --- a/pkg/downloader/testdata/signtest/alpine/README.md +++ /dev/null @@ -1,9 +0,0 @@ -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.yaml` file contains the default values for the -`alpine-pod.yaml` template. - -You can install this example using `helm install ./alpine`. diff --git a/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml b/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml deleted file mode 100644 index 5bbae10af..000000000 --- a/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml +++ /dev/null @@ -1,14 +0,0 @@ -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/pkg/downloader/testdata/signtest/alpine/values.yaml b/pkg/downloader/testdata/signtest/alpine/values.yaml deleted file mode 100644 index bb6c06ae4..000000000 --- a/pkg/downloader/testdata/signtest/alpine/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# The pod name -name: my-alpine diff --git a/pkg/downloader/testdata/signtest/templates/pod.yaml b/pkg/downloader/testdata/signtest/templates/pod.yaml deleted file mode 100644 index 9b00ccaf7..000000000 --- a/pkg/downloader/testdata/signtest/templates/pod.yaml +++ /dev/null @@ -1,10 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: signtest -spec: - restartPolicy: Never - containers: - - name: waiter - image: "alpine:3.3" - command: ["/bin/sleep","9000"] diff --git a/pkg/downloader/testdata/signtest/values.yaml b/pkg/downloader/testdata/signtest/values.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/engine/doc.go b/pkg/engine/doc.go deleted file mode 100644 index 6ff875c46..000000000 --- a/pkg/engine/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -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 engine implements the Go text template engine as needed for Helm. - -When Helm renders templates it does so with additional functions and different -modes (e.g., strict, lint mode). This package handles the helm specific -implementation. -*/ -package engine // import "helm.sh/helm/v3/pkg/engine" diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go deleted file mode 100644 index 00494f9d7..000000000 --- a/pkg/engine/engine.go +++ /dev/null @@ -1,401 +0,0 @@ -/* -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 engine - -import ( - "fmt" - "log" - "path" - "path/filepath" - "regexp" - "sort" - "strings" - "text/template" - - "github.com/pkg/errors" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -// Engine is an implementation of the Helm rendering implementation for templates. -type Engine struct { - // If strict is enabled, template rendering will fail if a template references - // a value that was not passed in. - Strict bool - // In LintMode, some 'required' template values may be missing, so don't fail - LintMode bool - // the rest config to connect to the kubernetes api - config *rest.Config -} - -// Render takes a chart, optional values, and value overrides, and attempts to render the Go templates. -// -// Render can be called repeatedly on the same engine. -// -// This will look in the chart's 'templates' data (e.g. the 'templates/' directory) -// and attempt to render the templates there using the values passed in. -// -// Values are scoped to their templates. A dependency template will not have -// access to the values set for its parent. If chart "foo" includes chart "bar", -// "bar" will not have access to the values for "foo". -// -// Values should be prepared with something like `chartutils.ReadValues`. -// -// Values are passed through the templates according to scope. If the top layer -// chart includes the chart foo, which includes the chart bar, the values map -// will be examined for a table called "foo". If "foo" is found in vals, -// that section of the values will be passed into the "foo" chart. And if that -// section contains a value named "bar", that value will be passed on to the -// bar chart during render time. -func (e Engine) Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { - tmap := allTemplates(chrt, values) - return e.render(tmap) -} - -// Render takes a chart, optional values, and value overrides, and attempts to -// render the Go templates using the default options. -func Render(chrt *chart.Chart, values chartutil.Values) (map[string]string, error) { - return new(Engine).Render(chrt, values) -} - -// RenderWithClient takes a chart, optional values, and value overrides, and attempts to -// render the Go templates using the default options. This engine is client aware and so can have template -// functions that interact with the client -func RenderWithClient(chrt *chart.Chart, values chartutil.Values, config *rest.Config) (map[string]string, error) { - return Engine{ - config: config, - }.Render(chrt, values) -} - -// renderable is an object that can be rendered. -type renderable struct { - // tpl is the current template. - tpl string - // vals are the values to be supplied to the template. - vals chartutil.Values - // namespace prefix to the templates of the current chart - basePath string -} - -const warnStartDelim = "HELM_ERR_START" -const warnEndDelim = "HELM_ERR_END" -const recursionMaxNums = 1000 - -var warnRegex = regexp.MustCompile(warnStartDelim + `((?s).*)` + warnEndDelim) - -func warnWrap(warn string) string { - return warnStartDelim + warn + warnEndDelim -} - -// initFunMap creates the Engine's FuncMap and adds context-specific functions. -func (e Engine) initFunMap(t *template.Template, referenceTpls map[string]renderable) { - funcMap := funcMap() - includedNames := make(map[string]int) - - // Add the 'include' function here so we can close over t. - funcMap["include"] = func(name string, data interface{}) (string, error) { - var buf strings.Builder - if v, ok := includedNames[name]; ok { - if v > recursionMaxNums { - return "", errors.Wrapf(fmt.Errorf("unable to execute template"), "rendering template has a nested reference name: %s", name) - } - includedNames[name]++ - } else { - includedNames[name] = 1 - } - err := t.ExecuteTemplate(&buf, name, data) - includedNames[name]-- - return buf.String(), err - } - - // Add the 'tpl' function here - funcMap["tpl"] = func(tpl string, vals chartutil.Values) (string, error) { - basePath, err := vals.PathValue("Template.BasePath") - if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Basepath from values inside tpl function: %s", tpl) - } - - templateName, err := vals.PathValue("Template.Name") - if err != nil { - return "", errors.Wrapf(err, "cannot retrieve Template.Name from values inside tpl function: %s", tpl) - } - - templates := map[string]renderable{ - templateName.(string): { - tpl: tpl, - vals: vals, - basePath: basePath.(string), - }, - } - - result, err := e.renderWithReferences(templates, referenceTpls) - if err != nil { - return "", errors.Wrapf(err, "error during tpl function execution for %q", tpl) - } - return result[templateName.(string)], nil - } - - // Add the `required` function here so we can use lintMode - funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { - if val == nil { - if e.LintMode { - // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) - return "", nil - } - return val, errors.Errorf(warnWrap(warn)) - } else if _, ok := val.(string); ok { - if val == "" { - if e.LintMode { - // Don't fail on missing required values when linting - log.Printf("[INFO] Missing required value: %s", warn) - return "", nil - } - return val, errors.Errorf(warnWrap(warn)) - } - } - return val, nil - } - - // Override sprig fail function for linting and wrapping message - funcMap["fail"] = func(msg string) (string, error) { - if e.LintMode { - // Don't fail when linting - log.Printf("[INFO] Fail: %s", msg) - return "", nil - } - return "", errors.New(warnWrap(msg)) - } - - // If we are not linting and have a cluster connection, provide a Kubernetes-backed - // implementation. - if !e.LintMode && e.config != nil { - funcMap["lookup"] = NewLookupFunction(e.config) - } - - t.Funcs(funcMap) -} - -// render takes a map of templates/values and renders them. -func (e Engine) render(tpls map[string]renderable) (map[string]string, error) { - return e.renderWithReferences(tpls, tpls) -} - -// renderWithReferences takes a map of templates/values to render, and a map of -// templates which can be referenced within them. -func (e Engine) renderWithReferences(tpls, referenceTpls map[string]renderable) (rendered map[string]string, err error) { - // Basically, what we do here is start with an empty parent template and then - // build up a list of templates -- one for each file. Once all of the templates - // have been parsed, we loop through again and execute every template. - // - // The idea with this process is to make it possible for more complex templates - // to share common blocks, but to make the entire thing feel like a file-based - // template engine. - defer func() { - if r := recover(); r != nil { - err = errors.Errorf("rendering template failed: %v", r) - } - }() - t := template.New("gotpl") - if e.Strict { - t.Option("missingkey=error") - } else { - // Not that zero will attempt to add default values for types it knows, - // but will still emit for others. We mitigate that later. - t.Option("missingkey=zero") - } - - e.initFunMap(t, referenceTpls) - - // We want to parse the templates in a predictable order. The order favors - // higher-level (in file system) templates over deeply nested templates. - keys := sortTemplates(tpls) - referenceKeys := sortTemplates(referenceTpls) - - for _, filename := range keys { - r := tpls[filename] - if _, err := t.New(filename).Parse(r.tpl); err != nil { - return map[string]string{}, cleanupParseError(filename, err) - } - } - - // Adding the reference templates to the template context - // so they can be referenced in the tpl function - for _, filename := range referenceKeys { - if t.Lookup(filename) == nil { - r := referenceTpls[filename] - if _, err := t.New(filename).Parse(r.tpl); err != nil { - return map[string]string{}, cleanupParseError(filename, err) - } - } - } - - rendered = make(map[string]string, len(keys)) - for _, filename := range keys { - // Don't render partials. We don't care out the direct output of partials. - // They are only included from other templates. - if strings.HasPrefix(path.Base(filename), "_") { - continue - } - // At render time, add information about the template that is being rendered. - vals := tpls[filename].vals - vals["Template"] = chartutil.Values{"Name": filename, "BasePath": tpls[filename].basePath} - var buf strings.Builder - if err := t.ExecuteTemplate(&buf, filename, vals); err != nil { - return map[string]string{}, cleanupExecError(filename, err) - } - - // Work around the issue where Go will emit "" even if Options(missing=zero) - // is set. Since missing=error will never get here, we do not need to handle - // the Strict case. - rendered[filename] = strings.ReplaceAll(buf.String(), "", "") - } - - return rendered, nil -} - -func cleanupParseError(filename string, err error) error { - tokens := strings.Split(err.Error(), ": ") - if len(tokens) == 1 { - // This might happen if a non-templating error occurs - return fmt.Errorf("parse error in (%s): %s", filename, err) - } - // The first token is "template" - // The second token is either "filename:lineno" or "filename:lineNo:columnNo" - location := tokens[1] - // The remaining tokens make up a stacktrace-like chain, ending with the relevant error - errMsg := tokens[len(tokens)-1] - return fmt.Errorf("parse error at (%s): %s", string(location), errMsg) -} - -func cleanupExecError(filename string, err error) error { - if _, isExecError := err.(template.ExecError); !isExecError { - return err - } - - tokens := strings.SplitN(err.Error(), ": ", 3) - if len(tokens) != 3 { - // This might happen if a non-templating error occurs - return fmt.Errorf("execution error in (%s): %s", filename, err) - } - - // The first token is "template" - // The second token is either "filename:lineno" or "filename:lineNo:columnNo" - location := tokens[1] - - parts := warnRegex.FindStringSubmatch(tokens[2]) - if len(parts) >= 2 { - return fmt.Errorf("execution error at (%s): %s", string(location), parts[1]) - } - - return err -} - -func sortTemplates(tpls map[string]renderable) []string { - keys := make([]string, len(tpls)) - i := 0 - for key := range tpls { - keys[i] = key - i++ - } - sort.Sort(sort.Reverse(byPathLen(keys))) - return keys -} - -type byPathLen []string - -func (p byPathLen) Len() int { return len(p) } -func (p byPathLen) Swap(i, j int) { p[j], p[i] = p[i], p[j] } -func (p byPathLen) Less(i, j int) bool { - a, b := p[i], p[j] - ca, cb := strings.Count(a, "/"), strings.Count(b, "/") - if ca == cb { - return strings.Compare(a, b) == -1 - } - return ca < cb -} - -// allTemplates returns all templates for a chart and its dependencies. -// -// As it goes, it also prepares the values in a scope-sensitive manner. -func allTemplates(c *chart.Chart, vals chartutil.Values) map[string]renderable { - templates := make(map[string]renderable) - recAllTpls(c, templates, vals) - return templates -} - -// recAllTpls recurses through the templates in a chart. -// -// As it recurses, it also sets the values to be appropriate for the template -// scope. -func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.Values) map[string]interface{} { - subCharts := make(map[string]interface{}) - chartMetaData := struct { - chart.Metadata - IsRoot bool - }{*c.Metadata, c.IsRoot()} - - next := map[string]interface{}{ - "Chart": chartMetaData, - "Files": newFiles(c.Files), - "Release": vals["Release"], - "Capabilities": vals["Capabilities"], - "Values": make(chartutil.Values), - "Subcharts": subCharts, - } - - // If there is a {{.Values.ThisChart}} in the parent metadata, - // copy that into the {{.Values}} for this template. - if c.IsRoot() { - next["Values"] = vals["Values"] - } else if vs, err := vals.Table("Values." + c.Name()); err == nil { - next["Values"] = vs - } - - for _, child := range c.Dependencies() { - subCharts[child.Name()] = recAllTpls(child, templates, next) - } - - newParentID := c.ChartFullPath() - for _, t := range c.Templates { - if !isTemplateValid(c, t.Name) { - continue - } - templates[path.Join(newParentID, t.Name)] = renderable{ - tpl: string(t.Data), - vals: next, - basePath: path.Join(newParentID, "templates"), - } - } - - return next -} - -// isTemplateValid returns true if the template is valid for the chart type -func isTemplateValid(ch *chart.Chart, templateName string) bool { - if isLibraryChart(ch) { - return strings.HasPrefix(filepath.Base(templateName), "_") - } - return true -} - -// isLibraryChart returns true if the chart is a library chart -func isLibraryChart(c *chart.Chart) bool { - return strings.EqualFold(c.Metadata.Type, "library") -} diff --git a/pkg/engine/engine_test.go b/pkg/engine/engine_test.go deleted file mode 100644 index 54cd21ae2..000000000 --- a/pkg/engine/engine_test.go +++ /dev/null @@ -1,832 +0,0 @@ -/* -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 engine - -import ( - "fmt" - "strings" - "sync" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" -) - -func TestSortTemplates(t *testing.T) { - tpls := map[string]renderable{ - "/mychart/templates/foo.tpl": {}, - "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl": {}, - "/mychart/templates/bar.tpl": {}, - "/mychart/templates/charts/foo/templates/bar.tpl": {}, - "/mychart/templates/_foo.tpl": {}, - "/mychart/templates/charts/foo/templates/foo.tpl": {}, - "/mychart/templates/charts/bar/templates/foo.tpl": {}, - } - got := sortTemplates(tpls) - if len(got) != len(tpls) { - t.Fatal("Sorted results are missing templates") - } - - expect := []string{ - "/mychart/templates/charts/foo/charts/bar/templates/foo.tpl", - "/mychart/templates/charts/foo/templates/foo.tpl", - "/mychart/templates/charts/foo/templates/bar.tpl", - "/mychart/templates/charts/bar/templates/foo.tpl", - "/mychart/templates/foo.tpl", - "/mychart/templates/bar.tpl", - "/mychart/templates/_foo.tpl", - } - for i, e := range expect { - if got[i] != e { - t.Fatalf("\n\tExp:\n%s\n\tGot:\n%s", - strings.Join(expect, "\n"), - strings.Join(got, "\n"), - ) - } - } -} - -func TestFuncMap(t *testing.T) { - fns := funcMap() - forbidden := []string{"env", "expandenv"} - for _, f := range forbidden { - if _, ok := fns[f]; ok { - t.Errorf("Forbidden function %s exists in FuncMap.", f) - } - } - - // Test for Engine-specific template functions. - expect := []string{"include", "required", "tpl", "toYaml", "fromYaml", "toToml", "toJson", "fromJson", "lookup"} - for _, f := range expect { - if _, ok := fns[f]; !ok { - t.Errorf("Expected add-on function %q", f) - } - } -} - -func TestRender(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "moby", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/test1", Data: []byte("{{.Values.outer | title }} {{.Values.inner | title}}")}, - {Name: "templates/test2", Data: []byte("{{.Values.global.callme | lower }}")}, - {Name: "templates/test3", Data: []byte("{{.noValue}}")}, - {Name: "templates/test4", Data: []byte("{{toJson .Values}}")}, - }, - Values: map[string]interface{}{"outer": "DEFAULT", "inner": "DEFAULT"}, - } - - vals := map[string]interface{}{ - "Values": map[string]interface{}{ - "outer": "spouter", - "inner": "inn", - "global": map[string]interface{}{ - "callme": "Ishmael", - }, - }, - } - - v, err := chartutil.CoalesceValues(c, vals) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } - out, err := Render(c, v) - if err != nil { - t.Errorf("Failed to render templates: %s", err) - } - - expect := map[string]string{ - "moby/templates/test1": "Spouter Inn", - "moby/templates/test2": "ishmael", - "moby/templates/test3": "", - "moby/templates/test4": `{"global":{"callme":"Ishmael"},"inner":"inn","outer":"spouter"}`, - } - - for name, data := range expect { - if out[name] != data { - t.Errorf("Expected %q, got %q", data, out[name]) - } - } -} - -func TestRenderRefsOrdering(t *testing.T) { - parentChart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "parent", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}parent value{{- end -}}`)}, - {Name: "templates/test.yaml", Data: []byte(`{{ tpl "{{ include \"test\" . }}" . }}`)}, - }, - } - childChart := &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "child", - Version: "1.2.3", - }, - Templates: []*chart.File{ - {Name: "templates/_helpers.tpl", Data: []byte(`{{- define "test" -}}child value{{- end -}}`)}, - }, - } - parentChart.AddDependency(childChart) - - expect := map[string]string{ - "parent/templates/test.yaml": "parent value", - } - - for i := 0; i < 100; i++ { - out, err := Render(parentChart, chartutil.Values{}) - if err != nil { - t.Fatalf("Failed to render templates: %s", err) - } - - for name, data := range expect { - if out[name] != data { - t.Fatalf("Expected %q, got %q (iteration %d)", data, out[name], i+1) - } - } - } -} - -func TestRenderInternals(t *testing.T) { - // Test the internals of the rendering tool. - - vals := chartutil.Values{"Name": "one", "Value": "two"} - tpls := map[string]renderable{ - "one": {tpl: `Hello {{title .Name}}`, vals: vals}, - "two": {tpl: `Goodbye {{upper .Value}}`, vals: vals}, - // Test whether a template can reliably reference another template - // without regard for ordering. - "three": {tpl: `{{template "two" dict "Value" "three"}}`, vals: vals}, - } - - out, err := new(Engine).render(tpls) - if err != nil { - t.Fatalf("Failed template rendering: %s", err) - } - - if len(out) != 3 { - t.Fatalf("Expected 3 templates, got %d", len(out)) - } - - if out["one"] != "Hello One" { - t.Errorf("Expected 'Hello One', got %q", out["one"]) - } - - if out["two"] != "Goodbye TWO" { - t.Errorf("Expected 'Goodbye TWO'. got %q", out["two"]) - } - - if out["three"] != "Goodbye THREE" { - t.Errorf("Expected 'Goodbye THREE'. got %q", out["two"]) - } -} - -func TestParallelRenderInternals(t *testing.T) { - // Make sure that we can use one Engine to run parallel template renders. - e := new(Engine) - var wg sync.WaitGroup - for i := 0; i < 20; i++ { - wg.Add(1) - go func(i int) { - tt := fmt.Sprintf("expect-%d", i) - tpls := map[string]renderable{ - "t": { - tpl: `{{.val}}`, - vals: map[string]interface{}{"val": tt}, - }, - } - out, err := e.render(tpls) - if err != nil { - t.Errorf("Failed to render %s: %s", tt, err) - } - if out["t"] != tt { - t.Errorf("Expected %q, got %q", tt, out["t"]) - } - wg.Done() - }(i) - } - wg.Wait() -} - -func TestParseErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - - tplsUndefinedFunction := map[string]renderable{ - "undefined_function": {tpl: `{{foo}}`, vals: vals}, - } - _, err := new(Engine).render(tplsUndefinedFunction) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - expected := `parse error at (undefined_function:1): function "foo" not defined` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } -} - -func TestExecErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - cases := []struct { - name string - tpls map[string]renderable - expected string - }{ - { - name: "MissingRequired", - tpls: map[string]renderable{ - "missing_required": {tpl: `{{required "foo is required" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (missing_required:1:2): foo is required`, - }, - { - name: "MissingRequiredWithColons", - tpls: map[string]renderable{ - "missing_required_with_colons": {tpl: `{{required ":this: message: has many: colons:" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (missing_required_with_colons:1:2): :this: message: has many: colons:`, - }, - { - name: "Issue6044", - tpls: map[string]renderable{ - "issue6044": { - vals: vals, - tpl: `{{ $someEmptyValue := "" }} -{{ $myvar := "abc" }} -{{- required (printf "%s: something is missing" $myvar) $someEmptyValue | repeat 0 }}`, - }, - }, - expected: `execution error at (issue6044:3:4): abc: something is missing`, - }, - { - name: "MissingRequiredWithNewlines", - tpls: map[string]renderable{ - "issue9981": {tpl: `{{required "foo is required\nmore info after the break" .Values.foo}}`, vals: vals}, - }, - expected: `execution error at (issue9981:1:2): foo is required -more info after the break`, - }, - { - name: "FailWithNewlines", - tpls: map[string]renderable{ - "issue9981": {tpl: `{{fail "something is wrong\nlinebreak"}}`, vals: vals}, - }, - expected: `execution error at (issue9981:1:2): something is wrong -linebreak`, - }, - } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { - _, err := new(Engine).render(tt.tpls) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - if err.Error() != tt.expected { - t.Errorf("Expected %q, got %q", tt.expected, err.Error()) - } - }) - } -} - -func TestFailErrors(t *testing.T) { - vals := chartutil.Values{"Values": map[string]interface{}{}} - - failtpl := `All your base are belong to us{{ fail "This is an error" }}` - tplsFailed := map[string]renderable{ - "failtpl": {tpl: failtpl, vals: vals}, - } - _, err := new(Engine).render(tplsFailed) - if err == nil { - t.Fatalf("Expected failures while rendering: %s", err) - } - expected := `execution error at (failtpl:1:33): This is an error` - if err.Error() != expected { - t.Errorf("Expected '%s', got %q", expected, err.Error()) - } - - var e Engine - e.LintMode = true - out, err := e.render(tplsFailed) - if err != nil { - t.Fatal(err) - } - - expectStr := "All your base are belong to us" - if gotStr := out["failtpl"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } -} - -func TestAllTemplates(t *testing.T) { - ch1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "ch1"}, - Templates: []*chart.File{ - {Name: "templates/foo", Data: []byte("foo")}, - {Name: "templates/bar", Data: []byte("bar")}, - }, - } - dep1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "laboratory mice"}, - Templates: []*chart.File{ - {Name: "templates/pinky", Data: []byte("pinky")}, - {Name: "templates/brain", Data: []byte("brain")}, - }, - } - ch1.AddDependency(dep1) - - dep2 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "same thing we do every night"}, - Templates: []*chart.File{ - {Name: "templates/innermost", Data: []byte("innermost")}, - }, - } - dep1.AddDependency(dep2) - - tpls := allTemplates(ch1, chartutil.Values{}) - if len(tpls) != 5 { - t.Errorf("Expected 5 charts, got %d", len(tpls)) - } -} - -func TestChartValuesContainsIsRoot(t *testing.T) { - ch1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "parent"}, - Templates: []*chart.File{ - {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")}, - }, - } - dep1 := &chart.Chart{ - Metadata: &chart.Metadata{Name: "child"}, - Templates: []*chart.File{ - {Name: "templates/isroot", Data: []byte("{{.Chart.IsRoot}}")}, - }, - } - ch1.AddDependency(dep1) - - out, err := Render(ch1, chartutil.Values{}) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - expects := map[string]string{ - "parent/charts/child/templates/isroot": "false", - "parent/templates/isroot": "true", - } - for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } - } -} - -func TestRenderDependency(t *testing.T) { - deptpl := `{{define "myblock"}}World{{end}}` - toptpl := `Hello {{template "myblock"}}` - ch := &chart.Chart{ - Metadata: &chart.Metadata{Name: "outerchart"}, - Templates: []*chart.File{ - {Name: "templates/outer", Data: []byte(toptpl)}, - }, - } - ch.AddDependency(&chart.Chart{ - Metadata: &chart.Metadata{Name: "innerchart"}, - Templates: []*chart.File{ - {Name: "templates/inner", Data: []byte(deptpl)}, - }, - }) - - out, err := Render(ch, map[string]interface{}{}) - if err != nil { - t.Fatalf("failed to render chart: %s", err) - } - - if len(out) != 2 { - t.Errorf("Expected 2, got %d", len(out)) - } - - expect := "Hello World" - if out["outerchart/templates/outer"] != expect { - t.Errorf("Expected %q, got %q", expect, out["outer"]) - } - -} - -func TestRenderNestedValues(t *testing.T) { - innerpath := "templates/inner.tpl" - outerpath := "templates/outer.tpl" - // Ensure namespacing rules are working. - deepestpath := "templates/inner.tpl" - checkrelease := "templates/release.tpl" - // Ensure subcharts scopes are working. - subchartspath := "templates/subcharts.tpl" - - deepest := &chart.Chart{ - Metadata: &chart.Metadata{Name: "deepest"}, - Templates: []*chart.File{ - {Name: deepestpath, Data: []byte(`And this same {{.Values.what}} that smiles {{.Values.global.when}}`)}, - {Name: checkrelease, Data: []byte(`Tomorrow will be {{default "happy" .Release.Name }}`)}, - }, - Values: map[string]interface{}{"what": "milkshake", "where": "here"}, - } - - inner := &chart.Chart{ - Metadata: &chart.Metadata{Name: "herrick"}, - Templates: []*chart.File{ - {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, - }, - Values: map[string]interface{}{"who": "Robert", "what": "glasses"}, - } - inner.AddDependency(deepest) - - outer := &chart.Chart{ - Metadata: &chart.Metadata{Name: "top"}, - Templates: []*chart.File{ - {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, - {Name: subchartspath, Data: []byte(`The glorious Lamp of {{.Subcharts.herrick.Subcharts.deepest.Values.where}}, the {{.Subcharts.herrick.Values.what}}`)}, - }, - Values: map[string]interface{}{ - "what": "stinkweed", - "who": "me", - "herrick": map[string]interface{}{ - "who": "time", - "what": "Sun", - }, - }, - } - outer.AddDependency(inner) - - injValues := map[string]interface{}{ - "what": "rosebuds", - "herrick": map[string]interface{}{ - "deepest": map[string]interface{}{ - "what": "flower", - "where": "Heaven", - }, - }, - "global": map[string]interface{}{ - "when": "to-day", - }, - } - - tmp, err := chartutil.CoalesceValues(outer, injValues) - if err != nil { - t.Fatalf("Failed to coalesce values: %s", err) - } - - inject := chartutil.Values{ - "Values": tmp, - "Chart": outer.Metadata, - "Release": chartutil.Values{ - "Name": "dyin", - }, - } - - t.Logf("Calculated values: %v", inject) - - out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - - fullouterpath := "top/" + outerpath - if out[fullouterpath] != "Gather ye rosebuds while ye may" { - t.Errorf("Unexpected outer: %q", out[fullouterpath]) - } - - fullinnerpath := "top/charts/herrick/" + innerpath - if out[fullinnerpath] != "Old time is still a-flyin'" { - t.Errorf("Unexpected inner: %q", out[fullinnerpath]) - } - - fulldeepestpath := "top/charts/herrick/charts/deepest/" + deepestpath - if out[fulldeepestpath] != "And this same flower that smiles to-day" { - t.Errorf("Unexpected deepest: %q", out[fulldeepestpath]) - } - - fullcheckrelease := "top/charts/herrick/charts/deepest/" + checkrelease - if out[fullcheckrelease] != "Tomorrow will be dyin" { - t.Errorf("Unexpected release: %q", out[fullcheckrelease]) - } - - fullchecksubcharts := "top/" + subchartspath - if out[fullchecksubcharts] != "The glorious Lamp of Heaven, the Sun" { - t.Errorf("Unexpected subcharts: %q", out[fullchecksubcharts]) - } -} - -func TestRenderBuiltinValues(t *testing.T) { - inner := &chart.Chart{ - Metadata: &chart.Metadata{Name: "Latium"}, - Templates: []*chart.File{ - {Name: "templates/Lavinia", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, - {Name: "templates/From", Data: []byte(`{{.Files.author | printf "%s"}} {{.Files.Get "book/title.txt"}}`)}, - }, - Files: []*chart.File{ - {Name: "author", Data: []byte("Virgil")}, - {Name: "book/title.txt", Data: []byte("Aeneid")}, - }, - } - - outer := &chart.Chart{ - Metadata: &chart.Metadata{Name: "Troy"}, - Templates: []*chart.File{ - {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, - {Name: "templates/Amata", Data: []byte(`{{.Subcharts.Latium.Chart.Name}} {{.Subcharts.Latium.Files.author | printf "%s"}}`)}, - }, - } - outer.AddDependency(inner) - - inject := chartutil.Values{ - "Values": "", - "Chart": outer.Metadata, - "Release": chartutil.Values{ - "Name": "Aeneid", - }, - } - - t.Logf("Calculated values: %v", outer) - - out, err := Render(outer, inject) - if err != nil { - t.Fatalf("failed to render templates: %s", err) - } - - expects := map[string]string{ - "Troy/charts/Latium/templates/Lavinia": "Troy/charts/Latium/templates/LaviniaLatiumAeneid", - "Troy/templates/Aeneas": "Troy/templates/AeneasTroyAeneid", - "Troy/templates/Amata": "Latium Virgil", - "Troy/charts/Latium/templates/From": "Virgil Aeneid", - } - for file, expect := range expects { - if out[file] != expect { - t.Errorf("Expected %q, got %q", expect, out[file]) - } - } - -} - -func TestAlterFuncMap_include(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "conrad"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, - {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, - }, - } - - // Check nested reference in include FuncMap - d := &chart.Chart{ - Metadata: &chart.Metadata{Name: "nested"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`{{include "nested/templates/quote" . | indent 2}} dead.`)}, - {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, - }, - } - - v := chartutil.Values{ - "Values": "", - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "Mistah Kurtz", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := " Mistah Kurtz - he dead." - if got := out["conrad/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - - _, err = Render(d, v) - expectErrName := "nested/templates/quote" - if err == nil { - t.Errorf("Expected err of nested reference name: %v", expectErrName) - } -} - -func TestAlterFuncMap_require(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "conan"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(`All your base are belong to {{ required "A valid 'who' is required" .Values.who }}`)}, - {Name: "templates/bases", Data: []byte(`All {{ required "A valid 'bases' is required" .Values.bases }} of them!`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "who": "us", - "bases": 2, - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "That 90s meme", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expectStr := "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum := "All 2 of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } - - // test required without passing in needed values with lint mode on - // verifies lint replaces required with an empty string (should not fail) - lintValues := chartutil.Values{ - "Values": chartutil.Values{ - "who": "us", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "That 90s meme", - }, - } - var e Engine - e.LintMode = true - out, err = e.Render(c, lintValues) - if err != nil { - t.Fatal(err) - } - - expectStr = "All your base are belong to us" - if gotStr := out["conan/templates/quote"]; gotStr != expectStr { - t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, out) - } - expectNum = "All of them!" - if gotNum := out["conan/templates/bases"]; gotNum != expectNum { - t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, out) - } -} - -func TestAlterFuncMap_tpl(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: myvalue" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } -} - -func TestAlterFuncMap_tplfunc(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, - }, - } - - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "Evaluate tpl Value: \"myvalue\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } -} - -func TestAlterFuncMap_tplinclude(t *testing.T) { - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "TplFunction"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, - {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, - }, - } - v := chartutil.Values{ - "Values": chartutil.Values{ - "value": "myvalue", - }, - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - - out, err := Render(c, v) - if err != nil { - t.Fatal(err) - } - - expect := "\"TplFunction/templates/base\"" - if got := out["TplFunction/templates/base"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - -} - -func TestRenderRecursionLimit(t *testing.T) { - // endless recursion should produce an error - c := &chart.Chart{ - Metadata: &chart.Metadata{Name: "bad"}, - Templates: []*chart.File{ - {Name: "templates/base", Data: []byte(`{{include "recursion" . }}`)}, - {Name: "templates/recursion", Data: []byte(`{{define "recursion"}}{{include "recursion" . }}{{end}}`)}, - }, - } - v := chartutil.Values{ - "Values": "", - "Chart": c.Metadata, - "Release": chartutil.Values{ - "Name": "TestRelease", - }, - } - expectErr := "rendering template has a nested reference name: recursion: unable to execute template" - - _, err := Render(c, v) - if err == nil || !strings.HasSuffix(err.Error(), expectErr) { - t.Errorf("Expected err with suffix: %s", expectErr) - } - - // calling the same function many times is ok - times := 4000 - phrase := "All work and no play makes Jack a dull boy" - printFunc := `{{define "overlook"}}{{printf "` + phrase + `\n"}}{{end}}` - var repeatedIncl string - for i := 0; i < times; i++ { - repeatedIncl += `{{include "overlook" . }}` - } - - d := &chart.Chart{ - Metadata: &chart.Metadata{Name: "overlook"}, - Templates: []*chart.File{ - {Name: "templates/quote", Data: []byte(repeatedIncl)}, - {Name: "templates/_function", Data: []byte(printFunc)}, - }, - } - - out, err := Render(d, v) - if err != nil { - t.Fatal(err) - } - - var expect string - for i := 0; i < times; i++ { - expect += phrase + "\n" - } - if got := out["overlook/templates/quote"]; got != expect { - t.Errorf("Expected %q, got %q (%v)", expect, got, out) - } - -} diff --git a/pkg/engine/files.go b/pkg/engine/files.go deleted file mode 100644 index d7e62da5a..000000000 --- a/pkg/engine/files.go +++ /dev/null @@ -1,160 +0,0 @@ -/* -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 engine - -import ( - "encoding/base64" - "path" - "strings" - - "github.com/gobwas/glob" - - "helm.sh/helm/v3/pkg/chart" -) - -// files is a map of files in a chart that can be accessed from a template. -type files map[string][]byte - -// NewFiles creates a new files from chart files. -// Given an []*chart.File (the format for files in a chart.Chart), extract a map of files. -func newFiles(from []*chart.File) files { - files := make(map[string][]byte) - for _, f := range from { - files[f.Name] = f.Data - } - return files -} - -// GetBytes gets a file by path. -// -// The returned data is raw. In a template context, this is identical to calling -// {{index .Files $path}}. -// -// This is intended to be accessed from within a template, so a missed key returns -// an empty []byte. -func (f files) GetBytes(name string) []byte { - if v, ok := f[name]; ok { - return v - } - return []byte{} -} - -// Get returns a string representation of the given file. -// -// Fetch the contents of a file as a string. It is designed to be called in a -// template. -// -// {{.Files.Get "foo"}} -func (f files) Get(name string) string { - return string(f.GetBytes(name)) -} - -// Glob takes a glob pattern and returns another files object only containing -// matched files. -// -// This is designed to be called from a template. -// -// {{ range $name, $content := .Files.Glob("foo/**") }} -// {{ $name }}: | -// {{ .Files.Get($name) | indent 4 }}{{ end }} -func (f files) Glob(pattern string) files { - g, err := glob.Compile(pattern, '/') - if err != nil { - g, _ = glob.Compile("**") - } - - nf := newFiles(nil) - for name, contents := range f { - if g.Match(name) { - nf[name] = contents - } - } - - return nf -} - -// AsConfig turns a Files group and flattens it to a YAML map suitable for -// including in the 'data' section of a Kubernetes ConfigMap definition. -// Duplicate keys will be overwritten, so be aware that your file names -// (regardless of path) should be unique. -// -// This is designed to be called from a template, and will return empty string -// (via toYAML function) if it cannot be serialized to YAML, or if the Files -// object is nil. -// -// The output will not be indented, so you will want to pipe this to the -// 'indent' template function. -// -// data: -// {{ .Files.Glob("config/**").AsConfig() | indent 4 }} -func (f files) AsConfig() string { - if f == nil { - return "" - } - - m := make(map[string]string) - - // Explicitly convert to strings, and file names - for k, v := range f { - m[path.Base(k)] = string(v) - } - - return toYAML(m) -} - -// AsSecrets returns the base64-encoded value of a Files object suitable for -// including in the 'data' section of a Kubernetes Secret definition. -// Duplicate keys will be overwritten, so be aware that your file names -// (regardless of path) should be unique. -// -// This is designed to be called from a template, and will return empty string -// (via toYAML function) if it cannot be serialized to YAML, or if the Files -// object is nil. -// -// The output will not be indented, so you will want to pipe this to the -// 'indent' template function. -// -// data: -// {{ .Files.Glob("secrets/*").AsSecrets() }} -func (f files) AsSecrets() string { - if f == nil { - return "" - } - - m := make(map[string]string) - - for k, v := range f { - m[path.Base(k)] = base64.StdEncoding.EncodeToString(v) - } - - return toYAML(m) -} - -// Lines returns each line of a named file (split by "\n") as a slice, so it can -// be ranged over in your templates. -// -// This is designed to be called from a template. -// -// {{ range .Files.Lines "foo/bar.html" }} -// {{ . }}{{ end }} -func (f files) Lines(path string) []string { - if f == nil || f[path] == nil { - return []string{} - } - - return strings.Split(string(f[path]), "\n") -} diff --git a/pkg/engine/files_test.go b/pkg/engine/files_test.go deleted file mode 100644 index 4b37724f9..000000000 --- a/pkg/engine/files_test.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -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 engine - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -var cases = []struct { - path, data string -}{ - {"ship/captain.txt", "The Captain"}, - {"ship/stowaway.txt", "Legatt"}, - {"story/name.txt", "The Secret Sharer"}, - {"story/author.txt", "Joseph Conrad"}, - {"multiline/test.txt", "bar\nfoo"}, -} - -func getTestFiles() files { - a := make(files, len(cases)) - for _, c := range cases { - a[c.path] = []byte(c.data) - } - return a -} - -func TestNewFiles(t *testing.T) { - files := getTestFiles() - if len(files) != len(cases) { - t.Errorf("Expected len() = %d, got %d", len(cases), len(files)) - } - - for i, f := range cases { - if got := string(files.GetBytes(f.path)); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } - if got := files.Get(f.path); got != f.data { - t.Errorf("%d: expected %q, got %q", i, f.data, got) - } - } -} - -func TestFileGlob(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - matched := f.Glob("story/**") - - as.Len(matched, 2, "Should be two files in glob story/**") - as.Equal("Joseph Conrad", matched.Get("story/author.txt")) -} - -func TestToConfig(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - out := f.Glob("**/captain.txt").AsConfig() - as.Equal("captain.txt: The Captain", out) - - out = f.Glob("ship/**").AsConfig() - as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt", out) -} - -func TestToSecret(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - out := f.Glob("ship/**").AsSecrets() - as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0", out) -} - -func TestLines(t *testing.T) { - as := assert.New(t) - - f := getTestFiles() - - out := f.Lines("multiline/test.txt") - as.Len(out, 2) - - as.Equal("bar", out[0]) -} diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go deleted file mode 100644 index 92b4c3383..000000000 --- a/pkg/engine/funcs.go +++ /dev/null @@ -1,177 +0,0 @@ -/* -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 engine - -import ( - "bytes" - "encoding/json" - "strings" - "text/template" - - "github.com/BurntSushi/toml" - "github.com/Masterminds/sprig/v3" - "sigs.k8s.io/yaml" -) - -// funcMap returns a mapping of all of the functions that Engine has. -// -// Because some functions are late-bound (e.g. contain context-sensitive -// data), the functions may not all perform identically outside of an Engine -// as they will inside of an Engine. -// -// Known late-bound functions: -// -// - "include" -// - "tpl" -// -// These are late-bound in Engine.Render(). The -// version included in the FuncMap is a placeholder. -// -func funcMap() template.FuncMap { - f := sprig.TxtFuncMap() - delete(f, "env") - delete(f, "expandenv") - - // Add some extra functionality - extra := template.FuncMap{ - "toToml": toTOML, - "toYaml": toYAML, - "fromYaml": fromYAML, - "fromYamlArray": fromYAMLArray, - "toJson": toJSON, - "fromJson": fromJSON, - "fromJsonArray": fromJSONArray, - - // This is a placeholder for the "include" function, which is - // late-bound to a template. By declaring it here, we preserve the - // integrity of the linter. - "include": func(string, interface{}) string { return "not implemented" }, - "tpl": func(string, interface{}) interface{} { return "not implemented" }, - "required": func(string, interface{}) (interface{}, error) { return "not implemented", nil }, - // Provide a placeholder for the "lookup" function, which requires a kubernetes - // connection. - "lookup": func(string, string, string, string) (map[string]interface{}, error) { - return map[string]interface{}{}, nil - }, - } - - for k, v := range extra { - f[k] = v - } - - return f -} - -// toYAML takes an interface, marshals it to yaml, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toYAML(v interface{}) string { - data, err := yaml.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return strings.TrimSuffix(string(data), "\n") -} - -// fromYAML converts a YAML document into a map[string]interface{}. -// -// This is not a general-purpose YAML parser, and will not parse all valid -// YAML documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string into -// m["Error"] in the returned map. -func fromYAML(str string) map[string]interface{} { - m := map[string]interface{}{} - - if err := yaml.Unmarshal([]byte(str), &m); err != nil { - m["Error"] = err.Error() - } - return m -} - -// fromYAMLArray converts a YAML array into a []interface{}. -// -// This is not a general-purpose YAML parser, and will not parse all valid -// YAML documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string as -// the first and only item in the returned array. -func fromYAMLArray(str string) []interface{} { - a := []interface{}{} - - if err := yaml.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} - } - return a -} - -// toTOML takes an interface, marshals it to toml, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toTOML(v interface{}) string { - b := bytes.NewBuffer(nil) - e := toml.NewEncoder(b) - err := e.Encode(v) - if err != nil { - return err.Error() - } - return b.String() -} - -// toJSON takes an interface, marshals it to json, and returns a string. It will -// always return a string, even on marshal error (empty string). -// -// This is designed to be called from a template. -func toJSON(v interface{}) string { - data, err := json.Marshal(v) - if err != nil { - // Swallow errors inside of a template. - return "" - } - return string(data) -} - -// fromJSON converts a JSON document into a map[string]interface{}. -// -// This is not a general-purpose JSON parser, and will not parse all valid -// JSON documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string into -// m["Error"] in the returned map. -func fromJSON(str string) map[string]interface{} { - m := make(map[string]interface{}) - - if err := json.Unmarshal([]byte(str), &m); err != nil { - m["Error"] = err.Error() - } - return m -} - -// fromJSONArray converts a JSON array into a []interface{}. -// -// This is not a general-purpose JSON parser, and will not parse all valid -// JSON documents. Additionally, because its intended use is within templates -// it tolerates errors. It will insert the returned error message string as -// the first and only item in the returned array. -func fromJSONArray(str string) []interface{} { - a := []interface{}{} - - if err := json.Unmarshal([]byte(str), &a); err != nil { - a = []interface{}{err.Error()} - } - return a -} diff --git a/pkg/engine/funcs_test.go b/pkg/engine/funcs_test.go deleted file mode 100644 index 29bc121b5..000000000 --- a/pkg/engine/funcs_test.go +++ /dev/null @@ -1,178 +0,0 @@ -/* -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 engine - -import ( - "strings" - "testing" - "text/template" - - "github.com/stretchr/testify/assert" -) - -func TestFuncs(t *testing.T) { - //TODO write tests for failure cases - tests := []struct { - tpl, expect string - vars interface{} - }{{ - tpl: `{{ toYaml . }}`, - expect: `foo: bar`, - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ toToml . }}`, - expect: "foo = \"bar\"\n", - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ toJson . }}`, - expect: `{"foo":"bar"}`, - vars: map[string]interface{}{"foo": "bar"}, - }, { - tpl: `{{ fromYaml . }}`, - expect: "map[hello:world]", - vars: `hello: world`, - }, { - tpl: `{{ fromYamlArray . }}`, - expect: "[one 2 map[name:helm]]", - vars: "- one\n- 2\n- name: helm\n", - }, { - tpl: `{{ fromYamlArray . }}`, - expect: "[one 2 map[name:helm]]", - vars: `["one", 2, { "name": "helm" }]`, - }, { - // Regression for https://github.com/helm/helm/issues/2271 - tpl: `{{ toToml . }}`, - expect: "[mast]\n sail = \"white\"\n", - vars: map[string]map[string]string{"mast": {"sail": "white"}}, - }, { - tpl: `{{ fromYaml . }}`, - expect: "map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]", - vars: "- one\n- two\n", - }, { - tpl: `{{ fromJson .}}`, - expect: `map[hello:world]`, - vars: `{"hello":"world"}`, - }, { - tpl: `{{ fromJson . }}`, - expect: `map[Error:json: cannot unmarshal array into Go value of type map[string]interface {}]`, - vars: `["one", "two"]`, - }, { - tpl: `{{ fromJsonArray . }}`, - expect: `[one 2 map[name:helm]]`, - vars: `["one", 2, { "name": "helm" }]`, - }, { - tpl: `{{ fromJsonArray . }}`, - expect: `[json: cannot unmarshal object into Go value of type []interface {}]`, - vars: `{"hello": "world"}`, - }, { - tpl: `{{ merge .dict (fromYaml .yaml) }}`, - expect: `map[a:map[b:c]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, - }, { - tpl: `{{ merge (fromYaml .yaml) .dict }}`, - expect: `map[a:map[b:d]]`, - vars: map[string]interface{}{"dict": map[string]interface{}{"a": map[string]interface{}{"b": "c"}}, "yaml": `{"a":{"b":"d"}}`}, - }, { - tpl: `{{ fromYaml . }}`, - expect: `map[Error:error unmarshaling JSON: while decoding JSON: json: cannot unmarshal array into Go value of type map[string]interface {}]`, - vars: `["one", "two"]`, - }, { - tpl: `{{ fromYamlArray . }}`, - expect: `[error unmarshaling JSON: while decoding JSON: json: cannot unmarshal object into Go value of type []interface {}]`, - vars: `hello: world`, - }, { - // This should never result in a network lookup. Regression for #7955 - tpl: `{{ lookup "v1" "Namespace" "" "unlikelynamespace99999999" }}`, - expect: `map[]`, - vars: `["one", "two"]`, - }} - - for _, tt := range tests { - var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, tt.vars) - assert.NoError(t, err) - assert.Equal(t, tt.expect, b.String(), tt.tpl) - } -} - -// This test to check a function provided by sprig is due to a change in a -// dependency of sprig. mergo in v0.3.9 changed the way it merges and only does -// public fields (i.e. those starting with a capital letter). This test, from -// sprig, fails in the new version. This is a behavior change for mergo that -// impacts sprig and Helm users. This test will help us to not update to a -// version of mergo (even accidentally) that causes a breaking change. See -// sprig changelog and notes for more details. -// Note, Go modules assume semver is never broken. So, there is no way to tell -// the tooling to not update to a minor or patch version. `go install` could -// be used to accidentally update mergo. This test and message should catch -// the problem and explain why it's happening. -func TestMerge(t *testing.T) { - dict := map[string]interface{}{ - "src2": map[string]interface{}{ - "h": 10, - "i": "i", - "j": "j", - }, - "src1": map[string]interface{}{ - "a": 1, - "b": 2, - "d": map[string]interface{}{ - "e": "four", - }, - "g": []int{6, 7}, - "i": "aye", - "j": "jay", - "k": map[string]interface{}{ - "l": false, - }, - }, - "dst": map[string]interface{}{ - "a": "one", - "c": 3, - "d": map[string]interface{}{ - "f": 5, - }, - "g": []int{8, 9}, - "i": "eye", - "k": map[string]interface{}{ - "l": true, - }, - }, - } - tpl := `{{merge .dst .src1 .src2}}` - var b strings.Builder - err := template.Must(template.New("test").Funcs(funcMap()).Parse(tpl)).Execute(&b, dict) - assert.NoError(t, err) - - expected := map[string]interface{}{ - "a": "one", // key overridden - "b": 2, // merged from src1 - "c": 3, // merged from dst - "d": map[string]interface{}{ // deep merge - "e": "four", - "f": 5, - }, - "g": []int{8, 9}, // overridden - arrays are not merged - "h": 10, // merged from src2 - "i": "eye", // overridden twice - "j": "jay", // overridden and merged - "k": map[string]interface{}{ - "l": true, // overridden - }, - } - assert.Equal(t, expected, dict["dst"]) -} diff --git a/pkg/engine/lookup_func.go b/pkg/engine/lookup_func.go deleted file mode 100644 index d1bf1105a..000000000 --- a/pkg/engine/lookup_func.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 engine - -import ( - "context" - "log" - "strings" - - "github.com/pkg/errors" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" -) - -type lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) - -// NewLookupFunction returns a function for looking up objects in the cluster. -// -// If the resource does not exist, no error is raised. -// -// This function is considered deprecated, and will be renamed in Helm 4. It will no -// longer be a public function. -func NewLookupFunction(config *rest.Config) lookupFunc { - return func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) { - var client dynamic.ResourceInterface - c, namespaced, err := getDynamicClientOnKind(apiversion, resource, config) - if err != nil { - return map[string]interface{}{}, err - } - if namespaced && namespace != "" { - client = c.Namespace(namespace) - } else { - client = c - } - if name != "" { - // this will return a single object - obj, err := client.Get(context.Background(), name, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - // Just return an empty interface when the object was not found. - // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil - } - return map[string]interface{}{}, err - } - return obj.UnstructuredContent(), nil - } - // this will return a list - obj, err := client.List(context.Background(), metav1.ListOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - // Just return an empty interface when the object was not found. - // That way, users can use `if not (lookup ...)` in their templates. - return map[string]interface{}{}, nil - } - return map[string]interface{}{}, err - } - return obj.UnstructuredContent(), nil - } -} - -// getDynamicClientOnUnstructured returns a dynamic client on an Unstructured type. This client can be further namespaced. -func getDynamicClientOnKind(apiversion string, kind string, config *rest.Config) (dynamic.NamespaceableResourceInterface, bool, error) { - gvk := schema.FromAPIVersionAndKind(apiversion, kind) - apiRes, err := getAPIResourceForGVK(gvk, config) - if err != nil { - log.Printf("[ERROR] unable to get apiresource from unstructured: %s , error %s", gvk.String(), err) - return nil, false, errors.Wrapf(err, "unable to get apiresource from unstructured: %s", gvk.String()) - } - gvr := schema.GroupVersionResource{ - Group: apiRes.Group, - Version: apiRes.Version, - Resource: apiRes.Name, - } - intf, err := dynamic.NewForConfig(config) - if err != nil { - log.Printf("[ERROR] unable to get dynamic client %s", err) - return nil, false, err - } - res := intf.Resource(gvr) - return res, apiRes.Namespaced, nil -} - -func getAPIResourceForGVK(gvk schema.GroupVersionKind, config *rest.Config) (metav1.APIResource, error) { - res := metav1.APIResource{} - discoveryClient, err := discovery.NewDiscoveryClientForConfig(config) - if err != nil { - log.Printf("[ERROR] unable to create discovery client %s", err) - return res, err - } - resList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String()) - if err != nil { - log.Printf("[ERROR] unable to retrieve resource list for: %s , error: %s", gvk.GroupVersion().String(), err) - return res, err - } - for _, resource := range resList.APIResources { - // if a resource contains a "/" it's referencing a subresource. we don't support suberesource for now. - if resource.Kind == gvk.Kind && !strings.Contains(resource.Name, "/") { - res = resource - res.Group = gvk.Group - res.Version = gvk.Version - break - } - } - return res, nil -} diff --git a/pkg/gates/doc.go b/pkg/gates/doc.go deleted file mode 100644 index 762fdb8c6..000000000 --- a/pkg/gates/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 gates provides a general tool for working with experimental feature gates. - -This provides convenience methods where the user can determine if certain experimental features are enabled. -*/ -package gates diff --git a/pkg/gates/gates.go b/pkg/gates/gates.go deleted file mode 100644 index 69559219e..000000000 --- a/pkg/gates/gates.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -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 gates - -import ( - "fmt" - "os" -) - -// Gate is the name of the feature gate. -type Gate string - -// String returns the string representation of this feature gate. -func (g Gate) String() string { - return string(g) -} - -// IsEnabled determines whether a certain feature gate is enabled. -func (g Gate) IsEnabled() bool { - return os.Getenv(string(g)) != "" -} - -func (g Gate) Error() error { - return fmt.Errorf("this feature has been marked as experimental and is not enabled by default. Please set %s=1 in your environment to use this feature", g.String()) -} diff --git a/pkg/gates/gates_test.go b/pkg/gates/gates_test.go deleted file mode 100644 index 6bdd17ed6..000000000 --- a/pkg/gates/gates_test.go +++ /dev/null @@ -1,56 +0,0 @@ -/* -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 gates - -import ( - "os" - "testing" -) - -const name string = "HELM_EXPERIMENTAL_FEATURE" - -func TestIsEnabled(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.IsEnabled() { - t.Errorf("feature gate shows as available, but the environment variable %s was not set", name) - } - - os.Setenv(name, "1") - - if !g.IsEnabled() { - t.Errorf("feature gate shows as disabled, but the environment variable %s was set", name) - } -} - -func TestError(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.Error().Error() != "this feature has been marked as experimental and is not enabled by default. Please set HELM_EXPERIMENTAL_FEATURE=1 in your environment to use this feature" { - t.Errorf("incorrect error message. Received %s", g.Error().Error()) - } -} - -func TestString(t *testing.T) { - os.Unsetenv(name) - g := Gate(name) - - if g.String() != "HELM_EXPERIMENTAL_FEATURE" { - t.Errorf("incorrect string representation. Received %s", g.String()) - } -} diff --git a/pkg/getter/doc.go b/pkg/getter/doc.go deleted file mode 100644 index c53ef1ae0..000000000 --- a/pkg/getter/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -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 getter provides a generalize tool for fetching data by scheme. - -This provides a method by which the plugin system can load arbitrary protocol -handlers based upon a URL scheme. -*/ -package getter diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go deleted file mode 100644 index 653b032fe..000000000 --- a/pkg/getter/getter.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -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 getter - -import ( - "bytes" - "net/http" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -// options are generic parameters to be provided to the getter during instantiation. -// -// Getters may or may not ignore these parameters as they are passed in. -type options struct { - url string - certFile string - keyFile string - caFile string - unTar bool - insecureSkipVerifyTLS bool - username string - password string - passCredentialsAll bool - userAgent string - version string - registryClient *registry.Client - timeout time.Duration - transport *http.Transport -} - -// Option allows specifying various settings configurable by the user for overriding the defaults -// used when performing Get operations with the Getter. -type Option func(*options) - -// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with -// WithTLSClientConfig to set the TLSClientConfig's server name. -func WithURL(url string) Option { - return func(opts *options) { - opts.url = url - } -} - -// WithBasicAuth sets the request's Authorization header to use the provided credentials -func WithBasicAuth(username, password string) Option { - return func(opts *options) { - opts.username = username - opts.password = password - } -} - -func WithPassCredentialsAll(pass bool) Option { - return func(opts *options) { - opts.passCredentialsAll = pass - } -} - -// WithUserAgent sets the request's User-Agent header to use the provided agent name. -func WithUserAgent(userAgent string) Option { - return func(opts *options) { - opts.userAgent = userAgent - } -} - -// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked -func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { - return func(opts *options) { - opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS - } -} - -// WithTLSClientConfig sets the client auth with the provided credentials. -func WithTLSClientConfig(certFile, keyFile, caFile string) Option { - return func(opts *options) { - opts.certFile = certFile - opts.keyFile = keyFile - opts.caFile = caFile - } -} - -// WithTimeout sets the timeout for requests -func WithTimeout(timeout time.Duration) Option { - return func(opts *options) { - opts.timeout = timeout - } -} - -func WithTagName(tagname string) Option { - return func(opts *options) { - opts.version = tagname - } -} - -func WithRegistryClient(client *registry.Client) Option { - return func(opts *options) { - opts.registryClient = client - } -} - -func WithUntar() Option { - return func(opts *options) { - opts.unTar = true - } -} - -// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. -func WithTransport(transport *http.Transport) Option { - return func(opts *options) { - opts.transport = transport - } -} - -// Getter is an interface to support GET to the specified URL. -type Getter interface { - // Get file content by url string - Get(url string, options ...Option) (*bytes.Buffer, error) -} - -// Constructor is the function for every getter which creates a specific instance -// according to the configuration -type Constructor func(options ...Option) (Getter, error) - -// Provider represents any getter and the schemes that it supports. -// -// For example, an HTTP provider may provide one getter that handles both -// 'http' and 'https' schemes. -type Provider struct { - Schemes []string - New Constructor -} - -// Provides returns true if the given scheme is supported by this Provider. -func (p Provider) Provides(scheme string) bool { - for _, i := range p.Schemes { - if i == scheme { - return true - } - } - return false -} - -// Providers is a collection of Provider objects. -type Providers []Provider - -// ByScheme returns a Provider that handles the given scheme. -// -// If no provider handles this scheme, this will return an error. -func (p Providers) ByScheme(scheme string) (Getter, error) { - for _, pp := range p { - if pp.Provides(scheme) { - return pp.New() - } - } - return nil, errors.Errorf("scheme %q not supported", scheme) -} - -var httpProvider = Provider{ - Schemes: []string{"http", "https"}, - New: NewHTTPGetter, -} - -var ociProvider = Provider{ - Schemes: []string{registry.OCIScheme}, - New: NewOCIGetter, -} - -// All finds all of the registered getters as a list of Provider instances. -// Currently, the built-in getters and the discovered plugins with downloader -// notations are collected. -func All(settings *cli.EnvSettings) Providers { - result := Providers{httpProvider, ociProvider} - pluginDownloaders, _ := collectPlugins(settings) - result = append(result, pluginDownloaders...) - return result -} diff --git a/pkg/getter/getter_test.go b/pkg/getter/getter_test.go deleted file mode 100644 index ab14784ab..000000000 --- a/pkg/getter/getter_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -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 getter - -import ( - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -const pluginDir = "testdata/plugins" - -func TestProvider(t *testing.T) { - p := Provider{ - []string{"one", "three"}, - func(_ ...Option) (Getter, error) { return nil, nil }, - } - - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } -} - -func TestProviders(t *testing.T) { - ps := Providers{ - {[]string{"one", "three"}, func(_ ...Option) (Getter, error) { return nil, nil }}, - {[]string{"two", "four"}, func(_ ...Option) (Getter, error) { return nil, nil }}, - } - - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } - - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } -} - -func TestAll(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - all := All(env) - if len(all) != 4 { - t.Errorf("expected 4 providers (default plus three plugins), got %d", len(all)) - } - - if _, err := all.ByScheme("test2"); err != nil { - t.Error(err) - } -} - -func TestByScheme(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - g := All(env) - if _, err := g.ByScheme("test"); err != nil { - t.Error(err) - } - if _, err := g.ByScheme("https"); err != nil { - t.Error(err) - } -} diff --git a/pkg/getter/httpgetter.go b/pkg/getter/httpgetter.go deleted file mode 100644 index 081bf059e..000000000 --- a/pkg/getter/httpgetter.go +++ /dev/null @@ -1,157 +0,0 @@ -/* -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 getter - -import ( - "bytes" - "crypto/tls" - "io" - "net/http" - "net/url" - "sync" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/internal/version" -) - -// HTTPGetter is the default HTTP(/S) backend handler -type HTTPGetter struct { - opts options - transport *http.Transport - once sync.Once -} - -// Get performs a Get from repo.Getter and returns the body. -func (g *HTTPGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { - for _, opt := range options { - opt(&g.opts) - } - return g.get(href) -} - -func (g *HTTPGetter) get(href string) (*bytes.Buffer, error) { - // Set a helm specific user agent so that a repo server and metrics can - // separate helm calls from other tools interacting with repos. - req, err := http.NewRequest(http.MethodGet, href, nil) - if err != nil { - return nil, err - } - - req.Header.Set("User-Agent", version.GetUserAgent()) - if g.opts.userAgent != "" { - req.Header.Set("User-Agent", g.opts.userAgent) - } - - // Before setting the basic auth credentials, make sure the URL associated - // with the basic auth is the one being fetched. - u1, err := url.Parse(g.opts.url) - if err != nil { - return nil, errors.Wrap(err, "Unable to parse getter URL") - } - u2, err := url.Parse(href) - if err != nil { - return nil, errors.Wrap(err, "Unable to parse URL getting from") - } - - // Host on URL (returned from url.Parse) contains the port if present. - // This check ensures credentials are not passed between different - // services on different ports. - if g.opts.passCredentialsAll || (u1.Scheme == u2.Scheme && u1.Host == u2.Host) { - if g.opts.username != "" && g.opts.password != "" { - req.SetBasicAuth(g.opts.username, g.opts.password) - } - } - - client, err := g.httpClient() - if err != nil { - return nil, err - } - - resp, err := client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, errors.Errorf("failed to fetch %s : %s", href, resp.Status) - } - - buf := bytes.NewBuffer(nil) - _, err = io.Copy(buf, resp.Body) - return buf, err -} - -// NewHTTPGetter constructs a valid http/https client as a Getter -func NewHTTPGetter(options ...Option) (Getter, error) { - var client HTTPGetter - - for _, opt := range options { - opt(&client.opts) - } - - return &client, nil -} - -func (g *HTTPGetter) httpClient() (*http.Client, error) { - if g.opts.transport != nil { - return &http.Client{ - Transport: g.opts.transport, - Timeout: g.opts.timeout, - }, nil - } - - g.once.Do(func() { - g.transport = &http.Transport{ - DisableCompression: true, - Proxy: http.ProxyFromEnvironment, - } - }) - - if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" { - tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile) - if err != nil { - return nil, errors.Wrap(err, "can't create TLS config for client") - } - - sni, err := urlutil.ExtractHostname(g.opts.url) - if err != nil { - return nil, err - } - tlsConf.ServerName = sni - - g.transport.TLSClientConfig = tlsConf - } - - if g.opts.insecureSkipVerifyTLS { - if g.transport.TLSClientConfig == nil { - g.transport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } else { - g.transport.TLSClientConfig.InsecureSkipVerify = true - } - } - - client := &http.Client{ - Transport: g.transport, - Timeout: g.opts.timeout, - } - - return client, nil -} diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go deleted file mode 100644 index 5a90d8b99..000000000 --- a/pkg/getter/httpgetter_test.go +++ /dev/null @@ -1,531 +0,0 @@ -/* -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 getter - -import ( - "fmt" - "io" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "strconv" - "strings" - "testing" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/cli" -) - -func TestHTTPGetter(t *testing.T) { - g, err := NewHTTPGetter(WithURL("http://example.com")) - if err != nil { - t.Fatal(err) - } - - if _, ok := g.(*HTTPGetter); !ok { - t.Fatal("Expected NewHTTPGetter to produce an *HTTPGetter") - } - - cd := "../../testdata" - join := filepath.Join - ca, pub, priv := join(cd, "rootca.crt"), join(cd, "crt.pem"), join(cd, "key.pem") - insecure := false - timeout := time.Second * 5 - transport := &http.Transport{} - - // Test with options - g, err = NewHTTPGetter( - WithBasicAuth("I", "Am"), - WithPassCredentialsAll(false), - WithUserAgent("Groot"), - WithTLSClientConfig(pub, priv, ca), - WithInsecureSkipVerifyTLS(insecure), - WithTimeout(timeout), - WithTransport(transport), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok := g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.username != "I" { - t.Errorf("Expected NewHTTPGetter to contain %q as the username, got %q", "I", hg.opts.username) - } - - if hg.opts.password != "Am" { - t.Errorf("Expected NewHTTPGetter to contain %q as the password, got %q", "Am", hg.opts.password) - } - - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } - - if hg.opts.userAgent != "Groot" { - t.Errorf("Expected NewHTTPGetter to contain %q as the user agent, got %q", "Groot", hg.opts.userAgent) - } - - if hg.opts.certFile != pub { - t.Errorf("Expected NewHTTPGetter to contain %q as the public key file, got %q", pub, hg.opts.certFile) - } - - if hg.opts.keyFile != priv { - t.Errorf("Expected NewHTTPGetter to contain %q as the private key file, got %q", priv, hg.opts.keyFile) - } - - if hg.opts.caFile != ca { - t.Errorf("Expected NewHTTPGetter to contain %q as the CA file, got %q", ca, hg.opts.caFile) - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", false, hg.opts.insecureSkipVerifyTLS) - } - - if hg.opts.timeout != timeout { - t.Errorf("Expected NewHTTPGetter to contain %s as Timeout flag, got %s", timeout, hg.opts.timeout) - } - - if hg.opts.transport != transport { - t.Errorf("Expected NewHTTPGetter to contain %p as Transport, got %p", transport, hg.opts.transport) - } - - // Test if setting insecureSkipVerifyTLS is being passed to the ops - insecure = true - - g, err = NewHTTPGetter( - WithInsecureSkipVerifyTLS(insecure), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - - if hg.opts.insecureSkipVerifyTLS != insecure { - t.Errorf("Expected NewHTTPGetter to contain %t as InsecureSkipVerifyTLs flag, got %t", insecure, hg.opts.insecureSkipVerifyTLS) - } - - // Checking false by default - if hg.opts.passCredentialsAll != false { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", false, hg.opts.passCredentialsAll) - } - - // Test setting PassCredentialsAll - g, err = NewHTTPGetter( - WithBasicAuth("I", "Am"), - WithPassCredentialsAll(true), - ) - if err != nil { - t.Fatal(err) - } - - hg, ok = g.(*HTTPGetter) - if !ok { - t.Fatal("expected NewHTTPGetter to produce an *HTTPGetter") - } - if hg.opts.passCredentialsAll != true { - t.Errorf("Expected NewHTTPGetter to contain %t as PassCredentialsAll, got %t", true, hg.opts.passCredentialsAll) - } -} - -func TestDownload(t *testing.T) { - expect := "Call me Ishmael" - expectedUserAgent := "I am Groot" - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defaultUserAgent := "Helm/" + strings.TrimPrefix(version.GetVersion(), "v") - if r.UserAgent() != defaultUserAgent { - t.Errorf("Expected '%s', got '%s'", defaultUserAgent, r.UserAgent()) - } - fmt.Fprint(w, expect) - })) - defer srv.Close() - - g, err := All(cli.New()).ByScheme("http") - if err != nil { - t.Fatal(err) - } - got, err := g.Get(srv.URL, WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with http server - basicAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - if r.UserAgent() != expectedUserAgent { - t.Errorf("Expected '%s', got '%s'", expectedUserAgent, r.UserAgent()) - } - fmt.Fprint(w, expect) - })) - - defer basicAuthSrv.Close() - - u, _ := url.ParseRequestURI(basicAuthSrv.URL) - httpgetter, err := NewHTTPGetter( - WithURL(u.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(false), - WithUserAgent(expectedUserAgent), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with Get URL differing from withURL - crossAuthSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if ok || username == "username" || password == "password" { - t.Errorf("Expected request to not include but got '%v', '%s', '%s'", ok, username, password) - } - fmt.Fprint(w, expect) - })) - - defer crossAuthSrv.Close() - - u, _ = url.ParseRequestURI(crossAuthSrv.URL) - - // A different host is provided for the WithURL from the one used for Get - u2, _ := url.ParseRequestURI(crossAuthSrv.URL) - host := strings.Split(u2.Host, ":") - host[0] = host[0] + "a" - u2.Host = strings.Join(host, ":") - httpgetter, err = NewHTTPGetter( - WithURL(u2.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(false), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } - - // test with Get URL differing from withURL and should pass creds - crossAuthSrv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - fmt.Fprint(w, expect) - })) - - defer crossAuthSrv.Close() - - u, _ = url.ParseRequestURI(crossAuthSrv.URL) - - // A different host is provided for the WithURL from the one used for Get - u2, _ = url.ParseRequestURI(crossAuthSrv.URL) - host = strings.Split(u2.Host, ":") - host[0] = host[0] + "a" - u2.Host = strings.Join(host, ":") - httpgetter, err = NewHTTPGetter( - WithURL(u2.String()), - WithBasicAuth("username", "password"), - WithPassCredentialsAll(true), - ) - if err != nil { - t.Fatal(err) - } - got, err = httpgetter.Get(u.String()) - if err != nil { - t.Fatal(err) - } - - if got.String() != expect { - t.Errorf("Expected %q, got %q", expect, got.String()) - } -} - -func TestDownloadTLS(t *testing.T) { - cd := "../../testdata" - ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - - tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) - if err != nil { - t.Fatal(errors.Wrap(err, "can't create TLS config for client")) - } - tlsConf.ServerName = "helm.sh" - tlsSrv.TLS = tlsConf - tlsSrv.StartTLS() - defer tlsSrv.Close() - - u, _ := url.ParseRequestURI(tlsSrv.URL) - g, err := NewHTTPGetter( - WithURL(u.String()), - WithTLSClientConfig(pub, priv, ca), - ) - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String()); err != nil { - t.Error(err) - } - - // now test with TLS config being passed along in .Get (see #6635) - g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig(pub, priv, ca)); err != nil { - t.Error(err) - } - - // test with only the CA file (see also #6635) - g, err = NewHTTPGetter() - if err != nil { - t.Fatal(err) - } - - if _, err := g.Get(u.String(), WithURL(u.String()), WithTLSClientConfig("", "", ca)); err != nil { - t.Error(err) - } -} - -func TestDownloadInsecureSkipTLSVerify(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - defer ts.Close() - - u, _ := url.ParseRequestURI(ts.URL) - - // Ensure the default behavior did not change - g, err := NewHTTPGetter( - WithURL(u.String()), - ) - if err != nil { - t.Error(err) - } - - if _, err := g.Get(u.String()); err == nil { - t.Errorf("Expected Getter to throw an error, got %s", err) - } - - // Test certificate check skip - g, err = NewHTTPGetter( - WithURL(u.String()), - WithInsecureSkipVerifyTLS(true), - ) - if err != nil { - t.Error(err) - } - if _, err = g.Get(u.String()); err != nil { - t.Error(err) - } - -} - -func TestHTTPGetterTarDownload(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - f, _ := os.Open("testdata/empty-0.0.1.tgz") - defer f.Close() - - b := make([]byte, 512) - f.Read(b) - //Get the file size - FileStat, _ := f.Stat() - FileSize := strconv.FormatInt(FileStat.Size(), 10) - - //Simulating improper header values from bitbucket - w.Header().Set("Content-Type", "application/x-tar") - w.Header().Set("Content-Encoding", "gzip") - w.Header().Set("Content-Length", FileSize) - - f.Seek(0, 0) - io.Copy(w, f) - })) - - defer srv.Close() - - g, err := NewHTTPGetter(WithURL(srv.URL)) - if err != nil { - t.Fatal(err) - } - - data, _ := g.Get(srv.URL) - mimeType := http.DetectContentType(data.Bytes()) - - expectedMimeType := "application/x-gzip" - if mimeType != expectedMimeType { - t.Fatalf("Expected response with MIME type %s, but got %s", expectedMimeType, mimeType) - } -} - -func TestHttpClientInsecureSkipVerify(t *testing.T) { - g := HTTPGetter{} - g.opts.url = "https://localhost" - verifyInsecureSkipVerify(t, &g, "Blank HTTPGetter", false) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.caFile = "testdata/ca.crt" - verifyInsecureSkipVerify(t, &g, "HTTPGetter with ca file", false) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.insecureSkipVerifyTLS = true - verifyInsecureSkipVerify(t, &g, "HTTPGetter with skip cert verification only", true) - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.certFile = "testdata/client.crt" - g.opts.keyFile = "testdata/client.key" - g.opts.insecureSkipVerifyTLS = true - transport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", true) - if len(transport.TLSClientConfig.Certificates) <= 0 { - t.Fatal("transport.TLSClientConfig.Certificates is not present") - } - if transport.TLSClientConfig.ServerName == "" { - t.Fatal("TLSClientConfig.ServerName is blank") - } -} - -func verifyInsecureSkipVerify(t *testing.T, g *HTTPGetter, caseName string, expectedValue bool) *http.Transport { - returnVal, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if returnVal == nil { - t.Fatalf("Expected non nil value for http client") - } - transport := (returnVal.Transport).(*http.Transport) - gotValue := false - if transport.TLSClientConfig != nil { - gotValue = transport.TLSClientConfig.InsecureSkipVerify - } - if gotValue != expectedValue { - t.Fatalf("Case Name = %s\nInsecureSkipVerify did not come as expected. Expected = %t; Got = %v", - caseName, expectedValue, gotValue) - } - return transport -} - -func TestDefaultHTTPTransportReuse(t *testing.T) { - g := HTTPGetter{} - - httpClient1, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport1 := (httpClient1.Transport).(*http.Transport) - - httpClient2, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport2 := (httpClient2.Transport).(*http.Transport) - - if transport1 != transport2 { - t.Fatalf("Expected default transport to be reused") - } -} - -func TestHTTPTransportOption(t *testing.T) { - transport := &http.Transport{} - - g := HTTPGetter{} - g.opts.transport = transport - httpClient1, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient1 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport1 := (httpClient1.Transport).(*http.Transport) - - if transport1 != transport { - t.Fatalf("Expected transport option to be applied") - } - - httpClient2, err := g.httpClient() - - if err != nil { - t.Fatal(err) - } - - if httpClient2 == nil { - t.Fatalf("Expected non nil value for http client") - } - - transport2 := (httpClient2.Transport).(*http.Transport) - - if transport1 != transport2 { - t.Fatalf("Expected applied transport to be reused") - } - - g = HTTPGetter{} - g.opts.url = "https://localhost" - g.opts.certFile = "testdata/client.crt" - g.opts.keyFile = "testdata/client.key" - g.opts.insecureSkipVerifyTLS = true - g.opts.transport = transport - usedTransport := verifyInsecureSkipVerify(t, &g, "HTTPGetter with 2 way ssl", false) - if usedTransport.TLSClientConfig != nil { - t.Fatal("transport.TLSClientConfig should not be set") - } -} diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go deleted file mode 100644 index 14f5cb3ec..000000000 --- a/pkg/getter/ocigetter.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -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 getter - -import ( - "bytes" - "fmt" - "strings" - - "helm.sh/helm/v3/pkg/registry" -) - -// OCIGetter is the default HTTP(/S) backend handler -type OCIGetter struct { - opts options -} - -// Get performs a Get from repo.Getter and returns the body. -func (g *OCIGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { - for _, opt := range options { - opt(&g.opts) - } - return g.get(href) -} - -func (g *OCIGetter) get(href string) (*bytes.Buffer, error) { - client := g.opts.registryClient - - ref := strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)) - - var pullOpts []registry.PullOption - requestingProv := strings.HasSuffix(ref, ".prov") - if requestingProv { - ref = strings.TrimSuffix(ref, ".prov") - pullOpts = append(pullOpts, - registry.PullOptWithChart(false), - registry.PullOptWithProv(true)) - } - - result, err := client.Pull(ref, pullOpts...) - if err != nil { - return nil, err - } - - if requestingProv { - return bytes.NewBuffer(result.Prov.Data), nil - } - return bytes.NewBuffer(result.Chart.Data), nil -} - -// NewOCIGetter constructs a valid http/https client as a Getter -func NewOCIGetter(ops ...Option) (Getter, error) { - registryClient, err := registry.NewClient( - registry.ClientOptEnableCache(true), - ) - if err != nil { - return nil, err - } - - client := OCIGetter{ - opts: options{ - registryClient: registryClient, - }, - } - - for _, opt := range ops { - opt(&client.opts) - } - - return &client, nil -} diff --git a/pkg/getter/ocigetter_test.go b/pkg/getter/ocigetter_test.go deleted file mode 100644 index fc548b7a6..000000000 --- a/pkg/getter/ocigetter_test.go +++ /dev/null @@ -1,30 +0,0 @@ -/* -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 getter - -import ( - "testing" -) - -func TestNewOCIGetter(t *testing.T) { - testfn := func(ops *options) { - if ops.registryClient == nil { - t.Fatalf("the OCIGetter's registryClient should not be null") - } - } - - NewOCIGetter(testfn) -} diff --git a/pkg/getter/plugingetter.go b/pkg/getter/plugingetter.go deleted file mode 100644 index 0d13ade57..000000000 --- a/pkg/getter/plugingetter.go +++ /dev/null @@ -1,102 +0,0 @@ -/* -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 getter - -import ( - "bytes" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/plugin" -) - -// collectPlugins scans for getter plugins. -// This will load plugins according to the cli. -func collectPlugins(settings *cli.EnvSettings) (Providers, error) { - plugins, err := plugin.FindPlugins(settings.PluginsDirectory) - if err != nil { - return nil, err - } - var result Providers - for _, plugin := range plugins { - for _, downloader := range plugin.Metadata.Downloaders { - result = append(result, Provider{ - Schemes: downloader.Protocols, - New: NewPluginGetter( - downloader.Command, - settings, - plugin.Metadata.Name, - plugin.Dir, - ), - }) - } - } - return result, nil -} - -// pluginGetter is a generic type to invoke custom downloaders, -// implemented in plugins. -type pluginGetter struct { - command string - settings *cli.EnvSettings - name string - base string - opts options -} - -// Get runs downloader plugin command -func (p *pluginGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { - for _, opt := range options { - opt(&p.opts) - } - commands := strings.Split(p.command, " ") - argv := append(commands[1:], p.opts.certFile, p.opts.keyFile, p.opts.caFile, href) - prog := exec.Command(filepath.Join(p.base, commands[0]), argv...) - plugin.SetupPluginEnv(p.settings, p.name, p.base) - prog.Env = os.Environ() - buf := bytes.NewBuffer(nil) - prog.Stdout = buf - prog.Stderr = os.Stderr - if err := prog.Run(); err != nil { - if eerr, ok := err.(*exec.ExitError); ok { - os.Stderr.Write(eerr.Stderr) - return nil, errors.Errorf("plugin %q exited with error", p.command) - } - return nil, err - } - return buf, nil -} - -// NewPluginGetter constructs a valid plugin getter -func NewPluginGetter(command string, settings *cli.EnvSettings, name, base string) Constructor { - return func(options ...Option) (Getter, error) { - result := &pluginGetter{ - command: command, - settings: settings, - name: name, - base: base, - } - for _, opt := range options { - opt(&result.opts) - } - return result, nil - } -} diff --git a/pkg/getter/plugingetter_test.go b/pkg/getter/plugingetter_test.go deleted file mode 100644 index a18fa302b..000000000 --- a/pkg/getter/plugingetter_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -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 getter - -import ( - "runtime" - "strings" - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -func TestCollectPlugins(t *testing.T) { - env := cli.New() - env.PluginsDirectory = pluginDir - - p, err := collectPlugins(env) - if err != nil { - t.Fatal(err) - } - - if len(p) != 2 { - t.Errorf("Expected 2 plugins, got %d: %v", len(p), p) - } - - if _, err := p.ByScheme("test2"); err != nil { - t.Error(err) - } - - if _, err := p.ByScheme("test"); err != nil { - t.Error(err) - } - - if _, err := p.ByScheme("nosuchthing"); err == nil { - t.Fatal("did not expect protocol handler for nosuchthing") - } -} - -func TestPluginGetter(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("TODO: refactor this test to work on windows") - } - - env := cli.New() - env.PluginsDirectory = pluginDir - pg := NewPluginGetter("echo", env, "test", ".") - g, err := pg() - if err != nil { - t.Fatal(err) - } - - data, err := g.Get("test://foo/bar") - if err != nil { - t.Fatal(err) - } - - expect := "test://foo/bar" - got := strings.TrimSpace(data.String()) - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } -} - -func TestPluginSubCommands(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("TODO: refactor this test to work on windows") - } - - env := cli.New() - env.PluginsDirectory = pluginDir - - pg := NewPluginGetter("echo -n", env, "test", ".") - g, err := pg() - if err != nil { - t.Fatal(err) - } - - data, err := g.Get("test://foo/bar") - if err != nil { - t.Fatal(err) - } - - expect := " test://foo/bar" - got := data.String() - if got != expect { - t.Errorf("Expected %q, got %q", expect, got) - } -} diff --git a/pkg/getter/testdata/ca.crt b/pkg/getter/testdata/ca.crt deleted file mode 100644 index c17820085..000000000 --- a/pkg/getter/testdata/ca.crt +++ /dev/null @@ -1,25 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEJDCCAwygAwIBAgIUcGE5xyj7IH7sZLntsHKxZHCd3awwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCSU4xDzANBgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29j -aGkxGDAWBgNVBAoMD2NoYXJ0bXVzZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNl -dW1fY2EwIBcNMjAxMjA0MDkxMjU4WhgPMjI5NDA5MTkwOTEyNThaMGExCzAJBgNV -BAYTAklOMQ8wDQYDVQQIDAZLZXJhbGExDjAMBgNVBAcMBUtvY2hpMRgwFgYDVQQK -DA9jaGFydG11c2V1bS5jb20xFzAVBgNVBAMMDmNoYXJ0bXVzZXVtX2NhMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqQJi/BRWzaXlkDP48kUAWgaLtD0Y -72E30WBZDAw3S+BaYulRk1LWK1QM+ALiZQb1a6YgNvuERyywOv45pZaC2xtP6Bju -+59kwBrEtNCTNa2cSqs0hSw6NCDe+K8lpFKlTdh4c5sAkiDkMBr1R6uu7o4HvfO0 -iGMZ9VUdrbf4psZIyPVRdt/sAkAKqbjQfxr6VUmMktrZNND+mwPgrhS2kPL4P+JS -zpxgpkuSUvg5DvJuypmCI0fDr6GwshqXM1ONHE0HT8MEVy1xZj9rVHt7sgQhjBX1 -PsFySZrq1lSz8R864c1l+tCGlk9+1ldQjc9tBzdvCjJB+nYfTTpBUk/VKwIDAQAB -o4HRMIHOMB0GA1UdDgQWBBSv1IMZGHWsZVqJkJoPDzVLMcUivjCBngYDVR0jBIGW -MIGTgBSv1IMZGHWsZVqJkJoPDzVLMcUivqFlpGMwYTELMAkGA1UEBhMCSU4xDzAN -BgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29jaGkxGDAWBgNVBAoMD2NoYXJ0bXVz -ZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNldW1fY2GCFHBhOcco+yB+7GS57bBy -sWRwnd2sMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAI6Fg9F8cjB9 -2jJn1vZPpynSFs7XPlUBVh0YXBt+o6g7+nKInwFBPzPEQ7ZZotz3GIe4I7wYiQAn -c6TU2nnqK+9TLbJIyv6NOfikLgwrTy+dAW8wrOiu+IIzA8Gdy8z8m3B7v9RUYVhx -zoNoqCEvOIzCZKDH68PZDJrDVSuvPPK33Ywj3zxYeDNXU87BKGER0vjeVG4oTAcQ -hKJURh4IRy/eW9NWiFqvNgst7k5MldOgLIOUBh1faaxlWkjuGpfdr/EBAAr491S5 -IPFU7TopsrgANnxldSzVbcgfo2nt0A976T3xZQHy3xpk1rIt55xVzT0W55NRAc7v -+9NTUOB10so= ------END CERTIFICATE----- diff --git a/pkg/getter/testdata/client.crt b/pkg/getter/testdata/client.crt deleted file mode 100644 index f005f401d..000000000 --- a/pkg/getter/testdata/client.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDejCCAmKgAwIBAgIUfSn63/ldeo1prOaxXV8I0Id6HTEwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCSU4xDzANBgNVBAgMBktlcmFsYTEOMAwGA1UEBwwFS29j -aGkxGDAWBgNVBAoMD2NoYXJ0bXVzZXVtLmNvbTEXMBUGA1UEAwwOY2hhcnRtdXNl -dW1fY2EwIBcNMjAxMjA0MDkxMzIwWhgPMjI5NDA5MTkwOTEzMjBaMFwxCzAJBgNV -BAYTAklOMQ8wDQYDVQQIDAZLZXJhbGExDjAMBgNVBAcMBUtvY2hpMRgwFgYDVQQK -DA9jaGFydG11c2V1bS5jb20xEjAQBgNVBAMMCTEyNy4wLjAuMTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKeCbADaK+7yrM9rQszF54334mGoSXbXY6Ca -7FKdkgmKCjeeqZ+lr+i+6WQ+O+Tn0dhlyHier42IqUw5Rzzegvl7QrhiChd8C6sW -pEqDK7Z1U+cv9gIabYd+qWDwFw67xiMNQfdZxwI/AgPzixlfsMw3ZNKM3Q0Vxtdz -EEYdEDDNgZ34Cj+KXCPpYDi2i5hZnha4wzIfbL3+z2o7sPBBLBrrsOtPdVVkxysN -HM4h7wp7w7QyOosndFvcTaX7yRA1ka0BoulCt2wdVc2ZBRPiPMySi893VCQ8zeHP -QMFDL3rGmKVLbP1to2dgf9ZgckMEwE8chm2D8Ls87F9tsK9fVlUCAwEAAaMtMCsw -EwYDVR0lBAwwCgYIKwYBBQUHAwIwFAYDVR0RBA0wC4IJMTI3LjAuMC4xMA0GCSqG -SIb3DQEBCwUAA4IBAQCi7z5U9J5DkM6eYzyyH/8p32Azrunw+ZpwtxbKq3xEkpcX -0XtbyTG2szegKF0eLr9NizgEN8M1nvaMO1zuxFMB6tCWO/MyNWH/0T4xvFnnVzJ4 -OKlGSvyIuMW3wofxCLRG4Cpw750iWpJ0GwjTOu2ep5tbnEMC5Ueg55WqCAE/yDrd -nL1wZSGXy1bj5H6q8EM/4/yrzK80QkfdpbDR0NGkDO2mmAKL8d57NuASWljieyV3 -Ty5C8xXw5jF2JIESvT74by8ufozUOPKmgRqySgEPgAkNm0s5a05KAi5Cpyxgdylm -CEvjni1LYGhJp9wXucF9ehKSdsw4qn9T5ire8YfI ------END CERTIFICATE----- diff --git a/pkg/getter/testdata/client.key b/pkg/getter/testdata/client.key deleted file mode 100644 index 4f676ba42..000000000 --- a/pkg/getter/testdata/client.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAp4JsANor7vKsz2tCzMXnjffiYahJdtdjoJrsUp2SCYoKN56p -n6Wv6L7pZD475OfR2GXIeJ6vjYipTDlHPN6C+XtCuGIKF3wLqxakSoMrtnVT5y/2 -Ahpth36pYPAXDrvGIw1B91nHAj8CA/OLGV+wzDdk0ozdDRXG13MQRh0QMM2BnfgK -P4pcI+lgOLaLmFmeFrjDMh9svf7Pajuw8EEsGuuw6091VWTHKw0cziHvCnvDtDI6 -iyd0W9xNpfvJEDWRrQGi6UK3bB1VzZkFE+I8zJKLz3dUJDzN4c9AwUMvesaYpUts -/W2jZ2B/1mByQwTATxyGbYPwuzzsX22wr19WVQIDAQABAoIBABw7qUSDgUAm+uWC -6KFnAd4115wqJye2qf4Z3pcWI9UjxREW1vQnkvyhoOjabHHqeL4GecGKzYAHdrF4 -Pf+OaXjvQ5GcRKMsrzLJACvm6+k24UtoFAjKt4dM2/OQw/IhyAWEaIfuQ9KnGAne -dKV0MXJaK84pG+DmuLr7k9SddWskElEyxK2j0tvdyI5byRfjf5schac9M4i5ZAYV -pT+PuXZQh8L8GEY2koE+uEMpXGOstD7yUxyV8zHFyBC7FVDkqF4S8IWY+RXQtVd6 -l8B8dRLjKSLBKDB+neStepcwNUyCDYiqyqsKfN7eVHDd0arPm6LeTuAsHKBw2OoN -YdAmUUkCgYEA0vb9mxsMgr0ORTZ14vWghz9K12oKPk9ajYuLTQVn8GQazp0XTIi5 -Mil2I78Qj87ApzGqOyHbkEgpg0C9/mheYLOYNHC4of83kNF+pHbDi1TckwxIaIK0 -rZLb3Az3zZQ2rAWZ2IgSyoeVO9RxYK/RuvPFp+UBeucuXiYoI0YlEXcCgYEAy0Sk -LTiYuLsnk21RIFK01iq4Y+4112K1NGTKu8Wm6wPaPsnLznP6339cEkbiSgbRgERE -jgOfa/CiSw5CVT9dWZuQ3OoQ83pMRb7IB0TobPmhBS/HQZ8Ocbfb6PnxQ3o1Bx7I -QuIpZFxzuTP80p1p2DMDxEl+r/DCvT/wgBKX6ZMCgYAdw1bYMSK8tytyPFK5aGnz -asyGQ6GaVNuzqIJIpYCae6UEjUkiNQ/bsdnHBUey4jpv3CPmH8q4OlYQ/GtRnyvh -fLT2gQirYjRWrBev4EmKOLi9zjfQ9s/CxTtbekDjsgtcjZW85MWx6Rr2y+wK9gMi -2w2BuF9TFZaHFd8Hyvej1QKBgAoFbU6pbqYU3AOhrRE54p54ZrTOhqsCu8pEedY+ -DVeizfyweDLKdwDTx5dDFV7u7R80vmh99zscFvQ6VLzdLd4AFGk/xOwsCFyb5kKt -fAP7Xpvh2iH7FHw4w0e+Is3f1YNvWhIqEj5XbIEh9gHwLsqw4SupL+y+ousvnszB -nemvAoGBAJa7bYG8MMCFJ4OFAmkpgQzHSzq7dzOR6O4GKsQQhiZ/0nRK5l3sLcDO -9viuRfhRepJGbcQ/Hw0AVIRWU01y4mejbuxfUE/FgWBoBBvpbot2zfuJgeFAIvkY -iFsZwuxPQUFobTu2hj6gh0gOKj/LpNXHkZGbZ2zTXmK3GDYlf6bR ------END RSA PRIVATE KEY----- diff --git a/pkg/getter/testdata/empty-0.0.1.tgz b/pkg/getter/testdata/empty-0.0.1.tgz deleted file mode 100644 index 6c4c3d20597f344826ec27c18aeca1ae585392b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130 zcmb2|=3v-$Wpf+@^V *job.Spec.BackoffLimit { - c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName()) - return false - } - if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions { - c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName()) - return false - } - return true -} - -func (c *ReadyChecker) serviceReady(s *corev1.Service) bool { - // ExternalName Services are external to cluster so helm shouldn't be checking to see if they're 'ready' (i.e. have an IP Set) - if s.Spec.Type == corev1.ServiceTypeExternalName { - return true - } - - // Ensure that the service cluster IP is not empty - if s.Spec.ClusterIP == "" { - c.log("Service does not have cluster IP address: %s/%s", s.GetNamespace(), s.GetName()) - return false - } - - // This checks if the service has a LoadBalancer and that balancer has an Ingress defined - if s.Spec.Type == corev1.ServiceTypeLoadBalancer { - // do not wait when at least 1 external IP is set - if len(s.Spec.ExternalIPs) > 0 { - c.log("Service %s/%s has external IP addresses (%v), marking as ready", s.GetNamespace(), s.GetName(), s.Spec.ExternalIPs) - return true - } - - if s.Status.LoadBalancer.Ingress == nil { - c.log("Service does not have load balancer ingress IP address: %s/%s", s.GetNamespace(), s.GetName()) - return false - } - } - - return true -} - -func (c *ReadyChecker) volumeReady(v *corev1.PersistentVolumeClaim) bool { - if v.Status.Phase != corev1.ClaimBound { - c.log("PersistentVolumeClaim is not bound: %s/%s", v.GetNamespace(), v.GetName()) - return false - } - return true -} - -func (c *ReadyChecker) deploymentReady(rs *appsv1.ReplicaSet, dep *appsv1.Deployment) bool { - expectedReady := *dep.Spec.Replicas - deploymentutil.MaxUnavailable(*dep) - if !(rs.Status.ReadyReplicas >= expectedReady) { - c.log("Deployment is not ready: %s/%s. %d out of %d expected pods are ready", dep.Namespace, dep.Name, rs.Status.ReadyReplicas, expectedReady) - return false - } - return true -} - -func (c *ReadyChecker) daemonSetReady(ds *appsv1.DaemonSet) bool { - // If the update strategy is not a rolling update, there will be nothing to wait for - if ds.Spec.UpdateStrategy.Type != appsv1.RollingUpdateDaemonSetStrategyType { - return true - } - - // Make sure all the updated pods have been scheduled - if ds.Status.UpdatedNumberScheduled != ds.Status.DesiredNumberScheduled { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", ds.Namespace, ds.Name, ds.Status.UpdatedNumberScheduled, ds.Status.DesiredNumberScheduled) - return false - } - maxUnavailable, err := intstr.GetValueFromIntOrPercent(ds.Spec.UpdateStrategy.RollingUpdate.MaxUnavailable, int(ds.Status.DesiredNumberScheduled), true) - if err != nil { - // If for some reason the value is invalid, set max unavailable to the - // number of desired replicas. This is the same behavior as the - // `MaxUnavailable` function in deploymentutil - maxUnavailable = int(ds.Status.DesiredNumberScheduled) - } - - expectedReady := int(ds.Status.DesiredNumberScheduled) - maxUnavailable - if !(int(ds.Status.NumberReady) >= expectedReady) { - c.log("DaemonSet is not ready: %s/%s. %d out of %d expected pods are ready", ds.Namespace, ds.Name, ds.Status.NumberReady, expectedReady) - return false - } - return true -} - -// Because the v1 extensions API is not available on all supported k8s versions -// yet and because Go doesn't support generics, we need to have a duplicate -// function to support the v1beta1 types -func (c *ReadyChecker) crdBetaReady(crd apiextv1beta1.CustomResourceDefinition) bool { - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextv1beta1.Established: - if cond.Status == apiextv1beta1.ConditionTrue { - return true - } - case apiextv1beta1.NamesAccepted: - if cond.Status == apiextv1beta1.ConditionFalse { - // This indicates a naming conflict, but it's probably not the - // job of this function to fail because of that. Instead, - // we treat it as a success, since the process should be able to - // continue. - return true - } - } - } - return false -} - -func (c *ReadyChecker) crdReady(crd apiextv1.CustomResourceDefinition) bool { - for _, cond := range crd.Status.Conditions { - switch cond.Type { - case apiextv1.Established: - if cond.Status == apiextv1.ConditionTrue { - return true - } - case apiextv1.NamesAccepted: - if cond.Status == apiextv1.ConditionFalse { - // This indicates a naming conflict, but it's probably not the - // job of this function to fail because of that. Instead, - // we treat it as a success, since the process should be able to - // continue. - return true - } - } - } - return false -} - -func (c *ReadyChecker) statefulSetReady(sts *appsv1.StatefulSet) bool { - // If the update strategy is not a rolling update, there will be nothing to wait for - if sts.Spec.UpdateStrategy.Type != appsv1.RollingUpdateStatefulSetStrategyType { - c.log("StatefulSet skipped ready check: %s/%s. updateStrategy is %v", sts.Namespace, sts.Name, sts.Spec.UpdateStrategy.Type) - return true - } - - // Make sure the status is up-to-date with the StatefulSet changes - if sts.Status.ObservedGeneration < sts.Generation { - c.log("StatefulSet is not ready: %s/%s. update has not yet been observed", sts.Namespace, sts.Name) - return false - } - - // Dereference all the pointers because StatefulSets like them - var partition int - // 1 is the default for replicas if not set - var replicas = 1 - // For some reason, even if the update strategy is a rolling update, the - // actual rollingUpdate field can be nil. If it is, we can safely assume - // there is no partition value - if sts.Spec.UpdateStrategy.RollingUpdate != nil && sts.Spec.UpdateStrategy.RollingUpdate.Partition != nil { - partition = int(*sts.Spec.UpdateStrategy.RollingUpdate.Partition) - } - if sts.Spec.Replicas != nil { - replicas = int(*sts.Spec.Replicas) - } - - // Because an update strategy can use partitioning, we need to calculate the - // number of updated replicas we should have. For example, if the replicas - // is set to 3 and the partition is 2, we'd expect only one pod to be - // updated - expectedReplicas := replicas - partition - - // Make sure all the updated pods have been scheduled - if int(sts.Status.UpdatedReplicas) < expectedReplicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods have been scheduled", sts.Namespace, sts.Name, sts.Status.UpdatedReplicas, expectedReplicas) - return false - } - - if int(sts.Status.ReadyReplicas) != replicas { - c.log("StatefulSet is not ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) - return false - } - - if sts.Status.CurrentRevision != sts.Status.UpdateRevision { - c.log("StatefulSet is not ready: %s/%s. currentRevision %s does not yet match updateRevision %s", sts.Namespace, sts.Name, sts.Status.CurrentRevision, sts.Status.UpdateRevision) - return false - } - - c.log("StatefulSet is ready: %s/%s. %d out of %d expected pods are ready", sts.Namespace, sts.Name, sts.Status.ReadyReplicas, replicas) - return true -} - -func getPods(ctx context.Context, client kubernetes.Interface, namespace, selector string) ([]corev1.Pod, error) { - list, err := client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ - LabelSelector: selector, - }) - return list.Items, err -} diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go deleted file mode 100644 index 9fe20d8cb..000000000 --- a/pkg/kube/ready_test.go +++ /dev/null @@ -1,566 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - "testing" - - appsv1 "k8s.io/api/apps/v1" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes/fake" -) - -const defaultNamespace = metav1.NamespaceDefault - -func Test_ReadyChecker_deploymentReady(t *testing.T) { - type args struct { - rs *appsv1.ReplicaSet - dep *appsv1.Deployment - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "deployment is ready", - args: args{ - rs: newReplicaSet("foo", 1, 1), - dep: newDeployment("foo", 1, 1, 0), - }, - want: true, - }, - { - name: "deployment is not ready", - args: args{ - rs: newReplicaSet("foo", 0, 0), - dep: newDeployment("foo", 1, 1, 0), - }, - want: false, - }, - { - name: "deployment is ready when maxUnavailable is set", - args: args{ - rs: newReplicaSet("foo", 2, 1), - dep: newDeployment("foo", 2, 1, 1), - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.deploymentReady(tt.args.rs, tt.args.dep); got != tt.want { - t.Errorf("deploymentReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_daemonSetReady(t *testing.T) { - type args struct { - ds *appsv1.DaemonSet - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "daemonset is ready", - args: args{ - ds: newDaemonSet("foo", 0, 1, 1, 1), - }, - want: true, - }, - { - name: "daemonset is not ready", - args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 1), - }, - want: false, - }, - { - name: "daemonset pods have not been scheduled successfully", - args: args{ - ds: newDaemonSet("foo", 0, 0, 1, 0), - }, - want: false, - }, - { - name: "daemonset is ready when maxUnavailable is set", - args: args{ - ds: newDaemonSet("foo", 1, 1, 2, 2), - }, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.daemonSetReady(tt.args.ds); got != tt.want { - t.Errorf("daemonSetReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_statefulSetReady(t *testing.T) { - type args struct { - sts *appsv1.StatefulSet - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "statefulset is ready", - args: args{ - sts: newStatefulSet("foo", 1, 0, 1, 1), - }, - want: true, - }, - { - name: "statefulset is not ready", - args: args{ - sts: newStatefulSet("foo", 1, 0, 0, 1), - }, - want: false, - }, - { - name: "statefulset is ready when partition is specified", - args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 1), - }, - want: true, - }, - { - name: "statefulset is not ready when partition is set", - args: args{ - sts: newStatefulSet("foo", 2, 1, 1, 0), - }, - want: false, - }, - { - name: "statefulset is ready when partition is set and no change in template", - args: args{ - sts: newStatefulSet("foo", 2, 1, 2, 2), - }, - want: true, - }, - { - name: "statefulset is ready when partition is greater than replicas", - args: args{ - sts: newStatefulSet("foo", 1, 2, 1, 1), - }, - want: true, - }, - { - name: "statefulset is not ready when status of latest generation has not yet been observed", - args: args{ - sts: newStatefulSetWithNewGeneration("foo", 1, 0, 1, 1), - }, - want: false, - }, - { - name: "statefulset is not ready when current revision for current replicas does not match update revision for updated replicas", - args: args{ - sts: newStatefulSetWithUpdateRevision("foo", 1, 0, 1, 1, "foo-bbbbbbb"), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.statefulSetReady(tt.args.sts); got != tt.want { - t.Errorf("statefulSetReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_podsReadyForObject(t *testing.T) { - type args struct { - namespace string - obj runtime.Object - } - tests := []struct { - name string - args args - existPods []corev1.Pod - want bool - wantErr bool - }{ - { - name: "pods ready for a replicaset", - args: args{ - namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), - }, - existPods: []corev1.Pod{ - *newPodWithCondition("foo", corev1.ConditionTrue), - }, - want: true, - wantErr: false, - }, - { - name: "pods not ready for a replicaset", - args: args{ - namespace: defaultNamespace, - obj: newReplicaSet("foo", 1, 1), - }, - existPods: []corev1.Pod{ - *newPodWithCondition("foo", corev1.ConditionFalse), - }, - want: false, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - for _, pod := range tt.existPods { - if _, err := c.client.CoreV1().Pods(defaultNamespace).Create(context.TODO(), &pod, metav1.CreateOptions{}); err != nil { - t.Errorf("Failed to create Pod error: %v", err) - return - } - } - got, err := c.podsReadyForObject(context.TODO(), tt.args.namespace, tt.args.obj) - if (err != nil) != tt.wantErr { - t.Errorf("podsReadyForObject() error = %v, wantErr %v", err, tt.wantErr) - return - } - if got != tt.want { - t.Errorf("podsReadyForObject() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_jobReady(t *testing.T) { - type args struct { - job *batchv1.Job - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "job is completed", - args: args{job: newJob("foo", 1, intToInt32(1), 1, 0)}, - want: true, - }, - { - name: "job is incomplete", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 0)}, - want: false, - }, - { - name: "job is failed", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 1)}, - want: false, - }, - { - name: "job is completed with retry", - args: args{job: newJob("foo", 1, intToInt32(1), 1, 1)}, - want: true, - }, - { - name: "job is failed with retry", - args: args{job: newJob("foo", 1, intToInt32(1), 0, 2)}, - want: false, - }, - { - name: "job is completed single run", - args: args{job: newJob("foo", 0, intToInt32(1), 1, 0)}, - want: true, - }, - { - name: "job is failed single run", - args: args{job: newJob("foo", 0, intToInt32(1), 0, 1)}, - want: false, - }, - { - name: "job with null completions", - args: args{job: newJob("foo", 0, nil, 1, 0)}, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.jobReady(tt.args.job); got != tt.want { - t.Errorf("jobReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_ReadyChecker_volumeReady(t *testing.T) { - type args struct { - v *corev1.PersistentVolumeClaim - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "pvc is bound", - args: args{ - v: newPersistentVolumeClaim("foo", corev1.ClaimBound), - }, - want: true, - }, - { - name: "pvc is not ready", - args: args{ - v: newPersistentVolumeClaim("foo", corev1.ClaimPending), - }, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := NewReadyChecker(fake.NewSimpleClientset(), nil) - if got := c.volumeReady(tt.args.v); got != tt.want { - t.Errorf("volumeReady() = %v, want %v", got, tt.want) - } - }) - } -} - -func newDaemonSet(name string, maxUnavailable, numberReady, desiredNumberScheduled, updatedNumberScheduled int) *appsv1.DaemonSet { - return &appsv1.DaemonSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: appsv1.DaemonSetSpec{ - UpdateStrategy: appsv1.DaemonSetUpdateStrategy{ - Type: appsv1.RollingUpdateDaemonSetStrategyType, - RollingUpdate: &appsv1.RollingUpdateDaemonSet{ - MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(), - }, - }, - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: appsv1.DaemonSetStatus{ - DesiredNumberScheduled: int32(desiredNumberScheduled), - NumberReady: int32(numberReady), - UpdatedNumberScheduled: int32(updatedNumberScheduled), - }, - } -} - -func newStatefulSet(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { - return &appsv1.StatefulSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Generation: int64(1), - }, - Spec: appsv1.StatefulSetSpec{ - UpdateStrategy: appsv1.StatefulSetUpdateStrategy{ - Type: appsv1.RollingUpdateStatefulSetStrategyType, - RollingUpdate: &appsv1.RollingUpdateStatefulSetStrategy{ - Partition: intToInt32(partition), - }, - }, - Replicas: intToInt32(replicas), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: appsv1.StatefulSetStatus{ - ObservedGeneration: int64(1), - CurrentRevision: name + "-aaaaaaa", - UpdateRevision: name + "-aaaaaaa", - UpdatedReplicas: int32(updatedReplicas), - ReadyReplicas: int32(readyReplicas), - }, - } -} - -func newStatefulSetWithNewGeneration(name string, replicas, partition, readyReplicas, updatedReplicas int) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Generation++ - return ss -} - -func newStatefulSetWithUpdateRevision(name string, replicas, partition, readyReplicas, updatedReplicas int, updateRevision string) *appsv1.StatefulSet { - ss := newStatefulSet(name, replicas, partition, readyReplicas, updatedReplicas) - ss.Status.UpdateRevision = updateRevision - return ss -} - -func newDeployment(name string, replicas, maxSurge, maxUnavailable int) *appsv1.Deployment { - return &appsv1.Deployment{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: appsv1.DeploymentSpec{ - Strategy: appsv1.DeploymentStrategy{ - Type: appsv1.RollingUpdateDeploymentStrategyType, - RollingUpdate: &appsv1.RollingUpdateDeployment{ - MaxUnavailable: func() *intstr.IntOrString { i := intstr.FromInt(maxUnavailable); return &i }(), - MaxSurge: func() *intstr.IntOrString { i := intstr.FromInt(maxSurge); return &i }(), - }, - }, - Replicas: intToInt32(replicas), - Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"name": name}}, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - } -} - -func newReplicaSet(name string, replicas int, readyReplicas int) *appsv1.ReplicaSet { - d := newDeployment(name, replicas, 0, 0) - return &appsv1.ReplicaSet{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Labels: d.Spec.Selector.MatchLabels, - OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(d, d.GroupVersionKind())}, - }, - Spec: appsv1.ReplicaSetSpec{ - Selector: d.Spec.Selector, - Replicas: intToInt32(replicas), - Template: d.Spec.Template, - }, - Status: appsv1.ReplicaSetStatus{ - ReadyReplicas: int32(readyReplicas), - }, - } -} - -func newPodWithCondition(name string, podReadyCondition corev1.ConditionStatus) *corev1.Pod { - return &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - Status: corev1.PodStatus{ - Conditions: []corev1.PodCondition{ - { - Type: corev1.PodReady, - Status: podReadyCondition, - }, - }, - }, - } -} - -func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPhase) *corev1.PersistentVolumeClaim { - return &corev1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Status: corev1.PersistentVolumeClaimStatus{ - Phase: phase, - }, - } -} - -func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job { - return &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: defaultNamespace, - }, - Spec: batchv1.JobSpec{ - BackoffLimit: intToInt32(backoffLimit), - Completions: completions, - Template: corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{"name": name}, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Image: "nginx", - }, - }, - }, - }, - }, - Status: batchv1.JobStatus{ - Succeeded: int32(succeeded), - Failed: int32(failed), - }, - } -} - -func intToInt32(i int) *int32 { - i32 := int32(i) - return &i32 -} diff --git a/pkg/kube/resource.go b/pkg/kube/resource.go deleted file mode 100644 index ee8f83a25..000000000 --- a/pkg/kube/resource.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import "k8s.io/cli-runtime/pkg/resource" - -// ResourceList provides convenience methods for comparing collections of Infos. -type ResourceList []*resource.Info - -// Append adds an Info to the Result. -func (r *ResourceList) Append(val *resource.Info) { - *r = append(*r, val) -} - -// Visit implements resource.Visitor. -func (r ResourceList) Visit(fn resource.VisitorFunc) error { - for _, i := range r { - if err := fn(i, nil); err != nil { - return err - } - } - return nil -} - -// Filter returns a new Result with Infos that satisfy the predicate fn. -func (r ResourceList) Filter(fn func(*resource.Info) bool) ResourceList { - var result ResourceList - for _, i := range r { - if fn(i) { - result.Append(i) - } - } - return result -} - -// Get returns the Info from the result that matches the name and kind. -func (r ResourceList) Get(info *resource.Info) *resource.Info { - for _, i := range r { - if isMatchingInfo(i, info) { - return i - } - } - return nil -} - -// Contains checks to see if an object exists. -func (r ResourceList) Contains(info *resource.Info) bool { - for _, i := range r { - if isMatchingInfo(i, info) { - return true - } - } - return false -} - -// Difference will return a new Result with objects not contained in rs. -func (r ResourceList) Difference(rs ResourceList) ResourceList { - return r.Filter(func(info *resource.Info) bool { - return !rs.Contains(info) - }) -} - -// Intersect will return a new Result with objects contained in both Results. -func (r ResourceList) Intersect(rs ResourceList) ResourceList { - return r.Filter(rs.Contains) -} - -// isMatchingInfo returns true if infos match on Name and GroupVersionKind. -func isMatchingInfo(a, b *resource.Info) bool { - return a.Name == b.Name && a.Namespace == b.Namespace && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind -} diff --git a/pkg/kube/resource_policy.go b/pkg/kube/resource_policy.go deleted file mode 100644 index 5f391eb50..000000000 --- a/pkg/kube/resource_policy.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -// ResourcePolicyAnno is the annotation name for a resource policy -const ResourcePolicyAnno = "helm.sh/resource-policy" - -// KeepPolicy is the resource policy type for keep -// -// This resource policy type allows resources to skip being deleted -// during an uninstallRelease action. -const KeepPolicy = "keep" diff --git a/pkg/kube/resource_test.go b/pkg/kube/resource_test.go deleted file mode 100644 index 3c906ceca..000000000 --- a/pkg/kube/resource_test.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "testing" - - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/cli-runtime/pkg/resource" -) - -func TestResourceList(t *testing.T) { - mapping := &meta.RESTMapping{ - Resource: schema.GroupVersionResource{Group: "group", Version: "version", Resource: "pod"}, - } - - info := func(name string) *resource.Info { - return &resource.Info{Name: name, Mapping: mapping} - } - - var r1, r2 ResourceList - r1 = []*resource.Info{info("foo"), info("bar")} - r2 = []*resource.Info{info("bar")} - - if r1.Get(info("bar")).Mapping.Resource.Resource != "pod" { - t.Error("expected get pod") - } - - diff := r1.Difference(r2) - if len(diff) != 1 { - t.Error("expected 1 result") - } - - if !diff.Contains(info("foo")) { - t.Error("expected diff to return foo") - } - - inter := r1.Intersect(r2) - if len(inter) != 1 { - t.Error("expected 1 result") - } - - if !inter.Contains(info("bar")) { - t.Error("expected intersect to return bar") - } -} diff --git a/pkg/kube/result.go b/pkg/kube/result.go deleted file mode 100644 index c3e171c2e..000000000 --- a/pkg/kube/result.go +++ /dev/null @@ -1,28 +0,0 @@ -/* -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 kube - -// Result contains the information of created, updated, and deleted resources -// for various kube API calls along with helper methods for using those -// resources -type Result struct { - Created ResourceList - Updated ResourceList - Deleted ResourceList -} - -// If needed, we can add methods to the Result type for things like diffing diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go deleted file mode 100644 index fd01e9bc7..000000000 --- a/pkg/kube/wait.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "context" - "fmt" - "time" - - "github.com/pkg/errors" - "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" - "google.golang.org/grpc/codes" - - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/wait" -) - -type waiter struct { - c ReadyChecker - timeout time.Duration - log func(string, ...interface{}) -} - -// isServiceUnavailable helps figure out if the error is caused by etcd not being available -// see https://pkg.go.dev/go.etcd.io/etcd/api/v3/v3rpc/rpctypes for `codes.Unavailable` -// we use this to check if the etcdserver is not available we should retry in case -// this is a temporary situation -func isServiceUnavailable(err error) bool { - if err != nil { - err = rpctypes.Error(err) - if ev, ok := err.(rpctypes.EtcdError); ok { - if ev.Code() == codes.Unavailable { - return true - } - } - } - return false -} - -// waitForResources polls to get the current status of all pods, PVCs, Services and -// Jobs(optional) until all are ready or a timeout is reached -func (w *waiter) waitForResources(created ResourceList) error { - w.log("beginning wait for %d resources with timeout of %v", len(created), w.timeout) - - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) - defer cancel() - - return wait.PollImmediateUntil(2*time.Second, func() (bool, error) { - for _, v := range created { - ready, err := w.c.IsReady(ctx, v) - if !ready || err != nil { - if isServiceUnavailable(err) { - return false, nil - } - return false, err - } - } - return true, nil - }, ctx.Done()) -} - -// waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached -func (w *waiter) waitForDeletedResources(deleted ResourceList) error { - w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout) - - ctx, cancel := context.WithTimeout(context.Background(), w.timeout) - defer cancel() - - return wait.PollImmediateUntil(2*time.Second, func() (bool, error) { - for _, v := range deleted { - err := v.Get() - if err == nil || !apierrors.IsNotFound(err) { - if isServiceUnavailable(err) { - return false, nil - } - return false, err - } - } - return true, nil - }, ctx.Done()) -} - -// SelectorsForObject returns the pod label selector for a given object -// -// Modified version of https://github.com/kubernetes/kubernetes/blob/v1.14.1/pkg/kubectl/polymorphichelpers/helpers.go#L84 -func SelectorsForObject(object runtime.Object) (selector labels.Selector, err error) { - switch t := object.(type) { - case *extensionsv1beta1.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.ReplicaSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *corev1.ReplicationController: - selector = labels.SelectorFromSet(t.Spec.Selector) - case *appsv1.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta1.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.StatefulSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *extensionsv1beta1.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.DaemonSet: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *extensionsv1beta1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta1.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *appsv1beta2.Deployment: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *batchv1.Job: - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - case *corev1.Service: - if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { - return nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) - } - selector = labels.SelectorFromSet(t.Spec.Selector) - - default: - return nil, fmt.Errorf("selector for %T not implemented", object) - } - - return selector, errors.Wrap(err, "invalid label selector") -} diff --git a/pkg/kube/wait_test.go b/pkg/kube/wait_test.go deleted file mode 100644 index 5f18e49ce..000000000 --- a/pkg/kube/wait_test.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -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 kube // import "helm.sh/helm/v3/pkg/kube" - -import ( - "errors" - "testing" - - "go.etcd.io/etcd/api/v3/v3rpc/rpctypes" -) - -func Test_isServiceUnavailable(t *testing.T) { - tests := []struct { - err error - expect bool - }{ - {err: nil, expect: false}, - {err: errors.New("random error from somewhere"), expect: false}, - {err: rpctypes.ErrGRPCLeaderChanged, expect: true}, - {err: errors.New("etcdserver: leader changed"), expect: true}, - } - - for _, tt := range tests { - if isServiceUnavailable(tt.err) != tt.expect { - t.Errorf("failed test for %q (expect equal: %t)", tt.err, tt.expect) - } - } -} diff --git a/pkg/lint/lint.go b/pkg/lint/lint.go deleted file mode 100644 index 67e76bd3d..000000000 --- a/pkg/lint/lint.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 lint // import "helm.sh/helm/v3/pkg/lint" - -import ( - "path/filepath" - - "helm.sh/helm/v3/pkg/lint/rules" - "helm.sh/helm/v3/pkg/lint/support" -) - -// All runs all of the available linters on the given base directory. -func All(basedir string, values map[string]interface{}, namespace string, strict bool) support.Linter { - // Using abs path to get directory context - chartDir, _ := filepath.Abs(basedir) - - linter := support.Linter{ChartDir: chartDir} - rules.Chartfile(&linter) - rules.ValuesWithOverrides(&linter, values) - rules.Templates(&linter, values, namespace, strict) - rules.Dependencies(&linter) - return linter -} diff --git a/pkg/lint/lint_test.go b/pkg/lint/lint_test.go deleted file mode 100644 index 0e5d42391..000000000 --- a/pkg/lint/lint_test.go +++ /dev/null @@ -1,153 +0,0 @@ -/* -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 lint - -import ( - "strings" - "testing" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -var values map[string]interface{} - -const namespace = "testNamespace" -const strict = false - -const badChartDir = "rules/testdata/badchartfile" -const badValuesFileDir = "rules/testdata/badvaluesfile" -const badYamlFileDir = "rules/testdata/albatross" -const goodChartDir = "rules/testdata/goodone" -const subChartValuesDir = "rules/testdata/withsubchart" - -func TestBadChart(t *testing.T) { - m := All(badChartDir, values, namespace, strict).Messages - if len(m) != 8 { - t.Errorf("Number of errors %v", len(m)) - t.Errorf("All didn't fail with expected errors, got %#v", m) - } - // There should be one INFO, 2 WARNINGs and 2 ERROR messages, check for them - var i, w, e, e2, e3, e4, e5, e6 bool - for _, msg := range m { - if msg.Severity == support.InfoSev { - if strings.Contains(msg.Err.Error(), "icon is recommended") { - i = true - } - } - if msg.Severity == support.WarningSev { - if strings.Contains(msg.Err.Error(), "directory not found") { - w = true - } - } - if msg.Severity == support.ErrorSev { - if strings.Contains(msg.Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - e = true - } - if strings.Contains(msg.Err.Error(), "name is required") { - e2 = true - } - - if strings.Contains(msg.Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { - e3 = true - } - - if strings.Contains(msg.Err.Error(), "chart type is not valid in apiVersion") { - e4 = true - } - - if strings.Contains(msg.Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - e5 = true - } - // This comes from the dependency check, which loads dependency info from the Chart.yaml - if strings.Contains(msg.Err.Error(), "unable to load chart") { - e6 = true - } - } - } - if !e || !e2 || !e3 || !e4 || !e5 || !w || !i || !e6 { - t.Errorf("Didn't find all the expected errors, got %#v", m) - } -} - -func TestInvalidYaml(t *testing.T) { - m := All(badYamlFileDir, values, namespace, strict).Messages - if len(m) != 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("All didn't have the error for deliberateSyntaxError") - } -} - -func TestBadValues(t *testing.T) { - m := All(badValuesFileDir, values, namespace, strict).Messages - if len(m) < 1 { - t.Fatalf("All didn't fail with expected errors, got %#v", m) - } - if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") { - t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err) - } -} - -func TestGoodChart(t *testing.T) { - m := All(goodChartDir, values, namespace, strict).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") - for i, msg := range m { - t.Logf("Message %d: %s", i, msg) - } - } -} - -// TestHelmCreateChart tests that a `helm create` always passes a `helm lint` test. -// -// See https://github.com/helm/helm/issues/7923 -func TestHelmCreateChart(t *testing.T) { - dir := t.TempDir() - - createdChart, err := chartutil.Create("testhelmcreatepasseslint", dir) - if err != nil { - t.Error(err) - // Fatal is bad because of the defer. - return - } - - // Note: we test with strict=true here, even though others have - // strict = false. - m := All(createdChart, values, namespace, true).Messages - if ll := len(m); ll != 1 { - t.Errorf("All should have had exactly 1 error. Got %d", ll) - for i, msg := range m { - t.Logf("Message %d: %s", i, msg.Error()) - } - } else if msg := m[0].Err.Error(); !strings.Contains(msg, "icon is recommended") { - t.Errorf("Unexpected lint error: %s", msg) - } -} - -// lint ignores import-values -// See https://github.com/helm/helm/issues/9658 -func TestSubChartValuesChart(t *testing.T) { - m := All(subChartValuesDir, values, namespace, strict).Messages - if len(m) != 0 { - t.Error("All returned linter messages when it shouldn't have") - for i, msg := range m { - t.Logf("Message %d: %s", i, msg) - } - } -} diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go deleted file mode 100644 index b49f2cec0..000000000 --- a/pkg/lint/rules/chartfile.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - - "github.com/Masterminds/semver/v3" - "github.com/asaskevich/govalidator" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Chartfile runs a set of linter rules related to Chart.yaml file -func Chartfile(linter *support.Linter) { - chartFileName := "Chart.yaml" - chartPath := filepath.Join(linter.ChartDir, chartFileName) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlNotDirectory(chartPath)) - - chartFile, err := chartutil.LoadChartfile(chartPath) - validChartFile := linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartYamlFormat(err)) - - // Guard clause. Following linter rules require a parsable ChartFile - if !validChartFile { - return - } - - // type check for Chart.yaml . ignoring error as any parse - // errors would already be caught in the above load function - chartFileForTypeCheck, _ := loadChartFileForTypeCheck(chartPath) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) - - // Chart metadata - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAPIVersion(chartFile)) - - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersionType(chartFileForTypeCheck)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartAppVersionType(chartFileForTypeCheck)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartMaintainer(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartSources(chartFile)) - linter.RunLinterRule(support.InfoSev, chartFileName, validateChartIconPresence(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartIconURL(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartType(chartFile)) - linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartDependencies(chartFile)) -} - -func validateChartVersionType(data map[string]interface{}) error { - return isStringValue(data, "version") -} - -func validateChartAppVersionType(data map[string]interface{}) error { - return isStringValue(data, "appVersion") -} - -func isStringValue(data map[string]interface{}, key string) error { - value, ok := data[key] - if !ok { - return nil - } - valueType := fmt.Sprintf("%T", value) - if valueType != "string" { - return errors.Errorf("%s should be of type string but it's of type %s", key, valueType) - } - return nil -} - -func validateChartYamlNotDirectory(chartPath string) error { - fi, err := os.Stat(chartPath) - - if err == nil && fi.IsDir() { - return errors.New("should be a file, not a directory") - } - return nil -} - -func validateChartYamlFormat(chartFileError error) error { - if chartFileError != nil { - return errors.Errorf("unable to parse YAML\n\t%s", chartFileError.Error()) - } - return nil -} - -func validateChartName(cf *chart.Metadata) error { - if cf.Name == "" { - return errors.New("name is required") - } - return nil -} - -func validateChartAPIVersion(cf *chart.Metadata) error { - if cf.APIVersion == "" { - return errors.New("apiVersion is required. The value must be either \"v1\" or \"v2\"") - } - - if cf.APIVersion != chart.APIVersionV1 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("apiVersion '%s' is not valid. The value must be either \"v1\" or \"v2\"", cf.APIVersion) - } - - return nil -} - -func validateChartVersion(cf *chart.Metadata) error { - if cf.Version == "" { - return errors.New("version is required") - } - - version, err := semver.NewVersion(cf.Version) - - if err != nil { - return errors.Errorf("version '%s' is not a valid SemVer", cf.Version) - } - - c, err := semver.NewConstraint(">0.0.0-0") - if err != nil { - return err - } - valid, msg := c.Validate(version) - - if !valid && len(msg) > 0 { - return errors.Errorf("version %v", msg[0]) - } - - return nil -} - -func validateChartMaintainer(cf *chart.Metadata) error { - for _, maintainer := range cf.Maintainers { - if maintainer.Name == "" { - return errors.New("each maintainer requires a name") - } else if maintainer.Email != "" && !govalidator.IsEmail(maintainer.Email) { - return errors.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) - } else if maintainer.URL != "" && !govalidator.IsURL(maintainer.URL) { - return errors.Errorf("invalid url '%s' for maintainer '%s'", maintainer.URL, maintainer.Name) - } - } - return nil -} - -func validateChartSources(cf *chart.Metadata) error { - for _, source := range cf.Sources { - if source == "" || !govalidator.IsRequestURL(source) { - return errors.Errorf("invalid source URL '%s'", source) - } - } - return nil -} - -func validateChartIconPresence(cf *chart.Metadata) error { - if cf.Icon == "" { - return errors.New("icon is recommended") - } - return nil -} - -func validateChartIconURL(cf *chart.Metadata) error { - if cf.Icon != "" && !govalidator.IsRequestURL(cf.Icon) { - return errors.Errorf("invalid icon URL '%s'", cf.Icon) - } - return nil -} - -func validateChartDependencies(cf *chart.Metadata) error { - if len(cf.Dependencies) > 0 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("dependencies are not valid in the Chart file with apiVersion '%s'. They are valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) - } - return nil -} - -func validateChartType(cf *chart.Metadata) error { - if len(cf.Type) > 0 && cf.APIVersion != chart.APIVersionV2 { - return fmt.Errorf("chart type is not valid in apiVersion '%s'. It is valid in apiVersion '%s'", cf.APIVersion, chart.APIVersionV2) - } - return nil -} - -// loadChartFileForTypeCheck loads the Chart.yaml -// in a generic form of a map[string]interface{}, so that the type -// of the values can be checked -func loadChartFileForTypeCheck(filename string) (map[string]interface{}, error) { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - y := make(map[string]interface{}) - err = yaml.Unmarshal(b, &y) - return y, err -} diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go deleted file mode 100644 index 087cda047..000000000 --- a/pkg/lint/rules/chartfile_test.go +++ /dev/null @@ -1,247 +0,0 @@ -/* -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 rules - -import ( - "os" - "path/filepath" - "strings" - "testing" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -const ( - badChartDir = "testdata/badchartfile" - anotherBadChartDir = "testdata/anotherbadchartfile" -) - -var ( - badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") - nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") -) - -var badChart, _ = chartutil.LoadChartfile(badChartFilePath) - -// Validation functions Test -func TestValidateChartYamlNotDirectory(t *testing.T) { - _ = os.Mkdir(nonExistingChartFilePath, os.ModePerm) - defer os.Remove(nonExistingChartFilePath) - - err := validateChartYamlNotDirectory(nonExistingChartFilePath) - if err == nil { - t.Errorf("validateChartYamlNotDirectory to return a linter error, got no error") - } -} - -func TestValidateChartYamlFormat(t *testing.T) { - err := validateChartYamlFormat(errors.New("Read error")) - if err == nil { - t.Errorf("validateChartYamlFormat to return a linter error, got no error") - } - - err = validateChartYamlFormat(nil) - if err != nil { - t.Errorf("validateChartYamlFormat to return no error, got a linter error") - } -} - -func TestValidateChartName(t *testing.T) { - err := validateChartName(badChart) - if err == nil { - t.Errorf("validateChartName to return a linter error, got no error") - } -} - -func TestValidateChartVersion(t *testing.T) { - var failTest = []struct { - Version string - ErrorMsg string - }{ - {"", "version is required"}, - {"1.2.3.4", "version '1.2.3.4' is not a valid SemVer"}, - {"waps", "'waps' is not a valid SemVer"}, - {"-3", "'-3' is not a valid SemVer"}, - } - - var successTest = []string{"0.0.1", "0.0.1+build", "0.0.1-beta"} - - for _, test := range failTest { - badChart.Version = test.Version - err := validateChartVersion(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartVersion(%s) to return \"%s\", got no error", test.Version, test.ErrorMsg) - } - } - - for _, version := range successTest { - badChart.Version = version - err := validateChartVersion(badChart) - if err != nil { - t.Errorf("validateChartVersion(%s) to return no error, got a linter error", version) - } - } -} - -func TestValidateChartMaintainer(t *testing.T) { - var failTest = []struct { - Name string - Email string - ErrorMsg string - }{ - {"", "", "each maintainer requires a name"}, - {"", "test@test.com", "each maintainer requires a name"}, - {"John Snow", "wrongFormatEmail.com", "invalid email"}, - } - - var successTest = []struct { - Name string - Email string - }{ - {"John Snow", ""}, - {"John Snow", "john@winterfell.com"}, - } - - for _, test := range failTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err == nil || !strings.Contains(err.Error(), test.ErrorMsg) { - t.Errorf("validateChartMaintainer(%s, %s) to return \"%s\", got no error", test.Name, test.Email, test.ErrorMsg) - } - } - - for _, test := range successTest { - badChart.Maintainers = []*chart.Maintainer{{Name: test.Name, Email: test.Email}} - err := validateChartMaintainer(badChart) - if err != nil { - t.Errorf("validateChartMaintainer(%s, %s) to return no error, got %s", test.Name, test.Email, err.Error()) - } - } -} - -func TestValidateChartSources(t *testing.T) { - var failTest = []string{"", "RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish"} - for _, test := range failTest { - badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid source URL") { - t.Errorf("validateChartSources(%s) to return \"invalid source URL\", got no error", test) - } - } - - for _, test := range successTest { - badChart.Sources = []string{test} - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartSources(%s) to return no error, got %s", test, err.Error()) - } - } -} - -func TestValidateChartIconPresence(t *testing.T) { - err := validateChartIconPresence(badChart) - if err == nil { - t.Errorf("validateChartIconPresence to return a linter error, got no error") - } -} - -func TestValidateChartIconURL(t *testing.T) { - var failTest = []string{"RiverRun", "john@winterfell", "riverrun.io"} - var successTest = []string{"http://riverrun.io", "https://riverrun.io", "https://riverrun.io/blackfish.png"} - for _, test := range failTest { - badChart.Icon = test - err := validateChartIconURL(badChart) - if err == nil || !strings.Contains(err.Error(), "invalid icon URL") { - t.Errorf("validateChartIconURL(%s) to return \"invalid icon URL\", got no error", test) - } - } - - for _, test := range successTest { - badChart.Icon = test - err := validateChartSources(badChart) - if err != nil { - t.Errorf("validateChartIconURL(%s) to return no error, got %s", test, err.Error()) - } - } -} - -func TestChartfile(t *testing.T) { - t.Run("Chart.yaml basic validity issues", func(t *testing.T) { - linter := support.Linter{ChartDir: badChartDir} - Chartfile(&linter) - msgs := linter.Messages - expectedNumberOfErrorMessages := 6 - - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "name is required") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "apiVersion is required. The value must be either \"v1\" or \"v2\"") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "version '0.0.0.0' is not a valid SemVer") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - - if !strings.Contains(msgs[3].Err.Error(), "icon is recommended") { - t.Errorf("Unexpected message 3: %s", msgs[3].Err) - } - - if !strings.Contains(msgs[4].Err.Error(), "chart type is not valid in apiVersion") { - t.Errorf("Unexpected message 4: %s", msgs[4].Err) - } - - if !strings.Contains(msgs[5].Err.Error(), "dependencies are not valid in the Chart file with apiVersion") { - t.Errorf("Unexpected message 5: %s", msgs[5].Err) - } - }) - - t.Run("Chart.yaml validity issues due to type mismatch", func(t *testing.T) { - linter := support.Linter{ChartDir: anotherBadChartDir} - Chartfile(&linter) - msgs := linter.Messages - expectedNumberOfErrorMessages := 3 - - if len(msgs) != expectedNumberOfErrorMessages { - t.Errorf("Expected %d errors, got %d", expectedNumberOfErrorMessages, len(msgs)) - return - } - - if !strings.Contains(msgs[0].Err.Error(), "version should be of type string") { - t.Errorf("Unexpected message 0: %s", msgs[0].Err) - } - - if !strings.Contains(msgs[1].Err.Error(), "version '7.2445e+06' is not a valid SemVer") { - t.Errorf("Unexpected message 1: %s", msgs[1].Err) - } - - if !strings.Contains(msgs[2].Err.Error(), "appVersion should be of type string") { - t.Errorf("Unexpected message 2: %s", msgs[2].Err) - } - }) -} diff --git a/pkg/lint/rules/dependencies.go b/pkg/lint/rules/dependencies.go deleted file mode 100644 index abecd1feb..000000000 --- a/pkg/lint/rules/dependencies.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Dependencies runs lints against a chart's dependencies -// -// See https://github.com/helm/helm/issues/7910 -func Dependencies(linter *support.Linter) { - c, err := loader.LoadDir(linter.ChartDir) - if !linter.RunLinterRule(support.ErrorSev, "", validateChartFormat(err)) { - return - } - - linter.RunLinterRule(support.ErrorSev, linter.ChartDir, validateDependencyInMetadata(c)) - linter.RunLinterRule(support.WarningSev, linter.ChartDir, validateDependencyInChartsDir(c)) -} - -func validateChartFormat(chartError error) error { - if chartError != nil { - return errors.Errorf("unable to load chart\n\t%s", chartError) - } - return nil -} - -func validateDependencyInChartsDir(c *chart.Chart) (err error) { - dependencies := map[string]struct{}{} - missing := []string{} - for _, dep := range c.Dependencies() { - dependencies[dep.Metadata.Name] = struct{}{} - } - for _, dep := range c.Metadata.Dependencies { - if _, ok := dependencies[dep.Name]; !ok { - missing = append(missing, dep.Name) - } - } - if len(missing) > 0 { - err = fmt.Errorf("chart directory is missing these dependencies: %s", strings.Join(missing, ",")) - } - return err -} - -func validateDependencyInMetadata(c *chart.Chart) (err error) { - dependencies := map[string]struct{}{} - missing := []string{} - for _, dep := range c.Metadata.Dependencies { - dependencies[dep.Name] = struct{}{} - } - for _, dep := range c.Dependencies() { - if _, ok := dependencies[dep.Metadata.Name]; !ok { - missing = append(missing, dep.Metadata.Name) - } - } - if len(missing) > 0 { - err = fmt.Errorf("chart metadata is missing these dependencies: %s", strings.Join(missing, ",")) - } - return err -} diff --git a/pkg/lint/rules/dependencies_test.go b/pkg/lint/rules/dependencies_test.go deleted file mode 100644 index 075190eac..000000000 --- a/pkg/lint/rules/dependencies_test.go +++ /dev/null @@ -1,99 +0,0 @@ -/* -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 rules - -import ( - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -func chartWithBadDependencies() chart.Chart { - badChartDeps := chart.Chart{ - Metadata: &chart.Metadata{ - Name: "badchart", - Version: "0.1.0", - APIVersion: "v2", - Dependencies: []*chart.Dependency{ - { - Name: "sub2", - }, - { - Name: "sub3", - }, - }, - }, - } - - badChartDeps.SetDependencies( - &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "sub1", - Version: "0.1.0", - APIVersion: "v2", - }, - }, - &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "sub2", - Version: "0.1.0", - APIVersion: "v2", - }, - }, - ) - return badChartDeps -} - -func TestValidateDependencyInChartsDir(t *testing.T) { - c := chartWithBadDependencies() - - if err := validateDependencyInChartsDir(&c); err == nil { - t.Error("chart should have been flagged for missing deps in chart directory") - } -} - -func TestValidateDependencyInMetadata(t *testing.T) { - c := chartWithBadDependencies() - - if err := validateDependencyInMetadata(&c); err == nil { - t.Errorf("chart should have been flagged for missing deps in chart metadata") - } -} - -func TestDependencies(t *testing.T) { - tmp := ensure.TempDir(t) - defer os.RemoveAll(tmp) - - c := chartWithBadDependencies() - err := chartutil.SaveDir(&c, tmp) - if err != nil { - t.Fatal(err) - } - linter := support.Linter{ChartDir: filepath.Join(tmp, c.Metadata.Name)} - - Dependencies(&linter) - if l := len(linter.Messages); l != 2 { - t.Errorf("expected 2 linter errors for bad chart dependencies. Got %d.", l) - for i, msg := range linter.Messages { - t.Logf("Message: %d, Error: %#v", i, msg) - } - } -} diff --git a/pkg/lint/rules/deprecations.go b/pkg/lint/rules/deprecations.go deleted file mode 100644 index ce19b91d5..000000000 --- a/pkg/lint/rules/deprecations.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import ( - "fmt" - "strconv" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/endpoints/deprecation" - kscheme "k8s.io/client-go/kubernetes/scheme" -) - -var ( - // This should be set in the Makefile based on the version of client-go being imported. - // These constants will be overwritten with LDFLAGS. The version components must be - // strings in order for LDFLAGS to set them. - k8sVersionMajor = "1" - k8sVersionMinor = "20" -) - -// deprecatedAPIError indicates than an API is deprecated in Kubernetes -type deprecatedAPIError struct { - Deprecated string - Message string -} - -func (e deprecatedAPIError) Error() string { - msg := e.Message - return msg -} - -func validateNoDeprecations(resource *K8sYamlStruct) error { - // if `resource` does not have an APIVersion or Kind, we cannot test it for deprecation - if resource.APIVersion == "" { - return nil - } - if resource.Kind == "" { - return nil - } - - runtimeObject, err := resourceToRuntimeObject(resource) - if err != nil { - // do not error for non-kubernetes resources - if runtime.IsNotRegisteredError(err) { - return nil - } - return err - } - maj, err := strconv.Atoi(k8sVersionMajor) - if err != nil { - return err - } - min, err := strconv.Atoi(k8sVersionMinor) - if err != nil { - return err - } - - if !deprecation.IsDeprecated(runtimeObject, maj, min) { - return nil - } - gvk := fmt.Sprintf("%s %s", resource.APIVersion, resource.Kind) - return deprecatedAPIError{ - Deprecated: gvk, - Message: deprecation.WarningMessage(runtimeObject), - } -} - -func resourceToRuntimeObject(resource *K8sYamlStruct) (runtime.Object, error) { - scheme := runtime.NewScheme() - kscheme.AddToScheme(scheme) - - gvk := schema.FromAPIVersionAndKind(resource.APIVersion, resource.Kind) - out, err := scheme.New(gvk) - if err != nil { - return nil, err - } - out.GetObjectKind().SetGroupVersionKind(gvk) - return out, nil -} diff --git a/pkg/lint/rules/deprecations_test.go b/pkg/lint/rules/deprecations_test.go deleted file mode 100644 index 96e072d14..000000000 --- a/pkg/lint/rules/deprecations_test.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -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 rules // import "helm.sh/helm/v3/pkg/lint/rules" - -import "testing" - -func TestValidateNoDeprecations(t *testing.T) { - deprecated := &K8sYamlStruct{ - APIVersion: "extensions/v1beta1", - Kind: "Deployment", - } - err := validateNoDeprecations(deprecated) - if err == nil { - t.Fatal("Expected deprecated extension to be flagged") - } - depErr := err.(deprecatedAPIError) - if depErr.Message == "" { - t.Fatalf("Expected error message to be non-blank: %v", err) - } - - if err := validateNoDeprecations(&K8sYamlStruct{ - APIVersion: "v1", - Kind: "Pod", - }); err != nil { - t.Errorf("Expected a v1 Pod to not be deprecated") - } -} diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go deleted file mode 100644 index 61425f92e..000000000 --- a/pkg/lint/rules/template.go +++ /dev/null @@ -1,339 +0,0 @@ -/* -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 rules - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/api/validation" - apipath "k8s.io/apimachinery/pkg/api/validation/path" - "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/apimachinery/pkg/util/yaml" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/engine" - "helm.sh/helm/v3/pkg/lint/support" -) - -var ( - crdHookSearch = regexp.MustCompile(`"?helm\.sh/hook"?:\s+crd-install`) - releaseTimeSearch = regexp.MustCompile(`\.Release\.Time`) -) - -// Templates lints the templates in the Linter. -func Templates(linter *support.Linter, values map[string]interface{}, namespace string, strict bool) { - fpath := "templates/" - templatesPath := filepath.Join(linter.ChartDir, fpath) - - templatesDirExist := linter.RunLinterRule(support.WarningSev, fpath, validateTemplatesDir(templatesPath)) - - // Templates directory is optional for now - if !templatesDirExist { - return - } - - // Load chart and parse templates - chart, err := loader.Load(linter.ChartDir) - - chartLoaded := linter.RunLinterRule(support.ErrorSev, fpath, err) - - if !chartLoaded { - return - } - - options := chartutil.ReleaseOptions{ - Name: "test-release", - Namespace: namespace, - } - - // lint ignores import-values - // See https://github.com/helm/helm/issues/9658 - if err := chartutil.ProcessDependencies(chart, values); err != nil { - return - } - - cvals, err := chartutil.CoalesceValues(chart, values) - if err != nil { - return - } - valuesToRender, err := chartutil.ToRenderValues(chart, cvals, options, nil) - if err != nil { - linter.RunLinterRule(support.ErrorSev, fpath, err) - return - } - var e engine.Engine - e.LintMode = true - renderedContentMap, err := e.Render(chart, valuesToRender) - - renderOk := linter.RunLinterRule(support.ErrorSev, fpath, err) - - if !renderOk { - return - } - - /* Iterate over all the templates to check: - - It is a .yaml file - - All the values in the template file is defined - - {{}} include | quote - - Generated content is a valid Yaml file - - Metadata.Namespace is not set - */ - for _, template := range chart.Templates { - fileName, data := template.Name, template.Data - fpath = fileName - - linter.RunLinterRule(support.ErrorSev, fpath, validateAllowedExtension(fileName)) - // These are v3 specific checks to make sure and warn people if their - // chart is not compatible with v3 - linter.RunLinterRule(support.WarningSev, fpath, validateNoCRDHooks(data)) - linter.RunLinterRule(support.ErrorSev, fpath, validateNoReleaseTime(data)) - - // We only apply the following lint rules to yaml files - if filepath.Ext(fileName) != ".yaml" || filepath.Ext(fileName) == ".yml" { - continue - } - - // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1463 - // Check that all the templates have a matching value - // linter.RunLinterRule(support.WarningSev, fpath, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) - - // NOTE: disabled for now, Refs https://github.com/helm/helm/issues/1037 - // linter.RunLinterRule(support.WarningSev, fpath, validateQuotes(string(preExecutedTemplate))) - - renderedContent := renderedContentMap[path.Join(chart.Name(), fileName)] - if strings.TrimSpace(renderedContent) != "" { - linter.RunLinterRule(support.WarningSev, fpath, validateTopIndentLevel(renderedContent)) - - decoder := yaml.NewYAMLOrJSONDecoder(strings.NewReader(renderedContent), 4096) - - // Lint all resources if the file contains multiple documents separated by --- - for { - // Even though K8sYamlStruct only defines a few fields, an error in any other - // key will be raised as well - var yamlStruct *K8sYamlStruct - - err := decoder.Decode(&yamlStruct) - if err == io.EOF { - break - } - - // If YAML linting fails, we sill progress. So we don't capture the returned state - // on this linter run. - linter.RunLinterRule(support.ErrorSev, fpath, validateYamlContent(err)) - - if yamlStruct != nil { - // NOTE: set to warnings to allow users to support out-of-date kubernetes - // Refs https://github.com/helm/helm/issues/8596 - linter.RunLinterRule(support.WarningSev, fpath, validateMetadataName(yamlStruct)) - linter.RunLinterRule(support.WarningSev, fpath, validateNoDeprecations(yamlStruct)) - - linter.RunLinterRule(support.ErrorSev, fpath, validateMatchSelector(yamlStruct, renderedContent)) - linter.RunLinterRule(support.ErrorSev, fpath, validateListAnnotations(yamlStruct, renderedContent)) - } - } - } - } -} - -// validateTopIndentLevel checks that the content does not start with an indent level > 0. -// -// This error can occur when a template accidentally inserts space. It can cause -// unpredictable errors depending on whether the text is normalized before being passed -// into the YAML parser. So we trap it here. -// -// See https://github.com/helm/helm/issues/8467 -func validateTopIndentLevel(content string) error { - // Read lines until we get to a non-empty one - scanner := bufio.NewScanner(bytes.NewBufferString(content)) - for scanner.Scan() { - line := scanner.Text() - // If line is empty, skip - if strings.TrimSpace(line) == "" { - continue - } - // If it starts with one or more spaces, this is an error - if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") { - return fmt.Errorf("document starts with an illegal indent: %q, which may cause parsing problems", line) - } - // Any other condition passes. - return nil - } - return scanner.Err() -} - -// Validation functions -func validateTemplatesDir(templatesPath string) error { - if fi, err := os.Stat(templatesPath); err != nil { - return errors.New("directory not found") - } else if !fi.IsDir() { - return errors.New("not a directory") - } - return nil -} - -func validateAllowedExtension(fileName string) error { - ext := filepath.Ext(fileName) - validExtensions := []string{".yaml", ".yml", ".tpl", ".txt"} - - for _, b := range validExtensions { - if b == ext { - return nil - } - } - - return errors.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) -} - -func validateYamlContent(err error) error { - return errors.Wrap(err, "unable to parse YAML") -} - -// validateMetadataName uses the correct validation function for the object -// Kind, or if not set, defaults to the standard definition of a subdomain in -// DNS (RFC 1123), used by most resources. -func validateMetadataName(obj *K8sYamlStruct) error { - fn := validateMetadataNameFunc(obj) - allErrs := field.ErrorList{} - for _, msg := range fn(obj.Metadata.Name, false) { - allErrs = append(allErrs, field.Invalid(field.NewPath("metadata").Child("name"), obj.Metadata.Name, msg)) - } - if len(allErrs) > 0 { - return errors.Wrapf(allErrs.ToAggregate(), "object name does not conform to Kubernetes naming requirements: %q", obj.Metadata.Name) - } - return nil -} - -// validateMetadataNameFunc will return a name validation function for the -// object kind, if defined below. -// -// Rules should match those set in the various api validations: -// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/core/validation/validation.go#L205-L274 -// https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/apps/validation/validation.go#L39 -// ... -// -// Implementing here to avoid importing k/k. -// -// If no mapping is defined, returns NameIsDNSSubdomain. This is used by object -// kinds that don't have special requirements, so is the most likely to work if -// new kinds are added. -func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc { - switch strings.ToLower(obj.Kind) { - case "pod", "node", "secret", "endpoints", "resourcequota", // core - "controllerrevision", "daemonset", "deployment", "replicaset", "statefulset", // apps - "autoscaler", // autoscaler - "cronjob", "job", // batch - "lease", // coordination - "endpointslice", // discovery - "networkpolicy", "ingress", // networking - "podsecuritypolicy", // policy - "priorityclass", // scheduling - "podpreset", // settings - "storageclass", "volumeattachment", "csinode": // storage - return validation.NameIsDNSSubdomain - case "service": - return validation.NameIsDNS1035Label - case "namespace": - return validation.ValidateNamespaceName - case "serviceaccount": - return validation.ValidateServiceAccountName - case "certificatesigningrequest": - // No validation. - // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140 - return func(name string, prefix bool) []string { return nil } - case "role", "clusterrole", "rolebinding", "clusterrolebinding": - // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 - return func(name string, prefix bool) []string { - return apipath.IsValidPathSegmentName(name) - } - default: - return validation.NameIsDNSSubdomain - } -} - -func validateNoCRDHooks(manifest []byte) error { - if crdHookSearch.Match(manifest) { - return errors.New("manifest is a crd-install hook. This hook is no longer supported in v3 and all CRDs should also exist the crds/ directory at the top level of the chart") - } - return nil -} - -func validateNoReleaseTime(manifest []byte) error { - if releaseTimeSearch.Match(manifest) { - return errors.New(".Release.Time has been removed in v3, please replace with the `now` function in your templates") - } - return nil -} - -// validateMatchSelector ensures that template specs have a selector declared. -// See https://github.com/helm/helm/issues/1990 -func validateMatchSelector(yamlStruct *K8sYamlStruct, manifest string) error { - switch yamlStruct.Kind { - case "Deployment", "ReplicaSet", "DaemonSet", "StatefulSet": - // verify that matchLabels or matchExpressions is present - if !(strings.Contains(manifest, "matchLabels") || strings.Contains(manifest, "matchExpressions")) { - return fmt.Errorf("a %s must contain matchLabels or matchExpressions, and %q does not", yamlStruct.Kind, yamlStruct.Metadata.Name) - } - } - return nil -} -func validateListAnnotations(yamlStruct *K8sYamlStruct, manifest string) error { - if yamlStruct.Kind == "List" { - m := struct { - Items []struct { - Metadata struct { - Annotations map[string]string - } - } - }{} - - if err := yaml.Unmarshal([]byte(manifest), &m); err != nil { - return validateYamlContent(err) - } - - for _, i := range m.Items { - if _, ok := i.Metadata.Annotations["helm.sh/resource-policy"]; ok { - return errors.New("Annotation 'helm.sh/resource-policy' within List objects are ignored") - } - } - } - return nil -} - -// K8sYamlStruct stubs a Kubernetes YAML file. -// -// DEPRECATED: In Helm 4, this will be made a private type, as it is for use only within -// the rules package. -type K8sYamlStruct struct { - APIVersion string `json:"apiVersion"` - Kind string - Metadata k8sYamlMetadata -} - -type k8sYamlMetadata struct { - Namespace string - Name string -} diff --git a/pkg/lint/rules/template_test.go b/pkg/lint/rules/template_test.go deleted file mode 100644 index f3aa641f2..000000000 --- a/pkg/lint/rules/template_test.go +++ /dev/null @@ -1,464 +0,0 @@ -/* -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 rules - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -const templateTestBasedir = "./testdata/albatross" - -func TestValidateAllowedExtension(t *testing.T) { - var failTest = []string{"/foo", "/test.toml"} - for _, test := range failTest { - err := validateAllowedExtension(test) - if err == nil || !strings.Contains(err.Error(), "Valid extensions are .yaml, .yml, .tpl, or .txt") { - t.Errorf("validateAllowedExtension('%s') to return \"Valid extensions are .yaml, .yml, .tpl, or .txt\", got no error", test) - } - } - var successTest = []string{"/foo.yaml", "foo.yaml", "foo.tpl", "/foo/bar/baz.yaml", "NOTES.txt"} - for _, test := range successTest { - err := validateAllowedExtension(test) - if err != nil { - t.Errorf("validateAllowedExtension('%s') to return no error but got \"%s\"", test, err.Error()) - } - } -} - -var values = map[string]interface{}{"nameOverride": "", "httpPort": 80} - -const namespace = "testNamespace" -const strict = false - -func TestTemplateParsing(t *testing.T) { - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 1 { - t.Fatalf("Expected one error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "deliberateSyntaxError") { - t.Errorf("Unexpected error: %s", res[0]) - } -} - -var wrongTemplatePath = filepath.Join(templateTestBasedir, "templates", "fail.yaml") -var ignoredTemplatePath = filepath.Join(templateTestBasedir, "fail.yaml.ignored") - -// Test a template with all the existing features: -// namespaces, partial templates -func TestTemplateIntegrationHappyPath(t *testing.T) { - // Rename file so it gets ignored by the linter - os.Rename(wrongTemplatePath, ignoredTemplatePath) - defer os.Rename(ignoredTemplatePath, wrongTemplatePath) - - linter := support.Linter{ChartDir: templateTestBasedir} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 0 { - t.Fatalf("Expected no error, got %d, %v", len(res), res) - } -} - -func TestV3Fail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/v3-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 3 { - t.Fatalf("Expected 3 errors, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), ".Release.Time has been removed in v3") { - t.Errorf("Unexpected error: %s", res[0].Err) - } - if !strings.Contains(res[1].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[1].Err) - } - if !strings.Contains(res[2].Err.Error(), "manifest is a crd-install hook") { - t.Errorf("Unexpected error: %s", res[2].Err) - } -} - -func TestMultiTemplateFail(t *testing.T) { - linter := support.Linter{ChartDir: "./testdata/multi-template-fail"} - Templates(&linter, values, namespace, strict) - res := linter.Messages - - if len(res) != 1 { - t.Fatalf("Expected 1 error, got %d, %v", len(res), res) - } - - if !strings.Contains(res[0].Err.Error(), "object name does not conform to Kubernetes naming requirements") { - t.Errorf("Unexpected error: %s", res[0].Err) - } -} - -func TestValidateMetadataName(t *testing.T) { - tests := []struct { - obj *K8sYamlStruct - wantErr bool - }{ - // Most kinds use IsDNS1123Subdomain. - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: ""}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one-two"}}, false}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "-two"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "one_two"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "a..b"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true}, - {&K8sYamlStruct{Kind: "Pod", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "ServiceAccount", Metadata: k8sYamlMetadata{Name: "operator:sa"}}, true}, - - // Service uses IsDNS1035Label. - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "123baz"}}, true}, - {&K8sYamlStruct{Kind: "Service", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true}, - - // Namespace uses IsDNS1123Label. - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, true}, - {&K8sYamlStruct{Kind: "Namespace", Metadata: k8sYamlMetadata{Name: "foo-bar"}}, false}, - - // CertificateSigningRequest has no validation. - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: ""}}, false}, - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "CertificateSigningRequest", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, false}, - - // RBAC uses path validation. - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true}, - {&K8sYamlStruct{Kind: "Role", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "foo.bar"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator/role"}}, true}, - {&K8sYamlStruct{Kind: "ClusterRole", Metadata: k8sYamlMetadata{Name: "operator%role"}}, true}, - {&K8sYamlStruct{Kind: "RoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - {&K8sYamlStruct{Kind: "ClusterRoleBinding", Metadata: k8sYamlMetadata{Name: "operator:role"}}, false}, - - // Unknown Kind - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: ""}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.bar1234baz.seventyone"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "FOO"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "123baz"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "foo.BAR.baz"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one-two"}}, false}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "-two"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "one_two"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "a..b"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "%^&#$%*@^*@&#^"}}, true}, - {&K8sYamlStruct{Kind: "FutureKind", Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - - // No kind - {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "foo"}}, false}, - {&K8sYamlStruct{Metadata: k8sYamlMetadata{Name: "operator:pod"}}, true}, - } - for _, tt := range tests { - t.Run(fmt.Sprintf("%s/%s", tt.obj.Kind, tt.obj.Metadata.Name), func(t *testing.T) { - if err := validateMetadataName(tt.obj); (err != nil) != tt.wantErr { - t.Errorf("validateMetadataName() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestDeprecatedAPIFails(t *testing.T) { - mychart := chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "failapi", - Version: "0.1.0", - Icon: "satisfy-the-linting-gods.gif", - }, - Templates: []*chart.File{ - { - Name: "templates/baddeployment.yaml", - Data: []byte("apiVersion: apps/v1beta1\nkind: Deployment\nmetadata:\n name: baddep\nspec: {selector: {matchLabels: {foo: bar}}}"), - }, - { - Name: "templates/goodsecret.yaml", - Data: []byte("apiVersion: v1\nkind: Secret\nmetadata:\n name: goodsecret"), - }, - }, - } - tmpdir := ensure.TempDir(t) - defer os.RemoveAll(tmpdir) - - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } - - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l != 1 { - for i, msg := range linter.Messages { - t.Logf("Message %d: %s", i, msg) - } - t.Fatalf("Expected 1 lint error, got %d", l) - } - - err := linter.Messages[0].Err.(deprecatedAPIError) - if err.Deprecated != "apps/v1beta1 Deployment" { - t.Errorf("Surprised to learn that %q is deprecated", err.Deprecated) - } -} - -const manifest = `apiVersion: v1 -kind: ConfigMap -metadata: - name: foo -data: - myval1: {{default "val" .Values.mymap.key1 }} - myval2: {{default "val" .Values.mymap.key2 }} -` - -// TestStrictTemplateParsingMapError is a regression test. -// -// The template engine should not produce an error when a map in values.yaml does -// not contain all possible keys. -// -// See https://github.com/helm/helm/issues/7483 -func TestStrictTemplateParsingMapError(t *testing.T) { - - ch := chart.Chart{ - Metadata: &chart.Metadata{ - Name: "regression7483", - APIVersion: "v2", - Version: "0.1.0", - }, - Values: map[string]interface{}{ - "mymap": map[string]string{ - "key1": "val1", - }, - }, - Templates: []*chart.File{ - { - Name: "templates/configmap.yaml", - Data: []byte(manifest), - }, - }, - } - dir := ensure.TempDir(t) - defer os.RemoveAll(dir) - if err := chartutil.SaveDir(&ch, dir); err != nil { - t.Fatal(err) - } - linter := &support.Linter{ - ChartDir: filepath.Join(dir, ch.Metadata.Name), - } - Templates(linter, ch.Values, namespace, strict) - if len(linter.Messages) != 0 { - t.Errorf("expected zero messages, got %d", len(linter.Messages)) - for i, msg := range linter.Messages { - t.Logf("Message %d: %q", i, msg) - } - } -} - -func TestValidateMatchSelector(t *testing.T) { - md := &K8sYamlStruct{ - APIVersion: "apps/v1", - Kind: "Deployment", - Metadata: k8sYamlMetadata{ - Name: "mydeployment", - }, - } - manifest := ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchLabels: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } - manifest = ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - selector: - matchExpressions: - app: nginx - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err != nil { - t.Error(err) - } - manifest = ` - apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - labels: - app: nginx -spec: - replicas: 3 - template: - metadata: - labels: - app: nginx - spec: - containers: - - name: nginx - image: nginx:1.14.2 - ` - if err := validateMatchSelector(md, manifest); err == nil { - t.Error("expected Deployment with no selector to fail") - } -} - -func TestValidateTopIndentLevel(t *testing.T) { - for doc, shouldFail := range map[string]bool{ - // Should not fail - "\n\n\n\t\n \t\n": false, - "apiVersion:foo\n bar:baz": false, - "\n\n\napiVersion:foo\n\n\n": false, - // Should fail - " apiVersion:foo": true, - "\n\n apiVersion:foo\n\n": true, - } { - if err := validateTopIndentLevel(doc); (err == nil) == shouldFail { - t.Errorf("Expected %t for %q", shouldFail, doc) - } - } - -} - -// TestEmptyWithCommentsManifests checks the lint is not failing against empty manifests that contains only comments -// See https://github.com/helm/helm/issues/8621 -func TestEmptyWithCommentsManifests(t *testing.T) { - mychart := chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "emptymanifests", - Version: "0.1.0", - Icon: "satisfy-the-linting-gods.gif", - }, - Templates: []*chart.File{ - { - Name: "templates/empty-with-comments.yaml", - Data: []byte("#@formatter:off\n"), - }, - }, - } - tmpdir := ensure.TempDir(t) - defer os.RemoveAll(tmpdir) - - if err := chartutil.SaveDir(&mychart, tmpdir); err != nil { - t.Fatal(err) - } - - linter := support.Linter{ChartDir: filepath.Join(tmpdir, mychart.Name())} - Templates(&linter, values, namespace, strict) - if l := len(linter.Messages); l > 0 { - for i, msg := range linter.Messages { - t.Logf("Message %d: %s", i, msg) - } - t.Fatalf("Expected 0 lint errors, got %d", l) - } -} -func TestValidateListAnnotations(t *testing.T) { - md := &K8sYamlStruct{ - APIVersion: "v1", - Kind: "List", - Metadata: k8sYamlMetadata{ - Name: "list", - }, - } - manifest := ` -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ConfigMap - metadata: - annotations: - helm.sh/resource-policy: keep -` - - if err := validateListAnnotations(md, manifest); err == nil { - t.Fatal("expected list with nested keep annotations to fail") - } - - manifest = ` -apiVersion: v1 -kind: List -metadata: - annotations: - helm.sh/resource-policy: keep -items: - - apiVersion: v1 - kind: ConfigMap -` - - if err := validateListAnnotations(md, manifest); err != nil { - t.Fatalf("List objects keep annotations should pass. got: %s", err) - } -} diff --git a/pkg/lint/rules/testdata/albatross/Chart.yaml b/pkg/lint/rules/testdata/albatross/Chart.yaml deleted file mode 100644 index 21124acfc..000000000 --- a/pkg/lint/rules/testdata/albatross/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: albatross -description: testing chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl b/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl deleted file mode 100644 index 24f76db73..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl +++ /dev/null @@ -1,16 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{define "name"}}{{default "nginx" .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). -*/}} -{{define "fullname"}} -{{- $name := default "nginx" .Values.nameOverride -}} -{{printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{end}} diff --git a/pkg/lint/rules/testdata/albatross/templates/fail.yaml b/pkg/lint/rules/testdata/albatross/templates/fail.yaml deleted file mode 100644 index a11e0e90e..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/fail.yaml +++ /dev/null @@ -1 +0,0 @@ -{{ deliberateSyntaxError }} diff --git a/pkg/lint/rules/testdata/albatross/templates/svc.yaml b/pkg/lint/rules/testdata/albatross/templates/svc.yaml deleted file mode 100644 index 16bb27d55..000000000 --- a/pkg/lint/rules/testdata/albatross/templates/svc.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This is a service gateway to the replica set created by the deployment. -# Take a look at the deployment.yaml for general notes about this chart. -apiVersion: v1 -kind: Service -metadata: - name: "{{ .Values.name }}" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - helm.sh/chart: "{{.Chart.Name}}-{{.Chart.Version}}" - kubeVersion: {{ .Capabilities.KubeVersion.Major }} -spec: - ports: - - port: {{default 80 .Values.httpPort | quote}} - targetPort: 80 - protocol: TCP - name: http - selector: - app.kubernetes.io/name: {{template "fullname" .}} diff --git a/pkg/lint/rules/testdata/albatross/values.yaml b/pkg/lint/rules/testdata/albatross/values.yaml deleted file mode 100644 index 74cc6a0dc..000000000 --- a/pkg/lint/rules/testdata/albatross/values.yaml +++ /dev/null @@ -1 +0,0 @@ -name: "mariner" diff --git a/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml b/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml deleted file mode 100644 index e6bac7693..000000000 --- a/pkg/lint/rules/testdata/anotherbadchartfile/Chart.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: "some-chart" -apiVersion: v2 -description: A Helm chart for Kubernetes -version: 72445e2 -home: "" -type: application -appVersion: 72225e2 -icon: "https://some-url.com/icon.jpeg" -dependencies: - - name: mariadb - version: 5.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - database diff --git a/pkg/lint/rules/testdata/badchartfile/Chart.yaml b/pkg/lint/rules/testdata/badchartfile/Chart.yaml deleted file mode 100644 index 3564ede3e..000000000 --- a/pkg/lint/rules/testdata/badchartfile/Chart.yaml +++ /dev/null @@ -1,11 +0,0 @@ -description: A Helm chart for Kubernetes -version: 0.0.0.0 -home: "" -type: application -dependencies: -- name: mariadb - version: 5.x.x - repository: https://charts.helm.sh/stable/ - condition: mariadb.enabled - tags: - - database diff --git a/pkg/lint/rules/testdata/badchartfile/values.yaml b/pkg/lint/rules/testdata/badchartfile/values.yaml deleted file mode 100644 index 9f367033b..000000000 --- a/pkg/lint/rules/testdata/badchartfile/values.yaml +++ /dev/null @@ -1 +0,0 @@ -# Default values for badchartfile. diff --git a/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml b/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml deleted file mode 100644 index 632919d03..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v1 -name: badvaluesfile -description: A Helm chart for Kubernetes -version: 0.0.1 -home: "" -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml b/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml deleted file mode 100644 index 6c2ceb8db..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{.name | default "foo" | title}} diff --git a/pkg/lint/rules/testdata/badvaluesfile/values.yaml b/pkg/lint/rules/testdata/badvaluesfile/values.yaml deleted file mode 100644 index b5a10271c..000000000 --- a/pkg/lint/rules/testdata/badvaluesfile/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -# Invalid value for badvaluesfile for testing lint fails with invalid yaml format -name= "value" diff --git a/pkg/lint/rules/testdata/goodone/Chart.yaml b/pkg/lint/rules/testdata/goodone/Chart.yaml deleted file mode 100644 index cb7a4bf20..000000000 --- a/pkg/lint/rules/testdata/goodone/Chart.yaml +++ /dev/null @@ -1,5 +0,0 @@ -apiVersion: v1 -name: goodone -description: good testing chart -version: 199.44.12345-Alpha.1+cafe009 -icon: http://riverrun.io diff --git a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml b/pkg/lint/rules/testdata/goodone/templates/goodone.yaml deleted file mode 100644 index cd46f62c7..000000000 --- a/pkg/lint/rules/testdata/goodone/templates/goodone.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.name | default "foo" | lower }} diff --git a/pkg/lint/rules/testdata/goodone/values.yaml b/pkg/lint/rules/testdata/goodone/values.yaml deleted file mode 100644 index 92c3d9bb9..000000000 --- a/pkg/lint/rules/testdata/goodone/values.yaml +++ /dev/null @@ -1 +0,0 @@ -name: "goodone-here" diff --git a/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml b/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml deleted file mode 100644 index b57427de9..000000000 --- a/pkg/lint/rules/testdata/multi-template-fail/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v2 -name: multi-template-fail -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. -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 and it is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml b/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml deleted file mode 100644 index 835be07be..000000000 --- a/pkg/lint/rules/testdata/multi-template-fail/templates/multi-fail.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: game-config -data: - game.properties: cheat ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: -this:name-is-not_valid$ -data: - game.properties: empty diff --git a/pkg/lint/rules/testdata/v3-fail/Chart.yaml b/pkg/lint/rules/testdata/v3-fail/Chart.yaml deleted file mode 100644 index 7097e17d8..000000000 --- a/pkg/lint/rules/testdata/v3-fail/Chart.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v2 -name: v3-fail -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. -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 and it is recommended to use it with quotes. -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl b/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl deleted file mode 100644 index 0b89e723b..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/_helpers.tpl +++ /dev/null @@ -1,63 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "v3-fail.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 "v3-fail.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 "v3-fail.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Common labels -*/}} -{{- define "v3-fail.labels" -}} -helm.sh/chart: {{ include "v3-fail.chart" . }} -{{ include "v3-fail.selectorLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -{{- end -}} - -{{/* -Selector labels -*/}} -{{- define "v3-fail.selectorLabels" -}} -app.kubernetes.io/name: {{ include "v3-fail.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end -}} - -{{/* -Create the name of the service account to use -*/}} -{{- define "v3-fail.serviceAccountName" -}} -{{- if .Values.serviceAccount.create -}} - {{ default (include "v3-fail.fullname" .) .Values.serviceAccount.name }} -{{- else -}} - {{ default "default" .Values.serviceAccount.name }} -{{- end -}} -{{- end -}} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml b/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml deleted file mode 100644 index 6d651ab8e..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/deployment.yaml +++ /dev/null @@ -1,56 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "v3-fail.fullname" . }} - labels: - nope: {{ .Release.Time }} - {{- include "v3-fail.labels" . | nindent 4 }} -spec: - replicas: {{ .Values.replicaCount }} - selector: - matchLabels: - {{- include "v3-fail.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "v3-fail.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "v3-fail.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.podSecurityContext | nindent 8 }} - containers: - - name: {{ .Chart.Name }} - securityContext: - {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - ports: - - name: http - containerPort: 80 - protocol: TCP - livenessProbe: - httpGet: - path: / - port: http - readinessProbe: - httpGet: - path: / - port: http - resources: - {{- toYaml .Values.resources | nindent 12 }} - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.affinity }} - affinity: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml b/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml deleted file mode 100644 index 4790650d0..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/ingress.yaml +++ /dev/null @@ -1,62 +0,0 @@ -{{- if .Values.ingress.enabled -}} -{{- $fullName := include "v3-fail.fullname" . -}} -{{- $svcPort := .Values.service.port -}} -{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} - {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} - {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} - {{- end }} -{{- end }} -{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1 -{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} -apiVersion: networking.k8s.io/v1beta1 -{{- else -}} -apiVersion: extensions/v1beta1 -{{- end }} -kind: Ingress -metadata: - name: {{ $fullName }} - labels: - {{- include "v3-fail.labels" . | nindent 4 }} - {{- with .Values.ingress.annotations }} - annotations: - "helm.sh/hook": crd-install - {{- toYaml . | nindent 4 }} - {{- end }} -spec: - {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} - ingressClassName: {{ .Values.ingress.className }} - {{- 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 }} - {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} - pathType: {{ .pathType }} - {{- end }} - backend: - {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} - service: - name: {{ $fullName }} - port: - number: {{ $svcPort }} - {{- else }} - serviceName: {{ $fullName }} - servicePort: {{ $svcPort }} - {{- end }} - {{- end }} - {{- end }} -{{- end }} diff --git a/pkg/lint/rules/testdata/v3-fail/templates/service.yaml b/pkg/lint/rules/testdata/v3-fail/templates/service.yaml deleted file mode 100644 index 79a0f40b0..000000000 --- a/pkg/lint/rules/testdata/v3-fail/templates/service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: {{ include "v3-fail.fullname" . }} - annotations: - helm.sh/hook: crd-install - labels: - {{- include "v3-fail.labels" . | nindent 4 }} -spec: - type: {{ .Values.service.type }} - ports: - - port: {{ .Values.service.port }} - targetPort: http - protocol: TCP - name: http - selector: - {{- include "v3-fail.selectorLabels" . | nindent 4 }} diff --git a/pkg/lint/rules/testdata/v3-fail/values.yaml b/pkg/lint/rules/testdata/v3-fail/values.yaml deleted file mode 100644 index 01d99b4e6..000000000 --- a/pkg/lint/rules/testdata/v3-fail/values.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# Default values for v3-fail. -# This is a YAML-formatted file. -# Declare variables to be passed into your templates. - -replicaCount: 1 - -image: - repository: nginx - pullPolicy: IfNotPresent - -imagePullSecrets: [] -nameOverride: "" -fullnameOverride: "" - -serviceAccount: - # Specifies whether a service account should be created - create: true - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: - -podSecurityContext: {} - # fsGroup: 2000 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -service: - type: ClusterIP - port: 80 - -ingress: - enabled: false - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - hosts: - - host: chart-example.local - paths: [] - tls: [] - # - secretName: chart-example-tls - # hosts: - # - chart-example.local - -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 - -nodeSelector: {} - -tolerations: [] - -affinity: {} diff --git a/pkg/lint/rules/testdata/withsubchart/Chart.yaml b/pkg/lint/rules/testdata/withsubchart/Chart.yaml deleted file mode 100644 index 6648daf56..000000000 --- a/pkg/lint/rules/testdata/withsubchart/Chart.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v2 -name: withsubchart -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 -appVersion: "1.16.0" -icon: http://riverrun.io - -dependencies: - - name: subchart - version: 0.1.16 - repository: "file://../subchart" - import-values: - - child: subchart - parent: subchart - diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml deleted file mode 100644 index 8610a4f25..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/Chart.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: v2 -name: subchart -description: A Helm chart for Kubernetes -type: application -version: 0.1.0 -appVersion: "1.16.0" diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml deleted file mode 100644 index 6cb6cc2af..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/templates/subchart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.subchart.name | lower }} diff --git a/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml b/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml deleted file mode 100644 index 422a359d5..000000000 --- a/pkg/lint/rules/testdata/withsubchart/charts/subchart/values.yaml +++ /dev/null @@ -1,2 +0,0 @@ -subchart: - name: subchart \ No newline at end of file diff --git a/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml b/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml deleted file mode 100644 index 6cb6cc2af..000000000 --- a/pkg/lint/rules/testdata/withsubchart/templates/mainchart.yaml +++ /dev/null @@ -1,2 +0,0 @@ -metadata: - name: {{ .Values.subchart.name | lower }} diff --git a/pkg/lint/rules/testdata/withsubchart/values.yaml b/pkg/lint/rules/testdata/withsubchart/values.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/pkg/lint/rules/values.go b/pkg/lint/rules/values.go deleted file mode 100644 index 79a294326..000000000 --- a/pkg/lint/rules/values.go +++ /dev/null @@ -1,87 +0,0 @@ -/* -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 rules - -import ( - "io/ioutil" - "os" - "path/filepath" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/lint/support" -) - -// Values lints a chart's values.yaml file. -// -// This function is deprecated and will be removed in Helm 4. -func Values(linter *support.Linter) { - ValuesWithOverrides(linter, map[string]interface{}{}) -} - -// ValuesWithOverrides tests the values.yaml file. -// -// If a schema is present in the chart, values are tested against that. Otherwise, -// they are only tested for well-formedness. -// -// If additional values are supplied, they are coalesced into the values in values.yaml. -func ValuesWithOverrides(linter *support.Linter, values map[string]interface{}) { - file := "values.yaml" - vf := filepath.Join(linter.ChartDir, file) - fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(vf)) - - if !fileExists { - return - } - - linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(vf, values)) -} - -func validateValuesFileExistence(valuesPath string) error { - _, err := os.Stat(valuesPath) - if err != nil { - return errors.Errorf("file does not exist") - } - return nil -} - -func validateValuesFile(valuesPath string, overrides map[string]interface{}) error { - values, err := chartutil.ReadValuesFile(valuesPath) - if err != nil { - return errors.Wrap(err, "unable to parse YAML") - } - - // Helm 3.0.0 carried over the values linting from Helm 2.x, which only tests the top - // level values against the top-level expectations. Subchart values are not linted. - // We could change that. For now, though, we retain that strategy, and thus can - // coalesce tables (like reuse-values does) instead of doing the full chart - // CoalesceValues - coalescedValues := chartutil.CoalesceTables(make(map[string]interface{}, len(overrides)), overrides) - coalescedValues = chartutil.CoalesceTables(coalescedValues, values) - - ext := filepath.Ext(valuesPath) - schemaPath := valuesPath[:len(valuesPath)-len(ext)] + ".schema.json" - schema, err := ioutil.ReadFile(schemaPath) - if len(schema) == 0 { - return nil - } - if err != nil { - return err - } - return chartutil.ValidateAgainstSingleSchema(coalescedValues, schema) -} diff --git a/pkg/lint/rules/values_test.go b/pkg/lint/rules/values_test.go deleted file mode 100644 index 23335cc01..000000000 --- a/pkg/lint/rules/values_test.go +++ /dev/null @@ -1,175 +0,0 @@ -/* -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 rules - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/stretchr/testify/assert" - - "helm.sh/helm/v3/internal/test/ensure" -) - -var nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml") - -const testSchema = ` -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "helm values test schema", - "type": "object", - "additionalProperties": false, - "required": [ - "username", - "password" - ], - "properties": { - "username": { - "description": "Your username", - "type": "string" - }, - "password": { - "description": "Your password", - "type": "string" - } - } -} -` - -func TestValidateValuesYamlNotDirectory(t *testing.T) { - _ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm) - defer os.Remove(nonExistingValuesFilePath) - - err := validateValuesFileExistence(nonExistingValuesFilePath) - if err == nil { - t.Errorf("validateValuesFileExistence to return a linter error, got no error") - } -} - -func TestValidateValuesFileWellFormed(t *testing.T) { - badYaml := ` - not:well[]{}formed - ` - tmpdir := ensure.TempFile(t, "values.yaml", []byte(badYaml)) - defer os.RemoveAll(tmpdir) - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err == nil { - t.Fatal("expected values file to fail parsing") - } -} - -func TestValidateValuesFileSchema(t *testing.T) { - yaml := "username: admin\npassword: swordfish" - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, map[string]interface{}{}); err != nil { - t.Fatalf("Failed validation with %s", err) - } -} - -func TestValidateValuesFileSchemaFailure(t *testing.T) { - // 1234 is an int, not a string. This should fail. - yaml := "username: 1234\npassword: swordfish" - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, map[string]interface{}{}) - if err == nil { - t.Fatal("expected values file to fail parsing") - } - - assert.Contains(t, err.Error(), "Expected: string, given: integer", "integer should be caught by schema") -} - -func TestValidateValuesFileSchemaOverrides(t *testing.T) { - yaml := "username: admin" - overrides := map[string]interface{}{ - "password": "swordfish", - } - tmpdir := ensure.TempFile(t, "values.yaml", []byte(yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - if err := validateValuesFile(valfile, overrides); err != nil { - t.Fatalf("Failed validation with %s", err) - } -} - -func TestValidateValuesFile(t *testing.T) { - tests := []struct { - name string - yaml string - overrides map[string]interface{} - errorMessage string - }{ - { - name: "value added", - yaml: "username: admin", - overrides: map[string]interface{}{"password": "swordfish"}, - }, - { - name: "value not overridden", - yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser"}, - errorMessage: "Expected: string, given: null", - }, - { - name: "value overridden", - yaml: "username: admin\npassword:", - overrides: map[string]interface{}{"username": "anotherUser", "password": "swordfish"}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tmpdir := ensure.TempFile(t, "values.yaml", []byte(tt.yaml)) - defer os.RemoveAll(tmpdir) - createTestingSchema(t, tmpdir) - - valfile := filepath.Join(tmpdir, "values.yaml") - - err := validateValuesFile(valfile, tt.overrides) - - switch { - case err != nil && tt.errorMessage == "": - t.Errorf("Failed validation with %s", err) - case err == nil && tt.errorMessage != "": - t.Error("expected values file to fail parsing") - case err != nil && tt.errorMessage != "": - assert.Contains(t, err.Error(), tt.errorMessage, "Failed with unexpected error") - } - }) - } -} - -func createTestingSchema(t *testing.T, dir string) string { - t.Helper() - schemafile := filepath.Join(dir, "values.schema.json") - if err := ioutil.WriteFile(schemafile, []byte(testSchema), 0700); err != nil { - t.Fatalf("Failed to write schema to tmpdir: %s", err) - } - return schemafile -} diff --git a/pkg/lint/support/doc.go b/pkg/lint/support/doc.go deleted file mode 100644 index b9a9d0918..000000000 --- a/pkg/lint/support/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 support contains tools for linting charts. - -Linting is the process of testing charts for errors or warnings regarding -formatting, compilation, or standards compliance. -*/ -package support // import "helm.sh/helm/v3/pkg/lint/support" diff --git a/pkg/lint/support/message.go b/pkg/lint/support/message.go deleted file mode 100644 index 5efbc7a61..000000000 --- a/pkg/lint/support/message.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -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 support - -import "fmt" - -// Severity indicates the severity of a Message. -const ( - // UnknownSev indicates that the severity of the error is unknown, and should not stop processing. - UnknownSev = iota - // InfoSev indicates information, for example missing values.yaml file - InfoSev - // WarningSev indicates that something does not meet code standards, but will likely function. - WarningSev - // ErrorSev indicates that something will not likely function. - ErrorSev -) - -// sev matches the *Sev states. -var sev = []string{"UNKNOWN", "INFO", "WARNING", "ERROR"} - -// Linter encapsulates a linting run of a particular chart. -type Linter struct { - Messages []Message - // The highest severity of all the failing lint rules - HighestSeverity int - ChartDir string -} - -// Message describes an error encountered while linting. -type Message struct { - // Severity is one of the *Sev constants - Severity int - Path string - Err error -} - -func (m Message) Error() string { - return fmt.Sprintf("[%s] %s: %s", sev[m.Severity], m.Path, m.Err.Error()) -} - -// NewMessage creates a new Message struct -func NewMessage(severity int, path string, err error) Message { - return Message{Severity: severity, Path: path, Err: err} -} - -// RunLinterRule returns true if the validation passed -func (l *Linter) RunLinterRule(severity int, path string, err error) bool { - // severity is out of bound - if severity < 0 || severity >= len(sev) { - return false - } - - if err != nil { - l.Messages = append(l.Messages, NewMessage(severity, path, err)) - - if severity > l.HighestSeverity { - l.HighestSeverity = severity - } - } - return err == nil -} diff --git a/pkg/lint/support/message_test.go b/pkg/lint/support/message_test.go deleted file mode 100644 index 9e12a638b..000000000 --- a/pkg/lint/support/message_test.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -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 support - -import ( - "testing" - - "github.com/pkg/errors" -) - -var linter = Linter{} -var errLint = errors.New("lint failed") - -func TestRunLinterRule(t *testing.T) { - var tests = []struct { - Severity int - LintError error - ExpectedMessages int - ExpectedReturn bool - ExpectedHighestSeverity int - }{ - {InfoSev, errLint, 1, false, InfoSev}, - {WarningSev, errLint, 2, false, WarningSev}, - {ErrorSev, errLint, 3, false, ErrorSev}, - // No error so it returns true - {ErrorSev, nil, 3, true, ErrorSev}, - // Retains highest severity - {InfoSev, errLint, 4, false, ErrorSev}, - // Invalid severity values - {4, errLint, 4, false, ErrorSev}, - {22, errLint, 4, false, ErrorSev}, - {-1, errLint, 4, false, ErrorSev}, - } - - for _, test := range tests { - isValid := linter.RunLinterRule(test.Severity, "chart", test.LintError) - if len(linter.Messages) != test.ExpectedMessages { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.Messages should now have %d message, we got %d", test.Severity, test.LintError, test.ExpectedMessages, len(linter.Messages)) - } - - if linter.HighestSeverity != test.ExpectedHighestSeverity { - t.Errorf("RunLinterRule(%d, \"chart\", %v), linter.HighestSeverity should be %d, we got %d", test.Severity, test.LintError, test.ExpectedHighestSeverity, linter.HighestSeverity) - } - - if isValid != test.ExpectedReturn { - t.Errorf("RunLinterRule(%d, \"chart\", %v), should have returned %t but returned %t", test.Severity, test.LintError, test.ExpectedReturn, isValid) - } - } -} - -func TestMessage(t *testing.T) { - m := Message{ErrorSev, "Chart.yaml", errors.New("Foo")} - if m.Error() != "[ERROR] Chart.yaml: Foo" { - t.Errorf("Unexpected output: %s", m.Error()) - } - - m = Message{WarningSev, "templates/", errors.New("Bar")} - if m.Error() != "[WARNING] templates/: Bar" { - t.Errorf("Unexpected output: %s", m.Error()) - } - - m = Message{InfoSev, "templates/rc.yaml", errors.New("FooBar")} - if m.Error() != "[INFO] templates/rc.yaml: FooBar" { - t.Errorf("Unexpected output: %s", m.Error()) - } -} diff --git a/pkg/plugin/cache/cache.go b/pkg/plugin/cache/cache.go deleted file mode 100644 index 5f3345b63..000000000 --- a/pkg/plugin/cache/cache.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -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 cache provides a key generator for vcs urls. -package cache // import "helm.sh/helm/v3/pkg/plugin/cache" - -import ( - "net/url" - "regexp" - "strings" -) - -// Thanks glide! - -// scpSyntaxRe matches the SCP-like addresses used to access repos over SSH. -var scpSyntaxRe = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`) - -// Key generates a cache key based on a url or scp string. The key is file -// system safe. -func Key(repo string) (string, error) { - var ( - u *url.URL - err error - ) - if m := scpSyntaxRe.FindStringSubmatch(repo); m != nil { - // Match SCP-like syntax and convert it to a URL. - // Eg, "git@github.com:user/repo" becomes - // "ssh://git@github.com/user/repo". - u = &url.URL{ - User: url.User(m[1]), - Host: m[2], - Path: "/" + m[3], - } - } else { - u, err = url.Parse(repo) - if err != nil { - return "", err - } - } - - var key strings.Builder - if u.Scheme != "" { - key.WriteString(u.Scheme) - key.WriteString("-") - } - if u.User != nil && u.User.Username() != "" { - key.WriteString(u.User.Username()) - key.WriteString("-") - } - key.WriteString(u.Host) - if u.Path != "" { - key.WriteString(strings.ReplaceAll(u.Path, "/", "-")) - } - return strings.ReplaceAll(key.String(), ":", "-"), nil -} diff --git a/pkg/plugin/hooks.go b/pkg/plugin/hooks.go deleted file mode 100644 index e3481515f..000000000 --- a/pkg/plugin/hooks.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 plugin // import "helm.sh/helm/v3/pkg/plugin" - -// Types of hooks -const ( - // Install is executed after the plugin is added. - Install = "install" - // Delete is executed after the plugin is removed. - Delete = "delete" - // Update is executed after the plugin is updated. - Update = "update" -) - -// Hooks is a map of events to commands. -type Hooks map[string]string diff --git a/pkg/plugin/installer/base.go b/pkg/plugin/installer/base.go deleted file mode 100644 index ba6a55d55..000000000 --- a/pkg/plugin/installer/base.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "path/filepath" - - "helm.sh/helm/v3/pkg/cli" -) - -type base struct { - // Source is the reference to a plugin - Source string - // PluginsDirectory is the directory where plugins are installed - PluginsDirectory string -} - -func newBase(source string) base { - settings := cli.New() - return base{ - Source: source, - PluginsDirectory: settings.PluginsDirectory, - } -} - -// Path is where the plugin will be installed. -func (b *base) Path() string { - if b.Source == "" { - return "" - } - return filepath.Join(b.PluginsDirectory, filepath.Base(b.Source)) -} diff --git a/pkg/plugin/installer/base_test.go b/pkg/plugin/installer/base_test.go deleted file mode 100644 index 38ef28c3e..000000000 --- a/pkg/plugin/installer/base_test.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "testing" -) - -func TestPath(t *testing.T) { - tests := []struct { - source string - helmPluginsDir string - expectPath string - }{ - { - source: "", - helmPluginsDir: "/helm/data/plugins", - expectPath: "", - }, { - source: "https://github.com/jkroepke/helm-secrets", - helmPluginsDir: "/helm/data/plugins", - expectPath: "/helm/data/plugins/helm-secrets", - }, - } - - for _, tt := range tests { - - os.Setenv("HELM_PLUGINS", tt.helmPluginsDir) - baseIns := newBase(tt.source) - baseInsPath := baseIns.Path() - if baseInsPath != tt.expectPath { - t.Errorf("expected name %s, got %s", tt.expectPath, baseInsPath) - } - os.Unsetenv("HELM_PLUGINS") - } -} diff --git a/pkg/plugin/installer/doc.go b/pkg/plugin/installer/doc.go deleted file mode 100644 index 3e3b2ebeb..000000000 --- a/pkg/plugin/installer/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -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 installer provides an interface for installing Helm plugins. -package installer // import "helm.sh/helm/v3/pkg/plugin/installer" diff --git a/pkg/plugin/installer/http_installer.go b/pkg/plugin/installer/http_installer.go deleted file mode 100644 index bcbcbde93..000000000 --- a/pkg/plugin/installer/http_installer.go +++ /dev/null @@ -1,268 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "io" - "os" - "path" - "path/filepath" - "regexp" - "strings" - - securejoin "github.com/cyphar/filepath-securejoin" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" -) - -// HTTPInstaller installs plugins from an archive served by a web server. -type HTTPInstaller struct { - CacheDir string - PluginName string - base - extractor Extractor - getter getter.Getter -} - -// TarGzExtractor extracts gzip compressed tar archives -type TarGzExtractor struct{} - -// Extractor provides an interface for extracting archives -type Extractor interface { - Extract(buffer *bytes.Buffer, targetDir string) error -} - -// Extractors contains a map of suffixes and matching implementations of extractor to return -var Extractors = map[string]Extractor{ - ".tar.gz": &TarGzExtractor{}, - ".tgz": &TarGzExtractor{}, -} - -// Convert a media type to an extractor extension. -// -// This should be refactored in Helm 4, combined with the extension-based mechanism. -func mediaTypeToExtension(mt string) (string, bool) { - switch strings.ToLower(mt) { - case "application/gzip", "application/x-gzip", "application/x-tgz", "application/x-gtar": - return ".tgz", true - default: - return "", false - } -} - -// NewExtractor creates a new extractor matching the source file name -func NewExtractor(source string) (Extractor, error) { - for suffix, extractor := range Extractors { - if strings.HasSuffix(source, suffix) { - return extractor, nil - } - } - return nil, errors.Errorf("no extractor implemented yet for %s", source) -} - -// NewHTTPInstaller creates a new HttpInstaller. -func NewHTTPInstaller(source string) (*HTTPInstaller, error) { - key, err := cache.Key(source) - if err != nil { - return nil, err - } - - extractor, err := NewExtractor(source) - if err != nil { - return nil, err - } - - get, err := getter.All(new(cli.EnvSettings)).ByScheme("http") - if err != nil { - return nil, err - } - - i := &HTTPInstaller{ - CacheDir: helmpath.CachePath("plugins", key), - PluginName: stripPluginName(filepath.Base(source)), - base: newBase(source), - extractor: extractor, - getter: get, - } - return i, nil -} - -// helper that relies on some sort of convention for plugin name (plugin-name-) -func stripPluginName(name string) string { - var strippedName string - for suffix := range Extractors { - if strings.HasSuffix(name, suffix) { - strippedName = strings.TrimSuffix(name, suffix) - break - } - } - re := regexp.MustCompile(`(.*)-[0-9]+\..*`) - return re.ReplaceAllString(strippedName, `$1`) -} - -// Install downloads and extracts the tarball into the cache directory -// and installs into the plugin directory. -// -// Implements Installer. -func (i *HTTPInstaller) Install() error { - pluginData, err := i.getter.Get(i.Source) - if err != nil { - return err - } - - if err := i.extractor.Extract(pluginData, i.CacheDir); err != nil { - return errors.Wrap(err, "extracting files from archive") - } - - if !isPlugin(i.CacheDir) { - return ErrMissingMetadata - } - - src, err := filepath.Abs(i.CacheDir) - if err != nil { - return err - } - - debug("copying %s to %s", src, i.Path()) - return fs.CopyDir(src, i.Path()) -} - -// Update updates a local repository -// Not implemented for now since tarball most likely will be packaged by version -func (i *HTTPInstaller) Update() error { - return errors.Errorf("method Update() not implemented for HttpInstaller") -} - -// Path is overridden because we want to join on the plugin name not the file name -func (i HTTPInstaller) Path() string { - if i.base.Source == "" { - return "" - } - return helmpath.DataPath("plugins", i.PluginName) -} - -// CleanJoin resolves dest as a subpath of root. -// -// This function runs several security checks on the path, generating an error if -// the supplied `dest` looks suspicious or would result in dubious behavior on the -// filesystem. -// -// CleanJoin assumes that any attempt by `dest` to break out of the CWD is an attempt -// to be malicious. (If you don't care about this, use the securejoin-filepath library.) -// It will emit an error if it detects paths that _look_ malicious, operating on the -// assumption that we don't actually want to do anything with files that already -// appear to be nefarious. -// -// - The character `:` is considered illegal because it is a separator on UNIX and a -// drive designator on Windows. -// - The path component `..` is considered suspicions, and therefore illegal -// - The character \ (backslash) is treated as a path separator and is converted to /. -// - Beginning a path with a path separator is illegal -// - Rudimentary symlink protects are offered by SecureJoin. -func cleanJoin(root, dest string) (string, error) { - - // On Windows, this is a drive separator. On UNIX-like, this is the path list separator. - // In neither case do we want to trust a TAR that contains these. - if strings.Contains(dest, ":") { - return "", errors.New("path contains ':', which is illegal") - } - - // The Go tar library does not convert separators for us. - // We assume here, as we do elsewhere, that `\\` means a Windows path. - dest = strings.ReplaceAll(dest, "\\", "/") - - // We want to alert the user that something bad was attempted. Cleaning it - // is not a good practice. - for _, part := range strings.Split(dest, "/") { - if part == ".." { - return "", errors.New("path contains '..', which is illegal") - } - } - - // If a path is absolute, the creator of the TAR is doing something shady. - if path.IsAbs(dest) { - return "", errors.New("path is absolute, which is illegal") - } - - // SecureJoin will do some cleaning, as well as some rudimentary checking of symlinks. - newpath, err := securejoin.SecureJoin(root, dest) - if err != nil { - return "", err - } - - return filepath.ToSlash(newpath), nil -} - -// Extract extracts compressed archives -// -// Implements Extractor. -func (g *TarGzExtractor) Extract(buffer *bytes.Buffer, targetDir string) error { - uncompressedStream, err := gzip.NewReader(buffer) - if err != nil { - return err - } - - if err := os.MkdirAll(targetDir, 0755); err != nil { - return err - } - - tarReader := tar.NewReader(uncompressedStream) - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - - path, err := cleanJoin(targetDir, header.Name) - if err != nil { - return err - } - - switch header.Typeflag { - case tar.TypeDir: - if err := os.Mkdir(path, 0755); err != nil { - return err - } - case tar.TypeReg: - outFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) - if err != nil { - return err - } - if _, err := io.Copy(outFile, tarReader); err != nil { - outFile.Close() - return err - } - outFile.Close() - // We don't want to process these extension header files. - case tar.TypeXGlobalHeader, tar.TypeXHeader: - continue - default: - return errors.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) - } - } - return nil -} diff --git a/pkg/plugin/installer/http_installer_test.go b/pkg/plugin/installer/http_installer_test.go deleted file mode 100644 index 165007af0..000000000 --- a/pkg/plugin/installer/http_installer_test.go +++ /dev/null @@ -1,350 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "encoding/base64" - "fmt" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "syscall" - "testing" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(HTTPInstaller) - -// Fake http client -type TestHTTPGetter struct { - MockResponse *bytes.Buffer - MockError error -} - -func (t *TestHTTPGetter) Get(href string, _ ...getter.Option) (*bytes.Buffer, error) { - return t.MockResponse, t.MockError -} - -// Fake plugin tarball data -var fakePluginB64 = "H4sIAKRj51kAA+3UX0vCUBgGcC9jn+Iwuk3Peza3GeyiUlJQkcogCOzgli7dJm4TvYk+a5+k479UqquUCJ/fLs549sLO2TnvWnJa9aXnjwujYdYLovxMhsPcfnHOLdNkOXthM/IVQQYjg2yyLLJ4kXGhLp5j0z3P41tZksqxmspL3B/O+j/XtZu1y8rdYzkOZRCxduKPk53ny6Wwz/GfIIf1As8lxzGJSmoHNLJZphKHG4YpTCE0wVk3DULfpSJ3DMMqkj3P5JfMYLdX1Vr9Ie/5E5cstcdC8K04iGLX5HaJuKpWL17F0TCIBi5pf/0pjtLhun5j3f9v6r7wfnI/H0eNp9d1/5P6Gez0vzo7wsoxfrAZbTny/o9k6J8z/VkO/LPlWdC1iVpbEEcq5nmeJ13LEtmbV0k2r2PrOs9PuuNglC5rL1Y5S/syXRQmutaNw1BGnnp8Wq3UG51WvX1da3bKtZtCN/R09DwAAAAAAAAAAAAAAAAAAADAb30AoMczDwAoAAA=" - -func TestStripName(t *testing.T) { - if stripPluginName("fake-plugin-0.0.1.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin-0.0.1.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin.tgz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } - if stripPluginName("fake-plugin.tar.gz") != "fake-plugin" { - t.Errorf("name does not match expected value") - } -} - -func mockArchiveServer() *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !strings.HasSuffix(r.URL.Path, ".tar.gz") { - w.Header().Add("Content-Type", "text/html") - fmt.Fprintln(w, "broken") - return - } - w.Header().Add("Content-Type", "application/gzip") - fmt.Fprintln(w, "test") - })) -} - -func TestHTTPInstaller(t *testing.T) { - defer ensure.HelmHome(t)() - - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with minimal plugin tarball - mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } - - httpInstaller.getter = &TestHTTPGetter{ - MockResponse: bytes.NewBuffer(mockTgz), - } - - // install the plugin - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } - - // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatal("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } - -} - -func TestHTTPInstallerNonExistentVersion(t *testing.T) { - defer ensure.HelmHome(t)() - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.2") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with error - httpInstaller.getter = &TestHTTPGetter{ - MockError: errors.Errorf("failed to download plugin for some reason"), - } - - // attempt to install the plugin - if err := Install(i); err == nil { - t.Fatal("expected error from http client") - } - -} - -func TestHTTPInstallerUpdate(t *testing.T) { - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - defer ensure.HelmHome(t)() - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - i, err := NewForSource(source, "0.0.1") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a HTTPInstaller was returned - httpInstaller, ok := i.(*HTTPInstaller) - if !ok { - t.Fatal("expected a HTTPInstaller") - } - - // inject fake http client responding with minimal plugin tarball - mockTgz, err := base64.StdEncoding.DecodeString(fakePluginB64) - if err != nil { - t.Fatalf("Could not decode fake tgz plugin: %s", err) - } - - httpInstaller.getter = &TestHTTPGetter{ - MockResponse: bytes.NewBuffer(mockTgz), - } - - // install the plugin before updating - if err := Install(i); err != nil { - t.Fatal(err) - } - if i.Path() != helmpath.DataPath("plugins", "fake-plugin") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/fake-plugin', got %q", i.Path()) - } - - // Update plugin, should fail because it is not implemented - if err := Update(i); err == nil { - t.Fatal("update method not implemented for http installer") - } -} - -func TestExtract(t *testing.T) { - source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" - - tempDir := t.TempDir() - - // Set the umask to default open permissions so we can actually test - oldmask := syscall.Umask(0000) - defer func() { - syscall.Umask(oldmask) - }() - - // Write a tarball to a buffer for us to extract - var tarbuf bytes.Buffer - tw := tar.NewWriter(&tarbuf) - var files = []struct { - Name, Body string - Mode int64 - }{ - {"plugin.yaml", "plugin metadata", 0600}, - {"README.md", "some text", 0777}, - } - for _, file := range files { - hdr := &tar.Header{ - Name: file.Name, - Typeflag: tar.TypeReg, - Mode: file.Mode, - Size: int64(len(file.Body)), - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err := tw.Write([]byte(file.Body)); err != nil { - t.Fatal(err) - } - } - - // Add pax global headers. This should be ignored. - // Note the PAX header that isn't global cannot be written using WriteHeader. - // Details are in the internal Go function for the tar packaged named - // allowedFormats. For a TypeXHeader it will return a message stating - // "cannot manually encode TypeXHeader, TypeGNULongName, or TypeGNULongLink headers" - if err := tw.WriteHeader(&tar.Header{ - Name: "pax_global_header", - Typeflag: tar.TypeXGlobalHeader, - }); err != nil { - t.Fatal(err) - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - - var buf bytes.Buffer - gz := gzip.NewWriter(&buf) - if _, err := gz.Write(tarbuf.Bytes()); err != nil { - t.Fatal(err) - } - gz.Close() - // END tarball creation - - extractor, err := NewExtractor(source) - if err != nil { - t.Fatal(err) - } - - if err = extractor.Extract(&buf, tempDir); err != nil { - t.Fatalf("Did not expect error but got error: %v", err) - } - - pluginYAMLFullPath := filepath.Join(tempDir, "plugin.yaml") - if info, err := os.Stat(pluginYAMLFullPath); err != nil { - if os.IsNotExist(err) { - t.Fatalf("Expected %s to exist but doesn't", pluginYAMLFullPath) - } - t.Fatal(err) - } else if info.Mode().Perm() != 0600 { - t.Fatalf("Expected %s to have 0600 mode it but has %o", pluginYAMLFullPath, info.Mode().Perm()) - } - - readmeFullPath := filepath.Join(tempDir, "README.md") - if info, err := os.Stat(readmeFullPath); err != nil { - if os.IsNotExist(err) { - t.Fatalf("Expected %s to exist but doesn't", readmeFullPath) - } - t.Fatal(err) - } else if info.Mode().Perm() != 0777 { - t.Fatalf("Expected %s to have 0777 mode it but has %o", readmeFullPath, info.Mode().Perm()) - } - -} - -func TestCleanJoin(t *testing.T) { - for i, fixture := range []struct { - path string - expect string - expectError bool - }{ - {"foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"/foo/bar.txt", "", true}, - {"./foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"./././././foo/bar.txt", "/tmp/foo/bar.txt", false}, - {"../../../../foo/bar.txt", "", true}, - {"foo/../../../../bar.txt", "", true}, - {"c:/foo/bar.txt", "/tmp/c:/foo/bar.txt", true}, - {"foo\\bar.txt", "/tmp/foo/bar.txt", false}, - {"c:\\foo\\bar.txt", "", true}, - } { - out, err := cleanJoin("/tmp", fixture.path) - if err != nil { - if !fixture.expectError { - t.Errorf("Test %d: Path was not cleaned: %s", i, err) - } - continue - } - if fixture.expect != out { - t.Errorf("Test %d: Expected %q but got %q", i, fixture.expect, out) - } - } - -} - -func TestMediaTypeToExtension(t *testing.T) { - - for mt, shouldPass := range map[string]bool{ - "": false, - "application/gzip": true, - "application/x-gzip": true, - "application/x-tgz": true, - "application/x-gtar": true, - "application/json": false, - } { - ext, ok := mediaTypeToExtension(mt) - if ok != shouldPass { - t.Errorf("Media type %q failed test", mt) - } - if shouldPass && ext == "" { - t.Errorf("Expected an extension but got empty string") - } - if !shouldPass && len(ext) != 0 { - t.Error("Expected extension to be empty for unrecognized type") - } - } -} diff --git a/pkg/plugin/installer/installer.go b/pkg/plugin/installer/installer.go deleted file mode 100644 index 6f01494e5..000000000 --- a/pkg/plugin/installer/installer.go +++ /dev/null @@ -1,135 +0,0 @@ -/* -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 installer - -import ( - "fmt" - "log" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/plugin" -) - -// ErrMissingMetadata indicates that plugin.yaml is missing. -var ErrMissingMetadata = errors.New("plugin metadata (plugin.yaml) missing") - -// Debug enables verbose output. -var Debug bool - -// Installer provides an interface for installing helm client plugins. -type Installer interface { - // Install adds a plugin. - Install() error - // Path is the directory of the installed plugin. - Path() string - // Update updates a plugin. - Update() error -} - -// Install installs a plugin. -func Install(i Installer) error { - if err := os.MkdirAll(filepath.Dir(i.Path()), 0755); err != nil { - return err - } - if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { - return errors.New("plugin already exists") - } - return i.Install() -} - -// Update updates a plugin. -func Update(i Installer) error { - if _, pathErr := os.Stat(i.Path()); os.IsNotExist(pathErr) { - return errors.New("plugin does not exist") - } - return i.Update() -} - -// NewForSource determines the correct Installer for the given source. -func NewForSource(source, version string) (Installer, error) { - // Check if source is a local directory - if isLocalReference(source) { - return NewLocalInstaller(source) - } else if isRemoteHTTPArchive(source) { - return NewHTTPInstaller(source) - } - return NewVCSInstaller(source, version) -} - -// FindSource determines the correct Installer for the given source. -func FindSource(location string) (Installer, error) { - installer, err := existingVCSRepo(location) - if err != nil && err.Error() == "Cannot detect VCS" { - return installer, errors.New("cannot get information about plugin source") - } - return installer, err -} - -// isLocalReference checks if the source exists on the filesystem. -func isLocalReference(source string) bool { - _, err := os.Stat(source) - return err == nil -} - -// isRemoteHTTPArchive checks if the source is a http/https url and is an archive -// -// It works by checking whether the source looks like a URL and, if it does, running a -// HEAD operation to see if the remote resource is a file that we understand. -func isRemoteHTTPArchive(source string) bool { - if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { - res, err := http.Head(source) - if err != nil { - // If we get an error at the network layer, we can't install it. So - // we return false. - return false - } - - // Next, we look for the content type or content disposition headers to see - // if they have matching extractors. - contentType := res.Header.Get("content-type") - foundSuffix, ok := mediaTypeToExtension(contentType) - if !ok { - // Media type not recognized - return false - } - - for suffix := range Extractors { - if strings.HasSuffix(foundSuffix, suffix) { - return true - } - } - } - return false -} - -// isPlugin checks if the directory contains a plugin.yaml file. -func isPlugin(dirname string) bool { - _, err := os.Stat(filepath.Join(dirname, plugin.PluginFileName)) - return err == nil -} - -var logger = log.New(os.Stderr, "[debug] ", log.Lshortfile) - -func debug(format string, args ...interface{}) { - if Debug { - logger.Output(2, fmt.Sprintf(format, args...)) - } -} diff --git a/pkg/plugin/installer/installer_test.go b/pkg/plugin/installer/installer_test.go deleted file mode 100644 index a11464924..000000000 --- a/pkg/plugin/installer/installer_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -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 installer - -import "testing" - -func TestIsRemoteHTTPArchive(t *testing.T) { - srv := mockArchiveServer() - defer srv.Close() - source := srv.URL + "/plugins/fake-plugin-0.0.1.tar.gz" - - if isRemoteHTTPArchive("/not/a/URL") { - t.Errorf("Expected non-URL to return false") - } - - if isRemoteHTTPArchive("https://127.0.0.1:123/fake/plugin-1.2.3.tgz") { - t.Errorf("Bad URL should not have succeeded.") - } - - if !isRemoteHTTPArchive(source) { - t.Errorf("Expected %q to be a valid archive URL", source) - } - - if isRemoteHTTPArchive(source + "-not-an-extension") { - t.Error("Expected media type match to fail") - } -} diff --git a/pkg/plugin/installer/local_installer.go b/pkg/plugin/installer/local_installer.go deleted file mode 100644 index c92bc3fb0..000000000 --- a/pkg/plugin/installer/local_installer.go +++ /dev/null @@ -1,57 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "path/filepath" - - "github.com/pkg/errors" -) - -// LocalInstaller installs plugins from the filesystem. -type LocalInstaller struct { - base -} - -// NewLocalInstaller creates a new LocalInstaller. -func NewLocalInstaller(source string) (*LocalInstaller, error) { - src, err := filepath.Abs(source) - if err != nil { - return nil, errors.Wrap(err, "unable to get absolute path to plugin") - } - i := &LocalInstaller{ - base: newBase(src), - } - return i, nil -} - -// Install creates a symlink to the plugin directory. -// -// Implements Installer. -func (i *LocalInstaller) Install() error { - if !isPlugin(i.Source) { - return ErrMissingMetadata - } - debug("symlinking %s to %s", i.Source, i.Path()) - return os.Symlink(i.Source, i.Path()) -} - -// Update updates a local repository -func (i *LocalInstaller) Update() error { - debug("local repository is auto-updated") - return nil -} diff --git a/pkg/plugin/installer/local_installer_test.go b/pkg/plugin/installer/local_installer_test.go deleted file mode 100644 index 9b5cbf59e..000000000 --- a/pkg/plugin/installer/local_installer_test.go +++ /dev/null @@ -1,50 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "io/ioutil" - "os" - "path/filepath" - "testing" - - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(LocalInstaller) - -func TestLocalInstaller(t *testing.T) { - // Make a temp dir - tdir := t.TempDir() - if err := ioutil.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { - t.Fatal(err) - } - - source := "../testdata/plugdir/good/echo" - i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if err := Install(i); err != nil { - t.Fatal(err) - } - - if i.Path() != helmpath.DataPath("plugins", "echo") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) - } - defer os.RemoveAll(filepath.Dir(helmpath.DataPath())) // helmpath.DataPath is like /tmp/helm013130971/helm -} diff --git a/pkg/plugin/installer/vcs_installer.go b/pkg/plugin/installer/vcs_installer.go deleted file mode 100644 index f7df5b322..000000000 --- a/pkg/plugin/installer/vcs_installer.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "os" - "sort" - - "github.com/Masterminds/semver/v3" - "github.com/Masterminds/vcs" - "github.com/pkg/errors" - - "helm.sh/helm/v3/internal/third_party/dep/fs" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/plugin/cache" -) - -// VCSInstaller installs plugins from remote a repository. -type VCSInstaller struct { - Repo vcs.Repo - Version string - base -} - -func existingVCSRepo(location string) (Installer, error) { - repo, err := vcs.NewRepo("", location) - if err != nil { - return nil, err - } - i := &VCSInstaller{ - Repo: repo, - base: newBase(repo.Remote()), - } - return i, nil -} - -// NewVCSInstaller creates a new VCSInstaller. -func NewVCSInstaller(source, version string) (*VCSInstaller, error) { - key, err := cache.Key(source) - if err != nil { - return nil, err - } - cachedpath := helmpath.CachePath("plugins", key) - repo, err := vcs.NewRepo(source, cachedpath) - if err != nil { - return nil, err - } - i := &VCSInstaller{ - Repo: repo, - Version: version, - base: newBase(source), - } - return i, err -} - -// Install clones a remote repository and installs into the plugin directory. -// -// Implements Installer. -func (i *VCSInstaller) Install() error { - if err := i.sync(i.Repo); err != nil { - return err - } - - ref, err := i.solveVersion(i.Repo) - if err != nil { - return err - } - if ref != "" { - if err := i.setVersion(i.Repo, ref); err != nil { - return err - } - } - - if !isPlugin(i.Repo.LocalPath()) { - return ErrMissingMetadata - } - - debug("copying %s to %s", i.Repo.LocalPath(), i.Path()) - return fs.CopyDir(i.Repo.LocalPath(), i.Path()) -} - -// Update updates a remote repository -func (i *VCSInstaller) Update() error { - debug("updating %s", i.Repo.Remote()) - if i.Repo.IsDirty() { - return errors.New("plugin repo was modified") - } - if err := i.Repo.Update(); err != nil { - return err - } - if !isPlugin(i.Repo.LocalPath()) { - return ErrMissingMetadata - } - return nil -} - -func (i *VCSInstaller) solveVersion(repo vcs.Repo) (string, error) { - if i.Version == "" { - return "", nil - } - - if repo.IsReference(i.Version) { - return i.Version, nil - } - - // Create the constraint first to make sure it's valid before - // working on the repo. - constraint, err := semver.NewConstraint(i.Version) - if err != nil { - return "", err - } - - // Get the tags - refs, err := repo.Tags() - if err != nil { - return "", err - } - debug("found refs: %s", refs) - - // Convert and filter the list to semver.Version instances - semvers := getSemVers(refs) - - // Sort semver list - sort.Sort(sort.Reverse(semver.Collection(semvers))) - for _, v := range semvers { - if constraint.Check(v) { - // If the constraint passes get the original reference - ver := v.Original() - debug("setting to %s", ver) - return ver, nil - } - } - - return "", errors.Errorf("requested version %q does not exist for plugin %q", i.Version, i.Repo.Remote()) -} - -// setVersion attempts to checkout the version -func (i *VCSInstaller) setVersion(repo vcs.Repo, ref string) error { - debug("setting version to %q", i.Version) - return repo.UpdateVersion(ref) -} - -// sync will clone or update a remote repo. -func (i *VCSInstaller) sync(repo vcs.Repo) error { - if _, err := os.Stat(repo.LocalPath()); os.IsNotExist(err) { - debug("cloning %s to %s", repo.Remote(), repo.LocalPath()) - return repo.Get() - } - debug("updating %s", repo.Remote()) - return repo.Update() -} - -// Filter a list of versions to only included semantic versions. The response -// is a mapping of the original version to the semantic version. -func getSemVers(refs []string) []*semver.Version { - var sv []*semver.Version - for _, r := range refs { - if v, err := semver.NewVersion(r); err == nil { - sv = append(sv, v) - } - } - return sv -} diff --git a/pkg/plugin/installer/vcs_installer_test.go b/pkg/plugin/installer/vcs_installer_test.go deleted file mode 100644 index 6785264b3..000000000 --- a/pkg/plugin/installer/vcs_installer_test.go +++ /dev/null @@ -1,181 +0,0 @@ -/* -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 installer // import "helm.sh/helm/v3/pkg/plugin/installer" - -import ( - "fmt" - "os" - "path/filepath" - "testing" - - "github.com/Masterminds/vcs" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/helmpath" -) - -var _ Installer = new(VCSInstaller) - -type testRepo struct { - local, remote, current string - tags, branches []string - err error - vcs.Repo -} - -func (r *testRepo) LocalPath() string { return r.local } -func (r *testRepo) Remote() string { return r.remote } -func (r *testRepo) Update() error { return r.err } -func (r *testRepo) Get() error { return r.err } -func (r *testRepo) IsReference(string) bool { return false } -func (r *testRepo) Tags() ([]string, error) { return r.tags, r.err } -func (r *testRepo) Branches() ([]string, error) { return r.branches, r.err } -func (r *testRepo) UpdateVersion(version string) error { - r.current = version - return r.err -} - -func TestVCSInstaller(t *testing.T) { - defer ensure.HelmHome(t)() - - if err := os.MkdirAll(helmpath.DataPath("plugins"), 0755); err != nil { - t.Fatalf("Could not create %s: %s", helmpath.DataPath("plugins"), err) - } - - source := "https://github.com/adamreese/helm-env" - testRepoPath, _ := filepath.Abs("../testdata/plugdir/good/echo") - repo := &testRepo{ - local: testRepoPath, - tags: []string{"0.1.0", "0.1.1"}, - } - - i, err := NewForSource(source, "~0.1.0") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - vcsInstaller, ok := i.(*VCSInstaller) - if !ok { - t.Fatal("expected a VCSInstaller") - } - - // set the testRepo in the VCSInstaller - vcsInstaller.Repo = repo - - if err := Install(i); err != nil { - t.Fatal(err) - } - if repo.current != "0.1.1" { - t.Fatalf("expected version '0.1.1', got %q", repo.current) - } - if i.Path() != helmpath.DataPath("plugins", "helm-env") { - t.Fatalf("expected path '$XDG_CONFIG_HOME/helm/plugins/helm-env', got %q", i.Path()) - } - - // Install again to test plugin exists error - if err := Install(i); err == nil { - t.Fatalf("expected error for plugin exists, got none") - } else if err.Error() != "plugin already exists" { - t.Fatalf("expected error for plugin exists, got (%v)", err) - } - - // Testing FindSource method, expect error because plugin code is not a cloned repository - if _, err := FindSource(i.Path()); err == nil { - t.Fatalf("expected error for inability to find plugin source, got none") - } else if err.Error() != "cannot get information about plugin source" { - t.Fatalf("expected error for inability to find plugin source, got (%v)", err) - } -} - -func TestVCSInstallerNonExistentVersion(t *testing.T) { - defer ensure.HelmHome(t)() - - source := "https://github.com/adamreese/helm-env" - version := "0.2.0" - - i, err := NewForSource(source, version) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } - - if err := Install(i); err == nil { - t.Fatalf("expected error for version does not exists, got none") - } else if err.Error() != fmt.Sprintf("requested version %q does not exist for plugin %q", version, source) { - t.Fatalf("expected error for version does not exists, got (%v)", err) - } -} -func TestVCSInstallerUpdate(t *testing.T) { - defer ensure.HelmHome(t)() - - source := "https://github.com/adamreese/helm-env" - - i, err := NewForSource(source, "") - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - // ensure a VCSInstaller was returned - if _, ok := i.(*VCSInstaller); !ok { - t.Fatal("expected a VCSInstaller") - } - - if err := Update(i); err == nil { - t.Fatal("expected error for plugin does not exist, got none") - } else if err.Error() != "plugin does not exist" { - t.Fatalf("expected error for plugin does not exist, got (%v)", err) - } - - // Install plugin before update - if err := Install(i); err != nil { - t.Fatal(err) - } - - // Test FindSource method for positive result - pluginInfo, err := FindSource(i.Path()) - if err != nil { - t.Fatal(err) - } - - vcsInstaller := pluginInfo.(*VCSInstaller) - - repoRemote := vcsInstaller.Repo.Remote() - if repoRemote != source { - t.Fatalf("invalid source found, expected %q got %q", source, repoRemote) - } - - // Update plugin - if err := Update(i); err != nil { - t.Fatal(err) - } - - // Test update failure - if err := os.Remove(filepath.Join(vcsInstaller.Repo.LocalPath(), "plugin.yaml")); err != nil { - t.Fatal(err) - } - // Testing update for error - if err := Update(vcsInstaller); err == nil { - t.Fatalf("expected error for plugin modified, got none") - } else if err.Error() != "plugin repo was modified" { - t.Fatalf("expected error for plugin modified, got (%v)", err) - } - -} diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go deleted file mode 100644 index 1399b7116..000000000 --- a/pkg/plugin/plugin.go +++ /dev/null @@ -1,282 +0,0 @@ -/* -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 plugin // import "helm.sh/helm/v3/pkg/plugin" - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "unicode" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/cli" -) - -const PluginFileName = "plugin.yaml" - -// Downloaders represents the plugins capability if it can retrieve -// charts from special sources -type Downloaders struct { - // Protocols are the list of schemes from the charts URL. - Protocols []string `json:"protocols"` - // Command is the executable path with which the plugin performs - // the actual download for the corresponding Protocols - Command string `json:"command"` -} - -// PlatformCommand represents a command for a particular operating system and architecture -type PlatformCommand struct { - OperatingSystem string `json:"os"` - Architecture string `json:"arch"` - Command string `json:"command"` -} - -// Metadata describes a plugin. -// -// This is the plugin equivalent of a chart.Metadata. -type Metadata struct { - // Name is the name of the plugin - Name string `json:"name"` - - // Version is a SemVer 2 version of the plugin. - Version string `json:"version"` - - // Usage is the single-line usage text shown in help - Usage string `json:"usage"` - - // Description is a long description shown in places like `helm help` - Description string `json:"description"` - - // Command is the command, as a single string. - // - // The command will be passed through environment expansion, so env vars can - // be present in this command. Unless IgnoreFlags is set, this will - // also merge the flags passed from Helm. - // - // Note that command is not executed in a shell. To do so, we suggest - // pointing the command to a shell script. - // - // The following rules will apply to processing commands: - // - If platformCommand is present, it will be searched first - // - If both OS and Arch match the current platform, search will stop and the command will be executed - // - If OS matches and there is no more specific match, the command will be executed - // - If no OS/Arch match is found, the default command will be executed - // - If no command is present and no matches are found in platformCommand, Helm will exit with an error - PlatformCommand []PlatformCommand `json:"platformCommand"` - Command string `json:"command"` - - // IgnoreFlags ignores any flags passed in from Helm - // - // For example, if the plugin is invoked as `helm --debug myplugin`, if this - // is false, `--debug` will be appended to `--command`. If this is true, - // the `--debug` flag will be discarded. - IgnoreFlags bool `json:"ignoreFlags"` - - // Hooks are commands that will run on events. - Hooks Hooks - - // Downloaders field is used if the plugin supply downloader mechanism - // for special protocols. - Downloaders []Downloaders `json:"downloaders"` - - // UseTunnelDeprecated indicates that this command needs a tunnel. - // Setting this will cause a number of side effects, such as the - // automatic setting of HELM_HOST. - // DEPRECATED and unused, but retained for backwards compatibility with Helm 2 plugins. Remove in Helm 4 - UseTunnelDeprecated bool `json:"useTunnel,omitempty"` -} - -// Plugin represents a plugin. -type Plugin struct { - // Metadata is a parsed representation of a plugin.yaml - Metadata *Metadata - // Dir is the string path to the directory that holds the plugin. - Dir string -} - -// The following rules will apply to processing the Plugin.PlatformCommand.Command: -// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution -// - If OS matches and there is no more specific match, the command will be prepared for execution -// - If no OS/Arch match is found, return nil -func getPlatformCommand(cmds []PlatformCommand) []string { - var command []string - eq := strings.EqualFold - for _, c := range cmds { - if eq(c.OperatingSystem, runtime.GOOS) { - command = strings.Split(os.ExpandEnv(c.Command), " ") - } - if eq(c.OperatingSystem, runtime.GOOS) && eq(c.Architecture, runtime.GOARCH) { - return strings.Split(os.ExpandEnv(c.Command), " ") - } - } - return command -} - -// PrepareCommand takes a Plugin.PlatformCommand.Command, a Plugin.Command and will applying the following processing: -// - If platformCommand is present, it will be searched first -// - If both OS and Arch match the current platform, search will stop and the command will be prepared for execution -// - If OS matches and there is no more specific match, the command will be prepared for execution -// - If no OS/Arch match is found, the default command will be prepared for execution -// - If no command is present and no matches are found in platformCommand, will exit with an error -// -// It merges extraArgs into any arguments supplied in the plugin. It -// returns the name of the command and an args array. -// -// The result is suitable to pass to exec.Command. -func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string, error) { - var parts []string - platCmdLen := len(p.Metadata.PlatformCommand) - if platCmdLen > 0 { - parts = getPlatformCommand(p.Metadata.PlatformCommand) - } - if platCmdLen == 0 || parts == nil { - parts = strings.Split(os.ExpandEnv(p.Metadata.Command), " ") - } - if len(parts) == 0 || parts[0] == "" { - return "", nil, fmt.Errorf("no plugin command is applicable") - } - - main := parts[0] - baseArgs := []string{} - if len(parts) > 1 { - baseArgs = parts[1:] - } - if !p.Metadata.IgnoreFlags { - baseArgs = append(baseArgs, extraArgs...) - } - return main, baseArgs, nil -} - -// validPluginName is a regular expression that validates plugin names. -// -// Plugin names can only contain the ASCII characters a-z, A-Z, 0-9, ​_​ and ​-. -var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") - -// validatePluginData validates a plugin's YAML data. -func validatePluginData(plug *Plugin, filepath string) error { - if !validPluginName.MatchString(plug.Metadata.Name) { - return fmt.Errorf("invalid plugin name at %q", filepath) - } - plug.Metadata.Usage = sanitizeString(plug.Metadata.Usage) - - // We could also validate SemVer, executable, and other fields should we so choose. - return 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) -} - -func detectDuplicates(plugs []*Plugin) error { - names := map[string]string{} - - for _, plug := range plugs { - if oldpath, ok := names[plug.Metadata.Name]; ok { - return fmt.Errorf( - "two plugins claim the name %q at %q and %q", - plug.Metadata.Name, - oldpath, - plug.Dir, - ) - } - names[plug.Metadata.Name] = plug.Dir - } - - return nil -} - -// LoadDir loads a plugin from the given directory. -func LoadDir(dirname string) (*Plugin, error) { - pluginfile := filepath.Join(dirname, PluginFileName) - data, err := ioutil.ReadFile(pluginfile) - if err != nil { - return nil, errors.Wrapf(err, "failed to read plugin at %q", pluginfile) - } - - plug := &Plugin{Dir: dirname} - if err := yaml.UnmarshalStrict(data, &plug.Metadata); err != nil { - return nil, errors.Wrapf(err, "failed to load plugin at %q", pluginfile) - } - return plug, validatePluginData(plug, pluginfile) -} - -// LoadAll loads all plugins found beneath the base directory. -// -// This scans only one directory level. -func LoadAll(basedir string) ([]*Plugin, error) { - plugins := []*Plugin{} - // We want basedir/*/plugin.yaml - scanpath := filepath.Join(basedir, "*", PluginFileName) - matches, err := filepath.Glob(scanpath) - if err != nil { - return plugins, errors.Wrapf(err, "failed to find plugins in %q", scanpath) - } - - if matches == nil { - return plugins, nil - } - - for _, yaml := range matches { - dir := filepath.Dir(yaml) - p, err := LoadDir(dir) - if err != nil { - return plugins, err - } - plugins = append(plugins, p) - } - return plugins, detectDuplicates(plugins) -} - -// FindPlugins returns a list of YAML files that describe plugins. -func FindPlugins(plugdirs string) ([]*Plugin, error) { - found := []*Plugin{} - // Let's get all UNIXy and allow path separators - for _, p := range filepath.SplitList(plugdirs) { - matches, err := LoadAll(p) - if err != nil { - return matches, err - } - found = append(found, matches...) - } - return found, nil -} - -// SetupPluginEnv prepares os.Env for plugins. It operates on os.Env because -// the plugin subsystem itself needs access to the environment variables -// created here. -func SetupPluginEnv(settings *cli.EnvSettings, name, base string) { - env := settings.EnvVars() - env["HELM_PLUGIN_NAME"] = name - env["HELM_PLUGIN_DIR"] = base - for key, val := range env { - os.Setenv(key, val) - } -} diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go deleted file mode 100644 index 3b44a6eb5..000000000 --- a/pkg/plugin/plugin_test.go +++ /dev/null @@ -1,378 +0,0 @@ -/* -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 plugin // import "helm.sh/helm/v3/pkg/plugin" - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - "runtime" - "testing" - - "helm.sh/helm/v3/pkg/cli" -) - -func checkCommand(p *Plugin, extraArgs []string, osStrCmp string, t *testing.T) { - cmd, args, err := p.PrepareCommand(extraArgs) - if err != nil { - t.Fatal(err) - } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) - } - - if l := len(args); l != 5 { - t.Fatalf("expected 5 args, got %d", l) - } - - expect := []string{"-n", osStrCmp, "--debug", "--foo", "bar"} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } - } - - // Test with IgnoreFlags. This should omit --debug, --foo, bar - p.Metadata.IgnoreFlags = true - cmd, args, err = p.PrepareCommand(extraArgs) - if err != nil { - t.Fatal(err) - } - if cmd != "echo" { - t.Fatalf("Expected echo, got %q", cmd) - } - if l := len(args); l != 2 { - t.Fatalf("expected 2 args, got %d", l) - } - expect = []string{"-n", osStrCmp} - for i := 0; i < len(args); i++ { - if expect[i] != args[i] { - t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) - } - } -} - -func TestPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n foo", - }, - } - argv := []string{"--debug", "--foo", "bar"} - - checkCommand(p, argv, "foo", t) -} - -func TestPlatformPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n os-arch", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "i386", Command: "echo -n linux-i386"}, - {OperatingSystem: "linux", Architecture: "amd64", Command: "echo -n linux-amd64"}, - {OperatingSystem: "linux", Architecture: "arm64", Command: "echo -n linux-arm64"}, - {OperatingSystem: "linux", Architecture: "ppc64le", Command: "echo -n linux-ppc64le"}, - {OperatingSystem: "linux", Architecture: "s390x", Command: "echo -n linux-s390x"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, - }, - }, - } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" && arch == "i386" { - osStrCmp = "linux-i386" - } else if os == "linux" && arch == "amd64" { - osStrCmp = "linux-amd64" - } else if os == "linux" && arch == "arm64" { - osStrCmp = "linux-arm64" - } else if os == "linux" && arch == "ppc64le" { - osStrCmp = "linux-ppc64le" - } else if os == "linux" && arch == "s390x" { - osStrCmp = "linux-s390x" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" - } - - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) -} - -func TestPartialPlatformPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - Command: "echo -n os-arch", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "linux", Architecture: "i386", Command: "echo -n linux-i386"}, - {OperatingSystem: "windows", Architecture: "amd64", Command: "echo -n win-64"}, - }, - }, - } - var osStrCmp string - os := runtime.GOOS - arch := runtime.GOARCH - if os == "linux" { - osStrCmp = "linux-i386" - } else if os == "windows" && arch == "amd64" { - osStrCmp = "win-64" - } else { - osStrCmp = "os-arch" - } - - argv := []string{"--debug", "--foo", "bar"} - checkCommand(p, argv, osStrCmp, t) -} - -func TestNoPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - }, - } - argv := []string{"--debug", "--foo", "bar"} - - _, _, err := p.PrepareCommand(argv) - if err == nil { - t.Fatalf("Expected error to be returned") - } -} - -func TestNoMatchPrepareCommand(t *testing.T) { - p := &Plugin{ - Dir: "/tmp", // Unused - Metadata: &Metadata{ - Name: "test", - PlatformCommand: []PlatformCommand{ - {OperatingSystem: "no-os", Architecture: "amd64", Command: "echo -n linux-i386"}, - }, - }, - } - argv := []string{"--debug", "--foo", "bar"} - - if _, _, err := p.PrepareCommand(argv); err == nil { - t.Fatalf("Expected error to be returned") - } -} - -func TestLoadDir(t *testing.T) { - dirname := "testdata/plugdir/good/hello" - plug, err := LoadDir(dirname) - if err != nil { - t.Fatalf("error loading Hello plugin: %s", err) - } - - if plug.Dir != dirname { - t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) - } - - expect := &Metadata{ - Name: "hello", - Version: "0.1.0", - Usage: "usage", - Description: "description", - Command: "$HELM_PLUGIN_DIR/hello.sh", - IgnoreFlags: true, - Hooks: map[string]string{ - Install: "echo installing...", - }, - } - - if !reflect.DeepEqual(expect, plug.Metadata) { - t.Fatalf("Expected plugin metadata %v, got %v", expect, plug.Metadata) - } -} - -func TestLoadDirDuplicateEntries(t *testing.T) { - dirname := "testdata/plugdir/bad/duplicate-entries" - if _, err := LoadDir(dirname); err == nil { - t.Errorf("successfully loaded plugin with duplicate entries when it should've failed") - } -} - -func TestDownloader(t *testing.T) { - dirname := "testdata/plugdir/good/downloader" - plug, err := LoadDir(dirname) - if err != nil { - t.Fatalf("error loading Hello plugin: %s", err) - } - - if plug.Dir != dirname { - t.Fatalf("Expected dir %q, got %q", dirname, plug.Dir) - } - - expect := &Metadata{ - Name: "downloader", - Version: "1.2.3", - Usage: "usage", - Description: "download something", - Command: "echo Hello", - Downloaders: []Downloaders{ - { - Protocols: []string{"myprotocol", "myprotocols"}, - Command: "echo Download", - }, - }, - } - - if !reflect.DeepEqual(expect, plug.Metadata) { - t.Fatalf("Expected metadata %v, got %v", expect, plug.Metadata) - } -} - -func TestLoadAll(t *testing.T) { - - // Verify that empty dir loads: - if plugs, err := LoadAll("testdata"); err != nil { - t.Fatalf("error loading dir with no plugins: %s", err) - } else if len(plugs) > 0 { - t.Fatalf("expected empty dir to have 0 plugins") - } - - basedir := "testdata/plugdir/good" - plugs, err := LoadAll(basedir) - if err != nil { - t.Fatalf("Could not load %q: %s", basedir, err) - } - - if l := len(plugs); l != 3 { - t.Fatalf("expected 3 plugins, found %d", l) - } - - if plugs[0].Metadata.Name != "downloader" { - t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name) - } - if plugs[1].Metadata.Name != "echo" { - t.Errorf("Expected first plugin to be echo, got %q", plugs[0].Metadata.Name) - } - if plugs[2].Metadata.Name != "hello" { - t.Errorf("Expected second plugin to be hello, got %q", plugs[1].Metadata.Name) - } -} - -func TestFindPlugins(t *testing.T) { - cases := []struct { - name string - plugdirs string - expected int - }{ - { - name: "plugdirs is empty", - plugdirs: "", - expected: 0, - }, - { - name: "plugdirs isn't dir", - plugdirs: "./plugin_test.go", - expected: 0, - }, - { - name: "plugdirs doesn't have plugin", - plugdirs: ".", - expected: 0, - }, - { - name: "normal", - plugdirs: "./testdata/plugdir/good", - expected: 3, - }, - } - for _, c := range cases { - t.Run(t.Name(), func(t *testing.T) { - plugin, _ := FindPlugins(c.plugdirs) - if len(plugin) != c.expected { - t.Errorf("expected: %v, got: %v", c.expected, len(plugin)) - } - }) - } -} - -func TestSetupEnv(t *testing.T) { - name := "pequod" - base := filepath.Join("testdata/helmhome/helm/plugins", name) - - s := cli.New() - s.PluginsDirectory = "testdata/helmhome/helm/plugins" - - SetupPluginEnv(s, name, base) - for _, tt := range []struct { - name, expect string - }{ - {"HELM_PLUGIN_NAME", name}, - {"HELM_PLUGIN_DIR", base}, - } { - if got := os.Getenv(tt.name); got != tt.expect { - t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got) - } - } -} - -func TestValidatePluginData(t *testing.T) { - for i, item := range []struct { - pass bool - plug *Plugin - }{ - {true, mockPlugin("abcdefghijklmnopqrstuvwxyz0123456789_-ABC")}, - {true, mockPlugin("foo-bar-FOO-BAR_1234")}, - {false, mockPlugin("foo -bar")}, - {false, mockPlugin("$foo -bar")}, // Test leading chars - {false, mockPlugin("foo -bar ")}, // Test trailing chars - {false, mockPlugin("foo\nbar")}, // Test newline - } { - err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) - if item.pass && err != nil { - t.Errorf("failed to validate case %d: %s", i, err) - } else if !item.pass && err == nil { - t.Errorf("expected case %d to fail", i) - } - } -} - -func TestDetectDuplicates(t *testing.T) { - plugs := []*Plugin{ - mockPlugin("foo"), - mockPlugin("bar"), - } - if err := detectDuplicates(plugs); err != nil { - t.Error("no duplicates in the first set") - } - plugs = append(plugs, mockPlugin("foo")) - if err := detectDuplicates(plugs); err == nil { - t.Error("duplicates in the second set") - } -} - -func mockPlugin(name string) *Plugin { - return &Plugin{ - Metadata: &Metadata{ - Name: name, - Version: "v0.1.2", - Usage: "Mock plugin", - Description: "Mock plugin for testing", - Command: "echo mock plugin", - }, - Dir: "no-such-dir", - } -} diff --git a/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml b/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml deleted file mode 100644 index 66498be96..000000000 --- a/pkg/plugin/testdata/plugdir/bad/duplicate-entries/plugin.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: "duplicate-entries" -version: "0.1.0" -usage: "usage" -description: |- - description -command: "echo hello" -ignoreFlags: true -hooks: - install: "echo installing..." -hooks: - install: "echo installing something different" diff --git a/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml b/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml deleted file mode 100644 index c0b90379b..000000000 --- a/pkg/plugin/testdata/plugdir/good/downloader/plugin.yaml +++ /dev/null @@ -1,11 +0,0 @@ -name: "downloader" -version: "1.2.3" -usage: "usage" -description: |- - download something -command: "echo Hello" -downloaders: - - protocols: - - "myprotocol" - - "myprotocols" - command: "echo Download" diff --git a/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml b/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml deleted file mode 100644 index 8baa35b6d..000000000 --- a/pkg/plugin/testdata/plugdir/good/echo/plugin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -name: "echo" -version: "1.2.3" -usage: "echo something" -description: |- - This is a testing fixture. -command: "echo Hello" -hooks: - install: "echo Installing" diff --git a/pkg/plugin/testdata/plugdir/good/hello/hello.sh b/pkg/plugin/testdata/plugdir/good/hello/hello.sh deleted file mode 100755 index dcfd58876..000000000 --- a/pkg/plugin/testdata/plugdir/good/hello/hello.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -echo "Hello from a Helm plugin" - -echo "PARAMS" -echo $* - -$HELM_BIN ls --all - diff --git a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml b/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml deleted file mode 100644 index b857b55ee..000000000 --- a/pkg/plugin/testdata/plugdir/good/hello/plugin.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: "hello" -version: "0.1.0" -usage: "usage" -description: |- - description -command: "$HELM_PLUGIN_DIR/hello.sh" -ignoreFlags: true -hooks: - install: "echo installing..." diff --git a/pkg/postrender/exec.go b/pkg/postrender/exec.go deleted file mode 100644 index 167e737d6..000000000 --- a/pkg/postrender/exec.go +++ /dev/null @@ -1,109 +0,0 @@ -/* -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 postrender - -import ( - "bytes" - "io" - "os/exec" - "path/filepath" - - "github.com/pkg/errors" -) - -type execRender struct { - binaryPath string - args []string -} - -// NewExec returns a PostRenderer implementation that calls the provided binary. -// It returns an error if the binary cannot be found. If the path does not -// contain any separators, it will search in $PATH, otherwise it will resolve -// any relative paths to a fully qualified path -func NewExec(binaryPath string, args ...string) (PostRenderer, error) { - fullPath, err := getFullPath(binaryPath) - if err != nil { - return nil, err - } - return &execRender{fullPath, args}, nil -} - -// Run the configured binary for the post render -func (p *execRender) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { - cmd := exec.Command(p.binaryPath, p.args...) - stdin, err := cmd.StdinPipe() - if err != nil { - return nil, err - } - - var postRendered = &bytes.Buffer{} - var stderr = &bytes.Buffer{} - cmd.Stdout = postRendered - cmd.Stderr = stderr - - go func() { - defer stdin.Close() - io.Copy(stdin, renderedManifests) - }() - err = cmd.Run() - if err != nil { - return nil, errors.Wrapf(err, "error while running command %s. error output:\n%s", p.binaryPath, stderr.String()) - } - - return postRendered, nil -} - -// getFullPath returns the full filepath to the binary to execute. If the path -// does not contain any separators, it will search in $PATH, otherwise it will -// resolve any relative paths to a fully qualified path -func getFullPath(binaryPath string) (string, error) { - // NOTE(thomastaylor312): I am leaving this code commented out here. During - // the implementation of post-render, it was brought up that if we are - // relying on plugins, we should actually use the plugin system so it can - // properly handle multiple OSs. This will be a feature add in the future, - // so I left this code for reference. It can be deleted or reused once the - // feature is implemented - - // Manually check the plugin dir first - // if !strings.Contains(binaryPath, string(filepath.Separator)) { - // // First check the plugin dir - // pluginDir := helmpath.DataPath("plugins") // Default location - // // If location for plugins is explicitly set, check there - // if v, ok := os.LookupEnv("HELM_PLUGINS"); ok { - // pluginDir = v - // } - // // The plugins variable can actually contain multiple paths, so loop through those - // for _, p := range filepath.SplitList(pluginDir) { - // _, err := os.Stat(filepath.Join(p, binaryPath)) - // if err != nil && !os.IsNotExist(err) { - // return "", err - // } else if err == nil { - // binaryPath = filepath.Join(p, binaryPath) - // break - // } - // } - // } - - // Now check for the binary using the given path or check if it exists in - // the path and is executable - checkedPath, err := exec.LookPath(binaryPath) - if err != nil { - return "", errors.Wrapf(err, "unable to find binary at %s", binaryPath) - } - - return filepath.Abs(checkedPath) -} diff --git a/pkg/postrender/exec_test.go b/pkg/postrender/exec_test.go deleted file mode 100644 index 9788ed56e..000000000 --- a/pkg/postrender/exec_test.go +++ /dev/null @@ -1,193 +0,0 @@ -/* -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 postrender - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "helm.sh/helm/v3/internal/test/ensure" -) - -const testingScript = `#!/bin/sh -if [ $# -eq 0 ]; then -sed s/FOOTEST/BARTEST/g <&0 -else -sed s/FOOTEST/"$*"/g <&0 -fi -` - -func TestGetFullPath(t *testing.T) { - is := assert.New(t) - t.Run("full path resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - fullPath, err := getFullPath(testpath) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - t.Run("relative path resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - currentDir, err := os.Getwd() - require.NoError(t, err) - relative, err := filepath.Rel(currentDir, testpath) - require.NoError(t, err) - fullPath, err := getFullPath(relative) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - t.Run("binary in PATH resolves correctly", func(t *testing.T) { - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - realPath := os.Getenv("PATH") - os.Setenv("PATH", filepath.Dir(testpath)) - defer func() { - os.Setenv("PATH", realPath) - }() - - fullPath, err := getFullPath(filepath.Base(testpath)) - is.NoError(err) - is.Equal(testpath, fullPath) - }) - - // NOTE(thomastaylor312): See note in getFullPath for more details why this - // is here - - // t.Run("binary in plugin path resolves correctly", func(t *testing.T) { - // testpath, cleanup := setupTestingScript(t) - // defer cleanup() - - // realPath := os.Getenv("HELM_PLUGINS") - // os.Setenv("HELM_PLUGINS", filepath.Dir(testpath)) - // defer func() { - // os.Setenv("HELM_PLUGINS", realPath) - // }() - - // fullPath, err := getFullPath(filepath.Base(testpath)) - // is.NoError(err) - // is.Equal(testpath, fullPath) - // }) - - // t.Run("binary in multiple plugin paths resolves correctly", func(t *testing.T) { - // testpath, cleanup := setupTestingScript(t) - // defer cleanup() - - // realPath := os.Getenv("HELM_PLUGINS") - // os.Setenv("HELM_PLUGINS", filepath.Dir(testpath)+string(os.PathListSeparator)+"/another/dir") - // defer func() { - // os.Setenv("HELM_PLUGINS", realPath) - // }() - - // fullPath, err := getFullPath(filepath.Base(testpath)) - // is.NoError(err) - // is.Equal(testpath, fullPath) - // }) -} - -func TestExecRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath) - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "BARTEST") -} - -func TestNewExecWithOneArgsRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath, "ARG1") - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "ARG1") -} - -func TestNewExecWithTwoArgsRun(t *testing.T) { - if runtime.GOOS == "windows" { - // the actual Run test uses a basic sed example, so skip this test on windows - t.Skip("skipping on windows") - } - is := assert.New(t) - testpath, cleanup := setupTestingScript(t) - defer cleanup() - - renderer, err := NewExec(testpath, "ARG1", "ARG2") - require.NoError(t, err) - - output, err := renderer.Run(bytes.NewBufferString("FOOTEST")) - is.NoError(err) - is.Contains(output.String(), "ARG1 ARG2") -} - -func setupTestingScript(t *testing.T) (filepath string, cleanup func()) { - t.Helper() - - tempdir := ensure.TempDir(t) - - f, err := ioutil.TempFile(tempdir, "post-render-test.sh") - if err != nil { - t.Fatalf("unable to create tempfile for testing: %s", err) - } - - _, err = f.WriteString(testingScript) - if err != nil { - t.Fatalf("unable to write tempfile for testing: %s", err) - } - - err = f.Chmod(0755) - if err != nil { - t.Fatalf("unable to make tempfile executable for testing: %s", err) - } - - err = f.Close() - if err != nil { - t.Fatalf("unable to close tempfile after writing: %s", err) - } - - return f.Name(), func() { - os.RemoveAll(tempdir) - } -} diff --git a/pkg/postrender/postrender.go b/pkg/postrender/postrender.go deleted file mode 100644 index 3af384290..000000000 --- a/pkg/postrender/postrender.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -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 postrender contains an interface that can be implemented for custom -// post-renderers and an exec implementation that can be used for arbitrary -// binaries and scripts -package postrender - -import "bytes" - -type PostRenderer interface { - // Run expects a single buffer filled with Helm rendered manifests. It - // expects the modified results to be returned on a separate buffer or an - // error if there was an issue or failure while running the post render step - Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) -} diff --git a/pkg/provenance/doc.go b/pkg/provenance/doc.go deleted file mode 100644 index 3d2d0ea97..000000000 --- a/pkg/provenance/doc.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 provenance provides tools for establishing the authenticity of a chart. - -In Helm, provenance is established via several factors. The primary factor is the -cryptographic signature of a chart. Chart authors may sign charts, which in turn -provide the necessary metadata to ensure the integrity of the chart file, the -Chart.yaml, and the referenced Docker images. - -A provenance file is clear-signed. This provides cryptographic verification that -a particular block of information (Chart.yaml, archive file, images) have not -been tampered with or altered. To learn more, read the GnuPG documentation on -clear signatures: -https://www.gnupg.org/gph/en/manual/x135.html - -The cryptography used by Helm should be compatible with OpenGPG. For example, -you should be able to verify a signature by importing the desired public key -and using `gpg --verify`, `keybase pgp verify`, or similar: - - $ gpg --verify some.sig - gpg: Signature made Mon Jul 25 17:23:44 2016 MDT using RSA key ID 1FC18762 - gpg: Good signature from "Helm Testing (This key should only be used for testing. DO NOT TRUST.) " [ultimate] -*/ -package provenance // import "helm.sh/helm/v3/pkg/provenance" diff --git a/pkg/provenance/sign.go b/pkg/provenance/sign.go deleted file mode 100644 index c41f90c61..000000000 --- a/pkg/provenance/sign.go +++ /dev/null @@ -1,424 +0,0 @@ -/* -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 provenance - -import ( - "bytes" - "crypto" - "encoding/hex" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "golang.org/x/crypto/openpgp" //nolint - "golang.org/x/crypto/openpgp/clearsign" //nolint - "golang.org/x/crypto/openpgp/packet" //nolint - "sigs.k8s.io/yaml" - - hapi "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -var defaultPGPConfig = packet.Config{ - DefaultHash: crypto.SHA512, -} - -// SumCollection represents a collection of file and image checksums. -// -// Files are of the form: -// FILENAME: "sha256:SUM" -// Images are of the form: -// "IMAGE:TAG": "sha256:SUM" -// Docker optionally supports sha512, and if this is the case, the hash marker -// will be 'sha512' instead of 'sha256'. -type SumCollection struct { - Files map[string]string `json:"files"` - Images map[string]string `json:"images,omitempty"` -} - -// Verification contains information about a verification operation. -type Verification struct { - // SignedBy contains the entity that signed a chart. - SignedBy *openpgp.Entity - // FileHash is the hash, prepended with the scheme, for the file that was verified. - FileHash string - // FileName is the name of the file that FileHash verifies. - FileName string -} - -// Signatory signs things. -// -// Signatories can be constructed from a PGP private key file using NewFromFiles -// or they can be constructed manually by setting the Entity to a valid -// PGP entity. -// -// The same Signatory can be used to sign or validate multiple charts. -type Signatory struct { - // The signatory for this instance of Helm. This is used for signing. - Entity *openpgp.Entity - // The keyring for this instance of Helm. This is used for verification. - KeyRing openpgp.EntityList -} - -// NewFromFiles constructs a new Signatory from the PGP key in the given filename. -// -// This will emit an error if it cannot find a valid GPG keyfile (entity) at the -// given location. -// -// Note that the keyfile may have just a public key, just a private key, or -// both. The Signatory methods may have different requirements of the keys. For -// example, ClearSign must have a valid `openpgp.Entity.PrivateKey` before it -// can sign something. -func NewFromFiles(keyfile, keyringfile string) (*Signatory, error) { - e, err := loadKey(keyfile) - if err != nil { - return nil, err - } - - ring, err := loadKeyRing(keyringfile) - if err != nil { - return nil, err - } - - return &Signatory{ - Entity: e, - KeyRing: ring, - }, nil -} - -// NewFromKeyring reads a keyring file and creates a Signatory. -// -// If id is not the empty string, this will also try to find an Entity in the -// keyring whose name matches, and set that as the signing entity. It will return -// an error if the id is not empty and also not found. -func NewFromKeyring(keyringfile, id string) (*Signatory, error) { - ring, err := loadKeyRing(keyringfile) - if err != nil { - return nil, err - } - - s := &Signatory{KeyRing: ring} - - // If the ID is empty, we can return now. - if id == "" { - return s, nil - } - - // We're gonna go all GnuPG on this and look for a string that _contains_. If - // two or more keys contain the string and none are a direct match, we error - // out. - var candidate *openpgp.Entity - vague := false - for _, e := range ring { - for n := range e.Identities { - if n == id { - s.Entity = e - return s, nil - } - if strings.Contains(n, id) { - if candidate != nil { - vague = true - } - candidate = e - } - } - } - if vague { - return s, errors.Errorf("more than one key contain the id %q", id) - } - - s.Entity = candidate - return s, nil -} - -// PassphraseFetcher returns a passphrase for decrypting keys. -// -// This is used as a callback to read a passphrase from some other location. The -// given name is the Name field on the key, typically of the form: -// -// USER_NAME (COMMENT) -type PassphraseFetcher func(name string) ([]byte, error) - -// DecryptKey decrypts a private key in the Signatory. -// -// If the key is not encrypted, this will return without error. -// -// If the key does not exist, this will return an error. -// -// If the key exists, but cannot be unlocked with the passphrase returned by -// the PassphraseFetcher, this will return an error. -// -// If the key is successfully unlocked, it will return nil. -func (s *Signatory) DecryptKey(fn PassphraseFetcher) error { - if s.Entity == nil { - return errors.New("private key not found") - } else if s.Entity.PrivateKey == nil { - return errors.New("provided key is not a private key. Try providing a keyring with secret keys") - } - - // Nothing else to do if key is not encrypted. - if !s.Entity.PrivateKey.Encrypted { - return nil - } - - fname := "Unknown" - for i := range s.Entity.Identities { - if i != "" { - fname = i - break - } - } - - p, err := fn(fname) - if err != nil { - return err - } - - return s.Entity.PrivateKey.Decrypt(p) -} - -// ClearSign signs a chart with the given key. -// -// This takes the path to a chart archive file and a key, and it returns a clear signature. -// -// The Signatory must have a valid Entity.PrivateKey for this to work. If it does -// not, an error will be returned. -func (s *Signatory) ClearSign(chartpath string) (string, error) { - if s.Entity == nil { - return "", errors.New("private key not found") - } else if s.Entity.PrivateKey == nil { - return "", errors.New("provided key is not a private key. Try providing a keyring with secret keys") - } - - if fi, err := os.Stat(chartpath); err != nil { - return "", err - } else if fi.IsDir() { - return "", errors.New("cannot sign a directory") - } - - out := bytes.NewBuffer(nil) - - b, err := messageBlock(chartpath) - if err != nil { - return "", err - } - - // Sign the buffer - w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig) - if err != nil { - return "", err - } - - _, err = io.Copy(w, b) - - if err != nil { - // NB: We intentionally don't call `w.Close()` here! `w.Close()` is the method which - // actually does the PGP signing, and therefore is the part which uses the private key. - // In other words, if we call Close here, there's a risk that there's an attempt to use the - // private key to sign garbage data (since we know that io.Copy failed, `w` won't contain - // anything useful). - return "", errors.Wrap(err, "failed to write to clearsign encoder") - } - - err = w.Close() - if err != nil { - return "", errors.Wrap(err, "failed to either sign or armor message block") - } - - return out.String(), nil -} - -// Verify checks a signature and verifies that it is legit for a chart. -func (s *Signatory) Verify(chartpath, sigpath string) (*Verification, error) { - ver := &Verification{} - for _, fname := range []string{chartpath, sigpath} { - if fi, err := os.Stat(fname); err != nil { - return ver, err - } else if fi.IsDir() { - return ver, errors.Errorf("%s cannot be a directory", fname) - } - } - - // First verify the signature - sig, err := s.decodeSignature(sigpath) - if err != nil { - return ver, errors.Wrap(err, "failed to decode signature") - } - - by, err := s.verifySignature(sig) - if err != nil { - return ver, err - } - ver.SignedBy = by - - // Second, verify the hash of the tarball. - sum, err := DigestFile(chartpath) - if err != nil { - return ver, err - } - _, sums, err := parseMessageBlock(sig.Plaintext) - if err != nil { - return ver, err - } - - sum = "sha256:" + sum - basename := filepath.Base(chartpath) - if sha, ok := sums.Files[basename]; !ok { - return ver, errors.Errorf("provenance does not contain a SHA for a file named %q", basename) - } else if sha != sum { - return ver, errors.Errorf("sha256 sum does not match for %s: %q != %q", basename, sha, sum) - } - ver.FileHash = sum - ver.FileName = basename - - // TODO: when image signing is added, verify that here. - - return ver, nil -} - -func (s *Signatory) decodeSignature(filename string) (*clearsign.Block, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - - block, _ := clearsign.Decode(data) - if block == nil { - // There was no sig in the file. - return nil, errors.New("signature block not found") - } - - return block, nil -} - -// verifySignature verifies that the given block is validly signed, and returns the signer. -func (s *Signatory) verifySignature(block *clearsign.Block) (*openpgp.Entity, error) { - return openpgp.CheckDetachedSignature( - s.KeyRing, - bytes.NewBuffer(block.Bytes), - block.ArmoredSignature.Body, - ) -} - -func messageBlock(chartpath string) (*bytes.Buffer, error) { - var b *bytes.Buffer - // Checksum the archive - chash, err := DigestFile(chartpath) - if err != nil { - return b, err - } - - base := filepath.Base(chartpath) - sums := &SumCollection{ - Files: map[string]string{ - base: "sha256:" + chash, - }, - } - - // Load the archive into memory. - chart, err := loader.LoadFile(chartpath) - if err != nil { - return b, err - } - - // Buffer a hash + checksums YAML file - data, err := yaml.Marshal(chart.Metadata) - if err != nil { - return b, err - } - - // FIXME: YAML uses ---\n as a file start indicator, but this is not legal in a PGP - // clearsign block. So we use ...\n, which is the YAML document end marker. - // http://yaml.org/spec/1.2/spec.html#id2800168 - b = bytes.NewBuffer(data) - b.WriteString("\n...\n") - - data, err = yaml.Marshal(sums) - if err != nil { - return b, err - } - b.Write(data) - - return b, nil -} - -// parseMessageBlock -func parseMessageBlock(data []byte) (*hapi.Metadata, *SumCollection, error) { - // This sucks. - parts := bytes.Split(data, []byte("\n...\n")) - if len(parts) < 2 { - return nil, nil, errors.New("message block must have at least two parts") - } - - md := &hapi.Metadata{} - sc := &SumCollection{} - - if err := yaml.Unmarshal(parts[0], md); err != nil { - return md, sc, err - } - err := yaml.Unmarshal(parts[1], sc) - return md, sc, err -} - -// loadKey loads a GPG key found at a particular path. -func loadKey(keypath string) (*openpgp.Entity, error) { - f, err := os.Open(keypath) - if err != nil { - return nil, err - } - defer f.Close() - - pr := packet.NewReader(f) - return openpgp.ReadEntity(pr) -} - -func loadKeyRing(ringpath string) (openpgp.EntityList, error) { - f, err := os.Open(ringpath) - if err != nil { - return nil, err - } - defer f.Close() - return openpgp.ReadKeyRing(f) -} - -// DigestFile calculates a SHA256 hash (like Docker) for a given file. -// -// It takes the path to the archive file, and returns a string representation of -// the SHA256 sum. -// -// The intended use of this function is to generate a sum of a chart TGZ file. -func DigestFile(filename string) (string, error) { - f, err := os.Open(filename) - if err != nil { - return "", err - } - defer f.Close() - return Digest(f) -} - -// Digest hashes a reader and returns a SHA256 digest. -// -// Helm uses SHA256 as its default hash for all non-cryptographic applications. -func Digest(in io.Reader) (string, error) { - hash := crypto.SHA256.New() - if _, err := io.Copy(hash, in); err != nil { - return "", nil - } - return hex.EncodeToString(hash.Sum(nil)), nil -} diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go deleted file mode 100644 index 93c169263..000000000 --- a/pkg/provenance/sign_test.go +++ /dev/null @@ -1,345 +0,0 @@ -/* -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 provenance - -import ( - "crypto" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - - pgperrors "golang.org/x/crypto/openpgp/errors" //nolint -) - -const ( - // testKeyFile is the secret key. - // Generating keys should be done with `gpg --gen-key`. The current key - // was generated to match Go's defaults (RSA/RSA 2048). It has no pass - // phrase. Use `gpg --export-secret-keys helm-test` to export the secret. - testKeyfile = "testdata/helm-test-key.secret" - - // testPasswordKeyFile is a keyfile with a password. - testPasswordKeyfile = "testdata/helm-password-key.secret" - - // testPubfile is the public key file. - // Use `gpg --export helm-test` to export the public key. - testPubfile = "testdata/helm-test-key.pub" - - // Generated name for the PGP key in testKeyFile. - testKeyName = `Helm Testing (This key should only be used for testing. DO NOT TRUST.) ` - - testPasswordKeyName = `password key (fake) ` - - testChartfile = "testdata/hashtest-1.2.3.tgz" - - // testSigBlock points to a signature generated by an external tool. - // This file was generated with GnuPG: - // gpg --clearsign -u helm-test --openpgp testdata/msgblock.yaml - testSigBlock = "testdata/msgblock.yaml.asc" - - // testTamperedSigBlock is a tampered copy of msgblock.yaml.asc - testTamperedSigBlock = "testdata/msgblock.yaml.tampered" - - // testSumfile points to a SHA256 sum generated by an external tool. - // We always want to validate against an external tool's representation to - // verify that we haven't done something stupid. This file was generated - // with shasum. - // shasum -a 256 hashtest-1.2.3.tgz > testdata/hashtest.sha256 - testSumfile = "testdata/hashtest.sha256" -) - -// testMessageBlock represents the expected message block for the testdata/hashtest chart. -const testMessageBlock = `apiVersion: v1 -description: Test chart versioning -name: hashtest -version: 1.2.3 - -... -files: - hashtest-1.2.3.tgz: sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888 -` - -func TestMessageBlock(t *testing.T) { - out, err := messageBlock(testChartfile) - if err != nil { - t.Fatal(err) - } - got := out.String() - - if got != testMessageBlock { - t.Errorf("Expected:\n%q\nGot\n%q\n", testMessageBlock, got) - } -} - -func TestParseMessageBlock(t *testing.T) { - md, sc, err := parseMessageBlock([]byte(testMessageBlock)) - if err != nil { - t.Fatal(err) - } - - if md.Name != "hashtest" { - t.Errorf("Expected name %q, got %q", "hashtest", md.Name) - } - - if lsc := len(sc.Files); lsc != 1 { - t.Errorf("Expected 1 file, got %d", lsc) - } - - if hash, ok := sc.Files["hashtest-1.2.3.tgz"]; !ok { - t.Errorf("hashtest file not found in Files") - } else if hash != "sha256:c6841b3a895f1444a6738b5d04564a57e860ce42f8519c3be807fb6d9bee7888" { - t.Errorf("Unexpected hash: %q", hash) - } -} - -func TestLoadKey(t *testing.T) { - k, err := loadKey(testKeyfile) - if err != nil { - t.Fatal(err) - } - - if _, ok := k.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } -} - -func TestLoadKeyRing(t *testing.T) { - k, err := loadKeyRing(testPubfile) - if err != nil { - t.Fatal(err) - } - - if len(k) > 1 { - t.Errorf("Expected 1, got %d", len(k)) - } - - for _, e := range k { - if ii, ok := e.Identities[testKeyName]; !ok { - t.Errorf("Expected %s in %v", testKeyName, ii) - } - } -} - -func TestDigest(t *testing.T) { - f, err := os.Open(testChartfile) - if err != nil { - t.Fatal(err) - } - defer f.Close() - - hash, err := Digest(f) - if err != nil { - t.Fatal(err) - } - - sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } -} - -func TestNewFromFiles(t *testing.T) { - s, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - if _, ok := s.Entity.Identities[testKeyName]; !ok { - t.Errorf("Expected to load a key for user %q", testKeyName) - } -} - -func TestDigestFile(t *testing.T) { - hash, err := DigestFile(testChartfile) - if err != nil { - t.Fatal(err) - } - - sig, err := readSumFile(testSumfile) - if err != nil { - t.Fatal(err) - } - - if !strings.Contains(sig, hash) { - t.Errorf("Expected %s to be in %s", hash, sig) - } -} - -func TestDecryptKey(t *testing.T) { - k, err := NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } - - if !k.Entity.PrivateKey.Encrypted { - t.Fatal("Key is not encrypted") - } - - // We give this a simple callback that returns the password. - if err := k.DecryptKey(func(s string) ([]byte, error) { - return []byte("secret"), nil - }); err != nil { - t.Fatal(err) - } - - // Re-read the key (since we already unlocked it) - k, err = NewFromKeyring(testPasswordKeyfile, testPasswordKeyName) - if err != nil { - t.Fatal(err) - } - // Now we give it a bogus password. - if err := k.DecryptKey(func(s string) ([]byte, error) { - return []byte("secrets_and_lies"), nil - }); err == nil { - t.Fatal("Expected an error when giving a bogus passphrase") - } -} - -func TestClearSign(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - sig, err := signer.ClearSign(testChartfile) - if err != nil { - t.Fatal(err) - } - t.Logf("Sig:\n%s", sig) - - if !strings.Contains(sig, testMessageBlock) { - t.Errorf("expected message block to be in sig: %s", sig) - } -} - -// failSigner always fails to sign and returns an error -type failSigner struct{} - -func (s failSigner) Public() crypto.PublicKey { - return nil -} - -func (s failSigner) Sign(_ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) { - return nil, fmt.Errorf("always fails") -} - -func TestClearSignError(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - // ensure that signing always fails - signer.Entity.PrivateKey.PrivateKey = failSigner{} - - sig, err := signer.ClearSign(testChartfile) - if err == nil { - t.Fatal("didn't get an error from ClearSign but expected one") - } - - if sig != "" { - t.Fatalf("expected an empty signature after failed ClearSign but got %q", sig) - } -} - -func TestDecodeSignature(t *testing.T) { - // Unlike other tests, this does a round-trip test, ensuring that a signature - // generated by the library can also be verified by the library. - - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - sig, err := signer.ClearSign(testChartfile) - if err != nil { - t.Fatal(err) - } - - f, err := ioutil.TempFile("", "helm-test-sig-") - if err != nil { - t.Fatal(err) - } - - tname := f.Name() - defer func() { - os.Remove(tname) - }() - f.WriteString(sig) - f.Close() - - sig2, err := signer.decodeSignature(tname) - if err != nil { - t.Fatal(err) - } - - by, err := signer.verifySignature(sig2) - if err != nil { - t.Fatal(err) - } - - if _, ok := by.Identities[testKeyName]; !ok { - t.Errorf("Expected identity %q", testKeyName) - } -} - -func TestVerify(t *testing.T) { - signer, err := NewFromFiles(testKeyfile, testPubfile) - if err != nil { - t.Fatal(err) - } - - if ver, err := signer.Verify(testChartfile, testSigBlock); err != nil { - t.Errorf("Failed to pass verify. Err: %s", err) - } else if len(ver.FileHash) == 0 { - t.Error("Verification is missing hash.") - } else if ver.SignedBy == nil { - t.Error("No SignedBy field") - } else if ver.FileName != filepath.Base(testChartfile) { - t.Errorf("FileName is unexpectedly %q", ver.FileName) - } - - if _, err = signer.Verify(testChartfile, testTamperedSigBlock); err == nil { - t.Errorf("Expected %s to fail.", testTamperedSigBlock) - } - - switch err.(type) { - case pgperrors.SignatureError: - t.Logf("Tampered sig block error: %s (%T)", err, err) - default: - t.Errorf("Expected invalid signature error, got %q (%T)", err, err) - } -} - -// readSumFile reads a file containing a sum generated by the UNIX shasum tool. -func readSumFile(sumfile string) (string, error) { - data, err := ioutil.ReadFile(sumfile) - if err != nil { - return "", err - } - - sig := string(data) - parts := strings.SplitN(sig, " ", 2) - return parts[0], nil -} diff --git a/pkg/provenance/testdata/hashtest-1.2.3.tgz b/pkg/provenance/testdata/hashtest-1.2.3.tgz deleted file mode 100644 index 7bbc533cae13f1b34f023d7d19f0f30214be2768..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 399 zcmV;A0dW2wiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL2*YU3~vKy%ht%#zD4ELzziG32_vw~)|lQS7NCD$7C|I}N4( zUgD6nm_jH?Q%dzNvW3OW;CW+f88u;~fB&@%#5c0GqjMvK5XE%buR@67Dzg0by5G<8 zVw#OWoIR6wXd}rm5+}zR7WYk%-rQn{rg3xVGFD+MgYnegFEJ97+rEHq_l$9hIa%Wz z_I<;;{ppP7e8RrEp2WvE_}@v}V*Hc=aLj)pCIkN`xhRtVm*D9+Df^Q-_|G-k+9=lq zJa>#^{^zsV(EqGR-~Wp+h8szQ!9}13cJCk78Xc@!V=Lnncv(J3g^{x{EVYRcTko4d z5u!$GLD^;nwWLxTr~X1fE6H>SUXp&ZFI~mP;84l!-v#{k0q@NtR=H3V8=T1;oo-SPr+@$c$lCNz)uj=h_{vmYR}R79|B^49bnLhz<~;GETy54> zJ{Un-NN{0L)0xeL>XtE8lE)K1V*j#;5_-R{8()WB@?m@{1uqs{rEWsC&J zRmZ@RDQS&Ghr<{!euy9->k0{4u9e#Z*mqVT)qv`$>XLtkE*e&$*p|+O-~&yXl=?-` z=&fw1Zbf?ypPu20Z01o%laoFJ z|4X8nKT%m6;wfosI&+%}1#*mKd@ol!tS{p)EswcxGU z4FklxTTba+1dg@RKBy3=To~w|6WkMtOLZ$Km^_zTW~kgVINa=Lkb=>L;l-xN#2x!F z0SImLEhGjgcoB8Mw&hCh>!+Dm*){uH4;+*RAZxR_lbT8MY|u@nM4zjOgyvtp|9>pL zC$XT#l0!dRic@j(Lz!94Z3F41nd?hpBn=c_0%I^JR?K@Vd0%sadsKYnXh^=e-&c5F z@*>Gx?1e7*X!sNtUsBY(Zxr>QT`VI{h0zKn07QgU!PDpvh8j#o15LD`!)Q083{cju zT$_X4Red8llclG(;8L0hbuA&V$|WzX`V%!4?8$pcN|K}qh*0#QkyTP=Ni!_nBgX}n zWj8VR7CE0)4G%nrOh^r=RX}7z3@U%F*?M%Pa$LqPu$469l3prHFyIA+xIP%-({hGw z>a^H^#xTz(W}D6#T2x!_p$U%{c?9=R9{FK@-f;T{04VbCA4-AtjX#hCY-#t^xoW+9 zEG?-^a<8^X;RWr$X=YTe#u<@c!F-{3T@cR(fdnQV7ISMq_hlpyLfoe@DKN!!5zf`0 zc8`Edp`Rhdr0!Ck6^mB>G9QX`(Jq%rQikyfBG=|QjbnJmL1SJ`p?P$XN5{od+MWO0 zgAVcrhHbdO(v4Utuobm-qo_E|4te9{^(V^T%Oy;Q@QI!6uoF1cIiLaE_Xhal)62Du z_OWO($sROUaT7Hmo)6^Aavj`H9n#A?w8W1@~F@iWVlLV5~Vv?6d}b8~lZa%3QDWqBYdW?^e( zDIh#%VQXbTXk~0|E^}x;i2*kR69EbUAq4_h58Flp8v_Lk2?z%R1r-Vj2nz)k0s{d6 z0v-VZ7k~f?2@raT7BR66qOsaZ2mUs<*aiSz2B-<))eW~`+bQkAx&5TEVV`A8`m`{Y zSmSfX*KF6eml(Vp_=4AJ7mraMJnJsafoZ0D6zOu-HyZPmM!P&SNfg#jtW4kf$xE>6 zqT-b8-_~w%jH&Qww@M=rdeW|uE<wy39o%k%a(^us7T@st#`i(Ui z*xr4R90Psj`>>-uZL>1A714Akg!NeNbZ%lEa;p6kukRzIrPiGogERBnmxSS7wP_#$ z#C@BIr*xQB4QYJUg#qY4D}$rVaR&9|Kcz64kg+eQHh0KBRgK#xZ0PuECJBt1~f7q^(%qm)_k zxuW~o^7>9uDuSKZA;I=0#N?Xvkt3JK+wF49bIP6ObEo*P!b)?9e_hX%Tn?df-p)&< zqT<7SGb=Z?w5s^#CDnV@H07Vty`YfU(I7aG-K)3I zKxt)1TxdS$uN+_NjQkqHY3>hiKqE7&pkZ8d z3Y|qYX72}77>9M2ald@7Mo9(+G{X>dA~;-5roNw+lhtTi0shrsNw{QRNOH@%0x~#E zvZt$Z;Eq}3GX$6+`5(Dhr{f8dW6E|T;}o7J_0i$vDQ2<@8c-lv>wx-Y59`VrJ#Dz^ zl5#Wqj)1qwzDxnOL!;ELc_0ZQC)a*Xx>#W?9Sf`Q9TAJpDGS~u*rF{CEzAtw@0GQ< zk7f4hM^y@22|b3JKJZMz&x}nlSm+j|A6#V_#dip;@C#3GXP`n?+5%U#=N~%Bv`KfU zHU^eE{q&<*x)Brj^O#ULldk_LSbpv%L_Q0DwLSDc^qzxGP-DVc>weL@slnHJ0jW^l z17vXJjW_n-YovP(B_N$cwMaNx7*)$Jgsz??{*qg8?p-5uL`Yii@&msQh=3MsE$=hc zna`a~Me6aLP3~8@&FRYovboD4qQ))eSIh8WkJzUL#aG2h*=8y82UetPl@W#MjB2a3 zm)$tRSTuWb5i%+PA*N1%x{@PTRoF*KLZz>D6zBWtCEzq+jI=m%kslwY@0v2d;`M{5 z?7Db6I+k({A92bs<23ojz&nCJ1aUv6N0%vFOBZ{l4DOSOLCbp@O-2>n5i?Wtz;&e+ za+Ua)Ec}0ba}P>@`33`wx_#qp^LIrbgGr{zD{)U7V4?m+`9@n0?|!x|CUXHh9yzf& zA6B2uSC$CcJj6ch_qJyttDQ3~@TYOCd!{c24TnHG8-)Bxi&nROOl`D@0Urby0SW*K z1p-(P+eQK#3;+rV5PFFgF|iGzv0o$y{xFcdf`-DHXxm;#2MuT(f>rhV#>PO-Z5>|W zG)iO zHQIznJ)0H>Pa%p>s9Xm{Q;3{J-$KTX+UI;!&+3wPwy|!!=Nf3GR(3E6CVJ)}N-It> zR8W`wSeH{d12&%hE367sJQ7P@m8a+mNK zWH;!lmfFOUqlP7ucV#NaNvQz;9#qeU7V>yl3mQtp%j+q0C%{=t(~sL()Xqu};Spe} YR26Mae070tz>^S098h1bS^5J0SyFKmTjH^2mr{k15wFPQdpTAAEclY4oV^-7nTy5x$&xF;PC4il}o-Pk4@#$knlp#(|J0GE?qli_lr;7-o zyY8vBsN_GJe;#w<`JdR7riNL&RJlcS)FG+W=91;dYS6NZ2tY?kZ8Sw9{r=e4|L3E{ zRod|EPC!PWgW&pe&4qiqKQAijj;G~fyjcC^m%0p54Tn{h%YFKd0VC4n=#~SRc@BVd znj66*)Om%*SEQfX{1*Tb0RRECT}WkYZ6H)-b98BLXCNq4XlZjGYh`&Lb7*gMY-AvB zZftoVVr3w8b7f>8W^ZyJbY*jNX>MmOAVg0fPES-IR8mz_R4yqXJZNQXZ7p6uVakV zT7GGV$jaKjyjfI_a~N1!Hk?5C$0wa&4)R=i$v7t&ZMycW#RkavpF%A?>MTT2anNDzOQUm<++zEOykJ9-@&c2QXq3owqf7fek`=L@+7iF zv;IW2Q>Q&r+V@cWDF&hAUUsCKlDinerKgvJUJCl$5gjb7NhM{mBP%!M^mX-iS8xFf zuLB{@MDqvtZzF#Bxd9CXSC(y_0SExW>8~h=U8|!do4*OJj2u#!KDe3v+1T+aVzU5di=Ji2)x37y$|Z2?YXImTjH_8w>yn2@r%kznCAv zhhp0`2mpxVj5j%o&5i)?`r7iES|8dA@p2kk@+XS(tjBGN)6>tm^=gayCn`gTEC*K74Y~{I_PREk) z)PstIMx1RxB@cK8%Mey%;nVnKriAKUk2Ky?dBMG3uXItKL$3N(#3P^pQa*K$l)wUy F^>pMLK0g2e diff --git a/pkg/provenance/testdata/helm-test-key.secret b/pkg/provenance/testdata/helm-test-key.secret deleted file mode 100644 index a966aef93ed97d01d764f29940738df6df2d9d24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2545 zcmVclY4oV^-7nTy5x$&xF;PC4il}o-Pk4@#$knlp#(|J0GE?qli_lr;7-o zyY8vBsN_GJe;#w<`JdR7riNL&RJlcS)FG+W=91;dYS6NZ2tY?kZ8Sw9{r=e4|L3E{ zRod|EPC!PWgW&pe&4qiqKQAijj;G~fyjcC^m%0p54Tn{h%YFKd0VC4n=#~SRc@BVd znj66*)Om%*SEQfX{1*Tb0RRC22mUT~!#(ymA#eaSp1lpODzX${Vf^l{qDyu}xC-Z; zRnH<54GSVm<$?Ua1k#(+mu~3_*CIx=sPuoZB#9t`5)>)SncaZ0<~%)I$~BM-5aP3W z%`ewoaI;P40uHnDeE!9-_o2Lr{wDfL45jGGU-JZ36T9ToJqMX(TnRN-EvGi{o6aI#oT_2HU(J8=theYZsj5h?ml@F2 zqCpxqkdZi=~i+&Z}q^cR< zq>lNT5cnJ5X@K!3vOww0B>@Bg*7x*i59vbegj}$ELl?K2l`+`uY;jn;@-#}^!(c8$ z&Y`@LLxZ_Y>^#gGbxsy-2s=w7cVmR@z_%b#0_e^qDmIrpKw6U7N;6^TN}@&nxKj6i zje++&m}XQA&G8O8FX86?Frxrjmu5ktfDRyHBb|j&n&H#v>T!Mdmk8Y#1OV>P(*gow;}0v-BdsmdUSV3M9tIkRO0OTBw16eYCzxs>OEG!?i}$^8yY+hFlb3GJ~F z@#2Vimrfeb0(o3X?>!tSIROL!mGC1>cHXGVp;VD$oE{N!h=IF(C(PNLd6^nZO^!ix zHnE%@Y*d~bJl_M}WW0D1EM+&xdQI5#y67>-8{P4^*?j9-RL!3>e89fC4fbJFTGXY* zQA`Z&jZ*qV!0N>p>(<2RFPDhHj^h*B*O(i139Dwv{>MY%puY021Or@)I~ufINM&qo zAXH^@bZKs9AShI5X>%ZJWqBZTXm53FWFT*DY^`Z*m}XWpi|CZf7na zL{A`2PgEdOQdLt_E-4^9Xk~0|Ep%mbbZKs9Kxk!bZ7y?YK8XQ01QP)Y03iheSC(y_ z0viJb3ke7Z0|gZd2?z@X76JnS00JHX0vCV)3JDN|JHMD8!G~grMF;@2`RLQ-(ihS@ zk(ZDi8>PUMNBttVp^;f=(#~5ORUCP*V~o^Verbou%G$oXSyYd67+6|1oIv=;C!Jsp z@?3ezI42oxy7sHZ2FUrJLM=V)2rULvozb@g^vZ~+Ui10l{t^9T2DBYydv1DFI?mTjH^2mrz9 zuPBIJtD_~GzX`6498#D*yg_W@HI~u}LQvFZ zjHz2I7O5nm<}d0gU&SbRw}dGu2{gYWzK!Qb2tL4r=Ttf(&pz_gadeY}n}E@spby2h zn?Jq8s}cOpE&?`36cTnn-abrV^*hkY1rlNa6>W(?OAePZXMfE&?IzWku7z=T;E66)b)o_po?XSOFY;U!8IY8l|z)F~<`!sdiAt3+}0RRC2 z2mUKrERA52qzq^qU4-%uqeMA@h`$YTvMnKwO3MFdg819*{h|i5{tcC;Av-jm`%7`? zISDa>*_u$~x5)kpVt_aYB_e`#K)Xd5tcJ05BQ>ps?qeo`#OS{-ilRZ+9`nljqxsy1 zp;Lu#*--$l?6qncfhI%m^w(3lOt}ywL5?%+_Ov|T=-O)O#|1&>=}51a%Sb~KTR2_K z!};{n;NgPO;;v%0;n-j>b-Y|l)x=^&d84lKmr8o*+q*$Sul50u>9%n+e!b~90-}xc znpRXgsh*hBzGXpmnXaxdFnD1FEnbiC?537`DY#mL7&iHNEY4|+!A|s9dFssoYIy_z z+imR1K+cnVPeX&M1X~ed#U~gsS6HR0zgm1NR~u@{BN;*5Gvl)42%Kq{=4gSyFIAOo zw)ZGKn^3RZn+iXfb*zL1mnJJGsvTLnDB5DF8)!;+KX&@(mJ7k5LnTlXYxI(#)c`4{ zo6I4Djv|uTRI{JJ9glvUHq0WkzV7H91OVbY6u%#c1Z-!^cIjhIC)Ek7Hx7cRvtc6M zYLV(#kP^D1#2+7pDzLBFanZFqRw>On{`4qC48A)&{zk{n0CZEKY$SfN1Rk^!_V}?Oa05R<~;U7Vou+rQZcj7^Zr@2q2}K8g2gzsQ|Y$Hp^5`riTL^4T#Q?}_!b9ge@36zaVNe`|(D|D@%b z?q#ETmMPVDW6=SC^ zp>(H_BkTP!*5u$7;(xt$0Z3AJ%*#wE`2MxJYbiqwMV z55Sy@$Oto*a)E^@IG(B#1R_APzVCxJvjDnj!`b?U+KFs4f-?w1(lA6SB!RPKJ5Wx? zlJL}niiAd-Z9pXtcm~T5R%GGR_+_Sq>RpdC-c)(Py hashtest.sha256 diff --git a/pkg/pusher/doc.go b/pkg/pusher/doc.go deleted file mode 100644 index df89ab112..000000000 --- a/pkg/pusher/doc.go +++ /dev/null @@ -1,21 +0,0 @@ -/* -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 pusher provides a generalized tool for uploading data by scheme. -This provides a method by which the plugin system can load arbitrary protocol -handlers based upon a URL scheme. -*/ -package pusher diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go deleted file mode 100644 index 7c90e85a4..000000000 --- a/pkg/pusher/ocipusher.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -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 pusher - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/registry" -) - -// OCIPusher is the default OCI backend handler -type OCIPusher struct { - opts options -} - -// Push performs a Push from repo.Pusher. -func (pusher *OCIPusher) Push(chartRef, href string, options ...Option) error { - for _, opt := range options { - opt(&pusher.opts) - } - return pusher.push(chartRef, href) -} - -func (pusher *OCIPusher) push(chartRef, href string) error { - stat, err := os.Stat(chartRef) - if err != nil { - if os.IsNotExist(err) { - return errors.Errorf("%s: no such file", chartRef) - } - return err - } - if stat.IsDir() { - return errors.New("cannot push directory, must provide chart archive (.tgz)") - } - - meta, err := loader.Load(chartRef) - if err != nil { - return err - } - - client := pusher.opts.registryClient - - chartBytes, err := ioutil.ReadFile(chartRef) - if err != nil { - return err - } - - var pushOpts []registry.PushOption - provRef := fmt.Sprintf("%s.prov", chartRef) - if _, err := os.Stat(provRef); err == nil { - provBytes, err := ioutil.ReadFile(provRef) - if err != nil { - return err - } - pushOpts = append(pushOpts, registry.PushOptProvData(provBytes)) - } - - ref := fmt.Sprintf("%s:%s", - path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name), - meta.Metadata.Version) - - _, err = client.Push(chartBytes, ref, pushOpts...) - return err -} - -// NewOCIPusher constructs a valid OCI client as a Pusher -func NewOCIPusher(ops ...Option) (Pusher, error) { - registryClient, err := registry.NewClient( - registry.ClientOptEnableCache(true), - ) - if err != nil { - return nil, err - } - - client := OCIPusher{ - opts: options{ - registryClient: registryClient, - }, - } - - for _, opt := range ops { - opt(&client.opts) - } - - return &client, nil -} diff --git a/pkg/pusher/ocipusher_test.go b/pkg/pusher/ocipusher_test.go deleted file mode 100644 index 27be15b5d..000000000 --- a/pkg/pusher/ocipusher_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -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 pusher - -import ( - "testing" -) - -func TestNewOCIPusher(t *testing.T) { - testfn := func(ops *options) { - if ops.registryClient == nil { - t.Fatalf("the OCIPusher's registryClient should not be null") - } - } - - p, err := NewOCIPusher(testfn) - if p == nil { - t.Error("NewOCIPusher returned nil") - } - if err != nil { - t.Error(err) - } -} diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go deleted file mode 100644 index 30c6af97c..000000000 --- a/pkg/pusher/pusher.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -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 pusher - -import ( - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -// options are generic parameters to be provided to the pusher during instantiation. -// -// Pushers may or may not ignore these parameters as they are passed in. -type options struct { - registryClient *registry.Client -} - -// Option allows specifying various settings configurable by the user for overriding the defaults -// used when performing Push operations with the Pusher. -type Option func(*options) - -// WithRegistryClient sets the registryClient option. -func WithRegistryClient(client *registry.Client) Option { - return func(opts *options) { - opts.registryClient = client - } -} - -// Pusher is an interface to support upload to the specified URL. -type Pusher interface { - // Push file content by url string - Push(chartRef, url string, options ...Option) error -} - -// Constructor is the function for every pusher which creates a specific instance -// according to the configuration -type Constructor func(options ...Option) (Pusher, error) - -// Provider represents any pusher and the schemes that it supports. -type Provider struct { - Schemes []string - New Constructor -} - -// Provides returns true if the given scheme is supported by this Provider. -func (p Provider) Provides(scheme string) bool { - for _, i := range p.Schemes { - if i == scheme { - return true - } - } - return false -} - -// Providers is a collection of Provider objects. -type Providers []Provider - -// ByScheme returns a Provider that handles the given scheme. -// -// If no provider handles this scheme, this will return an error. -func (p Providers) ByScheme(scheme string) (Pusher, error) { - for _, pp := range p { - if pp.Provides(scheme) { - return pp.New() - } - } - return nil, errors.Errorf("scheme %q not supported", scheme) -} - -var ociProvider = Provider{ - Schemes: []string{registry.OCIScheme}, - New: NewOCIPusher, -} - -// All finds all of the registered pushers as a list of Provider instances. -// Currently, just the built-in pushers are collected. -func All(settings *cli.EnvSettings) Providers { - result := Providers{ociProvider} - return result -} diff --git a/pkg/pusher/pusher_test.go b/pkg/pusher/pusher_test.go deleted file mode 100644 index d43e6c9ec..000000000 --- a/pkg/pusher/pusher_test.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -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 pusher - -import ( - "testing" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -func TestProvider(t *testing.T) { - p := Provider{ - []string{"one", "three"}, - func(_ ...Option) (Pusher, error) { return nil, nil }, - } - - if !p.Provides("three") { - t.Error("Expected provider to provide three") - } -} - -func TestProviders(t *testing.T) { - ps := Providers{ - {[]string{"one", "three"}, func(_ ...Option) (Pusher, error) { return nil, nil }}, - {[]string{"two", "four"}, func(_ ...Option) (Pusher, error) { return nil, nil }}, - } - - if _, err := ps.ByScheme("one"); err != nil { - t.Error(err) - } - if _, err := ps.ByScheme("four"); err != nil { - t.Error(err) - } - - if _, err := ps.ByScheme("five"); err == nil { - t.Error("Did not expect handler for five") - } -} - -func TestAll(t *testing.T) { - env := cli.New() - all := All(env) - if len(all) != 1 { - t.Errorf("expected 1 provider (OCI), got %d", len(all)) - } -} - -func TestByScheme(t *testing.T) { - env := cli.New() - g := All(env) - if _, err := g.ByScheme(registry.OCIScheme); err != nil { - t.Error(err) - } -} diff --git a/pkg/registry/client.go b/pkg/registry/client.go deleted file mode 100644 index c1004f956..000000000 --- a/pkg/registry/client.go +++ /dev/null @@ -1,643 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -import ( - "context" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "sort" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/containerd/containerd/remotes" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "oras.land/oras-go/pkg/auth" - dockerauth "oras.land/oras-go/pkg/auth/docker" - "oras.land/oras-go/pkg/content" - "oras.land/oras-go/pkg/oras" - "oras.land/oras-go/pkg/registry" - registryremote "oras.land/oras-go/pkg/registry/remote" - registryauth "oras.land/oras-go/pkg/registry/remote/auth" - - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/helmpath" -) - -// See https://github.com/helm/helm/issues/10166 -const registryUnderscoreMessage = ` -OCI artifact references (e.g. tags) do not support the plus sign (+). To support -storing semantic versions, Helm adopts the convention of changing plus (+) to -an underscore (_) in chart version tags when pushing to a registry and back to -a plus (+) when pulling from a registry.` - -type ( - // Client works with OCI-compliant registries - Client struct { - debug bool - enableCache bool - // path to repository config file e.g. ~/.docker/config.json - credentialsFile string - out io.Writer - authorizer auth.Client - registryAuthorizer *registryauth.Client - resolver remotes.Resolver - } - - // ClientOption allows specifying various settings configurable by the user for overriding the defaults - // used when creating a new default client - ClientOption func(*Client) -) - -// NewClient returns a new registry client with config -func NewClient(options ...ClientOption) (*Client, error) { - client := &Client{ - out: ioutil.Discard, - } - for _, option := range options { - option(client) - } - if client.credentialsFile == "" { - client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) - } - if client.authorizer == nil { - authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile) - if err != nil { - return nil, err - } - client.authorizer = authClient - } - if client.resolver == nil { - headers := http.Header{} - headers.Set("User-Agent", version.GetUserAgent()) - opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)} - resolver, err := client.authorizer.ResolverWithOpts(opts...) - if err != nil { - return nil, err - } - client.resolver = resolver - } - - // allocate a cache if option is set - var cache registryauth.Cache - if client.enableCache { - cache = registryauth.DefaultCache - } - if client.registryAuthorizer == nil { - client.registryAuthorizer = ®istryauth.Client{ - Header: http.Header{ - "User-Agent": {version.GetUserAgent()}, - }, - Cache: cache, - Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) { - dockerClient, ok := client.authorizer.(*dockerauth.Client) - if !ok { - return registryauth.EmptyCredential, errors.New("unable to obtain docker client") - } - - username, password, err := dockerClient.Credential(reg) - if err != nil { - return registryauth.EmptyCredential, errors.New("unable to retrieve credentials") - } - - // A blank returned username and password value is a bearer token - if username == "" && password != "" { - return registryauth.Credential{ - RefreshToken: password, - }, nil - } - - return registryauth.Credential{ - Username: username, - Password: password, - }, nil - - }, - } - - } - return client, nil -} - -// ClientOptDebug returns a function that sets the debug setting on client options set -func ClientOptDebug(debug bool) ClientOption { - return func(client *Client) { - client.debug = debug - } -} - -// ClientOptEnableCache returns a function that sets the enableCache setting on a client options set -func ClientOptEnableCache(enableCache bool) ClientOption { - return func(client *Client) { - client.enableCache = enableCache - } -} - -// ClientOptWriter returns a function that sets the writer setting on client options set -func ClientOptWriter(out io.Writer) ClientOption { - return func(client *Client) { - client.out = out - } -} - -// ClientOptCredentialsFile returns a function that sets the credentialsFile setting on a client options set -func ClientOptCredentialsFile(credentialsFile string) ClientOption { - return func(client *Client) { - client.credentialsFile = credentialsFile - } -} - -type ( - // LoginOption allows specifying various settings on login - LoginOption func(*loginOperation) - - loginOperation struct { - username string - password string - insecure bool - } -) - -// Login logs into a registry -func (c *Client) Login(host string, options ...LoginOption) error { - operation := &loginOperation{} - for _, option := range options { - option(operation) - } - authorizerLoginOpts := []auth.LoginOption{ - auth.WithLoginContext(ctx(c.out, c.debug)), - auth.WithLoginHostname(host), - auth.WithLoginUsername(operation.username), - auth.WithLoginSecret(operation.password), - auth.WithLoginUserAgent(version.GetUserAgent()), - } - if operation.insecure { - authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) - } - if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil { - return err - } - fmt.Fprintln(c.out, "Login Succeeded") - return nil -} - -// LoginOptBasicAuth returns a function that sets the username/password settings on login -func LoginOptBasicAuth(username string, password string) LoginOption { - return func(operation *loginOperation) { - operation.username = username - operation.password = password - } -} - -// LoginOptInsecure returns a function that sets the insecure setting on login -func LoginOptInsecure(insecure bool) LoginOption { - return func(operation *loginOperation) { - operation.insecure = insecure - } -} - -type ( - // LogoutOption allows specifying various settings on logout - LogoutOption func(*logoutOperation) - - logoutOperation struct{} -) - -// Logout logs out of a registry -func (c *Client) Logout(host string, opts ...LogoutOption) error { - operation := &logoutOperation{} - for _, opt := range opts { - opt(operation) - } - if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil { - return err - } - fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) - return nil -} - -type ( - // PullOption allows specifying various settings on pull - PullOption func(*pullOperation) - - // PullResult is the result returned upon successful pull. - PullResult struct { - Manifest *descriptorPullSummary `json:"manifest"` - Config *descriptorPullSummary `json:"config"` - Chart *descriptorPullSummaryWithMeta `json:"chart"` - Prov *descriptorPullSummary `json:"prov"` - Ref string `json:"ref"` - } - - descriptorPullSummary struct { - Data []byte `json:"-"` - Digest string `json:"digest"` - Size int64 `json:"size"` - } - - descriptorPullSummaryWithMeta struct { - descriptorPullSummary - Meta *chart.Metadata `json:"meta"` - } - - pullOperation struct { - withChart bool - withProv bool - ignoreMissingProv bool - } -) - -// Pull downloads a chart from a registry -func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) { - parsedRef, err := parseReference(ref) - if err != nil { - return nil, err - } - - operation := &pullOperation{ - withChart: true, // By default, always download the chart layer - } - for _, option := range options { - option(operation) - } - if !operation.withChart && !operation.withProv { - return nil, errors.New( - "must specify at least one layer to pull (chart/prov)") - } - memoryStore := content.NewMemory() - allowedMediaTypes := []string{ - ConfigMediaType, - } - minNumDescriptors := 1 // 1 for the config - if operation.withChart { - minNumDescriptors++ - allowedMediaTypes = append(allowedMediaTypes, ChartLayerMediaType, LegacyChartLayerMediaType) - } - if operation.withProv { - if !operation.ignoreMissingProv { - minNumDescriptors++ - } - allowedMediaTypes = append(allowedMediaTypes, ProvLayerMediaType) - } - - var descriptors, layers []ocispec.Descriptor - registryStore := content.Registry{Resolver: c.resolver} - - manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "", - oras.WithPullEmptyNameAllowed(), - oras.WithAllowedMediaTypes(allowedMediaTypes), - oras.WithLayerDescriptors(func(l []ocispec.Descriptor) { - layers = l - })) - if err != nil { - return nil, err - } - - descriptors = append(descriptors, manifest) - descriptors = append(descriptors, layers...) - - numDescriptors := len(descriptors) - if numDescriptors < minNumDescriptors { - return nil, fmt.Errorf("manifest does not contain minimum number of descriptors (%d), descriptors found: %d", - minNumDescriptors, numDescriptors) - } - var configDescriptor *ocispec.Descriptor - var chartDescriptor *ocispec.Descriptor - var provDescriptor *ocispec.Descriptor - for _, descriptor := range descriptors { - d := descriptor - switch d.MediaType { - case ConfigMediaType: - configDescriptor = &d - case ChartLayerMediaType: - chartDescriptor = &d - case ProvLayerMediaType: - provDescriptor = &d - case LegacyChartLayerMediaType: - chartDescriptor = &d - fmt.Fprintf(c.out, "Warning: chart media type %s is deprecated\n", LegacyChartLayerMediaType) - } - } - if configDescriptor == nil { - return nil, fmt.Errorf("could not load config with mediatype %s", ConfigMediaType) - } - if operation.withChart && chartDescriptor == nil { - return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", - ChartLayerMediaType) - } - var provMissing bool - if operation.withProv && provDescriptor == nil { - if operation.ignoreMissingProv { - provMissing = true - } else { - return nil, fmt.Errorf("manifest does not contain a layer with mediatype %s", - ProvLayerMediaType) - } - } - result := &PullResult{ - Manifest: &descriptorPullSummary{ - Digest: manifest.Digest.String(), - Size: manifest.Size, - }, - Config: &descriptorPullSummary{ - Digest: configDescriptor.Digest.String(), - Size: configDescriptor.Size, - }, - Chart: &descriptorPullSummaryWithMeta{}, - Prov: &descriptorPullSummary{}, - Ref: parsedRef.String(), - } - var getManifestErr error - if _, manifestData, ok := memoryStore.Get(manifest); !ok { - getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest) - } else { - result.Manifest.Data = manifestData - } - if getManifestErr != nil { - return nil, getManifestErr - } - var getConfigDescriptorErr error - if _, configData, ok := memoryStore.Get(*configDescriptor); !ok { - getConfigDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest) - } else { - result.Config.Data = configData - var meta *chart.Metadata - if err := json.Unmarshal(configData, &meta); err != nil { - return nil, err - } - result.Chart.Meta = meta - } - if getConfigDescriptorErr != nil { - return nil, getConfigDescriptorErr - } - if operation.withChart { - var getChartDescriptorErr error - if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok { - getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest) - } else { - result.Chart.Data = chartData - result.Chart.Digest = chartDescriptor.Digest.String() - result.Chart.Size = chartDescriptor.Size - } - if getChartDescriptorErr != nil { - return nil, getChartDescriptorErr - } - } - if operation.withProv && !provMissing { - var getProvDescriptorErr error - if _, provData, ok := memoryStore.Get(*provDescriptor); !ok { - getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest) - } else { - result.Prov.Data = provData - result.Prov.Digest = provDescriptor.Digest.String() - result.Prov.Size = provDescriptor.Size - } - if getProvDescriptorErr != nil { - return nil, getProvDescriptorErr - } - } - - fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) - fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) - - if strings.Contains(result.Ref, "_") { - fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) - fmt.Fprint(c.out, registryUnderscoreMessage+"\n") - } - - return result, nil -} - -// PullOptWithChart returns a function that sets the withChart setting on pull -func PullOptWithChart(withChart bool) PullOption { - return func(operation *pullOperation) { - operation.withChart = withChart - } -} - -// PullOptWithProv returns a function that sets the withProv setting on pull -func PullOptWithProv(withProv bool) PullOption { - return func(operation *pullOperation) { - operation.withProv = withProv - } -} - -// PullOptIgnoreMissingProv returns a function that sets the ignoreMissingProv setting on pull -func PullOptIgnoreMissingProv(ignoreMissingProv bool) PullOption { - return func(operation *pullOperation) { - operation.ignoreMissingProv = ignoreMissingProv - } -} - -type ( - // PushOption allows specifying various settings on push - PushOption func(*pushOperation) - - // PushResult is the result returned upon successful push. - PushResult struct { - Manifest *descriptorPushSummary `json:"manifest"` - Config *descriptorPushSummary `json:"config"` - Chart *descriptorPushSummaryWithMeta `json:"chart"` - Prov *descriptorPushSummary `json:"prov"` - Ref string `json:"ref"` - } - - descriptorPushSummary struct { - Digest string `json:"digest"` - Size int64 `json:"size"` - } - - descriptorPushSummaryWithMeta struct { - descriptorPushSummary - Meta *chart.Metadata `json:"meta"` - } - - pushOperation struct { - provData []byte - strictMode bool - } -) - -// Push uploads a chart to a registry. -func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResult, error) { - parsedRef, err := parseReference(ref) - if err != nil { - return nil, err - } - - operation := &pushOperation{ - strictMode: true, // By default, enable strict mode - } - for _, option := range options { - option(operation) - } - meta, err := extractChartMeta(data) - if err != nil { - return nil, err - } - if operation.strictMode { - if !strings.HasSuffix(ref, fmt.Sprintf("/%s:%s", meta.Name, meta.Version)) { - return nil, errors.New( - "strict mode enabled, ref basename and tag must match the chart name and version") - } - } - memoryStore := content.NewMemory() - chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data) - if err != nil { - return nil, err - } - - configData, err := json.Marshal(meta) - if err != nil { - return nil, err - } - - configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData) - if err != nil { - return nil, err - } - - descriptors := []ocispec.Descriptor{chartDescriptor} - var provDescriptor ocispec.Descriptor - if operation.provData != nil { - provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData) - if err != nil { - return nil, err - } - - descriptors = append(descriptors, provDescriptor) - } - - manifestData, manifest, err := content.GenerateManifest(&configDescriptor, nil, descriptors...) - if err != nil { - return nil, err - } - - if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { - return nil, err - } - - registryStore := content.Registry{Resolver: c.resolver} - _, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.String(), registryStore, "", - oras.WithNameValidation(nil)) - if err != nil { - return nil, err - } - chartSummary := &descriptorPushSummaryWithMeta{ - Meta: meta, - } - chartSummary.Digest = chartDescriptor.Digest.String() - chartSummary.Size = chartDescriptor.Size - result := &PushResult{ - Manifest: &descriptorPushSummary{ - Digest: manifest.Digest.String(), - Size: manifest.Size, - }, - Config: &descriptorPushSummary{ - Digest: configDescriptor.Digest.String(), - Size: configDescriptor.Size, - }, - Chart: chartSummary, - Prov: &descriptorPushSummary{}, // prevent nil references - Ref: parsedRef.String(), - } - if operation.provData != nil { - result.Prov = &descriptorPushSummary{ - Digest: provDescriptor.Digest.String(), - Size: provDescriptor.Size, - } - } - fmt.Fprintf(c.out, "Pushed: %s\n", result.Ref) - fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) - if strings.Contains(parsedRef.Reference, "_") { - fmt.Fprintf(c.out, "%s contains an underscore.\n", result.Ref) - fmt.Fprint(c.out, registryUnderscoreMessage+"\n") - } - - return result, err -} - -// PushOptProvData returns a function that sets the prov bytes setting on push -func PushOptProvData(provData []byte) PushOption { - return func(operation *pushOperation) { - operation.provData = provData - } -} - -// PushOptStrictMode returns a function that sets the strictMode setting on push -func PushOptStrictMode(strictMode bool) PushOption { - return func(operation *pushOperation) { - operation.strictMode = strictMode - } -} - -// Tags provides a sorted list all semver compliant tags for a given repository -func (c *Client) Tags(ref string) ([]string, error) { - parsedReference, err := registry.ParseReference(ref) - if err != nil { - return nil, err - } - - repository := registryremote.Repository{ - Reference: parsedReference, - Client: c.registryAuthorizer, - } - - var registryTags []string - - for { - registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository) - if err != nil { - // Fallback to http based request - if !repository.PlainHTTP && strings.Contains(err.Error(), "server gave HTTP response") { - repository.PlainHTTP = true - continue - } - return nil, err - } - - break - - } - - var tagVersions []*semver.Version - for _, tag := range registryTags { - // Change underscore (_) back to plus (+) for Helm - // See https://github.com/helm/helm/issues/10166 - tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) - if err == nil { - tagVersions = append(tagVersions, tagVersion) - } - } - - // Sort the collection - sort.Sort(sort.Reverse(semver.Collection(tagVersions))) - - tags := make([]string, len(tagVersions)) - - for iTv, tv := range tagVersions { - tags[iTv] = tv.String() - } - - return tags, nil - -} diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go deleted file mode 100644 index 138dd4245..000000000 --- a/pkg/registry/client_test.go +++ /dev/null @@ -1,373 +0,0 @@ -/* -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 registry - -import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/containerd/containerd/errdefs" - "github.com/distribution/distribution/v3/configuration" - "github.com/distribution/distribution/v3/registry" - _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" - _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" - "github.com/phayes/freeport" - "github.com/stretchr/testify/suite" - "golang.org/x/crypto/bcrypt" -) - -var ( - testWorkspaceDir = "helm-registry-test" - testHtpasswdFileBasename = "authtest.htpasswd" - testUsername = "myuser" - testPassword = "mypass" -) - -type RegistryClientTestSuite struct { - suite.Suite - Out io.Writer - DockerRegistryHost string - CompromisedRegistryHost string - WorkspaceDir string - RegistryClient *Client -} - -func (suite *RegistryClientTestSuite) SetupSuite() { - suite.WorkspaceDir = testWorkspaceDir - os.RemoveAll(suite.WorkspaceDir) - os.Mkdir(suite.WorkspaceDir, 0700) - - var out bytes.Buffer - suite.Out = &out - credentialsFile := filepath.Join(suite.WorkspaceDir, CredentialsFileBasename) - - // init test client - var err error - suite.RegistryClient, err = NewClient( - ClientOptDebug(true), - ClientOptEnableCache(true), - ClientOptWriter(suite.Out), - ClientOptCredentialsFile(credentialsFile), - ) - suite.Nil(err, "no error creating registry client") - - // create htpasswd file (w BCrypt, which is required) - pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - suite.Nil(err, "no error generating bcrypt password for test htpasswd file") - htpasswdPath := filepath.Join(suite.WorkspaceDir, testHtpasswdFileBasename) - err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644) - suite.Nil(err, "no error creating test htpasswd file") - - // Registry config - config := &configuration.Configuration{} - port, err := freeport.GetFreePort() - suite.Nil(err, "no error finding free port for test registry") - suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) - config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port) - config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, - } - dockerRegistry, err := registry.NewRegistry(context.Background(), config) - suite.Nil(err, "no error creating test registry") - - suite.CompromisedRegistryHost = initCompromisedRegistryTestServer() - - // Start Docker registry - go dockerRegistry.ListenAndServe() -} - -func (suite *RegistryClientTestSuite) TearDownSuite() { - os.RemoveAll(suite.WorkspaceDir) -} - -func (suite *RegistryClientTestSuite) Test_0_Login() { - err := suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptInsecure(false)) - suite.NotNil(err, "error logging into registry with bad credentials") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth("badverybad", "ohsobad"), - LoginOptInsecure(true)) - suite.NotNil(err, "error logging into registry with bad credentials, insecure mode") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth(testUsername, testPassword), - LoginOptInsecure(false)) - suite.Nil(err, "no error logging into registry with good credentials") - - err = suite.RegistryClient.Login(suite.DockerRegistryHost, - LoginOptBasicAuth(testUsername, testPassword), - LoginOptInsecure(true)) - suite.Nil(err, "no error logging into registry with good credentials, insecure mode") -} - -func (suite *RegistryClientTestSuite) Test_1_Push() { - // Bad bytes - ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost) - _, err := suite.RegistryClient.Push([]byte("hello"), ref) - suite.NotNil(err, "error pushing non-chart bytes") - - // Load a test chart - chartData, err := ioutil.ReadFile("../repo/repotest/testdata/examplechart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - - // non-strict ref (chart name) - ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.NotNil(err, "error pushing non-strict ref (bad basename)") - - // non-strict ref (chart name), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false)) - suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") - - // non-strict ref (chart version) - ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.NotNil(err, "error pushing non-strict ref (bad tag)") - - // non-strict ref (chart version), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false)) - suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") - - // basic push, good ref - chartData, err = ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref) - suite.Nil(err, "no error pushing good ref") - - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Load another test chart - chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - - // Load prov file - provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") - - // push with prov - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData)) - suite.Nil(err, "no error pushing good ref with prov") - - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Validate the output - // Note: these digests/sizes etc may change if the test chart/prov files are modified, - // or if the format of the OCI manifest changes - suite.Equal(ref, result.Ref) - suite.Equal(meta.Name, result.Chart.Meta.Name) - suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(512), result.Manifest.Size) - suite.Equal(int64(99), result.Config.Size) - suite.Equal(int64(973), result.Chart.Size) - suite.Equal(int64(695), result.Prov.Size) - suite.Equal( - "sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83", - result.Manifest.Digest) - suite.Equal( - "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", - result.Config.Digest) - suite.Equal( - "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", - result.Chart.Digest) - suite.Equal( - "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", - result.Prov.Digest) -} - -func (suite *RegistryClientTestSuite) Test_2_Pull() { - // bad/missing ref - ref := fmt.Sprintf("%s/testrepo/no-existy:1.2.3", suite.DockerRegistryHost) - _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err, "error on bad/missing ref") - - // Load test chart (to build ref pushed in previous test) - chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - - // Simple pull, chart only - _, err = suite.RegistryClient.Pull(ref) - suite.Nil(err, "no error pulling a simple chart") - - // Simple pull with prov (no prov uploaded) - _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.NotNil(err, "error pulling a chart with prov when no prov exists") - - // Simple pull with prov, ignoring missing prov - _, err = suite.RegistryClient.Pull(ref, - PullOptWithProv(true), - PullOptIgnoreMissingProv(true)) - suite.Nil(err, - "no error pulling a chart with prov when no prov exists, ignoring missing") - - // Load test chart (to build ref pushed in previous test) - chartData, err = ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err = extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - - // Load prov file - provData, err := ioutil.ReadFile("../downloader/testdata/signtest-0.1.0.tgz.prov") - suite.Nil(err, "no error loading test prov") - - // no chart and no prov causes error - _, err = suite.RegistryClient.Pull(ref, - PullOptWithChart(false), - PullOptWithProv(false)) - suite.NotNil(err, "error on both no chart and no prov") - - // full pull with chart and prov - result, err := suite.RegistryClient.Pull(ref, PullOptWithProv(true)) - suite.Nil(err, "no error pulling a chart with prov") - - // Validate the output - // Note: these digests/sizes etc may change if the test chart/prov files are modified, - // or if the format of the OCI manifest changes - suite.Equal(ref, result.Ref) - suite.Equal(meta.Name, result.Chart.Meta.Name) - suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(512), result.Manifest.Size) - suite.Equal(int64(99), result.Config.Size) - suite.Equal(int64(973), result.Chart.Size) - suite.Equal(int64(695), result.Prov.Size) - suite.Equal( - "sha256:af4c20a1df1431495e673c14ecfa3a2ba24839a7784349d6787cd67957392e83", - result.Manifest.Digest) - suite.Equal( - "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", - result.Config.Digest) - suite.Equal( - "sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55", - result.Chart.Digest) - suite.Equal( - "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", - result.Prov.Digest) - suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}]}", - string(result.Manifest.Data)) - suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}", - string(result.Config.Data)) - suite.Equal(chartData, result.Chart.Data) - suite.Equal(provData, result.Prov.Data) -} - -func (suite *RegistryClientTestSuite) Test_3_Tags() { - - // Load test chart (to build ref pushed in previous test) - chartData, err := ioutil.ReadFile("../downloader/testdata/local-subchart-0.1.0.tgz") - suite.Nil(err, "no error loading test chart") - meta, err := extractChartMeta(chartData) - suite.Nil(err, "no error extracting chart meta") - ref := fmt.Sprintf("%s/testrepo/%s", suite.DockerRegistryHost, meta.Name) - - // Query for tags and validate length - tags, err := suite.RegistryClient.Tags(ref) - suite.Nil(err, "no error retrieving tags") - suite.Equal(1, len(tags)) - -} - -func (suite *RegistryClientTestSuite) Test_4_Logout() { - err := suite.RegistryClient.Logout("this-host-aint-real:5000") - suite.NotNil(err, "error logging out of registry that has no entry") - - err = suite.RegistryClient.Logout(suite.DockerRegistryHost) - suite.Nil(err, "no error logging out of registry") -} - -func (suite *RegistryClientTestSuite) Test_5_ManInTheMiddle() { - ref := fmt.Sprintf("%s/testrepo/supposedlysafechart:9.9.9", suite.CompromisedRegistryHost) - - // returns content that does not match the expected digest - _, err := suite.RegistryClient.Pull(ref) - suite.NotNil(err) - suite.True(errdefs.IsFailedPrecondition(err)) -} - -func TestRegistryClientTestSuite(t *testing.T) { - suite.Run(t, new(RegistryClientTestSuite)) -} - -func initCompromisedRegistryTestServer() string { - s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if strings.Contains(r.URL.Path, "manifests") { - w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json") - w.WriteHeader(200) - - // layers[0] is the blob []byte("a") - w.Write([]byte( - fmt.Sprintf(`{ "schemaVersion": 2, "config": { - "mediaType": "%s", - "digest": "sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133", - "size": 181 - }, - "layers": [ - { - "mediaType": "%s", - "digest": "sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb", - "size": 1 - } - ] -}`, ConfigMediaType, ChartLayerMediaType))) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:a705ee2789ab50a5ba20930f246dbd5cc01ff9712825bb98f57ee8414377f133" { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - w.Write([]byte("{\"name\":\"mychart\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\\n" + - "an 'application' or a 'library' chart.\",\"apiVersion\":\"v2\",\"appVersion\":\"1.16.0\",\"type\":" + - "\"application\"}")) - } else if r.URL.Path == "/v2/testrepo/supposedlysafechart/blobs/sha256:ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" { - w.Header().Set("Content-Type", ChartLayerMediaType) - w.WriteHeader(200) - w.Write([]byte("b")) - } else { - w.WriteHeader(500) - } - })) - - u, _ := url.Parse(s.URL) - return fmt.Sprintf("localhost:%s", u.Port()) -} diff --git a/pkg/registry/constants.go b/pkg/registry/constants.go deleted file mode 100644 index 570b6f0d3..000000000 --- a/pkg/registry/constants.go +++ /dev/null @@ -1,37 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -const ( - // OCIScheme is the URL scheme for OCI-based requests - OCIScheme = "oci" - - // CredentialsFileBasename is the filename for auth credentials file - CredentialsFileBasename = "registry/config.json" - - // ConfigMediaType is the reserved media type for the Helm chart manifest config - ConfigMediaType = "application/vnd.cncf.helm.config.v1+json" - - // ChartLayerMediaType is the reserved media type for Helm chart package content - ChartLayerMediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" - - // ProvLayerMediaType is the reserved media type for Helm chart provenance files - ProvLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" - - // LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content. - LegacyChartLayerMediaType = "application/tar+gzip" -) diff --git a/pkg/registry/util.go b/pkg/registry/util.go deleted file mode 100644 index 47eed267f..000000000 --- a/pkg/registry/util.go +++ /dev/null @@ -1,131 +0,0 @@ -/* -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 registry // import "helm.sh/helm/v3/pkg/registry" - -import ( - "bytes" - "context" - "fmt" - "io" - "strings" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - orascontext "oras.land/oras-go/pkg/context" - "oras.land/oras-go/pkg/registry" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" -) - -// IsOCI determines whether or not a URL is to be treated as an OCI URL -func IsOCI(url string) bool { - return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) -} - -// ContainsTag determines whether a tag is found in a provided list of tags -func ContainsTag(tags []string, tag string) bool { - for _, t := range tags { - if tag == t { - return true - } - } - return false -} - -func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) { - var constraint *semver.Constraints - if versionString == "" { - // If string is empty, set wildcard constraint - constraint, _ = semver.NewConstraint("*") - } else { - // when customer input exact version, check whether have exact match - // one first - for _, v := range tags { - if versionString == v { - return v, nil - } - } - - // Otherwise set constraint to the string given - var err error - constraint, err = semver.NewConstraint(versionString) - if err != nil { - return "", err - } - } - - // Otherwise try to find the first available version matching the string, - // in case it is a constraint - for _, v := range tags { - test, err := semver.NewVersion(v) - if err != nil { - continue - } - if constraint.Check(test) { - return v, nil - } - } - - return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString) -} - -// extractChartMeta is used to extract a chart metadata from a byte array -func extractChartMeta(chartData []byte) (*chart.Metadata, error) { - ch, err := loader.LoadArchive(bytes.NewReader(chartData)) - if err != nil { - return nil, err - } - return ch.Metadata, nil -} - -// ctx retrieves a fresh context. -// disable verbose logging coming from ORAS (unless debug is enabled) -func ctx(out io.Writer, debug bool) context.Context { - if !debug { - return orascontext.Background() - } - ctx := orascontext.WithLoggerFromWriter(context.Background(), out) - orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel) - return ctx -} - -// parseReference will parse and validate the reference, and clean tags when -// applicable tags are only cleaned when plus (+) signs are present, and are -// converted to underscores (_) before pushing -// See https://github.com/helm/helm/issues/10166 -func parseReference(raw string) (registry.Reference, error) { - // The sole possible reference modification is replacing plus (+) signs - // present in tags with underscores (_). To do this properly, we first - // need to identify a tag, and then pass it on to the reference parser - // NOTE: Passing immediately to the reference parser will fail since (+) - // signs are an invalid tag character, and simply replacing all plus (+) - // occurrences could invalidate other portions of the URI - parts := strings.Split(raw, ":") - if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") { - tag := parts[len(parts)-1] - - if tag != "" { - // Replace any plus (+) signs with known underscore (_) conversion - newTag := strings.ReplaceAll(tag, "+", "_") - raw = strings.ReplaceAll(raw, tag, newTag) - } - } - - return registry.ParseReference(raw) -} diff --git a/pkg/release/hook.go b/pkg/release/hook.go deleted file mode 100644 index cb9955582..000000000 --- a/pkg/release/hook.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -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 release - -import ( - "helm.sh/helm/v3/pkg/time" -) - -// HookEvent specifies the hook event -type HookEvent string - -// Hook event types -const ( - HookPreInstall HookEvent = "pre-install" - HookPostInstall HookEvent = "post-install" - HookPreDelete HookEvent = "pre-delete" - HookPostDelete HookEvent = "post-delete" - HookPreUpgrade HookEvent = "pre-upgrade" - HookPostUpgrade HookEvent = "post-upgrade" - HookPreRollback HookEvent = "pre-rollback" - HookPostRollback HookEvent = "post-rollback" - HookTest HookEvent = "test" -) - -func (x HookEvent) String() string { return string(x) } - -// HookDeletePolicy specifies the hook delete policy -type HookDeletePolicy string - -// Hook delete policy types -const ( - HookSucceeded HookDeletePolicy = "hook-succeeded" - HookFailed HookDeletePolicy = "hook-failed" - HookBeforeHookCreation HookDeletePolicy = "before-hook-creation" -) - -func (x HookDeletePolicy) String() string { return string(x) } - -// HookAnnotation is the label name for a hook -const HookAnnotation = "helm.sh/hook" - -// HookWeightAnnotation is the label name for a hook weight -const HookWeightAnnotation = "helm.sh/hook-weight" - -// HookDeleteAnnotation is the label name for the delete policy for a hook -const HookDeleteAnnotation = "helm.sh/hook-delete-policy" - -// Hook defines a hook object. -type Hook struct { - Name string `json:"name,omitempty"` - // Kind is the Kubernetes kind. - Kind string `json:"kind,omitempty"` - // Path is the chart-relative path to the template. - Path string `json:"path,omitempty"` - // Manifest is the manifest contents. - Manifest string `json:"manifest,omitempty"` - // Events are the events that this hook fires on. - Events []HookEvent `json:"events,omitempty"` - // LastRun indicates the date/time this was last run. - LastRun HookExecution `json:"last_run,omitempty"` - // Weight indicates the sort order for execution among similar Hook type - Weight int `json:"weight,omitempty"` - // DeletePolicies are the policies that indicate when to delete the hook - DeletePolicies []HookDeletePolicy `json:"delete_policies,omitempty"` -} - -// A HookExecution records the result for the last execution of a hook for a given release. -type HookExecution struct { - // StartedAt indicates the date/time this hook was started - StartedAt time.Time `json:"started_at,omitempty"` - // CompletedAt indicates the date/time this hook was completed. - CompletedAt time.Time `json:"completed_at,omitempty"` - // Phase indicates whether the hook completed successfully - Phase HookPhase `json:"phase"` -} - -// A HookPhase indicates the state of a hook execution -type HookPhase string - -const ( - // HookPhaseUnknown indicates that a hook is in an unknown state - HookPhaseUnknown HookPhase = "Unknown" - // HookPhaseRunning indicates that a hook is currently executing - HookPhaseRunning HookPhase = "Running" - // HookPhaseSucceeded indicates that hook execution succeeded - HookPhaseSucceeded HookPhase = "Succeeded" - // HookPhaseFailed indicates that hook execution failed - HookPhaseFailed HookPhase = "Failed" -) - -// String converts a hook phase to a printable string -func (x HookPhase) String() string { return string(x) } diff --git a/pkg/release/info.go b/pkg/release/info.go deleted file mode 100644 index 0cb2bab64..000000000 --- a/pkg/release/info.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -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 release - -import ( - "helm.sh/helm/v3/pkg/time" -) - -// Info describes release information. -type Info struct { - // FirstDeployed is when the release was first deployed. - FirstDeployed time.Time `json:"first_deployed,omitempty"` - // LastDeployed is when the release was last deployed. - LastDeployed time.Time `json:"last_deployed,omitempty"` - // Deleted tracks when this object was deleted. - Deleted time.Time `json:"deleted"` - // Description is human-friendly "log entry" about this release. - Description string `json:"description,omitempty"` - // Status is the current state of the release - Status Status `json:"status,omitempty"` - // Contains the rendered templates/NOTES.txt if available - Notes string `json:"notes,omitempty"` -} diff --git a/pkg/release/mock.go b/pkg/release/mock.go deleted file mode 100644 index a28e1dc16..000000000 --- a/pkg/release/mock.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -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 release - -import ( - "fmt" - "math/rand" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/time" -) - -// MockHookTemplate is the hook template used for all mock release objects. -var MockHookTemplate = `apiVersion: v1 -kind: Job -metadata: - annotations: - "helm.sh/hook": pre-install -` - -// MockManifest is the manifest used for all mock release objects. -var MockManifest = `apiVersion: v1 -kind: Secret -metadata: - name: fixture -` - -// MockReleaseOptions allows for user-configurable options on mock release objects. -type MockReleaseOptions struct { - Name string - Version int - Chart *chart.Chart - Status Status - Namespace string -} - -// Mock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. -func Mock(opts *MockReleaseOptions) *Release { - date := time.Unix(242085845, 0).UTC() - - name := opts.Name - if name == "" { - name = "testrelease-" + fmt.Sprint(rand.Intn(100)) - } - - version := 1 - if opts.Version != 0 { - version = opts.Version - } - - namespace := opts.Namespace - if namespace == "" { - namespace = "default" - } - - ch := opts.Chart - if opts.Chart == nil { - ch = &chart.Chart{ - Metadata: &chart.Metadata{ - Name: "foo", - Version: "0.1.0-beta.1", - AppVersion: "1.0", - }, - Templates: []*chart.File{ - {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, - }, - } - } - - scode := StatusDeployed - if len(opts.Status) > 0 { - scode = opts.Status - } - - info := &Info{ - FirstDeployed: date, - LastDeployed: date, - Status: scode, - Description: "Release mock", - Notes: "Some mock release notes!", - } - - return &Release{ - Name: name, - Info: info, - Chart: ch, - Config: map[string]interface{}{"name": "value"}, - Version: version, - Namespace: namespace, - Hooks: []*Hook{ - { - Name: "pre-install-hook", - Kind: "Job", - Path: "pre-install-hook.yaml", - Manifest: MockHookTemplate, - LastRun: HookExecution{}, - Events: []HookEvent{HookPreInstall}, - }, - }, - Manifest: MockManifest, - } -} diff --git a/pkg/release/release.go b/pkg/release/release.go deleted file mode 100644 index b90612873..000000000 --- a/pkg/release/release.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 release - -import "helm.sh/helm/v3/pkg/chart" - -// Release describes a deployment of a chart, together with the chart -// and the variables used to deploy that chart. -type Release struct { - // Name is the name of the release - Name string `json:"name,omitempty"` - // Info provides information about a release - Info *Info `json:"info,omitempty"` - // Chart is the chart that was released. - Chart *chart.Chart `json:"chart,omitempty"` - // Config is the set of extra Values added to the chart. - // These values override the default values inside of the chart. - Config map[string]interface{} `json:"config,omitempty"` - // Manifest is the string representation of the rendered template. - Manifest string `json:"manifest,omitempty"` - // Hooks are all of the hooks declared for this release. - Hooks []*Hook `json:"hooks,omitempty"` - // Version is an int which represents the revision of the release. - Version int `json:"version,omitempty"` - // Namespace is the kubernetes namespace of the release. - Namespace string `json:"namespace,omitempty"` - // Labels of the release. - // Disabled encoding into Json cause labels are stored in storage driver metadata field. - Labels map[string]string `json:"-"` -} - -// SetStatus is a helper for setting the status on a release. -func (r *Release) SetStatus(status Status, msg string) { - r.Info.Status = status - r.Info.Description = msg -} diff --git a/pkg/release/responses.go b/pkg/release/responses.go deleted file mode 100644 index 7ee1fc2ee..000000000 --- a/pkg/release/responses.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -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 release - -// UninstallReleaseResponse represents a successful response to an uninstall request. -type UninstallReleaseResponse struct { - // Release is the release that was marked deleted. - Release *Release `json:"release,omitempty"` - // Info is an uninstall message - Info string `json:"info,omitempty"` -} diff --git a/pkg/release/status.go b/pkg/release/status.go deleted file mode 100644 index e0e3ed62a..000000000 --- a/pkg/release/status.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 release - -// Status is the status of a release -type Status string - -// Describe the status of a release -// NOTE: Make sure to update cmd/helm/status.go when adding or modifying any of these statuses. -const ( - // StatusUnknown indicates that a release is in an uncertain state. - StatusUnknown Status = "unknown" - // StatusDeployed indicates that the release has been pushed to Kubernetes. - StatusDeployed Status = "deployed" - // StatusUninstalled indicates that a release has been uninstalled from Kubernetes. - StatusUninstalled Status = "uninstalled" - // StatusSuperseded indicates that this release object is outdated and a newer one exists. - StatusSuperseded Status = "superseded" - // StatusFailed indicates that the release was not successfully deployed. - StatusFailed Status = "failed" - // StatusUninstalling indicates that a uninstall operation is underway. - StatusUninstalling Status = "uninstalling" - // StatusPendingInstall indicates that an install operation is underway. - StatusPendingInstall Status = "pending-install" - // StatusPendingUpgrade indicates that an upgrade operation is underway. - StatusPendingUpgrade Status = "pending-upgrade" - // StatusPendingRollback indicates that an rollback operation is underway. - StatusPendingRollback Status = "pending-rollback" -) - -func (x Status) String() string { return string(x) } - -// IsPending determines if this status is a state or a transition. -func (x Status) IsPending() bool { - return x == StatusPendingInstall || x == StatusPendingUpgrade || x == StatusPendingRollback -} diff --git a/pkg/releaseutil/filter.go b/pkg/releaseutil/filter.go deleted file mode 100644 index dbd0df8e2..000000000 --- a/pkg/releaseutil/filter.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import rspb "helm.sh/helm/v3/pkg/release" - -// FilterFunc returns true if the release object satisfies -// the predicate of the underlying filter func. -type FilterFunc func(*rspb.Release) bool - -// Check applies the FilterFunc to the release object. -func (fn FilterFunc) Check(rls *rspb.Release) bool { - if rls == nil { - return false - } - return fn(rls) -} - -// Filter applies the filter(s) to the list of provided releases -// returning the list that satisfies the filtering predicate. -func (fn FilterFunc) Filter(rels []*rspb.Release) (rets []*rspb.Release) { - for _, rel := range rels { - if fn.Check(rel) { - rets = append(rets, rel) - } - } - return -} - -// Any returns a FilterFunc that filters a list of releases -// determined by the predicate 'f0 || f1 || ... || fn'. -func Any(filters ...FilterFunc) FilterFunc { - return func(rls *rspb.Release) bool { - for _, filter := range filters { - if filter(rls) { - return true - } - } - return false - } -} - -// All returns a FilterFunc that filters a list of releases -// determined by the predicate 'f0 && f1 && ... && fn'. -func All(filters ...FilterFunc) FilterFunc { - return func(rls *rspb.Release) bool { - for _, filter := range filters { - if !filter(rls) { - return false - } - } - return true - } -} - -// StatusFilter filters a set of releases by status code. -func StatusFilter(status rspb.Status) FilterFunc { - return FilterFunc(func(rls *rspb.Release) bool { - if rls == nil { - return true - } - return rls.Info.Status == status - }) -} diff --git a/pkg/releaseutil/filter_test.go b/pkg/releaseutil/filter_test.go deleted file mode 100644 index 31ac306f6..000000000 --- a/pkg/releaseutil/filter_test.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestFilterAny(t *testing.T) { - ls := Any(StatusFilter(rspb.StatusUninstalled)).Filter(releases) - if len(ls) != 2 { - t.Fatalf("expected 2 results, got '%d'", len(ls)) - } - - r0, r1 := ls[0], ls[1] - switch { - case r0.Info.Status != rspb.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - case r1.Info.Status != rspb.StatusUninstalled: - t.Fatalf("expected UNINSTALLED result, got '%s'", r1.Info.Status.String()) - } -} - -func TestFilterAll(t *testing.T) { - fn := FilterFunc(func(rls *rspb.Release) bool { - // true if not uninstalled and version < 4 - v0 := !StatusFilter(rspb.StatusUninstalled).Check(rls) - v1 := rls.Version < 4 - return v0 && v1 - }) - - ls := All(fn).Filter(releases) - if len(ls) != 1 { - t.Fatalf("expected 1 result, got '%d'", len(ls)) - } - - switch r0 := ls[0]; { - case r0.Version == 4: - t.Fatal("got release with status revision 4") - case r0.Info.Status == rspb.StatusUninstalled: - t.Fatal("got release with status UNINSTALLED") - } -} diff --git a/pkg/releaseutil/kind_sorter.go b/pkg/releaseutil/kind_sorter.go deleted file mode 100644 index 1d1874cfa..000000000 --- a/pkg/releaseutil/kind_sorter.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -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 releaseutil - -import ( - "sort" - - "helm.sh/helm/v3/pkg/release" -) - -// KindSortOrder is an ordering of Kinds. -type KindSortOrder []string - -// InstallOrder is the order in which manifests should be installed (by Kind). -// -// Those occurring earlier in the list get installed before those occurring later in the list. -var InstallOrder KindSortOrder = []string{ - "Namespace", - "NetworkPolicy", - "ResourceQuota", - "LimitRange", - "PodSecurityPolicy", - "PodDisruptionBudget", - "ServiceAccount", - "Secret", - "SecretList", - "ConfigMap", - "StorageClass", - "PersistentVolume", - "PersistentVolumeClaim", - "CustomResourceDefinition", - "ClusterRole", - "ClusterRoleList", - "ClusterRoleBinding", - "ClusterRoleBindingList", - "Role", - "RoleList", - "RoleBinding", - "RoleBindingList", - "Service", - "DaemonSet", - "Pod", - "ReplicationController", - "ReplicaSet", - "Deployment", - "HorizontalPodAutoscaler", - "StatefulSet", - "Job", - "CronJob", - "IngressClass", - "Ingress", - "APIService", -} - -// UninstallOrder is the order in which manifests should be uninstalled (by Kind). -// -// Those occurring earlier in the list get uninstalled before those occurring later in the list. -var UninstallOrder KindSortOrder = []string{ - "APIService", - "Ingress", - "IngressClass", - "Service", - "CronJob", - "Job", - "StatefulSet", - "HorizontalPodAutoscaler", - "Deployment", - "ReplicaSet", - "ReplicationController", - "Pod", - "DaemonSet", - "RoleBindingList", - "RoleBinding", - "RoleList", - "Role", - "ClusterRoleBindingList", - "ClusterRoleBinding", - "ClusterRoleList", - "ClusterRole", - "CustomResourceDefinition", - "PersistentVolumeClaim", - "PersistentVolume", - "StorageClass", - "ConfigMap", - "SecretList", - "Secret", - "ServiceAccount", - "PodDisruptionBudget", - "PodSecurityPolicy", - "LimitRange", - "ResourceQuota", - "NetworkPolicy", - "Namespace", -} - -// sort manifests by kind. -// -// Results are sorted by 'ordering', keeping order of items with equal kind/priority -func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest { - sort.SliceStable(manifests, func(i, j int) bool { - return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering) - }) - - return manifests -} - -// sort hooks by kind, using an out-of-place sort to preserve the input parameters. -// -// Results are sorted by 'ordering', keeping order of items with equal kind/priority -func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.Hook { - h := hooks - sort.SliceStable(h, func(i, j int) bool { - return lessByKind(h[i], h[j], h[i].Kind, h[j].Kind, ordering) - }) - - return h -} - -func lessByKind(a interface{}, b interface{}, kindA string, kindB string, o KindSortOrder) bool { - ordering := make(map[string]int, len(o)) - for v, k := range o { - ordering[k] = v - } - - first, aok := ordering[kindA] - second, bok := ordering[kindB] - - if !aok && !bok { - // if both are unknown then sort alphabetically by kind, keep original order if same kind - if kindA != kindB { - return kindA < kindB - } - return first < second - } - // unknown kind is last - if !aok { - return false - } - if !bok { - return true - } - // sort different kinds, keep original order if same priority - return first < second -} diff --git a/pkg/releaseutil/kind_sorter_test.go b/pkg/releaseutil/kind_sorter_test.go deleted file mode 100644 index afcae6d16..000000000 --- a/pkg/releaseutil/kind_sorter_test.go +++ /dev/null @@ -1,335 +0,0 @@ -/* -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 releaseutil - -import ( - "bytes" - "testing" - - "helm.sh/helm/v3/pkg/release" -) - -func TestKindSorter(t *testing.T) { - manifests := []Manifest{ - { - Name: "U", - Head: &SimpleHead{Kind: "IngressClass"}, - }, - { - Name: "E", - Head: &SimpleHead{Kind: "SecretList"}, - }, - { - Name: "i", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "I", - Head: &SimpleHead{Kind: "ClusterRoleList"}, - }, - { - Name: "j", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "J", - Head: &SimpleHead{Kind: "ClusterRoleBindingList"}, - }, - { - Name: "f", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "u", - Head: &SimpleHead{Kind: "CronJob"}, - }, - { - Name: "2", - Head: &SimpleHead{Kind: "CustomResourceDefinition"}, - }, - { - Name: "n", - Head: &SimpleHead{Kind: "DaemonSet"}, - }, - { - Name: "r", - Head: &SimpleHead{Kind: "Deployment"}, - }, - { - Name: "!", - Head: &SimpleHead{Kind: "HonkyTonkSet"}, - }, - { - Name: "v", - Head: &SimpleHead{Kind: "Ingress"}, - }, - { - Name: "t", - Head: &SimpleHead{Kind: "Job"}, - }, - { - Name: "c", - Head: &SimpleHead{Kind: "LimitRange"}, - }, - { - Name: "a", - Head: &SimpleHead{Kind: "Namespace"}, - }, - { - Name: "A", - Head: &SimpleHead{Kind: "NetworkPolicy"}, - }, - { - Name: "g", - Head: &SimpleHead{Kind: "PersistentVolume"}, - }, - { - Name: "h", - Head: &SimpleHead{Kind: "PersistentVolumeClaim"}, - }, - { - Name: "o", - Head: &SimpleHead{Kind: "Pod"}, - }, - { - Name: "3", - Head: &SimpleHead{Kind: "PodDisruptionBudget"}, - }, - { - Name: "C", - Head: &SimpleHead{Kind: "PodSecurityPolicy"}, - }, - { - Name: "q", - Head: &SimpleHead{Kind: "ReplicaSet"}, - }, - { - Name: "p", - Head: &SimpleHead{Kind: "ReplicationController"}, - }, - { - Name: "b", - Head: &SimpleHead{Kind: "ResourceQuota"}, - }, - { - Name: "k", - Head: &SimpleHead{Kind: "Role"}, - }, - { - Name: "K", - Head: &SimpleHead{Kind: "RoleList"}, - }, - { - Name: "l", - Head: &SimpleHead{Kind: "RoleBinding"}, - }, - { - Name: "L", - Head: &SimpleHead{Kind: "RoleBindingList"}, - }, - { - Name: "e", - Head: &SimpleHead{Kind: "Secret"}, - }, - { - Name: "m", - Head: &SimpleHead{Kind: "Service"}, - }, - { - Name: "d", - Head: &SimpleHead{Kind: "ServiceAccount"}, - }, - { - Name: "s", - Head: &SimpleHead{Kind: "StatefulSet"}, - }, - { - Name: "1", - Head: &SimpleHead{Kind: "StorageClass"}, - }, - { - Name: "w", - Head: &SimpleHead{Kind: "APIService"}, - }, - { - Name: "x", - Head: &SimpleHead{Kind: "HorizontalPodAutoscaler"}, - }, - } - - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - {"install", InstallOrder, "aAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"}, - {"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAa!"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(manifests); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } - defer buf.Reset() - orig := manifests - for _, r := range sortManifestsByKind(manifests, test.order) { - buf.WriteString(r.Name) - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - for i, manifest := range orig { - if manifest != manifests[i] { - t.Fatal("Expected input to sortManifestsByKind to stay the same") - } - } - }) - } -} - -// TestKindSorterKeepOriginalOrder verifies manifests of same kind are kept in original order -func TestKindSorterKeepOriginalOrder(t *testing.T) { - manifests := []Manifest{ - { - Name: "a", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "A", - Head: &SimpleHead{Kind: "ClusterRole"}, - }, - { - Name: "0", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "1", - Head: &SimpleHead{Kind: "ConfigMap"}, - }, - { - Name: "z", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "!", - Head: &SimpleHead{Kind: "ClusterRoleBinding"}, - }, - { - Name: "u2", - Head: &SimpleHead{Kind: "Unknown"}, - }, - { - Name: "u1", - Head: &SimpleHead{Kind: "Unknown"}, - }, - { - Name: "t3", - Head: &SimpleHead{Kind: "Unknown2"}, - }, - } - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - // expectation is sorted by kind (unknown is last) and within each group of same kind, the order is kept - {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01aAz!u2u1t3"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - defer buf.Reset() - for _, r := range sortManifestsByKind(manifests, test.order) { - buf.WriteString(r.Name) - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - }) - } -} - -func TestKindSorterNamespaceAgainstUnknown(t *testing.T) { - unknown := Manifest{ - Name: "a", - Head: &SimpleHead{Kind: "Unknown"}, - } - namespace := Manifest{ - Name: "b", - Head: &SimpleHead{Kind: "Namespace"}, - } - - manifests := []Manifest{unknown, namespace} - manifests = sortManifestsByKind(manifests, InstallOrder) - - expectedOrder := []Manifest{namespace, unknown} - for i, manifest := range manifests { - if expectedOrder[i].Name != manifest.Name { - t.Errorf("Expected %s, got %s", expectedOrder[i].Name, manifest.Name) - } - } -} - -// test hook sorting with a small subset of kinds, since it uses the same algorithm as sortManifestsByKind -func TestKindSorterForHooks(t *testing.T) { - hooks := []*release.Hook{ - { - Name: "i", - Kind: "ClusterRole", - }, - { - Name: "j", - Kind: "ClusterRoleBinding", - }, - { - Name: "c", - Kind: "LimitRange", - }, - { - Name: "a", - Kind: "Namespace", - }, - } - - for _, test := range []struct { - description string - order KindSortOrder - expected string - }{ - {"install", InstallOrder, "acij"}, - {"uninstall", UninstallOrder, "jica"}, - } { - var buf bytes.Buffer - t.Run(test.description, func(t *testing.T) { - if got, want := len(test.expected), len(hooks); got != want { - t.Fatalf("Expected %d names in order, got %d", want, got) - } - defer buf.Reset() - orig := hooks - for _, r := range sortHooksByKind(hooks, test.order) { - buf.WriteString(r.Name) - } - for i, hook := range orig { - if hook != hooks[i] { - t.Fatal("Expected input to sortHooksByKind to stay the same") - } - } - if got := buf.String(); got != test.expected { - t.Errorf("Expected %q, got %q", test.expected, got) - } - }) - } -} diff --git a/pkg/releaseutil/manifest.go b/pkg/releaseutil/manifest.go deleted file mode 100644 index 0b04a4599..000000000 --- a/pkg/releaseutil/manifest.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -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 releaseutil - -import ( - "fmt" - "regexp" - "strconv" - "strings" -) - -// SimpleHead defines what the structure of the head of a manifest file -type SimpleHead struct { - Version string `json:"apiVersion"` - Kind string `json:"kind,omitempty"` - Metadata *struct { - Name string `json:"name"` - Annotations map[string]string `json:"annotations"` - } `json:"metadata,omitempty"` -} - -var sep = regexp.MustCompile("(?:^|\\s*\n)---\\s*") - -// SplitManifests takes a string of manifest and returns a map contains individual manifests -func SplitManifests(bigFile string) map[string]string { - // Basically, we're quickly splitting a stream of YAML documents into an - // array of YAML docs. The file name is just a place holder, but should be - // integer-sortable so that manifests get output in the same order as the - // input (see `BySplitManifestsOrder`). - tpl := "manifest-%d" - res := map[string]string{} - // Making sure that any extra whitespace in YAML stream doesn't interfere in splitting documents correctly. - bigFileTmp := strings.TrimSpace(bigFile) - docs := sep.Split(bigFileTmp, -1) - var count int - for _, d := range docs { - if d == "" { - continue - } - - d = strings.TrimSpace(d) - res[fmt.Sprintf(tpl, count)] = d - count = count + 1 - } - return res -} - -// BySplitManifestsOrder sorts by in-file manifest order, as provided in function `SplitManifests` -type BySplitManifestsOrder []string - -func (a BySplitManifestsOrder) Len() int { return len(a) } -func (a BySplitManifestsOrder) Less(i, j int) bool { - // Split `manifest-%d` - anum, _ := strconv.ParseInt(a[i][len("manifest-"):], 10, 0) - bnum, _ := strconv.ParseInt(a[j][len("manifest-"):], 10, 0) - return anum < bnum -} -func (a BySplitManifestsOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/pkg/releaseutil/manifest_sorter.go b/pkg/releaseutil/manifest_sorter.go deleted file mode 100644 index e83414500..000000000 --- a/pkg/releaseutil/manifest_sorter.go +++ /dev/null @@ -1,233 +0,0 @@ -/* -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 releaseutil - -import ( - "log" - "path" - "sort" - "strconv" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -// Manifest represents a manifest file, which has a name and some content. -type Manifest struct { - Name string - Content string - Head *SimpleHead -} - -// manifestFile represents a file that contains a manifest. -type manifestFile struct { - entries map[string]string - path string - apis chartutil.VersionSet -} - -// result is an intermediate structure used during sorting. -type result struct { - hooks []*release.Hook - generic []Manifest -} - -// TODO: Refactor this out. It's here because naming conventions were not followed through. -// So fix the Test hook names and then remove this. -var events = map[string]release.HookEvent{ - release.HookPreInstall.String(): release.HookPreInstall, - release.HookPostInstall.String(): release.HookPostInstall, - release.HookPreDelete.String(): release.HookPreDelete, - release.HookPostDelete.String(): release.HookPostDelete, - release.HookPreUpgrade.String(): release.HookPreUpgrade, - release.HookPostUpgrade.String(): release.HookPostUpgrade, - release.HookPreRollback.String(): release.HookPreRollback, - release.HookPostRollback.String(): release.HookPostRollback, - release.HookTest.String(): release.HookTest, - // Support test-success for backward compatibility with Helm 2 tests - "test-success": release.HookTest, -} - -// SortManifests takes a map of filename/YAML contents, splits the file -// by manifest entries, and sorts the entries into hook types. -// -// The resulting hooks struct will be populated with all of the generated hooks. -// Any file that does not declare one of the hook types will be placed in the -// 'generic' bucket. -// -// Files that do not parse into the expected format are simply placed into a map and -// returned. -func SortManifests(files map[string]string, apis chartutil.VersionSet, ordering KindSortOrder) ([]*release.Hook, []Manifest, error) { - result := &result{} - - var sortedFilePaths []string - for filePath := range files { - sortedFilePaths = append(sortedFilePaths, filePath) - } - sort.Strings(sortedFilePaths) - - for _, filePath := range sortedFilePaths { - content := files[filePath] - - // Skip partials. We could return these as a separate map, but there doesn't - // seem to be any need for that at this time. - if strings.HasPrefix(path.Base(filePath), "_") { - continue - } - // Skip empty files and log this. - if strings.TrimSpace(content) == "" { - continue - } - - manifestFile := &manifestFile{ - entries: SplitManifests(content), - path: filePath, - apis: apis, - } - - if err := manifestFile.sort(result); err != nil { - return result.hooks, result.generic, err - } - } - - return sortHooksByKind(result.hooks, ordering), sortManifestsByKind(result.generic, ordering), nil -} - -// sort takes a manifestFile object which may contain multiple resource definition -// entries and sorts each entry by hook types, and saves the resulting hooks and -// generic manifests (or non-hooks) to the result struct. -// -// To determine hook type, it looks for a YAML structure like this: -// -// kind: SomeKind -// apiVersion: v1 -// metadata: -// annotations: -// helm.sh/hook: pre-install -// -// To determine the policy to delete the hook, it looks for a YAML structure like this: -// -// kind: SomeKind -// apiVersion: v1 -// metadata: -// annotations: -// helm.sh/hook-delete-policy: hook-succeeded -func (file *manifestFile) sort(result *result) error { - // Go through manifests in order found in file (function `SplitManifests` creates integer-sortable keys) - var sortedEntryKeys []string - for entryKey := range file.entries { - sortedEntryKeys = append(sortedEntryKeys, entryKey) - } - sort.Sort(BySplitManifestsOrder(sortedEntryKeys)) - - for _, entryKey := range sortedEntryKeys { - m := file.entries[entryKey] - - var entry SimpleHead - if err := yaml.Unmarshal([]byte(m), &entry); err != nil { - return errors.Wrapf(err, "YAML parse error on %s", file.path) - } - - if !hasAnyAnnotation(entry) { - result.generic = append(result.generic, Manifest{ - Name: file.path, - Content: m, - Head: &entry, - }) - continue - } - - hookTypes, ok := entry.Metadata.Annotations[release.HookAnnotation] - if !ok { - result.generic = append(result.generic, Manifest{ - Name: file.path, - Content: m, - Head: &entry, - }) - continue - } - - hw := calculateHookWeight(entry) - - h := &release.Hook{ - Name: entry.Metadata.Name, - Kind: entry.Kind, - Path: file.path, - Manifest: m, - Events: []release.HookEvent{}, - Weight: hw, - DeletePolicies: []release.HookDeletePolicy{}, - } - - isUnknownHook := false - for _, hookType := range strings.Split(hookTypes, ",") { - hookType = strings.ToLower(strings.TrimSpace(hookType)) - e, ok := events[hookType] - if !ok { - isUnknownHook = true - break - } - h.Events = append(h.Events, e) - } - - if isUnknownHook { - log.Printf("info: skipping unknown hook: %q", hookTypes) - continue - } - - result.hooks = append(result.hooks, h) - - operateAnnotationValues(entry, release.HookDeleteAnnotation, func(value string) { - h.DeletePolicies = append(h.DeletePolicies, release.HookDeletePolicy(value)) - }) - } - - return nil -} - -// hasAnyAnnotation returns true if the given entry has any annotations at all. -func hasAnyAnnotation(entry SimpleHead) bool { - return entry.Metadata != nil && - entry.Metadata.Annotations != nil && - len(entry.Metadata.Annotations) != 0 -} - -// calculateHookWeight finds the weight in the hook weight annotation. -// -// If no weight is found, the assigned weight is 0 -func calculateHookWeight(entry SimpleHead) int { - hws := entry.Metadata.Annotations[release.HookWeightAnnotation] - hw, err := strconv.Atoi(hws) - if err != nil { - hw = 0 - } - return hw -} - -// operateAnnotationValues finds the given annotation and runs the operate function with the value of that annotation -func operateAnnotationValues(entry SimpleHead, annotation string, operate func(p string)) { - if dps, ok := entry.Metadata.Annotations[annotation]; ok { - for _, dp := range strings.Split(dps, ",") { - dp = strings.ToLower(strings.TrimSpace(dp)) - operate(dp) - } - } -} diff --git a/pkg/releaseutil/manifest_sorter_test.go b/pkg/releaseutil/manifest_sorter_test.go deleted file mode 100644 index 20d809317..000000000 --- a/pkg/releaseutil/manifest_sorter_test.go +++ /dev/null @@ -1,228 +0,0 @@ -/* -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 releaseutil - -import ( - "reflect" - "testing" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/release" -) - -func TestSortManifests(t *testing.T) { - - data := []struct { - name []string - path string - kind []string - hooks map[string][]release.HookEvent - manifest string - }{ - { - name: []string{"first"}, - path: "one", - kind: []string{"Job"}, - hooks: map[string][]release.HookEvent{"first": {release.HookPreInstall}}, - manifest: `apiVersion: v1 -kind: Job -metadata: - name: first - labels: - doesnot: matter - annotations: - "helm.sh/hook": pre-install -`, - }, - { - name: []string{"second"}, - path: "two", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"second": {release.HookPostInstall}}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: second - annotations: - "helm.sh/hook": post-install -`, - }, { - name: []string{"third"}, - path: "three", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"third": nil}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: third - annotations: - "helm.sh/hook": no-such-hook -`, - }, { - name: []string{"fourth"}, - path: "four", - kind: []string{"Pod"}, - hooks: map[string][]release.HookEvent{"fourth": nil}, - manifest: `kind: Pod -apiVersion: v1 -metadata: - name: fourth - annotations: - nothing: here`, - }, { - name: []string{"fifth"}, - path: "five", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"fifth": {release.HookPostDelete, release.HookPostInstall}}, - manifest: `kind: ReplicaSet -apiVersion: v1beta1 -metadata: - name: fifth - annotations: - "helm.sh/hook": post-delete, post-install -`, - }, { - // Regression test: files with an underscore in the base name should be skipped. - name: []string{"sixth"}, - path: "six/_six", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"sixth": nil}, - manifest: `invalid manifest`, // This will fail if partial is not skipped. - }, { - // Regression test: files with no content should be skipped. - name: []string{"seventh"}, - path: "seven", - kind: []string{"ReplicaSet"}, - hooks: map[string][]release.HookEvent{"seventh": nil}, - manifest: "", - }, - { - name: []string{"eighth", "example-test"}, - path: "eight", - kind: []string{"ConfigMap", "Pod"}, - hooks: map[string][]release.HookEvent{"eighth": nil, "example-test": {release.HookTest}}, - manifest: `kind: ConfigMap -apiVersion: v1 -metadata: - name: eighth -data: - name: value ---- -apiVersion: v1 -kind: Pod -metadata: - name: example-test - annotations: - "helm.sh/hook": test -`, - }, - } - - manifests := make(map[string]string, len(data)) - for _, o := range data { - manifests[o.path] = o.manifest - } - - hs, generic, err := SortManifests(manifests, chartutil.VersionSet{"v1", "v1beta1"}, InstallOrder) - if err != nil { - t.Fatalf("Unexpected error: %s", err) - } - - // This test will fail if 'six' or 'seven' was added. - if len(generic) != 2 { - t.Errorf("Expected 2 generic manifests, got %d", len(generic)) - } - - if len(hs) != 4 { - t.Errorf("Expected 4 hooks, got %d", len(hs)) - } - - for _, out := range hs { - found := false - for _, expect := range data { - if out.Path == expect.path { - found = true - if out.Path != expect.path { - t.Errorf("Expected path %s, got %s", expect.path, out.Path) - } - nameFound := false - for _, expectedName := range expect.name { - if out.Name == expectedName { - nameFound = true - } - } - if !nameFound { - t.Errorf("Got unexpected name %s", out.Name) - } - kindFound := false - for _, expectedKind := range expect.kind { - if out.Kind == expectedKind { - kindFound = true - } - } - if !kindFound { - t.Errorf("Got unexpected kind %s", out.Kind) - } - - expectedHooks := expect.hooks[out.Name] - if !reflect.DeepEqual(expectedHooks, out.Events) { - t.Errorf("expected events: %v but got: %v", expectedHooks, out.Events) - } - - } - } - if !found { - t.Errorf("Result not found: %v", out) - } - } - - // Verify the sort order - sorted := []Manifest{} - for _, s := range data { - manifests := SplitManifests(s.manifest) - - for _, m := range manifests { - var sh SimpleHead - if err := yaml.Unmarshal([]byte(m), &sh); err != nil { - // This is expected for manifests that are corrupt or empty. - t.Log(err) - continue - } - - name := sh.Metadata.Name - - // only keep track of non-hook manifests - if s.hooks[name] == nil { - another := Manifest{ - Content: m, - Name: name, - Head: &sh, - } - sorted = append(sorted, another) - } - } - } - - sorted = sortManifestsByKind(sorted, InstallOrder) - for i, m := range generic { - if m.Content != sorted[i].Content { - t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) - } - } -} diff --git a/pkg/releaseutil/manifest_test.go b/pkg/releaseutil/manifest_test.go deleted file mode 100644 index 8664d20ef..000000000 --- a/pkg/releaseutil/manifest_test.go +++ /dev/null @@ -1,61 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "reflect" - "testing" -) - -const mockManifestFile = ` - ---- -apiVersion: v1 -kind: Pod -metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test -spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command -` - -const expectedManifest = `apiVersion: v1 -kind: Pod -metadata: - name: finding-nemo, - annotations: - "helm.sh/hook": test -spec: - containers: - - name: nemo-test - image: fake-image - cmd: fake-command` - -func TestSplitManifest(t *testing.T) { - manifests := SplitManifests(mockManifestFile) - if len(manifests) != 1 { - t.Errorf("Expected 1 manifest, got %v", len(manifests)) - } - expected := map[string]string{"manifest-0": expectedManifest} - if !reflect.DeepEqual(manifests, expected) { - t.Errorf("Expected %v, got %v", expected, manifests) - } -} diff --git a/pkg/releaseutil/sorter.go b/pkg/releaseutil/sorter.go deleted file mode 100644 index 1a8aa78a6..000000000 --- a/pkg/releaseutil/sorter.go +++ /dev/null @@ -1,78 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "sort" - - rspb "helm.sh/helm/v3/pkg/release" -) - -type list []*rspb.Release - -func (s list) Len() int { return len(s) } -func (s list) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// ByName sorts releases by name -type ByName struct{ list } - -// Less compares to releases -func (s ByName) Less(i, j int) bool { return s.list[i].Name < s.list[j].Name } - -// ByDate sorts releases by date -type ByDate struct{ list } - -// Less compares to releases -func (s ByDate) Less(i, j int) bool { - ti := s.list[i].Info.LastDeployed.Unix() - tj := s.list[j].Info.LastDeployed.Unix() - return ti < tj -} - -// ByRevision sorts releases by revision number -type ByRevision struct{ list } - -// Less compares to releases -func (s ByRevision) Less(i, j int) bool { - return s.list[i].Version < s.list[j].Version -} - -// Reverse reverses the list of releases sorted by the sort func. -func Reverse(list []*rspb.Release, sortFn func([]*rspb.Release)) { - sortFn(list) - for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 { - list[i], list[j] = list[j], list[i] - } -} - -// SortByName returns the list of releases sorted -// in lexicographical order. -func SortByName(list []*rspb.Release) { - sort.Sort(ByName{list}) -} - -// SortByDate returns the list of releases sorted by a -// release's last deployed time (in seconds). -func SortByDate(list []*rspb.Release) { - sort.Sort(ByDate{list}) -} - -// SortByRevision returns the list of releases sorted by a -// release's revision number (release.Version). -func SortByRevision(list []*rspb.Release) { - sort.Sort(ByRevision{list}) -} diff --git a/pkg/releaseutil/sorter_test.go b/pkg/releaseutil/sorter_test.go deleted file mode 100644 index 9544d2018..000000000 --- a/pkg/releaseutil/sorter_test.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -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 releaseutil // import "helm.sh/helm/v3/pkg/releaseutil" - -import ( - "testing" - "time" - - rspb "helm.sh/helm/v3/pkg/release" - helmtime "helm.sh/helm/v3/pkg/time" -) - -// note: this test data is shared with filter_test.go. - -var releases = []*rspb.Release{ - tsRelease("quiet-bear", 2, 2000, rspb.StatusSuperseded), - tsRelease("angry-bird", 4, 3000, rspb.StatusDeployed), - tsRelease("happy-cats", 1, 4000, rspb.StatusUninstalled), - tsRelease("vocal-dogs", 3, 6000, rspb.StatusUninstalled), -} - -func tsRelease(name string, vers int, dur time.Duration, status rspb.Status) *rspb.Release { - info := &rspb.Info{Status: status, LastDeployed: helmtime.Now().Add(dur)} - return &rspb.Release{ - Name: name, - Version: vers, - Info: info, - } -} - -func check(t *testing.T, by string, fn func(int, int) bool) { - for i := len(releases) - 1; i > 0; i-- { - if fn(i, i-1) { - t.Errorf("release at positions '(%d,%d)' not sorted by %s", i-1, i, by) - } - } -} - -func TestSortByName(t *testing.T) { - SortByName(releases) - - check(t, "ByName", func(i, j int) bool { - ni := releases[i].Name - nj := releases[j].Name - return ni < nj - }) -} - -func TestSortByDate(t *testing.T) { - SortByDate(releases) - - check(t, "ByDate", func(i, j int) bool { - ti := releases[i].Info.LastDeployed.Second() - tj := releases[j].Info.LastDeployed.Second() - return ti < tj - }) -} - -func TestSortByRevision(t *testing.T) { - SortByRevision(releases) - - check(t, "ByRevision", func(i, j int) bool { - vi := releases[i].Version - vj := releases[j].Version - return vi < vj - }) -} - -func TestReverseSortByName(t *testing.T) { - Reverse(releases, SortByName) - check(t, "ByName", func(i, j int) bool { - ni := releases[i].Name - nj := releases[j].Name - return ni > nj - }) -} - -func TestReverseSortByDate(t *testing.T) { - Reverse(releases, SortByDate) - check(t, "ByDate", func(i, j int) bool { - ti := releases[i].Info.LastDeployed.Second() - tj := releases[j].Info.LastDeployed.Second() - return ti > tj - }) -} - -func TestReverseSortByRevision(t *testing.T) { - Reverse(releases, SortByRevision) - check(t, "ByRevision", func(i, j int) bool { - vi := releases[i].Version - vj := releases[j].Version - return vi > vj - }) -} diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go deleted file mode 100644 index fce947e4c..000000000 --- a/pkg/repo/chartrepo.go +++ /dev/null @@ -1,313 +0,0 @@ -/* -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 repo // import "helm.sh/helm/v3/pkg/repo" - -import ( - "crypto/rand" - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/provenance" -) - -// Entry represents a collection of parameters for chart repository -type Entry struct { - Name string `json:"name"` - URL string `json:"url"` - Username string `json:"username"` - Password string `json:"password"` - CertFile string `json:"certFile"` - KeyFile string `json:"keyFile"` - CAFile string `json:"caFile"` - InsecureSkipTLSverify bool `json:"insecure_skip_tls_verify"` - PassCredentialsAll bool `json:"pass_credentials_all"` -} - -// ChartRepository represents a chart repository -type ChartRepository struct { - Config *Entry - ChartPaths []string - IndexFile *IndexFile - Client getter.Getter - CachePath string -} - -// NewChartRepository constructs ChartRepository -func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) { - u, err := url.Parse(cfg.URL) - if err != nil { - return nil, errors.Errorf("invalid chart URL format: %s", cfg.URL) - } - - client, err := getters.ByScheme(u.Scheme) - if err != nil { - return nil, errors.Errorf("could not find protocol handler for: %s", u.Scheme) - } - - return &ChartRepository{ - Config: cfg, - IndexFile: NewIndexFile(), - Client: client, - CachePath: helmpath.CachePath("repository"), - }, nil -} - -// Load loads a directory of charts as if it were a repository. -// -// It requires the presence of an index.yaml file in the directory. -// -// Deprecated: remove in Helm 4. -func (r *ChartRepository) Load() error { - dirInfo, err := os.Stat(r.Config.Name) - if err != nil { - return err - } - if !dirInfo.IsDir() { - return errors.Errorf("%q is not a directory", r.Config.Name) - } - - // FIXME: Why are we recursively walking directories? - // FIXME: Why are we not reading the repositories.yaml to figure out - // what repos to use? - filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error { - if !f.IsDir() { - if strings.Contains(f.Name(), "-index.yaml") { - i, err := LoadIndexFile(path) - if err != nil { - return err - } - r.IndexFile = i - } else if strings.HasSuffix(f.Name(), ".tgz") { - r.ChartPaths = append(r.ChartPaths, path) - } - } - return nil - }) - return nil -} - -// DownloadIndexFile fetches the index from a repository. -func (r *ChartRepository) DownloadIndexFile() (string, error) { - parsedURL, err := url.Parse(r.Config.URL) - if err != nil { - return "", err - } - parsedURL.RawPath = path.Join(parsedURL.RawPath, "index.yaml") - parsedURL.Path = path.Join(parsedURL.Path, "index.yaml") - - indexURL := parsedURL.String() - // TODO add user-agent - resp, err := r.Client.Get(indexURL, - getter.WithURL(r.Config.URL), - getter.WithInsecureSkipVerifyTLS(r.Config.InsecureSkipTLSverify), - getter.WithTLSClientConfig(r.Config.CertFile, r.Config.KeyFile, r.Config.CAFile), - getter.WithBasicAuth(r.Config.Username, r.Config.Password), - getter.WithPassCredentialsAll(r.Config.PassCredentialsAll), - ) - if err != nil { - return "", err - } - - index, err := ioutil.ReadAll(resp) - if err != nil { - return "", err - } - - indexFile, err := loadIndex(index, r.Config.URL) - if err != nil { - return "", err - } - - // Create the chart list file in the cache directory - var charts strings.Builder - for name := range indexFile.Entries { - fmt.Fprintln(&charts, name) - } - chartsFile := filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - os.MkdirAll(filepath.Dir(chartsFile), 0755) - ioutil.WriteFile(chartsFile, []byte(charts.String()), 0644) - - // Create the index file in the cache directory - fname := filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name)) - os.MkdirAll(filepath.Dir(fname), 0755) - return fname, ioutil.WriteFile(fname, index, 0644) -} - -// Index generates an index for the chart repository and writes an index.yaml file. -func (r *ChartRepository) Index() error { - err := r.generateIndex() - if err != nil { - return err - } - return r.saveIndexFile() -} - -func (r *ChartRepository) saveIndexFile() error { - index, err := yaml.Marshal(r.IndexFile) - if err != nil { - return err - } - return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644) -} - -func (r *ChartRepository) generateIndex() error { - for _, path := range r.ChartPaths { - ch, err := loader.Load(path) - if err != nil { - return err - } - - digest, err := provenance.DigestFile(path) - if err != nil { - return err - } - - if !r.IndexFile.Has(ch.Name(), ch.Metadata.Version) { - if err := r.IndexFile.MustAdd(ch.Metadata, path, r.Config.URL, digest); err != nil { - return errors.Wrapf(err, "failed adding to %s to index", path) - } - } - // TODO: If a chart exists, but has a different Digest, should we error? - } - r.IndexFile.SortEntries() - return nil -} - -// FindChartInRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories -func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { - return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters) -} - -// FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials for the chart repository. -func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) { - return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters) -} - -// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials and TLS verify flag for the chart repository. -// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) { - return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, false, getters) -} - -// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL -// without adding repo to repositories, like FindChartInRepoURL, -// but it also receives credentials, TLS verify flag, and if credentials should -// be passed on to other domains. -// TODO Helm 4, FindChartInAuthAndTLSAndPassRepoURL should be integrated into FindChartInAuthRepoURL. -func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify, passCredentialsAll bool, getters getter.Providers) (string, error) { - - // Download and write the index file to a temporary location - buf := make([]byte, 20) - rand.Read(buf) - name := strings.ReplaceAll(base64.StdEncoding.EncodeToString(buf), "/", "-") - - c := Entry{ - URL: repoURL, - Username: username, - Password: password, - PassCredentialsAll: passCredentialsAll, - CertFile: certFile, - KeyFile: keyFile, - CAFile: caFile, - Name: name, - InsecureSkipTLSverify: insecureSkipTLSverify, - } - r, err := NewChartRepository(&c, getters) - if err != nil { - return "", err - } - idx, err := r.DownloadIndexFile() - if err != nil { - return "", errors.Wrapf(err, "looks like %q is not a valid chart repository or cannot be reached", repoURL) - } - defer func() { - os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name))) - os.RemoveAll(filepath.Join(r.CachePath, helmpath.CacheIndexFile(r.Config.Name))) - }() - - // Read the index file for the repository to get chart information and return chart URL - repoIndex, err := LoadIndexFile(idx) - if err != nil { - return "", err - } - - errMsg := fmt.Sprintf("chart %q", chartName) - if chartVersion != "" { - errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion) - } - cv, err := repoIndex.Get(chartName, chartVersion) - if err != nil { - return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL) - } - - if len(cv.URLs) == 0 { - return "", errors.Errorf("%s has no downloadable URLs", errMsg) - } - - chartURL := cv.URLs[0] - - absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) - if err != nil { - return "", errors.Wrap(err, "failed to make chart URL absolute") - } - - return absoluteChartURL, nil -} - -// ResolveReferenceURL resolves refURL relative to baseURL. -// If refURL is absolute, it simply returns refURL. -func ResolveReferenceURL(baseURL, refURL string) (string, error) { - // We need a trailing slash for ResolveReference to work, but make sure there isn't already one - parsedBaseURL, err := url.Parse(strings.TrimSuffix(baseURL, "/") + "/") - if err != nil { - return "", errors.Wrapf(err, "failed to parse %s as URL", baseURL) - } - - parsedRefURL, err := url.Parse(refURL) - if err != nil { - return "", errors.Wrapf(err, "failed to parse %s as URL", refURL) - } - - return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil -} - -func (e *Entry) String() string { - buf, err := json.Marshal(e) - if err != nil { - log.Panic(err) - } - return string(buf) -} diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go deleted file mode 100644 index 3dae90391..000000000 --- a/pkg/repo/chartrepo_test.go +++ /dev/null @@ -1,419 +0,0 @@ -/* -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 repo - -import ( - "bytes" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "reflect" - "runtime" - "strings" - "testing" - "time" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" -) - -const ( - testRepository = "testdata/repository" - testURL = "http://example-charts.com" -) - -func TestLoadChartRepository(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - paths := []string{ - filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), - filepath.Join(testRepository, "sprocket-1.1.0.tgz"), - filepath.Join(testRepository, "sprocket-1.2.0.tgz"), - filepath.Join(testRepository, "universe/zarthal-1.0.0.tgz"), - } - - if r.Config.Name != testRepository { - t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name) - } - - if !reflect.DeepEqual(r.ChartPaths, paths) { - t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths) - } - - if r.Config.URL != testURL { - t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL) - } -} - -func TestIndex(t *testing.T) { - r, err := NewChartRepository(&Entry{ - Name: testRepository, - URL: testURL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepository, err) - } - - if err := r.Load(); err != nil { - t.Errorf("Problem loading chart repository from %s: %v", testRepository, err) - } - - err = r.Index() - if err != nil { - t.Errorf("Error performing index: %v\n", err) - } - - tempIndexPath := filepath.Join(testRepository, indexPath) - actual, err := LoadIndexFile(tempIndexPath) - defer os.Remove(tempIndexPath) // clean up - if err != nil { - t.Errorf("Error loading index file %v", err) - } - verifyIndex(t, actual) - - // Re-index and test again. - err = r.Index() - if err != nil { - t.Errorf("Error performing re-index: %s\n", err) - } - second, err := LoadIndexFile(tempIndexPath) - if err != nil { - t.Errorf("Error re-loading index file %v", err) - } - verifyIndex(t, second) -} - -type CustomGetter struct { - repoUrls []string -} - -func (g *CustomGetter) Get(href string, options ...getter.Option) (*bytes.Buffer, error) { - index := &IndexFile{ - APIVersion: "v1", - Generated: time.Now(), - } - indexBytes, err := yaml.Marshal(index) - if err != nil { - return nil, err - } - g.repoUrls = append(g.repoUrls, href) - return bytes.NewBuffer(indexBytes), nil -} - -func TestIndexCustomSchemeDownload(t *testing.T) { - repoName := "gcs-repo" - repoURL := "gs://some-gcs-bucket" - myCustomGetter := &CustomGetter{} - customGetterConstructor := func(options ...getter.Option) (getter.Getter, error) { - return myCustomGetter, nil - } - providers := getter.Providers{{ - Schemes: []string{"gs"}, - New: customGetterConstructor, - }} - repo, err := NewChartRepository(&Entry{ - Name: repoName, - URL: repoURL, - }, providers) - if err != nil { - t.Fatalf("Problem loading chart repository from %s: %v", repoURL, err) - } - repo.CachePath = ensure.TempDir(t) - defer os.RemoveAll(repo.CachePath) - - tempIndexFile, err := ioutil.TempFile("", "test-repo") - if err != nil { - t.Fatalf("Failed to create temp index file: %v", err) - } - defer os.Remove(tempIndexFile.Name()) - - idx, err := repo.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %v", idx, err) - } - - if len(myCustomGetter.repoUrls) != 1 { - t.Fatalf("Custom Getter.Get should be called once") - } - - expectedRepoIndexURL := repoURL + "/index.yaml" - if myCustomGetter.repoUrls[0] != expectedRepoIndexURL { - t.Fatalf("Custom Getter.Get should be called with %s", expectedRepoIndexURL) - } -} - -func verifyIndex(t *testing.T, actual *IndexFile) { - var empty time.Time - if actual.Generated.Equal(empty) { - t.Errorf("Generated should be greater than 0: %s", actual.Generated) - } - - if actual.APIVersion != APIVersionV1 { - t.Error("Expected v1 API") - } - - entries := actual.Entries - if numEntries := len(entries); numEntries != 3 { - t.Errorf("Expected 3 charts to be listed in index file but got %v", numEntries) - } - - expects := map[string]ChartVersions{ - "frobnitz": { - { - Metadata: &chart.Metadata{ - Name: "frobnitz", - Version: "1.2.3", - }, - }, - }, - "sprocket": { - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.2.0", - }, - }, - { - Metadata: &chart.Metadata{ - Name: "sprocket", - Version: "1.1.0", - }, - }, - }, - "zarthal": { - { - Metadata: &chart.Metadata{ - Name: "zarthal", - Version: "1.0.0", - }, - }, - }, - } - - for name, versions := range expects { - got, ok := entries[name] - if !ok { - t.Errorf("Could not find %q entry", name) - continue - } - if len(versions) != len(got) { - t.Errorf("Expected %d versions, got %d", len(versions), len(got)) - continue - } - for i, e := range versions { - g := got[i] - if e.Name != g.Name { - t.Errorf("Expected %q, got %q", e.Name, g.Name) - } - if e.Version != g.Version { - t.Errorf("Expected %q, got %q", e.Version, g.Version) - } - if len(g.Keywords) != 3 { - t.Error("Expected 3 keywords.") - } - if len(g.Maintainers) != 2 { - t.Error("Expected 2 maintainers.") - } - if g.Created.Equal(empty) { - t.Error("Expected created to be non-empty") - } - if g.Description == "" { - t.Error("Expected description to be non-empty") - } - if g.Home == "" { - t.Error("Expected home to be non-empty") - } - if g.Digest == "" { - t.Error("Expected digest to be non-empty") - } - if len(g.URLs) != 1 { - t.Error("Expected exactly 1 URL") - } - } - } -} - -// startLocalServerForTests Start the local helm server -func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { - if handler == nil { - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - return nil, err - } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(fileBytes) - }) - } - - return httptest.NewServer(handler), nil -} - -// startLocalTLSServerForTests Start the local helm server with TLS -func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) { - if handler == nil { - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - return nil, err - } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write(fileBytes) - }) - } - - return httptest.NewTLSServer(handler), nil -} - -func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) { - srv, err := startLocalTLSServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } - - // If the insecureSkipTLsverify is false, it will return an error that contains "x509: certificate signed by unknown authority". - _, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{})) - // Go communicates with the platform and different platforms return different messages. Go itself tests darwin - // differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older - // versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test - // for both messages. - if runtime.GOOS == "darwin" { - if !strings.Contains(err.Error(), "x509: “Acme Co” certificate is not trusted") && !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) - } - } else if !strings.Contains(err.Error(), "x509: certificate signed by unknown authority") { - t.Errorf("Expected TLS error for function FindChartInAuthAndTLSAndPassRepoURL not found, but got a different error (%v)", err) - } -} - -func TestFindChartInRepoURL(t *testing.T) { - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{})) - if err != nil { - t.Fatalf("%v", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } - - chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.1.0.tgz" { - t.Errorf("%s is not the valid URL", chartURL) - } -} - -func TestErrorFindChartInRepoURL(t *testing.T) { - - g := getter.All(&cli.EnvSettings{ - RepositoryCache: ensure.TempDir(t), - }) - - if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil { - t.Errorf("Expected error for bad chart URL, but did not get any errors") - } else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) { - t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err) - } - - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } - - if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil { - t.Errorf("Expected error for chart not found, but did not get any errors") - } else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } - - if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil { - t.Errorf("Expected error for no chart URLs available, but did not get any errors") - } else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` { - t.Errorf("Expected error for chart not found, but got a different error (%v)", err) - } -} - -func TestResolveReferenceURL(t *testing.T) { - chartURL, err := ResolveReferenceURL("http://localhost:8123/charts/", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123/charts-with-no-trailing-slash", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts-with-no-trailing-slash/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123", "https://charts.helm.sh/stable/nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "https://charts.helm.sh/stable/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } - - chartURL, err = ResolveReferenceURL("http://localhost:8123/charts%2fwith%2fescaped%2fslash", "nginx-0.2.0.tgz") - if err != nil { - t.Errorf("%s", err) - } - if chartURL != "http://localhost:8123/charts%2fwith%2fescaped%2fslash/nginx-0.2.0.tgz" { - t.Errorf("%s", chartURL) - } -} diff --git a/pkg/repo/doc.go b/pkg/repo/doc.go deleted file mode 100644 index 05650100b..000000000 --- a/pkg/repo/doc.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -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 repo implements the Helm Chart Repository. - -A chart repository is an HTTP server that provides information on charts. A local -repository cache is an on-disk representation of a chart repository. - -There are two important file formats for chart repositories. - -The first is the 'index.yaml' format, which is expressed like this: - - apiVersion: v1 - entries: - frobnitz: - - created: 2016-09-29T12:14:34.830161306-06:00 - description: This is a frobnitz. - digest: 587bd19a9bd9d2bc4a6d25ab91c8c8e7042c47b4ac246e37bf8e1e74386190f4 - home: http://example.com - keywords: - - frobnitz - - sprocket - - dodad - maintainers: - - email: helm@example.com - name: The Helm Team - - email: nobody@example.com - name: Someone Else - name: frobnitz - urls: - - http://example-charts.com/testdata/repository/frobnitz-1.2.3.tgz - version: 1.2.3 - sprocket: - - created: 2016-09-29T12:14:34.830507606-06:00 - description: This is a sprocket" - digest: 8505ff813c39502cc849a38e1e4a8ac24b8e6e1dcea88f4c34ad9b7439685ae6 - home: http://example.com - keywords: - - frobnitz - - sprocket - - dodad - maintainers: - - email: helm@example.com - name: The Helm Team - - email: nobody@example.com - name: Someone Else - name: sprocket - urls: - - http://example-charts.com/testdata/repository/sprocket-1.2.0.tgz - version: 1.2.0 - generated: 2016-09-29T12:14:34.829721375-06:00 - -An index.yaml file contains the necessary descriptive information about what -charts are available in a repository, and how to get them. - -The second file format is the repositories.yaml file format. This file is for -facilitating local cached copies of one or more chart repositories. - -The format of a repository.yaml file is: - - apiVersion: v1 - generated: TIMESTAMP - repositories: - - name: stable - url: http://example.com/charts - cache: stable-index.yaml - - name: incubator - url: http://example.com/incubator - cache: incubator-index.yaml - -This file maps three bits of information about a repository: - - - The name the user uses to refer to it - - The fully qualified URL to the repository (index.yaml will be appended) - - The name of the local cachefile - -The format for both files was changed after Helm v2.0.0-Alpha.4. Helm is not -backwards compatible with those earlier versions. -*/ -package repo diff --git a/pkg/repo/index.go b/pkg/repo/index.go deleted file mode 100644 index 1b65ac497..000000000 --- a/pkg/repo/index.go +++ /dev/null @@ -1,356 +0,0 @@ -/* -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 repo - -import ( - "bytes" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "sort" - "strings" - "time" - - "github.com/Masterminds/semver/v3" - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/fileutil" - "helm.sh/helm/v3/internal/urlutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/provenance" -) - -var indexPath = "index.yaml" - -// APIVersionV1 is the v1 API version for index and repository files. -const APIVersionV1 = "v1" - -var ( - // ErrNoAPIVersion indicates that an API version was not specified. - ErrNoAPIVersion = errors.New("no API version specified") - // ErrNoChartVersion indicates that a chart with the given version is not found. - ErrNoChartVersion = errors.New("no chart version found") - // ErrNoChartName indicates that a chart with the given name is not found. - ErrNoChartName = errors.New("no chart name found") - // ErrEmptyIndexYaml indicates that the content of index.yaml is empty. - ErrEmptyIndexYaml = errors.New("empty index.yaml file") -) - -// ChartVersions is a list of versioned chart references. -// Implements a sorter on Version. -type ChartVersions []*ChartVersion - -// Len returns the length. -func (c ChartVersions) Len() int { return len(c) } - -// Swap swaps the position of two items in the versions slice. -func (c ChartVersions) Swap(i, j int) { c[i], c[j] = c[j], c[i] } - -// Less returns true if the version of entry a is less than the version of entry b. -func (c ChartVersions) Less(a, b int) bool { - // Failed parse pushes to the back. - i, err := semver.NewVersion(c[a].Version) - if err != nil { - return true - } - j, err := semver.NewVersion(c[b].Version) - if err != nil { - return false - } - return i.LessThan(j) -} - -// IndexFile represents the index file in a chart repository -type IndexFile struct { - // This is used ONLY for validation against chartmuseum's index files and is discarded after validation. - ServerInfo map[string]interface{} `json:"serverInfo,omitempty"` - APIVersion string `json:"apiVersion"` - Generated time.Time `json:"generated"` - Entries map[string]ChartVersions `json:"entries"` - PublicKeys []string `json:"publicKeys,omitempty"` - - // Annotations are additional mappings uninterpreted by Helm. They are made available for - // other applications to add information to the index file. - Annotations map[string]string `json:"annotations,omitempty"` -} - -// NewIndexFile initializes an index. -func NewIndexFile() *IndexFile { - return &IndexFile{ - APIVersion: APIVersionV1, - Generated: time.Now(), - Entries: map[string]ChartVersions{}, - PublicKeys: []string{}, - } -} - -// LoadIndexFile takes a file at the given path and returns an IndexFile object -func LoadIndexFile(path string) (*IndexFile, error) { - b, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - i, err := loadIndex(b, path) - if err != nil { - return nil, errors.Wrapf(err, "error loading %s", path) - } - return i, nil -} - -// MustAdd adds a file to the index -// This can leave the index in an unsorted state -func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error { - if md.APIVersion == "" { - md.APIVersion = chart.APIVersionV1 - } - if err := md.Validate(); err != nil { - return errors.Wrapf(err, "validate failed for %s", filename) - } - - u := filename - if baseURL != "" { - _, file := filepath.Split(filename) - var err error - u, err = urlutil.URLJoin(baseURL, file) - if err != nil { - u = path.Join(baseURL, file) - } - } - cr := &ChartVersion{ - URLs: []string{u}, - Metadata: md, - Digest: digest, - Created: time.Now(), - } - ee := i.Entries[md.Name] - i.Entries[md.Name] = append(ee, cr) - return nil -} - -// Add adds a file to the index and logs an error. -// -// Deprecated: Use index.MustAdd instead. -func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { - if err := i.MustAdd(md, filename, baseURL, digest); err != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", md.Name, md.Version, filename, err) - } -} - -// Has returns true if the index has an entry for a chart with the given name and exact version. -func (i IndexFile) Has(name, version string) bool { - _, err := i.Get(name, version) - return err == nil -} - -// SortEntries sorts the entries by version in descending order. -// -// In canonical form, the individual version records should be sorted so that -// the most recent release for every version is in the 0th slot in the -// Entries.ChartVersions array. That way, tooling can predict the newest -// version without needing to parse SemVers. -func (i IndexFile) SortEntries() { - for _, versions := range i.Entries { - sort.Sort(sort.Reverse(versions)) - } -} - -// Get returns the ChartVersion for the given name. -// -// If version is empty, this will return the chart with the latest stable version, -// prerelease versions will be skipped. -func (i IndexFile) Get(name, version string) (*ChartVersion, error) { - vs, ok := i.Entries[name] - if !ok { - return nil, ErrNoChartName - } - if len(vs) == 0 { - return nil, ErrNoChartVersion - } - - var constraint *semver.Constraints - if version == "" { - constraint, _ = semver.NewConstraint("*") - } else { - var err error - constraint, err = semver.NewConstraint(version) - if err != nil { - return nil, err - } - } - - // when customer input exact version, check whether have exact match one first - if len(version) != 0 { - for _, ver := range vs { - if version == ver.Version { - return ver, nil - } - } - } - - for _, ver := range vs { - test, err := semver.NewVersion(ver.Version) - if err != nil { - continue - } - - if constraint.Check(test) { - return ver, nil - } - } - return nil, errors.Errorf("no chart version found for %s-%s", name, version) -} - -// WriteFile writes an index file to the given destination path. -// -// The mode on the file is set to 'mode'. -func (i IndexFile) WriteFile(dest string, mode os.FileMode) error { - b, err := yaml.Marshal(i) - if err != nil { - return err - } - return fileutil.AtomicWriteFile(dest, bytes.NewReader(b), mode) -} - -// Merge merges the given index file into this index. -// -// This merges by name and version. -// -// If one of the entries in the given index does _not_ already exist, it is added. -// In all other cases, the existing record is preserved. -// -// This can leave the index in an unsorted state -func (i *IndexFile) Merge(f *IndexFile) { - for _, cvs := range f.Entries { - for _, cv := range cvs { - if !i.Has(cv.Name, cv.Version) { - e := i.Entries[cv.Name] - i.Entries[cv.Name] = append(e, cv) - } - } - } -} - -// ChartVersion represents a chart entry in the IndexFile -type ChartVersion struct { - *chart.Metadata - URLs []string `json:"urls"` - Created time.Time `json:"created,omitempty"` - Removed bool `json:"removed,omitempty"` - Digest string `json:"digest,omitempty"` - - // ChecksumDeprecated is deprecated in Helm 3, and therefore ignored. Helm 3 replaced - // this with Digest. However, with a strict YAML parser enabled, a field must be - // present on the struct for backwards compatibility. - ChecksumDeprecated string `json:"checksum,omitempty"` - - // EngineDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. - EngineDeprecated string `json:"engine,omitempty"` - - // TillerVersionDeprecated is deprecated in Helm 3, and therefore ignored. However, with a strict - // YAML parser enabled, this field must be present. - TillerVersionDeprecated string `json:"tillerVersion,omitempty"` - - // URLDeprecated is deprecated in Helm 3, superseded by URLs. It is ignored. However, - // with a strict YAML parser enabled, this must be present on the struct. - URLDeprecated string `json:"url,omitempty"` -} - -// IndexDirectory reads a (flat) directory and generates an index. -// -// It indexes only charts that have been packaged (*.tgz). -// -// The index returned will be in an unsorted state -func IndexDirectory(dir, baseURL string) (*IndexFile, error) { - archives, err := filepath.Glob(filepath.Join(dir, "*.tgz")) - if err != nil { - return nil, err - } - moreArchives, err := filepath.Glob(filepath.Join(dir, "**/*.tgz")) - if err != nil { - return nil, err - } - archives = append(archives, moreArchives...) - - index := NewIndexFile() - for _, arch := range archives { - fname, err := filepath.Rel(dir, arch) - if err != nil { - return index, err - } - - var parentDir string - parentDir, fname = filepath.Split(fname) - // filepath.Split appends an extra slash to the end of parentDir. We want to strip that out. - parentDir = strings.TrimSuffix(parentDir, string(os.PathSeparator)) - parentURL, err := urlutil.URLJoin(baseURL, parentDir) - if err != nil { - parentURL = path.Join(baseURL, parentDir) - } - - c, err := loader.Load(arch) - if err != nil { - // Assume this is not a chart. - continue - } - hash, err := provenance.DigestFile(arch) - if err != nil { - return index, err - } - if err := index.MustAdd(c.Metadata, fname, parentURL, hash); err != nil { - return index, errors.Wrapf(err, "failed adding to %s to index", fname) - } - } - return index, nil -} - -// loadIndex loads an index file and does minimal validity checking. -// -// The source parameter is only used for logging. -// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. -func loadIndex(data []byte, source string) (*IndexFile, error) { - i := &IndexFile{} - - if len(data) == 0 { - return i, ErrEmptyIndexYaml - } - - if err := yaml.UnmarshalStrict(data, i); err != nil { - return i, err - } - - for name, cvs := range i.Entries { - for idx := len(cvs) - 1; idx >= 0; idx-- { - if cvs[idx].APIVersion == "" { - cvs[idx].APIVersion = chart.APIVersionV1 - } - if err := cvs[idx].Validate(); err != nil { - log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) - cvs = append(cvs[:idx], cvs[idx+1:]...) - } - } - } - i.SortEntries() - if i.APIVersion == "" { - return i, ErrNoAPIVersion - } - return i, nil -} diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go deleted file mode 100644 index a75a4177a..000000000 --- a/pkg/repo/index_test.go +++ /dev/null @@ -1,528 +0,0 @@ -/* -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 repo - -import ( - "bufio" - "bytes" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "sort" - "strings" - "testing" - - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/helmpath" -) - -const ( - testfile = "testdata/local-index.yaml" - annotationstestfile = "testdata/local-index-annotations.yaml" - chartmuseumtestfile = "testdata/chartmuseum-index.yaml" - unorderedTestfile = "testdata/local-index-unordered.yaml" - testRepo = "test-repo" - indexWithDuplicates = ` -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - nginx: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" -` -) - -func TestIndexFile(t *testing.T) { - i := NewIndexFile() - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.1.1"}, "cutter-0.1.1.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.1.0"}, "cutter-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "cutter", Version: "0.2.0"}, "cutter-0.2.0.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.9+alpha"}, "setter-0.1.9+alpha.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - {&chart.Metadata{APIVersion: "v2", Name: "setter", Version: "0.1.9+beta"}, "setter-0.1.9+beta.tgz", "http://example.com/charts", "sha256:1234567890abc"}, - } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } - } - - i.SortEntries() - - if i.APIVersion != APIVersionV1 { - t.Error("Expected API version v1") - } - - if len(i.Entries) != 3 { - t.Errorf("Expected 3 charts. Got %d", len(i.Entries)) - } - - if i.Entries["clipper"][0].Name != "clipper" { - t.Errorf("Expected clipper, got %s", i.Entries["clipper"][0].Name) - } - - if len(i.Entries["cutter"]) != 3 { - t.Error("Expected three cutters.") - } - - // Test that the sort worked. 0.2 should be at the first index for Cutter. - if v := i.Entries["cutter"][0].Version; v != "0.2.0" { - t.Errorf("Unexpected first version: %s", v) - } - - cv, err := i.Get("setter", "0.1.9") - if err == nil && !strings.Contains(cv.Metadata.Version, "0.1.9") { - t.Errorf("Unexpected version: %s", cv.Metadata.Version) - } - - cv, err = i.Get("setter", "0.1.9+alpha") - if err != nil || cv.Metadata.Version != "0.1.9+alpha" { - t.Errorf("Expected version: 0.1.9+alpha") - } -} - -func TestLoadIndex(t *testing.T) { - - tests := []struct { - Name string - Filename string - }{ - { - Name: "regular index file", - Filename: testfile, - }, - { - Name: "chartmuseum index file", - Filename: chartmuseumtestfile, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.Name, func(t *testing.T) { - t.Parallel() - i, err := LoadIndexFile(tc.Filename) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) - }) - } -} - -// TestLoadIndex_Duplicates is a regression to make sure that we don't non-deterministically allow duplicate packages. -func TestLoadIndex_Duplicates(t *testing.T) { - if _, err := loadIndex([]byte(indexWithDuplicates), "indexWithDuplicates"); err == nil { - t.Errorf("Expected an error when duplicate entries are present") - } -} - -func TestLoadIndex_Empty(t *testing.T) { - if _, err := loadIndex([]byte(""), "indexWithEmpty"); err == nil { - t.Errorf("Expected an error when index.yaml is empty.") - } -} - -func TestLoadIndexFileAnnotations(t *testing.T) { - i, err := LoadIndexFile(annotationstestfile) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) - - if len(i.Annotations) != 1 { - t.Fatalf("Expected 1 annotation but got %d", len(i.Annotations)) - } - if i.Annotations["helm.sh/test"] != "foo bar" { - t.Error("Did not get expected value for helm.sh/test annotation") - } -} - -func TestLoadUnorderedIndex(t *testing.T) { - i, err := LoadIndexFile(unorderedTestfile) - if err != nil { - t.Fatal(err) - } - verifyLocalIndex(t, i) -} - -func TestMerge(t *testing.T) { - ind1 := NewIndexFile() - - if err := ind1.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.1.0"}, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa"); err != nil { - t.Fatalf("unexpected error: %s", err) - } - - ind2 := NewIndexFile() - - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - {&chart.Metadata{APIVersion: "v2", Name: "dreadnought", Version: "0.2.0"}, "dreadnought-0.2.0.tgz", "http://example.com", "aaaabbbb"}, - {&chart.Metadata{APIVersion: "v2", Name: "doughnut", Version: "0.2.0"}, "doughnut-0.2.0.tgz", "http://example.com", "ccccbbbb"}, - } { - if err := ind2.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error: %s", err) - } - } - - ind1.Merge(ind2) - - if len(ind1.Entries) != 2 { - t.Errorf("Expected 2 entries, got %d", len(ind1.Entries)) - } - - vs := ind1.Entries["dreadnought"] - if len(vs) != 2 { - t.Errorf("Expected 2 versions, got %d", len(vs)) - } - - if v := vs[1]; v.Version != "0.2.0" { - t.Errorf("Expected %q version to be 0.2.0, got %s", v.Name, v.Version) - } - -} - -func TestDownloadIndexFile(t *testing.T) { - t.Run("should download index file", func(t *testing.T) { - srv, err := startLocalServerForTests(nil) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - r, err := NewChartRepository(&Entry{ - Name: testRepo, - URL: srv.URL, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } - - idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } - - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } - - i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } - verifyLocalIndex(t, i) - - // Check that charts file is also created - idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } - - b, err := ioutil.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } - verifyLocalChartsFile(t, b, i) - }) - - t.Run("should not decode the path in the repo url while downloading index", func(t *testing.T) { - chartRepoURLPath := "/some%2Fpath/test" - fileBytes, err := ioutil.ReadFile("testdata/local-index.yaml") - if err != nil { - t.Fatal(err) - } - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.RawPath == chartRepoURLPath+"/index.yaml" { - w.Write(fileBytes) - } - }) - srv, err := startLocalServerForTests(handler) - if err != nil { - t.Fatal(err) - } - defer srv.Close() - - r, err := NewChartRepository(&Entry{ - Name: testRepo, - URL: srv.URL + chartRepoURLPath, - }, getter.All(&cli.EnvSettings{})) - if err != nil { - t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) - } - - idx, err := r.DownloadIndexFile() - if err != nil { - t.Fatalf("Failed to download index file to %s: %#v", idx, err) - } - - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created index file: %#v", err) - } - - i, err := LoadIndexFile(idx) - if err != nil { - t.Fatalf("Index %q failed to parse: %s", testfile, err) - } - verifyLocalIndex(t, i) - - // Check that charts file is also created - idx = filepath.Join(r.CachePath, helmpath.CacheChartsFile(r.Config.Name)) - if _, err := os.Stat(idx); err != nil { - t.Fatalf("error finding created charts file: %#v", err) - } - - b, err := ioutil.ReadFile(idx) - if err != nil { - t.Fatalf("error reading charts file: %#v", err) - } - verifyLocalChartsFile(t, b, i) - }) -} - -func verifyLocalIndex(t *testing.T, i *IndexFile) { - numEntries := len(i.Entries) - if numEntries != 3 { - t.Errorf("Expected 3 entries in index file but got %d", numEntries) - } - - alpine, ok := i.Entries["alpine"] - if !ok { - t.Fatalf("'alpine' section not found.") - } - - if l := len(alpine); l != 1 { - t.Fatalf("'alpine' should have 1 chart, got %d", l) - } - - nginx, ok := i.Entries["nginx"] - if !ok || len(nginx) != 2 { - t.Fatalf("Expected 2 nginx entries") - } - - expects := []*ChartVersion{ - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "alpine", - Description: "string", - Version: "1.0.0", - Keywords: []string{"linux", "alpine", "small", "sumtin"}, - Home: "https://github.com/something", - }, - URLs: []string{ - "https://charts.helm.sh/stable/alpine-1.0.0.tgz", - "http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "nginx", - Description: "string", - Version: "0.2.0", - Keywords: []string{"popular", "web server", "proxy"}, - Home: "https://github.com/something/else", - }, - URLs: []string{ - "https://charts.helm.sh/stable/nginx-0.2.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - { - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "nginx", - Description: "string", - Version: "0.1.0", - Keywords: []string{"popular", "web server", "proxy"}, - Home: "https://github.com/something", - }, - URLs: []string{ - "https://charts.helm.sh/stable/nginx-0.1.0.tgz", - }, - Digest: "sha256:1234567890abcdef", - }, - } - tests := []*ChartVersion{alpine[0], nginx[0], nginx[1]} - - for i, tt := range tests { - expect := expects[i] - if tt.Name != expect.Name { - t.Errorf("Expected name %q, got %q", expect.Name, tt.Name) - } - if tt.Description != expect.Description { - t.Errorf("Expected description %q, got %q", expect.Description, tt.Description) - } - if tt.Version != expect.Version { - t.Errorf("Expected version %q, got %q", expect.Version, tt.Version) - } - if tt.Digest != expect.Digest { - t.Errorf("Expected digest %q, got %q", expect.Digest, tt.Digest) - } - if tt.Home != expect.Home { - t.Errorf("Expected home %q, got %q", expect.Home, tt.Home) - } - - for i, url := range tt.URLs { - if url != expect.URLs[i] { - t.Errorf("Expected URL %q, got %q", expect.URLs[i], url) - } - } - for i, kw := range tt.Keywords { - if kw != expect.Keywords[i] { - t.Errorf("Expected keywords %q, got %q", expect.Keywords[i], kw) - } - } - } -} - -func verifyLocalChartsFile(t *testing.T, chartsContent []byte, indexContent *IndexFile) { - var expected, real []string - for chart := range indexContent.Entries { - expected = append(expected, chart) - } - sort.Strings(expected) - - scanner := bufio.NewScanner(bytes.NewReader(chartsContent)) - for scanner.Scan() { - real = append(real, scanner.Text()) - } - sort.Strings(real) - - if strings.Join(expected, " ") != strings.Join(real, " ") { - t.Errorf("Cached charts file content unexpected. Expected:\n%s\ngot:\n%s", expected, real) - } -} - -func TestIndexDirectory(t *testing.T) { - dir := "testdata/repository" - index, err := IndexDirectory(dir, "http://localhost:8080") - if err != nil { - t.Fatal(err) - } - - if l := len(index.Entries); l != 3 { - t.Fatalf("Expected 3 entries, got %d", l) - } - - // Other things test the entry generation more thoroughly. We just test a - // few fields. - - corpus := []struct{ chartName, downloadLink string }{ - {"frobnitz", "http://localhost:8080/frobnitz-1.2.3.tgz"}, - {"zarthal", "http://localhost:8080/universe/zarthal-1.0.0.tgz"}, - } - - for _, test := range corpus { - cname := test.chartName - frobs, ok := index.Entries[cname] - if !ok { - t.Fatalf("Could not read chart %s", cname) - } - - frob := frobs[0] - if frob.Digest == "" { - t.Errorf("Missing digest of file %s.", frob.Name) - } - if frob.URLs[0] != test.downloadLink { - t.Errorf("Unexpected URLs: %v", frob.URLs) - } - if frob.Name != cname { - t.Errorf("Expected %q, got %q", cname, frob.Name) - } - } -} - -func TestIndexAdd(t *testing.T) { - i := NewIndexFile() - - for _, x := range []struct { - md *chart.Metadata - filename string - baseURL string - digest string - }{ - - {&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"}, - {&chart.Metadata{APIVersion: "v2", Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890"}, - } { - if err := i.MustAdd(x.md, x.filename, x.baseURL, x.digest); err != nil { - t.Errorf("unexpected error adding to index: %s", err) - } - } - - if i.Entries["clipper"][0].URLs[0] != "http://example.com/charts/clipper-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/clipper-0.1.0.tgz, got %s", i.Entries["clipper"][0].URLs[0]) - } - if i.Entries["alpine"][0].URLs[0] != "http://example.com/charts/alpine-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/alpine-0.1.0.tgz, got %s", i.Entries["alpine"][0].URLs[0]) - } - if i.Entries["deis"][0].URLs[0] != "http://example.com/charts/deis-0.1.0.tgz" { - t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0]) - } - - // test error condition - if err := i.MustAdd(&chart.Metadata{}, "error-0.1.0.tgz", "", ""); err == nil { - t.Fatal("expected error adding to index") - } -} - -func TestIndexWrite(t *testing.T) { - i := NewIndexFile() - if err := i.MustAdd(&chart.Metadata{APIVersion: "v2", Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890"); err != nil { - t.Fatalf("unexpected error: %s", err) - } - dir := t.TempDir() - testpath := filepath.Join(dir, "test") - i.WriteFile(testpath, 0600) - - got, err := ioutil.ReadFile(testpath) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(got), "clipper-0.1.0.tgz") { - t.Fatal("Index files doesn't contain expected content") - } -} diff --git a/pkg/repo/repo.go b/pkg/repo/repo.go deleted file mode 100644 index 6f1e90dad..000000000 --- a/pkg/repo/repo.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -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 repo // import "helm.sh/helm/v3/pkg/repo" - -import ( - "io/ioutil" - "os" - "path/filepath" - "time" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" -) - -// File represents the repositories.yaml file -type File struct { - APIVersion string `json:"apiVersion"` - Generated time.Time `json:"generated"` - Repositories []*Entry `json:"repositories"` -} - -// NewFile generates an empty repositories file. -// -// Generated and APIVersion are automatically set. -func NewFile() *File { - return &File{ - APIVersion: APIVersionV1, - Generated: time.Now(), - Repositories: []*Entry{}, - } -} - -// LoadFile takes a file at the given path and returns a File object -func LoadFile(path string) (*File, error) { - r := new(File) - b, err := ioutil.ReadFile(path) - if err != nil { - return r, errors.Wrapf(err, "couldn't load repositories file (%s)", path) - } - - err = yaml.Unmarshal(b, r) - return r, err -} - -// Add adds one or more repo entries to a repo file. -func (r *File) Add(re ...*Entry) { - r.Repositories = append(r.Repositories, re...) -} - -// Update attempts to replace one or more repo entries in a repo file. If an -// entry with the same name doesn't exist in the repo file it will add it. -func (r *File) Update(re ...*Entry) { - for _, target := range re { - r.update(target) - } -} - -func (r *File) update(e *Entry) { - for j, repo := range r.Repositories { - if repo.Name == e.Name { - r.Repositories[j] = e - return - } - } - r.Add(e) -} - -// Has returns true if the given name is already a repository name. -func (r *File) Has(name string) bool { - entry := r.Get(name) - return entry != nil -} - -// Get returns an entry with the given name if it exists, otherwise returns nil -func (r *File) Get(name string) *Entry { - for _, entry := range r.Repositories { - if entry.Name == name { - return entry - } - } - return nil -} - -// Remove removes the entry from the list of repositories. -func (r *File) Remove(name string) bool { - cp := []*Entry{} - found := false - for _, rf := range r.Repositories { - if rf.Name == name { - found = true - continue - } - cp = append(cp, rf) - } - r.Repositories = cp - return found -} - -// WriteFile writes a repositories file to the given path. -func (r *File) WriteFile(path string, perm os.FileMode) error { - data, err := yaml.Marshal(r) - if err != nil { - return err - } - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - return err - } - return ioutil.WriteFile(path, data, perm) -} diff --git a/pkg/repo/repo_test.go b/pkg/repo/repo_test.go deleted file mode 100644 index f87d2c202..000000000 --- a/pkg/repo/repo_test.go +++ /dev/null @@ -1,227 +0,0 @@ -/* -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 repo - -import ( - "io/ioutil" - "os" - "strings" - "testing" -) - -const testRepositoriesFile = "testdata/repositories.yaml" - -func TestFile(t *testing.T) { - rf := NewFile() - rf.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - if len(rf.Repositories) != 2 { - t.Fatal("Expected 2 repositories") - } - - if rf.Has("nosuchrepo") { - t.Error("Found nonexistent repo") - } - if !rf.Has("incubator") { - t.Error("incubator repo is missing") - } - - stable := rf.Repositories[0] - if stable.Name != "stable" { - t.Error("stable is not named stable") - } - if stable.URL != "https://example.com/stable/charts" { - t.Error("Wrong URL for stable") - } -} - -func TestNewFile(t *testing.T) { - expects := NewFile() - expects.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - file, err := LoadFile(testRepositoriesFile) - if err != nil { - t.Errorf("%q could not be loaded: %s", testRepositoriesFile, err) - } - - if len(expects.Repositories) != len(file.Repositories) { - t.Fatalf("Unexpected repo data: %#v", file.Repositories) - } - - for i, expect := range expects.Repositories { - got := file.Repositories[i] - if expect.Name != got.Name { - t.Errorf("Expected name %q, got %q", expect.Name, got.Name) - } - if expect.URL != got.URL { - t.Errorf("Expected url %q, got %q", expect.URL, got.URL) - } - } -} - -func TestRepoFile_Get(t *testing.T) { - repo := NewFile() - repo.Add( - &Entry{ - Name: "first", - URL: "https://example.com/first", - }, - &Entry{ - Name: "second", - URL: "https://example.com/second", - }, - &Entry{ - Name: "third", - URL: "https://example.com/third", - }, - &Entry{ - Name: "fourth", - URL: "https://example.com/fourth", - }, - ) - - name := "second" - - entry := repo.Get(name) - if entry == nil { - t.Fatalf("Expected repo entry %q to be found", name) - } - - if entry.URL != "https://example.com/second" { - t.Errorf("Expected repo URL to be %q but got %q", "https://example.com/second", entry.URL) - } - - entry = repo.Get("nonexistent") - if entry != nil { - t.Errorf("Got unexpected entry %+v", entry) - } -} - -func TestRemoveRepository(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - removeRepository := "stable" - found := sampleRepository.Remove(removeRepository) - if !found { - t.Errorf("expected repository %s not found", removeRepository) - } - - found = sampleRepository.Has(removeRepository) - if found { - t.Errorf("repository %s not deleted", removeRepository) - } -} - -func TestUpdateRepository(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - newRepoName := "sample" - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - }) - - if !sampleRepository.Has(newRepoName) { - t.Errorf("expected repository %s not found", newRepoName) - } - repoCount := len(sampleRepository.Repositories) - - sampleRepository.Update(&Entry{Name: newRepoName, - URL: "https://example.com/sample", - }) - - if repoCount != len(sampleRepository.Repositories) { - t.Errorf("invalid number of repositories found %d, expected number of repositories %d", len(sampleRepository.Repositories), repoCount) - } -} - -func TestWriteFile(t *testing.T) { - sampleRepository := NewFile() - sampleRepository.Add( - &Entry{ - Name: "stable", - URL: "https://example.com/stable/charts", - }, - &Entry{ - Name: "incubator", - URL: "https://example.com/incubator", - }, - ) - - file, err := ioutil.TempFile("", "helm-repo") - if err != nil { - t.Errorf("failed to create test-file (%v)", err) - } - defer os.Remove(file.Name()) - if err := sampleRepository.WriteFile(file.Name(), 0644); err != nil { - t.Errorf("failed to write file (%v)", err) - } - - repos, err := LoadFile(file.Name()) - if err != nil { - t.Errorf("failed to load file (%v)", err) - } - for _, repo := range sampleRepository.Repositories { - if !repos.Has(repo.Name) { - t.Errorf("expected repository %s not found", repo.Name) - } - } -} - -func TestRepoNotExists(t *testing.T) { - if _, err := LoadFile("/this/path/does/not/exist.yaml"); err == nil { - t.Errorf("expected err to be non-nil when path does not exist") - } else if !strings.Contains(err.Error(), "couldn't load repositories file") { - t.Errorf("expected prompt `couldn't load repositories file`") - } -} diff --git a/pkg/repo/repotest/doc.go b/pkg/repo/repotest/doc.go deleted file mode 100644 index 3bf98aa7e..000000000 --- a/pkg/repo/repotest/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 repotest provides utilities for testing. - -The server provides a testing server that can be set up and torn down quickly. -*/ -package repotest diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go deleted file mode 100644 index 90ad3d856..000000000 --- a/pkg/repo/repotest/server.go +++ /dev/null @@ -1,425 +0,0 @@ -/* -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 repotest - -import ( - "context" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "testing" - "time" - - "github.com/distribution/distribution/v3/configuration" - "github.com/distribution/distribution/v3/registry" - _ "github.com/distribution/distribution/v3/registry/auth/htpasswd" // used for docker test registry - _ "github.com/distribution/distribution/v3/registry/storage/driver/inmemory" // used for docker test registry - "github.com/phayes/freeport" - "golang.org/x/crypto/bcrypt" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/tlsutil" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/chartutil" - ociRegistry "helm.sh/helm/v3/pkg/registry" - "helm.sh/helm/v3/pkg/repo" -) - -// NewTempServerWithCleanup creates a server inside of a temp dir. -// -// If the passed in string is not "", it will be treated as a shell glob, and files -// will be copied from that path to the server's docroot. -// -// The caller is responsible for stopping the server. -// The temp dir will be removed by testing package automatically when test finished. -func NewTempServerWithCleanup(t *testing.T, glob string) (*Server, error) { - srv, err := NewTempServer(glob) - t.Cleanup(func() { os.RemoveAll(srv.docroot) }) - return srv, err -} - -// Set up a fake repo with basic auth enabled -func NewTempServerWithCleanupAndBasicAuth(t *testing.T, glob string) *Server { - srv, err := NewTempServerWithCleanup(t, glob) - srv.Stop() - if err != nil { - t.Fatal(err) - } - srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - username, password, ok := r.BasicAuth() - if !ok || username != "username" || password != "password" { - t.Errorf("Expected request to use basic auth and for username == 'username' and password == 'password', got '%v', '%s', '%s'", ok, username, password) - } - })) - srv.Start() - return srv -} - -type OCIServer struct { - *registry.Registry - RegistryURL string - Dir string - TestUsername string - TestPassword string - Client *ociRegistry.Client -} - -type OCIServerRunConfig struct { - DependingChart *chart.Chart -} - -type OCIServerOpt func(config *OCIServerRunConfig) - -func WithDependingChart(c *chart.Chart) OCIServerOpt { - return func(config *OCIServerRunConfig) { - config.DependingChart = c - } -} - -func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) { - testHtpasswdFileBasename := "authtest.htpasswd" - testUsername, testPassword := "username", "password" - - pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) - if err != nil { - t.Fatal("error generating bcrypt password for test htpasswd file") - } - htpasswdPath := filepath.Join(dir, testHtpasswdFileBasename) - err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644) - if err != nil { - t.Fatalf("error creating test htpasswd file") - } - - // Registry config - config := &configuration.Configuration{} - port, err := freeport.GetFreePort() - if err != nil { - t.Fatalf("error finding free port for test registry") - } - - config.HTTP.Addr = fmt.Sprintf(":%d", port) - config.HTTP.DrainTimeout = time.Duration(10) * time.Second - config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} - config.Auth = configuration.Auth{ - "htpasswd": configuration.Parameters{ - "realm": "localhost", - "path": htpasswdPath, - }, - } - - registryURL := fmt.Sprintf("localhost:%d", port) - - r, err := registry.NewRegistry(context.Background(), config) - if err != nil { - t.Fatal(err) - } - - return &OCIServer{ - Registry: r, - RegistryURL: registryURL, - TestUsername: testUsername, - TestPassword: testPassword, - Dir: dir, - }, nil -} - -func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) { - cfg := &OCIServerRunConfig{} - for _, fn := range opts { - fn(cfg) - } - - go srv.ListenAndServe() - - credentialsFile := filepath.Join(srv.Dir, "config.json") - - // init test client - registryClient, err := ociRegistry.NewClient( - ociRegistry.ClientOptDebug(true), - ociRegistry.ClientOptEnableCache(true), - ociRegistry.ClientOptWriter(os.Stdout), - ociRegistry.ClientOptCredentialsFile(credentialsFile), - ) - if err != nil { - t.Fatalf("error creating registry client") - } - - err = registryClient.Login( - srv.RegistryURL, - ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword), - ociRegistry.LoginOptInsecure(false)) - if err != nil { - t.Fatalf("error logging into registry with good credentials") - } - - ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL) - - err = chartutil.ExpandFile(srv.Dir, filepath.Join(srv.Dir, "oci-dependent-chart-0.1.0.tgz")) - if err != nil { - t.Fatal(err) - } - - // valid chart - ch, err := loader.LoadDir(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error loading chart") - } - - err = os.RemoveAll(filepath.Join(srv.Dir, "oci-dependent-chart")) - if err != nil { - t.Fatal("error removing chart before push") - } - - // save it back to disk.. - absPath, err := chartutil.Save(ch, srv.Dir) - if err != nil { - t.Fatal("could not create chart archive") - } - - // load it into memory... - contentBytes, err := ioutil.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } - - result, err := registryClient.Push(contentBytes, ref) - if err != nil { - t.Fatalf("error pushing dependent chart: %s", err) - } - t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ - "Config.Digest: %s, Config.Size: %d, "+ - "Chart.Digest: %s, Chart.Size: %d", - result.Manifest.Digest, result.Manifest.Size, - result.Config.Digest, result.Config.Size, - result.Chart.Digest, result.Chart.Size) - - srv.Client = registryClient - c := cfg.DependingChart - if c == nil { - return - } - - dependingRef := fmt.Sprintf("%s/u/ocitestuser/%s:%s", - srv.RegistryURL, c.Metadata.Name, c.Metadata.Version) - - // load it into memory... - absPath = filepath.Join(srv.Dir, - fmt.Sprintf("%s-%s.tgz", c.Metadata.Name, c.Metadata.Version)) - contentBytes, err = ioutil.ReadFile(absPath) - if err != nil { - t.Fatal("could not load chart into memory") - } - - result, err = registryClient.Push(contentBytes, dependingRef) - if err != nil { - t.Fatalf("error pushing depending chart: %s", err) - } - t.Logf("Manifest.Digest: %s, Manifest.Size: %d, "+ - "Config.Digest: %s, Config.Size: %d, "+ - "Chart.Digest: %s, Chart.Size: %d", - result.Manifest.Digest, result.Manifest.Size, - result.Config.Digest, result.Config.Size, - result.Chart.Digest, result.Chart.Size) -} - -// NewTempServer creates a server inside of a temp dir. -// -// If the passed in string is not "", it will be treated as a shell glob, and files -// will be copied from that path to the server's docroot. -// -// The caller is responsible for destroying the temp directory as well as stopping -// the server. -// -// Deprecated: use NewTempServerWithCleanup -func NewTempServer(glob string) (*Server, error) { - tdir, err := ioutil.TempDir("", "helm-repotest-") - if err != nil { - return nil, err - } - srv := NewServer(tdir) - - if glob != "" { - if _, err := srv.CopyCharts(glob); err != nil { - srv.Stop() - return srv, err - } - } - - return srv, nil -} - -// NewServer creates a repository server for testing. -// -// docroot should be a temp dir managed by the caller. -// -// This will start the server, serving files off of the docroot. -// -// Use CopyCharts to move charts into the repository and then index them -// for service. -func NewServer(docroot string) *Server { - root, err := filepath.Abs(docroot) - if err != nil { - panic(err) - } - srv := &Server{ - docroot: root, - } - srv.Start() - // Add the testing repository as the only repo. - if err := setTestingRepository(srv.URL(), filepath.Join(root, "repositories.yaml")); err != nil { - panic(err) - } - return srv -} - -// Server is an implementation of a repository server for testing. -type Server struct { - docroot string - srv *httptest.Server - middleware http.HandlerFunc -} - -// WithMiddleware injects middleware in front of the server. This can be used to inject -// additional functionality like layering in an authentication frontend. -func (s *Server) WithMiddleware(middleware http.HandlerFunc) { - s.middleware = middleware -} - -// Root gets the docroot for the server. -func (s *Server) Root() string { - return s.docroot -} - -// CopyCharts takes a glob expression and copies those charts to the server root. -func (s *Server) CopyCharts(origin string) ([]string, error) { - files, err := filepath.Glob(origin) - if err != nil { - return []string{}, err - } - copied := make([]string, len(files)) - for i, f := range files { - base := filepath.Base(f) - newname := filepath.Join(s.docroot, base) - data, err := ioutil.ReadFile(f) - if err != nil { - return []string{}, err - } - if err := ioutil.WriteFile(newname, data, 0644); err != nil { - return []string{}, err - } - copied[i] = newname - } - - err = s.CreateIndex() - return copied, err -} - -// CreateIndex will read docroot and generate an index.yaml file. -func (s *Server) CreateIndex() error { - // generate the index - index, err := repo.IndexDirectory(s.docroot, s.URL()) - if err != nil { - return err - } - - d, err := yaml.Marshal(index) - if err != nil { - return err - } - - ifile := filepath.Join(s.docroot, "index.yaml") - return ioutil.WriteFile(ifile, d, 0644) -} - -func (s *Server) Start() { - s.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.docroot)).ServeHTTP(w, r) - })) -} - -func (s *Server) StartTLS() { - cd := "../../testdata" - ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") - - s.srv = httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if s.middleware != nil { - s.middleware.ServeHTTP(w, r) - } - http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r) - })) - tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca) - if err != nil { - panic(err) - } - tlsConf.ServerName = "helm.sh" - s.srv.TLS = tlsConf - s.srv.StartTLS() - - // Set up repositories config with ca file - repoConfig := filepath.Join(s.Root(), "repositories.yaml") - - r := repo.NewFile() - r.Add(&repo.Entry{ - Name: "test", - URL: s.URL(), - CAFile: filepath.Join("../../testdata", "rootca.crt"), - }) - - if err := r.WriteFile(repoConfig, 0644); err != nil { - panic(err) - } -} - -// Stop stops the server and closes all connections. -// -// It should be called explicitly. -func (s *Server) Stop() { - s.srv.Close() -} - -// URL returns the URL of the server. -// -// Example: -// http://localhost:1776 -func (s *Server) URL() string { - return s.srv.URL -} - -// LinkIndices links the index created with CreateIndex and makes a symbolic link to the cache index. -// -// This makes it possible to simulate a local cache of a repository. -func (s *Server) LinkIndices() error { - lstart := filepath.Join(s.docroot, "index.yaml") - ldest := filepath.Join(s.docroot, "test-index.yaml") - return os.Symlink(lstart, ldest) -} - -// setTestingRepository sets up a testing repository.yaml with only the given URL. -func setTestingRepository(url, fname string) error { - r := repo.NewFile() - r.Add(&repo.Entry{ - Name: "test", - URL: url, - }) - return r.WriteFile(fname, 0644) -} diff --git a/pkg/repo/repotest/server_test.go b/pkg/repo/repotest/server_test.go deleted file mode 100644 index 1ad979fdc..000000000 --- a/pkg/repo/repotest/server_test.go +++ /dev/null @@ -1,118 +0,0 @@ -/* -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 repotest - -import ( - "io/ioutil" - "net/http" - "os" - "path/filepath" - "testing" - - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/internal/test/ensure" - "helm.sh/helm/v3/pkg/repo" -) - -// Young'n, in these here parts, we test our tests. - -func TestServer(t *testing.T) { - defer ensure.HelmHome(t)() - - rootDir := ensure.TempDir(t) - defer os.RemoveAll(rootDir) - - srv := NewServer(rootDir) - defer srv.Stop() - - c, err := srv.CopyCharts("testdata/*.tgz") - if err != nil { - // Some versions of Go don't correctly fire defer on Fatal. - t.Fatal(err) - } - - if len(c) != 1 { - t.Errorf("Unexpected chart count: %d", len(c)) - } - - if filepath.Base(c[0]) != "examplechart-0.1.0.tgz" { - t.Errorf("Unexpected chart: %s", c[0]) - } - - res, err := http.Get(srv.URL() + "/examplechart-0.1.0.tgz") - res.Body.Close() - if err != nil { - t.Fatal(err) - } - - if res.ContentLength < 500 { - t.Errorf("Expected at least 500 bytes of data, got %d", res.ContentLength) - } - - res, err = http.Get(srv.URL() + "/index.yaml") - if err != nil { - t.Fatal(err) - } - - data, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - t.Fatal(err) - } - - m := repo.NewIndexFile() - if err := yaml.Unmarshal(data, m); err != nil { - t.Fatal(err) - } - - if l := len(m.Entries); l != 1 { - t.Fatalf("Expected 1 entry, got %d", l) - } - - expect := "examplechart" - if !m.Has(expect, "0.1.0") { - t.Errorf("missing %q", expect) - } - - res, err = http.Get(srv.URL() + "/index.yaml-nosuchthing") - res.Body.Close() - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 404 { - t.Fatalf("Expected 404, got %d", res.StatusCode) - } -} - -func TestNewTempServer(t *testing.T) { - defer ensure.HelmHome(t)() - - srv, err := NewTempServerWithCleanup(t, "testdata/examplechart-0.1.0.tgz") - if err != nil { - t.Fatal(err) - } - defer srv.Stop() - - res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz") - res.Body.Close() - if err != nil { - t.Error(err) - } - if res.StatusCode != 200 { - t.Errorf("Expected 200, got %d", res.StatusCode) - } -} diff --git a/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz b/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz deleted file mode 100644 index c5ea741eb90fec6a94653cfde780d697f33c4439..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 500 zcmVgiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL3EYui2$!1JuX;=cIhyD((AX*%fJNCz8x*dQaX<7{7SHTuO# zcG`{YZy#~U)@F=Q=*hoFXr>1RZ1PV zs+gU9e;)G1>_S~kK%KOTU(m&bJW+o<*YV<>2pIH=IBM@Mp)W*Dv0@$4(0>W9$ygZn z2CO~9+pZ*blqd>^mM-D$?h*ZA93=~>#NS@o$nS8^{~k?8@#F`My6?_jl9LajLp+31uH zabLcW|Oq2pc1Q17htUoMKP6e8^lzWxDivCY!K70B7+sO_FaTG qSFfUwf&DdANl0B2=KJ^9Uymp0$jHc3;#&X!0RR7Fckh`15C8yY4Da3m diff --git a/pkg/repo/repotest/testdata/examplechart/.helmignore b/pkg/repo/repotest/testdata/examplechart/.helmignore deleted file mode 100644 index f0c131944..000000000 --- a/pkg/repo/repotest/testdata/examplechart/.helmignore +++ /dev/null @@ -1,21 +0,0 @@ -# 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 -*~ -# Various IDEs -.project -.idea/ -*.tmproj diff --git a/pkg/repo/repotest/testdata/examplechart/Chart.yaml b/pkg/repo/repotest/testdata/examplechart/Chart.yaml deleted file mode 100644 index a7d297285..000000000 --- a/pkg/repo/repotest/testdata/examplechart/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: examplechart -version: 0.1.0 diff --git a/pkg/repo/repotest/testdata/examplechart/values.yaml b/pkg/repo/repotest/testdata/examplechart/values.yaml deleted file mode 100644 index 5170c61e3..000000000 --- a/pkg/repo/repotest/testdata/examplechart/values.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# Default values for examplechart. -# This is a YAML-formatted file. -# Declare name/value pairs to be passed into your templates. -# name: value diff --git a/pkg/repo/testdata/chartmuseum-index.yaml b/pkg/repo/testdata/chartmuseum-index.yaml deleted file mode 100644 index 349a529aa..000000000 --- a/pkg/repo/testdata/chartmuseum-index.yaml +++ /dev/null @@ -1,54 +0,0 @@ -serverInfo: - contextPath: /v1/helm -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/local-index-annotations.yaml b/pkg/repo/testdata/local-index-annotations.yaml deleted file mode 100644 index 833ab854b..000000000 --- a/pkg/repo/testdata/local-index-annotations.yaml +++ /dev/null @@ -1,54 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 -annotations: - helm.sh/test: foo bar diff --git a/pkg/repo/testdata/local-index-unordered.yaml b/pkg/repo/testdata/local-index-unordered.yaml deleted file mode 100644 index cdfaa7f24..000000000 --- a/pkg/repo/testdata/local-index-unordered.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/local-index.yaml b/pkg/repo/testdata/local-index.yaml deleted file mode 100644 index d61f40dda..000000000 --- a/pkg/repo/testdata/local-index.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - apiVersion: v2 - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 - chartWithNoURL: - - name: chartWithNoURL - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - small - - sumtin - digest: "sha256:1234567890abcdef" - apiVersion: v2 diff --git a/pkg/repo/testdata/old-repositories.yaml b/pkg/repo/testdata/old-repositories.yaml deleted file mode 100644 index 3fb55b060..000000000 --- a/pkg/repo/testdata/old-repositories.yaml +++ /dev/null @@ -1,3 +0,0 @@ -best-charts-ever: http://best-charts-ever.com -okay-charts: http://okay-charts.org -example123: http://examplecharts.net/charts/123 diff --git a/pkg/repo/testdata/repositories.yaml b/pkg/repo/testdata/repositories.yaml deleted file mode 100644 index a28c48eab..000000000 --- a/pkg/repo/testdata/repositories.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -repositories: - - name: stable - url: https://example.com/stable/charts - cache: stable-index.yaml - - name: incubator - url: https://example.com/incubator - cache: incubator-index.yaml diff --git a/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz b/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz deleted file mode 100644 index 8731dce02cc9603e7813a07670a7d057129a8b20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/pkg/repo/testdata/repository/sprocket-1.1.0.tgz b/pkg/repo/testdata/repository/sprocket-1.1.0.tgz deleted file mode 100644 index 48d65f491545e344400446040aae6b2c855a6a68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 414 zcmV;P0b%|hiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK{_j@mE~fI0gqM%)Qb0*#bYrK%FAUW$4j;z6yE6i>6F zm<)j5J(DY}eLy$hZ$7JE+!p~G`G=gB)|9YGnIK;|*@X6eUP}+s1H$>13|Vb8l~9M! zl;bh|McpV$Dy!Lwc3bO&FWCr|&Mu4$|JbP8$wnZoK*2Oh6Hpp*?NZVxRS7jI{r&al z01O^an%4v~QX1xj`m)~Gg%!K2JAPYD))4$up4chWC9ye|nkH$&*+>8UPeAAY2GyGU zyEK4X{_}5n&;LBjqW=%!;tc#kP0Ul(%HQd9jDj@BCUBaH);trP|ABO)Fp%$_lF;0z z72NcHlJ)$b<^_-bKY|%4WnsThTF?K3pe3$VfZ^6UC5DGX)IAc3M4pnD00030|F3AL I;{Xr<07fvz=l}o! diff --git a/pkg/repo/testdata/repository/sprocket-1.2.0.tgz b/pkg/repo/testdata/repository/sprocket-1.2.0.tgz deleted file mode 100644 index 6fdc73c2b5c1153b7f22d2a13a5fd98de4b4cc7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 413 zcmV;O0b>3iiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PK{_ZsITyfI0gqM%)P|fsK^YN~@JP?PXE#Lp&%J{)ub@rIdHC z2qIJw5{FXSB7YYrV>_OS{Ee@jEteFAe=2k#*`ijxTf;f$`DD_Job&F;v%H-<#z$$M zk9)vxp2-DPKA`LI51-X7?uUT2d?M$iH3h6kOpq^~tV8oYtE30%0pWB@`mC~=3aCP; zi{X%-QP+x+(rUJ(?Z!Ic3pRj-vvVWE3mddM*#LwUD40fR0!l-!SxOqEDxe~zf4~16 zfWgB_vx;C!O2dp$pVu2Zw_J_fI|Khv6Z2Fy^0zu2q9Dzo2^^=QHIGE=e;{2e4CLFVB-FQQ z1y}taryc(%SDc zVQyr3R8em|NM&qo0PK~`ZsITyfI0gqM%)PwSwPBZrPWHDdMWCCz=LAdKap*qKza9y zl!Pj+#G#b7$lt}z#Exg8e3LD@P@wAnE6{~xgIaYi4(FVYhr?>*oVQ1wjnigc#nZuv zXC2^|Pvt#JAJBF9r_X8^_e;Q1K9KX$njBUs6XfSkmZ7?y7Se-8fv^Gv(0?UkOmNrx1u?>MD2edT`COZn^_Xd3$=o? z{{I=Y{2vbD|NlBnNGS`uT~c}e41yN8Q~|nc>y+s3_fhvqBoetvUIG9B|NpK_u)qKi F001%(!)E{h diff --git a/pkg/repo/testdata/server/index.yaml b/pkg/repo/testdata/server/index.yaml deleted file mode 100644 index d627928b2..000000000 --- a/pkg/repo/testdata/server/index.yaml +++ /dev/null @@ -1,39 +0,0 @@ -apiVersion: v1 -entries: - nginx: - - urls: - - https://charts.helm.sh/stable/nginx-0.1.0.tgz - name: nginx - description: string - version: 0.1.0 - home: https://github.com/something - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - - urls: - - https://charts.helm.sh/stable/nginx-0.2.0.tgz - name: nginx - description: string - version: 0.2.0 - home: https://github.com/something/else - digest: "sha256:1234567890abcdef" - keywords: - - popular - - web server - - proxy - alpine: - - urls: - - https://charts.helm.sh/stable/alpine-1.0.0.tgz - - http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz - name: alpine - description: string - version: 1.0.0 - home: https://github.com/something - keywords: - - linux - - alpine - - small - - sumtin - digest: "sha256:1234567890abcdef" diff --git a/pkg/repo/testdata/server/test.txt b/pkg/repo/testdata/server/test.txt deleted file mode 100644 index 557db03de..000000000 --- a/pkg/repo/testdata/server/test.txt +++ /dev/null @@ -1 +0,0 @@ -Hello World diff --git a/pkg/storage/driver/cfgmaps.go b/pkg/storage/driver/cfgmaps.go deleted file mode 100644 index 94c278875..000000000 --- a/pkg/storage/driver/cfgmaps.go +++ /dev/null @@ -1,257 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/validation" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*ConfigMaps)(nil) - -// ConfigMapsDriverName is the string name of the driver. -const ConfigMapsDriverName = "ConfigMap" - -// ConfigMaps is a wrapper around an implementation of a kubernetes -// ConfigMapsInterface. -type ConfigMaps struct { - impl corev1.ConfigMapInterface - Log func(string, ...interface{}) -} - -// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of -// the kubernetes ConfigMapsInterface. -func NewConfigMaps(impl corev1.ConfigMapInterface) *ConfigMaps { - return &ConfigMaps{ - impl: impl, - Log: func(_ string, _ ...interface{}) {}, - } -} - -// Name returns the name of the driver. -func (cfgmaps *ConfigMaps) Name() string { - return ConfigMapsDriverName -} - -// Get fetches the release named by key. The corresponding release is returned -// or error if not found. -func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) { - // fetch the configmap holding the release named by key - obj, err := cfgmaps.impl.Get(context.Background(), key, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound - } - - cfgmaps.Log("get: failed to get %q: %s", key, err) - return nil, err - } - // found the configmap, decode the base64 data string - r, err := decodeRelease(obj.Data["release"]) - if err != nil { - cfgmaps.Log("get: failed to decode data %q: %s", key, err) - return nil, err - } - // return the release object - return r, nil -} - -// List fetches all releases and returns the list releases such -// that filter(release) == true. An error is returned if the -// configmap fails to retrieve the releases. -func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - lsel := kblabels.Set{"owner": "helm"}.AsSelector() - opts := metav1.ListOptions{LabelSelector: lsel.String()} - - list, err := cfgmaps.impl.List(context.Background(), opts) - if err != nil { - cfgmaps.Log("list: failed to list: %s", err) - return nil, err - } - - var results []*rspb.Release - - // iterate over the configmaps object list - // and decode each release - for _, item := range list.Items { - rls, err := decodeRelease(item.Data["release"]) - if err != nil { - cfgmaps.Log("list: failed to decode release: %v: %s", item, err) - continue - } - - rls.Labels = item.ObjectMeta.Labels - - if filter(rls) { - results = append(results, rls) - } - } - return results, nil -} - -// Query fetches all releases that match the provided map of labels. -// An error is returned if the configmap fails to retrieve the releases. -func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, error) { - ls := kblabels.Set{} - for k, v := range labels { - if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) - } - ls[k] = v - } - - opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} - - list, err := cfgmaps.impl.List(context.Background(), opts) - if err != nil { - cfgmaps.Log("query: failed to query with labels: %s", err) - return nil, err - } - - if len(list.Items) == 0 { - return nil, ErrReleaseNotFound - } - - var results []*rspb.Release - for _, item := range list.Items { - rls, err := decodeRelease(item.Data["release"]) - if err != nil { - cfgmaps.Log("query: failed to decode release: %s", err) - continue - } - results = append(results, rls) - } - return results, nil -} - -// Create creates a new ConfigMap holding the release. If the -// ConfigMap already exists, ErrReleaseExists is returned. -func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error { - // set labels for configmaps object meta data - var lbs labels - - lbs.init() - lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new configmap to hold the release - obj, err := newConfigMapsObject(key, rls, lbs) - if err != nil { - cfgmaps.Log("create: failed to encode release %q: %s", rls.Name, err) - return err - } - // push the configmap object out into the kubiverse - if _, err := cfgmaps.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists - } - - cfgmaps.Log("create: failed to create: %s", err) - return err - } - return nil -} - -// Update updates the ConfigMap holding the release. If not found -// the ConfigMap is created to hold the release. -func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error { - // set labels for configmaps object meta data - var lbs labels - - lbs.init() - lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new configmap object to hold the release - obj, err := newConfigMapsObject(key, rls, lbs) - if err != nil { - cfgmaps.Log("update: failed to encode release %q: %s", rls.Name, err) - return err - } - // push the configmap object out into the kubiverse - _, err = cfgmaps.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) - if err != nil { - cfgmaps.Log("update: failed to update: %s", err) - return err - } - return nil -} - -// Delete deletes the ConfigMap holding the release named by key. -func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) { - // fetch the release to check existence - if rls, err = cfgmaps.Get(key); err != nil { - return nil, err - } - // delete the release - if err = cfgmaps.impl.Delete(context.Background(), key, metav1.DeleteOptions{}); err != nil { - return rls, err - } - return rls, nil -} - -// newConfigMapsObject constructs a kubernetes ConfigMap object -// to store a release. Each configmap data entry is the base64 -// encoded gzipped string of a release. -// -// The following labels are used within each configmap: -// -// "modifiedAt" - timestamp indicating when this configmap was last modified. (set in Update) -// "createdAt" - timestamp indicating when this configmap was created. (set in Create) -// "version" - version of the release. -// "status" - status of the release (see pkg/release/status.go for variants) -// "owner" - owner of the configmap, currently "helm". -// "name" - name of the release. -// -func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) { - const owner = "helm" - - // encode the release - s, err := encodeRelease(rls) - if err != nil { - return nil, err - } - - if lbs == nil { - lbs.init() - } - - // apply labels - lbs.set("name", rls.Name) - lbs.set("owner", owner) - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // create and return configmap object - return &v1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: key, - Labels: lbs.toMap(), - }, - Data: map[string]string{"release": s}, - }, nil -} diff --git a/pkg/storage/driver/cfgmaps_test.go b/pkg/storage/driver/cfgmaps_test.go deleted file mode 100644 index 626c36cb9..000000000 --- a/pkg/storage/driver/cfgmaps_test.go +++ /dev/null @@ -1,241 +0,0 @@ -/* -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 driver - -import ( - "encoding/base64" - "encoding/json" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestConfigMapName(t *testing.T) { - c := newTestFixtureCfgMaps(t) - if c.Name() != ConfigMapsDriverName { - t.Errorf("Expected name to be %q, got %q", ConfigMapsDriverName, c.Name()) - } -} - -func TestConfigMapGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // get release with key - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestUncompressedConfigMapGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // Create a test fixture which contains an uncompressed release - cfgmap, err := newConfigMapsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } - b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } - cfgmap.Data["release"] = base64.StdEncoding.EncodeToString(b) - var mock MockConfigMapsInterface - mock.objects = map[string]*v1.ConfigMap{key: cfgmap} - cfgmaps := NewConfigMaps(&mock) - - // get release with key - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestConfigMapList(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - // list all deleted releases - del, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := cfgmaps.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } -} - -func TestConfigMapQuery(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - rls, err := cfgmaps.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Errorf("Expected 2 results, got %d", len(rls)) - } - - _, err = cfgmaps.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} - -func TestConfigMapCreate(t *testing.T) { - cfgmaps := newTestFixtureCfgMaps(t) - - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // store the release in a configmap - if err := cfgmaps.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } - - // get the release back - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestConfigMapUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // modify release status code - rel.Info.Status = rspb.StatusSuperseded - - // perform the update - if err := cfgmaps.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } - - // fetch the updated release - got, err := cfgmaps.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } -} - -func TestConfigMapDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) - - // perform the delete on a non-existent release - _, err := cfgmaps.Delete("nonexistent") - if err != ErrReleaseNotFound { - t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err) - } - - // perform the delete - rls, err := cfgmaps.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } - - // fetch the deleted release - _, err = cfgmaps.Get(key) - if !reflect.DeepEqual(ErrReleaseNotFound, err) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} diff --git a/pkg/storage/driver/driver.go b/pkg/storage/driver/driver.go deleted file mode 100644 index 9c01f3766..000000000 --- a/pkg/storage/driver/driver.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "fmt" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var ( - // ErrReleaseNotFound indicates that a release is not found. - ErrReleaseNotFound = errors.New("release: not found") - // ErrReleaseExists indicates that a release already exists. - ErrReleaseExists = errors.New("release: already exists") - // ErrInvalidKey indicates that a release key could not be parsed. - ErrInvalidKey = errors.New("release: invalid key") - // ErrNoDeployedReleases indicates that there are no releases with the given key in the deployed state - ErrNoDeployedReleases = errors.New("has no deployed releases") -) - -// StorageDriverError records an error and the release name that caused it -type StorageDriverError struct { - ReleaseName string - Err error -} - -func (e *StorageDriverError) Error() string { - return fmt.Sprintf("%q %s", e.ReleaseName, e.Err.Error()) -} - -func (e *StorageDriverError) Unwrap() error { return e.Err } - -func NewErrNoDeployedReleases(releaseName string) error { - return &StorageDriverError{ - ReleaseName: releaseName, - Err: ErrNoDeployedReleases, - } -} - -// Creator is the interface that wraps the Create method. -// -// Create stores the release or returns ErrReleaseExists -// if an identical release already exists. -type Creator interface { - Create(key string, rls *rspb.Release) error -} - -// Updator is the interface that wraps the Update method. -// -// Update updates an existing release or returns -// ErrReleaseNotFound if the release does not exist. -type Updator interface { - Update(key string, rls *rspb.Release) error -} - -// Deletor is the interface that wraps the Delete method. -// -// Delete deletes the release named by key or returns -// ErrReleaseNotFound if the release does not exist. -type Deletor interface { - Delete(key string) (*rspb.Release, error) -} - -// Queryor is the interface that wraps the Get and List methods. -// -// Get returns the release named by key or returns ErrReleaseNotFound -// if the release does not exist. -// -// List returns the set of all releases that satisfy the filter predicate. -// -// Query returns the set of all releases that match the provided label set. -type Queryor interface { - Get(key string) (*rspb.Release, error) - List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) - Query(labels map[string]string) ([]*rspb.Release, error) -} - -// Driver is the interface composed of Creator, Updator, Deletor, and Queryor -// interfaces. It defines the behavior for storing, updating, deleted, -// and retrieving Helm releases from some underlying storage mechanism, -// e.g. memory, configmaps. -type Driver interface { - Creator - Updator - Deletor - Queryor - Name() string -} diff --git a/pkg/storage/driver/labels.go b/pkg/storage/driver/labels.go deleted file mode 100644 index eb7118fe5..000000000 --- a/pkg/storage/driver/labels.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -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 driver - -// labels is a map of key value pairs to be included as metadata in a configmap object. -type labels map[string]string - -func (lbs *labels) init() { *lbs = labels(make(map[string]string)) } -func (lbs labels) get(key string) string { return lbs[key] } -func (lbs labels) set(key, val string) { lbs[key] = val } - -func (lbs labels) keys() (ls []string) { - for key := range lbs { - ls = append(ls, key) - } - return -} - -func (lbs labels) match(set labels) bool { - for _, key := range set.keys() { - if lbs.get(key) != set.get(key) { - return false - } - } - return true -} - -func (lbs labels) toMap() map[string]string { return lbs } - -func (lbs *labels) fromMap(kvs map[string]string) { - for k, v := range kvs { - lbs.set(k, v) - } -} diff --git a/pkg/storage/driver/labels_test.go b/pkg/storage/driver/labels_test.go deleted file mode 100644 index bfd80911b..000000000 --- a/pkg/storage/driver/labels_test.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "testing" -) - -func TestLabelsMatch(t *testing.T) { - var tests = []struct { - desc string - set1 labels - set2 labels - expect bool - }{ - { - "equal labels sets", - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - true, - }, - { - "disjoint label sets", - labels(map[string]string{"KEY_C": "VAL_C", "KEY_D": "VAL_D"}), - labels(map[string]string{"KEY_A": "VAL_A", "KEY_B": "VAL_B"}), - false, - }, - } - - for _, tt := range tests { - if !tt.set1.match(tt.set2) && tt.expect { - t.Fatalf("Expected match '%s'\n", tt.desc) - } - } -} diff --git a/pkg/storage/driver/memory.go b/pkg/storage/driver/memory.go deleted file mode 100644 index 91378f588..000000000 --- a/pkg/storage/driver/memory.go +++ /dev/null @@ -1,240 +0,0 @@ -/* -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 driver - -import ( - "strconv" - "strings" - "sync" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*Memory)(nil) - -const ( - // MemoryDriverName is the string name of this driver. - MemoryDriverName = "Memory" - - defaultNamespace = "default" -) - -// A map of release names to list of release records -type memReleases map[string]records - -// Memory is the in-memory storage driver implementation. -type Memory struct { - sync.RWMutex - namespace string - // A map of namespaces to releases - cache map[string]memReleases -} - -// NewMemory initializes a new memory driver. -func NewMemory() *Memory { - return &Memory{cache: map[string]memReleases{}, namespace: "default"} -} - -// SetNamespace sets a specific namespace in which releases will be accessed. -// An empty string indicates all namespaces (for the list operation) -func (mem *Memory) SetNamespace(ns string) { - mem.namespace = ns -} - -// Name returns the name of the driver. -func (mem *Memory) Name() string { - return MemoryDriverName -} - -// Get returns the release named by key or returns ErrReleaseNotFound. -func (mem *Memory) Get(key string) (*rspb.Release, error) { - defer unlock(mem.rlock()) - - keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") - switch elems := strings.Split(keyWithoutPrefix, ".v"); len(elems) { - case 2: - name, ver := elems[0], elems[1] - if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey - } - if recs, ok := mem.cache[mem.namespace][name]; ok { - if r := recs.Get(key); r != nil { - return r.rls, nil - } - } - return nil, ErrReleaseNotFound - default: - return nil, ErrInvalidKey - } -} - -// List returns the list of all releases such that filter(release) == true -func (mem *Memory) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - defer unlock(mem.rlock()) - - var ls []*rspb.Release - for namespace := range mem.cache { - if mem.namespace != "" { - // Should only list releases of this namespace - namespace = mem.namespace - } - for _, recs := range mem.cache[namespace] { - recs.Iter(func(_ int, rec *record) bool { - if filter(rec.rls) { - ls = append(ls, rec.rls) - } - return true - }) - } - if mem.namespace != "" { - // Should only list releases of this namespace - break - } - } - return ls, nil -} - -// Query returns the set of releases that match the provided set of labels -func (mem *Memory) Query(keyvals map[string]string) ([]*rspb.Release, error) { - defer unlock(mem.rlock()) - - var lbs labels - - lbs.init() - lbs.fromMap(keyvals) - - var ls []*rspb.Release - for namespace := range mem.cache { - if mem.namespace != "" { - // Should only query releases of this namespace - namespace = mem.namespace - } - for _, recs := range mem.cache[namespace] { - recs.Iter(func(_ int, rec *record) bool { - // A query for a release name that doesn't exist (has been deleted) - // can cause rec to be nil. - if rec == nil { - return false - } - if rec.lbs.match(lbs) { - ls = append(ls, rec.rls) - } - return true - }) - } - if mem.namespace != "" { - // Should only query releases of this namespace - break - } - } - - if len(ls) == 0 { - return nil, ErrReleaseNotFound - } - - return ls, nil -} - -// Create creates a new release or returns ErrReleaseExists. -func (mem *Memory) Create(key string, rls *rspb.Release) error { - defer unlock(mem.wlock()) - - // For backwards compatibility, we protect against an unset namespace - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - mem.SetNamespace(namespace) - - if _, ok := mem.cache[namespace]; !ok { - mem.cache[namespace] = memReleases{} - } - - if recs, ok := mem.cache[namespace][rls.Name]; ok { - if err := recs.Add(newRecord(key, rls)); err != nil { - return err - } - mem.cache[namespace][rls.Name] = recs - return nil - } - mem.cache[namespace][rls.Name] = records{newRecord(key, rls)} - return nil -} - -// Update updates a release or returns ErrReleaseNotFound. -func (mem *Memory) Update(key string, rls *rspb.Release) error { - defer unlock(mem.wlock()) - - // For backwards compatibility, we protect against an unset namespace - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - mem.SetNamespace(namespace) - - if _, ok := mem.cache[namespace]; ok { - if rs, ok := mem.cache[namespace][rls.Name]; ok && rs.Exists(key) { - rs.Replace(key, newRecord(key, rls)) - return nil - } - } - return ErrReleaseNotFound -} - -// Delete deletes a release or returns ErrReleaseNotFound. -func (mem *Memory) Delete(key string) (*rspb.Release, error) { - defer unlock(mem.wlock()) - - keyWithoutPrefix := strings.TrimPrefix(key, "sh.helm.release.v1.") - elems := strings.Split(keyWithoutPrefix, ".v") - - if len(elems) != 2 { - return nil, ErrInvalidKey - } - - name, ver := elems[0], elems[1] - if _, err := strconv.Atoi(ver); err != nil { - return nil, ErrInvalidKey - } - if _, ok := mem.cache[mem.namespace]; ok { - if recs, ok := mem.cache[mem.namespace][name]; ok { - if r := recs.Remove(key); r != nil { - // recs.Remove changes the slice reference, so we have to re-assign it. - mem.cache[mem.namespace][name] = recs - return r.rls, nil - } - } - } - return nil, ErrReleaseNotFound -} - -// wlock locks mem for writing -func (mem *Memory) wlock() func() { - mem.Lock() - return func() { mem.Unlock() } -} - -// rlock locks mem for reading -func (mem *Memory) rlock() func() { - mem.RLock() - return func() { mem.RUnlock() } -} - -// unlock calls fn which reverses a mem.rlock or mem.wlock. e.g: -// ```defer unlock(mem.rlock())```, locks mem for reading at the -// call point of defer and unlocks upon exiting the block. -func unlock(fn func()) { fn() } diff --git a/pkg/storage/driver/memory_test.go b/pkg/storage/driver/memory_test.go deleted file mode 100644 index 7a2e8578e..000000000 --- a/pkg/storage/driver/memory_test.go +++ /dev/null @@ -1,289 +0,0 @@ -/* -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 driver - -import ( - "fmt" - "reflect" - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestMemoryName(t *testing.T) { - if mem := NewMemory(); mem.Name() != MemoryDriverName { - t.Errorf("Expected name to be %q, got %q", MemoryDriverName, mem.Name()) - } -} - -func TestMemoryCreate(t *testing.T) { - var tests = []struct { - desc string - rls *rspb.Release - err bool - }{ - { - "create should succeed", - releaseStub("rls-c", 1, "default", rspb.StatusDeployed), - false, - }, - { - "create should fail (release already exists)", - releaseStub("rls-a", 1, "default", rspb.StatusDeployed), - true, - }, - { - "create in namespace should succeed", - releaseStub("rls-a", 1, "mynamespace", rspb.StatusDeployed), - false, - }, - { - "create in other namespace should fail (release already exists)", - releaseStub("rls-c", 1, "mynamespace", rspb.StatusDeployed), - true, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - key := testKey(tt.rls.Name, tt.rls.Version) - rls := tt.rls - - if err := ts.Create(key, rls); err != nil { - if !tt.err { - t.Fatalf("failed to create %q: %s", tt.desc, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q\n", tt.desc) - } - } -} - -func TestMemoryGet(t *testing.T) { - var tests = []struct { - desc string - key string - namespace string - err bool - }{ - {"release key should exist", "rls-a.v1", "default", false}, - {"release key should not exist", "rls-a.v5", "default", true}, - {"release key in namespace should exist", "rls-c.v1", "mynamespace", false}, - {"release key in namespace should not exist", "rls-a.v1", "mynamespace", true}, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - if _, err := ts.Get(tt.key); err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) - } - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } - } -} - -func TestMemoryList(t *testing.T) { - ts := tsFixtureMemory(t) - ts.SetNamespace("default") - - // list all deployed releases - dpl, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed releases: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded releases: %s", err) - } - if len(ssd) != 6 { - t.Errorf("Expected 6 superseded, got %d", len(ssd)) - } - - // list all deleted releases - del, err := ts.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted releases: %s", err) - } - if len(del) != 0 { - t.Errorf("Expected 0 deleted, got %d", len(del)) - } -} - -func TestMemoryQuery(t *testing.T) { - var tests = []struct { - desc string - xlen int - namespace string - lbs map[string]string - }{ - { - "should be 2 query results", - 2, - "default", - map[string]string{"status": "deployed"}, - }, - { - "should be 1 query result", - 1, - "mynamespace", - map[string]string{"status": "deployed"}, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - l, err := ts.Query(tt.lbs) - if err != nil { - t.Fatalf("Failed to query: %s\n", err) - } - - if tt.xlen != len(l) { - t.Fatalf("Expected %d results, actual %d\n", tt.xlen, len(l)) - } - } -} - -func TestMemoryUpdate(t *testing.T) { - var tests = []struct { - desc string - key string - rls *rspb.Release - err bool - }{ - { - "update release status", - "rls-a.v4", - releaseStub("rls-a", 4, "default", rspb.StatusSuperseded), - false, - }, - { - "update release does not exist", - "rls-c.v1", - releaseStub("rls-c", 1, "default", rspb.StatusUninstalled), - true, - }, - { - "update release status in namespace", - "rls-c.v4", - releaseStub("rls-c", 4, "mynamespace", rspb.StatusSuperseded), - false, - }, - { - "update release in namespace does not exist", - "rls-a.v1", - releaseStub("rls-a", 1, "mynamespace", rspb.StatusUninstalled), - true, - }, - } - - ts := tsFixtureMemory(t) - for _, tt := range tests { - if err := ts.Update(tt.key, tt.rls); err != nil { - if !tt.err { - t.Fatalf("Failed %q: %s\n", tt.desc, err) - } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } - - ts.SetNamespace(tt.rls.Namespace) - r, err := ts.Get(tt.key) - if err != nil { - t.Fatalf("Failed to get: %s\n", err) - } - - if !reflect.DeepEqual(r, tt.rls) { - t.Fatalf("Expected %v, actual %v\n", tt.rls, r) - } - } -} - -func TestMemoryDelete(t *testing.T) { - var tests = []struct { - desc string - key string - namespace string - err bool - }{ - {"release key should exist", "rls-a.v4", "default", false}, - {"release key should not exist", "rls-a.v5", "default", true}, - {"release key from other namespace should not exist", "rls-c.v4", "default", true}, - {"release key from namespace should exist", "rls-c.v4", "mynamespace", false}, - {"release key from namespace should not exist", "rls-c.v5", "mynamespace", true}, - {"release key from namespace2 should not exist", "rls-a.v4", "mynamespace", true}, - } - - ts := tsFixtureMemory(t) - ts.SetNamespace("") - start, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } - startLen := len(start) - for _, tt := range tests { - ts.SetNamespace(tt.namespace) - if rel, err := ts.Delete(tt.key); err != nil { - if !tt.err { - t.Fatalf("Failed %q to get '%s': %q\n", tt.desc, tt.key, err) - } - continue - } else if tt.err { - t.Fatalf("Did not get expected error for %q '%s'\n", tt.desc, tt.key) - } else if fmt.Sprintf("%s.v%d", rel.Name, rel.Version) != tt.key { - t.Fatalf("Asked for delete on %s, but deleted %d", tt.key, rel.Version) - } - _, err := ts.Get(tt.key) - if err == nil { - t.Errorf("Expected an error when asking for a deleted key") - } - } - - // Make sure that the deleted records are gone. - ts.SetNamespace("") - end, err := ts.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Errorf("Query failed: %s", err) - } - endLen := len(end) - - if startLen-2 != endLen { - t.Errorf("expected end to be %d instead of %d", startLen-2, endLen) - for _, ee := range end { - t.Logf("Name: %s, Version: %d", ee.Name, ee.Version) - } - } - -} diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go deleted file mode 100644 index c0236ece8..000000000 --- a/pkg/storage/driver/mock_test.go +++ /dev/null @@ -1,265 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "fmt" - "testing" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - sq "github.com/Masterminds/squirrel" - "github.com/jmoiron/sqlx" - - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func releaseStub(name string, vers int, namespace string, status rspb.Status) *rspb.Release { - return &rspb.Release{ - Name: name, - Version: vers, - Namespace: namespace, - Info: &rspb.Info{Status: status}, - } -} - -func testKey(name string, vers int) string { - return fmt.Sprintf("%s.v%d", name, vers) -} - -func tsFixtureMemory(t *testing.T) *Memory { - hs := []*rspb.Release{ - // rls-a - releaseStub("rls-a", 4, "default", rspb.StatusDeployed), - releaseStub("rls-a", 1, "default", rspb.StatusSuperseded), - releaseStub("rls-a", 3, "default", rspb.StatusSuperseded), - releaseStub("rls-a", 2, "default", rspb.StatusSuperseded), - // rls-b - releaseStub("rls-b", 4, "default", rspb.StatusDeployed), - releaseStub("rls-b", 1, "default", rspb.StatusSuperseded), - releaseStub("rls-b", 3, "default", rspb.StatusSuperseded), - releaseStub("rls-b", 2, "default", rspb.StatusSuperseded), - // rls-c in other namespace - releaseStub("rls-c", 4, "mynamespace", rspb.StatusDeployed), - releaseStub("rls-c", 1, "mynamespace", rspb.StatusSuperseded), - releaseStub("rls-c", 3, "mynamespace", rspb.StatusSuperseded), - releaseStub("rls-c", 2, "mynamespace", rspb.StatusSuperseded), - } - - mem := NewMemory() - for _, tt := range hs { - err := mem.Create(testKey(tt.Name, tt.Version), tt) - if err != nil { - t.Fatalf("Test setup failed to create: %s\n", err) - } - } - return mem -} - -// newTestFixture initializes a MockConfigMapsInterface. -// ConfigMaps are created for each release provided. -func newTestFixtureCfgMaps(t *testing.T, releases ...*rspb.Release) *ConfigMaps { - var mock MockConfigMapsInterface - mock.Init(t, releases...) - - return NewConfigMaps(&mock) -} - -// MockConfigMapsInterface mocks a kubernetes ConfigMapsInterface -type MockConfigMapsInterface struct { - corev1.ConfigMapInterface - - objects map[string]*v1.ConfigMap -} - -// Init initializes the MockConfigMapsInterface with the set of releases. -func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) { - mock.objects = map[string]*v1.ConfigMap{} - - for _, rls := range releases { - objkey := testKey(rls.Name, rls.Version) - - cfgmap, err := newConfigMapsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create configmap: %s", err) - } - mock.objects[objkey] = cfgmap - } -} - -// Get returns the ConfigMap by name. -func (mock *MockConfigMapsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.ConfigMap, error) { - object, ok := mock.objects[name] - if !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - return object, nil -} - -// List returns the a of ConfigMaps. -func (mock *MockConfigMapsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.ConfigMapList, error) { - var list v1.ConfigMapList - - labelSelector, err := kblabels.Parse(opts.LabelSelector) - if err != nil { - return nil, err - } - - for _, cfgmap := range mock.objects { - if labelSelector.Matches(kblabels.Set(cfgmap.ObjectMeta.Labels)) { - list.Items = append(list.Items, *cfgmap) - } - } - return &list, nil -} - -// Create creates a new ConfigMap. -func (mock *MockConfigMapsInterface) Create(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.CreateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name - if object, ok := mock.objects[name]; ok { - return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) - } - mock.objects[name] = cfgmap - return cfgmap, nil -} - -// Update updates a ConfigMap. -func (mock *MockConfigMapsInterface) Update(_ context.Context, cfgmap *v1.ConfigMap, _ metav1.UpdateOptions) (*v1.ConfigMap, error) { - name := cfgmap.ObjectMeta.Name - if _, ok := mock.objects[name]; !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - mock.objects[name] = cfgmap - return cfgmap, nil -} - -// Delete deletes a ConfigMap by name. -func (mock *MockConfigMapsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { - if _, ok := mock.objects[name]; !ok { - return apierrors.NewNotFound(v1.Resource("tests"), name) - } - delete(mock.objects, name) - return nil -} - -// newTestFixture initializes a MockSecretsInterface. -// Secrets are created for each release provided. -func newTestFixtureSecrets(t *testing.T, releases ...*rspb.Release) *Secrets { - var mock MockSecretsInterface - mock.Init(t, releases...) - - return NewSecrets(&mock) -} - -// MockSecretsInterface mocks a kubernetes SecretsInterface -type MockSecretsInterface struct { - corev1.SecretInterface - - objects map[string]*v1.Secret -} - -// Init initializes the MockSecretsInterface with the set of releases. -func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) { - mock.objects = map[string]*v1.Secret{} - - for _, rls := range releases { - objkey := testKey(rls.Name, rls.Version) - - secret, err := newSecretsObject(objkey, rls, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } - mock.objects[objkey] = secret - } -} - -// Get returns the Secret by name. -func (mock *MockSecretsInterface) Get(_ context.Context, name string, _ metav1.GetOptions) (*v1.Secret, error) { - object, ok := mock.objects[name] - if !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - return object, nil -} - -// List returns the a of Secret. -func (mock *MockSecretsInterface) List(_ context.Context, opts metav1.ListOptions) (*v1.SecretList, error) { - var list v1.SecretList - - labelSelector, err := kblabels.Parse(opts.LabelSelector) - if err != nil { - return nil, err - } - - for _, secret := range mock.objects { - if labelSelector.Matches(kblabels.Set(secret.ObjectMeta.Labels)) { - list.Items = append(list.Items, *secret) - } - } - return &list, nil -} - -// Create creates a new Secret. -func (mock *MockSecretsInterface) Create(_ context.Context, secret *v1.Secret, _ metav1.CreateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name - if object, ok := mock.objects[name]; ok { - return object, apierrors.NewAlreadyExists(v1.Resource("tests"), name) - } - mock.objects[name] = secret - return secret, nil -} - -// Update updates a Secret. -func (mock *MockSecretsInterface) Update(_ context.Context, secret *v1.Secret, _ metav1.UpdateOptions) (*v1.Secret, error) { - name := secret.ObjectMeta.Name - if _, ok := mock.objects[name]; !ok { - return nil, apierrors.NewNotFound(v1.Resource("tests"), name) - } - mock.objects[name] = secret - return secret, nil -} - -// Delete deletes a Secret by name. -func (mock *MockSecretsInterface) Delete(_ context.Context, name string, _ metav1.DeleteOptions) error { - if _, ok := mock.objects[name]; !ok { - return apierrors.NewNotFound(v1.Resource("tests"), name) - } - delete(mock.objects, name) - return nil -} - -// newTestFixtureSQL mocks the SQL database (for testing purposes) -func newTestFixtureSQL(t *testing.T, releases ...*rspb.Release) (*SQL, sqlmock.Sqlmock) { - sqlDB, mock, err := sqlmock.New() - if err != nil { - t.Fatalf("error when opening stub database connection: %v", err) - } - - sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") - return &SQL{ - db: sqlxDB, - Log: func(a string, b ...interface{}) {}, - namespace: "default", - statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), - }, mock -} diff --git a/pkg/storage/driver/records.go b/pkg/storage/driver/records.go deleted file mode 100644 index 9df173384..000000000 --- a/pkg/storage/driver/records.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "sort" - "strconv" - - rspb "helm.sh/helm/v3/pkg/release" -) - -// records holds a list of in-memory release records -type records []*record - -func (rs records) Len() int { return len(rs) } -func (rs records) Swap(i, j int) { rs[i], rs[j] = rs[j], rs[i] } -func (rs records) Less(i, j int) bool { return rs[i].rls.Version < rs[j].rls.Version } - -func (rs *records) Add(r *record) error { - if r == nil { - return nil - } - - if rs.Exists(r.key) { - return ErrReleaseExists - } - - *rs = append(*rs, r) - sort.Sort(*rs) - - return nil -} - -func (rs records) Get(key string) *record { - if i, ok := rs.Index(key); ok { - return rs[i] - } - return nil -} - -func (rs *records) Iter(fn func(int, *record) bool) { - cp := make([]*record, len(*rs)) - copy(cp, *rs) - - for i, r := range cp { - if !fn(i, r) { - return - } - } -} - -func (rs *records) Index(key string) (int, bool) { - for i, r := range *rs { - if r.key == key { - return i, true - } - } - return -1, false -} - -func (rs records) Exists(key string) bool { - _, ok := rs.Index(key) - return ok -} - -func (rs *records) Remove(key string) (r *record) { - if i, ok := rs.Index(key); ok { - return rs.removeAt(i) - } - return nil -} - -func (rs *records) Replace(key string, rec *record) *record { - if i, ok := rs.Index(key); ok { - old := (*rs)[i] - (*rs)[i] = rec - return old - } - return nil -} - -func (rs *records) removeAt(index int) *record { - r := (*rs)[index] - (*rs)[index] = nil - copy((*rs)[index:], (*rs)[index+1:]) - *rs = (*rs)[:len(*rs)-1] - return r -} - -// record is the data structure used to cache releases -// for the in-memory storage driver -type record struct { - key string - lbs labels - rls *rspb.Release -} - -// newRecord creates a new in-memory release record -func newRecord(key string, rls *rspb.Release) *record { - var lbs labels - - lbs.init() - lbs.set("name", rls.Name) - lbs.set("owner", "helm") - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)} - return &record{key: key, lbs: lbs, rls: rls} -} diff --git a/pkg/storage/driver/records_test.go b/pkg/storage/driver/records_test.go deleted file mode 100644 index 0a27839cc..000000000 --- a/pkg/storage/driver/records_test.go +++ /dev/null @@ -1,240 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "reflect" - "testing" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestRecordsAdd(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - ok bool - rec *record - }{ - { - "add valid key", - "rls-a.v3", - false, - newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)), - }, - { - "add already existing key", - "rls-a.v1", - true, - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusDeployed)), - }, - } - - for _, tt := range tests { - if err := rs.Add(tt.rec); err != nil { - if !tt.ok { - t.Fatalf("failed: %q: %s\n", tt.desc, err) - } - } - } -} - -func TestRecordsRemove(t *testing.T) { - var tests = []struct { - desc string - key string - ok bool - }{ - {"remove valid key", "rls-a.v1", false}, - {"remove invalid key", "rls-a.v", true}, - {"remove non-existent key", "rls-z.v1", true}, - } - - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - startLen := rs.Len() - - for _, tt := range tests { - if r := rs.Remove(tt.key); r == nil { - if !tt.ok { - t.Fatalf("Failed to %q (key = %s). Expected nil, got %v", - tt.desc, - tt.key, - r, - ) - } - } - } - - // We expect the total number of records will be less now than there were - // when we started. - endLen := rs.Len() - if endLen >= startLen { - t.Errorf("expected ending length %d to be less than starting length %d", endLen, startLen) - } -} - -func TestRecordsRemoveAt(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - if len(rs) != 2 { - t.Fatal("Expected len=2 for mock") - } - - rs.Remove("rls-a.v1") - if len(rs) != 1 { - t.Fatalf("Expected length of rs to be 1, got %d", len(rs)) - } -} - -func TestRecordsGet(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - rec *record - }{ - { - "get valid key", - "rls-a.v1", - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - }, - { - "get invalid key", - "rls-a.v3", - nil, - }, - } - - for _, tt := range tests { - got := rs.Get(tt.key) - if !reflect.DeepEqual(tt.rec, got) { - t.Fatalf("Expected %v, got %v", tt.rec, got) - } - } -} - -func TestRecordsIndex(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - sort int - }{ - { - "get valid key", - "rls-a.v1", - 0, - }, - { - "get invalid key", - "rls-a.v3", - -1, - }, - } - - for _, tt := range tests { - got, _ := rs.Index(tt.key) - if got != tt.sort { - t.Fatalf("Expected %d, got %d", tt.sort, got) - } - } -} - -func TestRecordsExists(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - ok bool - }{ - { - "get valid key", - "rls-a.v1", - true, - }, - { - "get invalid key", - "rls-a.v3", - false, - }, - } - - for _, tt := range tests { - got := rs.Exists(tt.key) - if got != tt.ok { - t.Fatalf("Expected %t, got %t", tt.ok, got) - } - } -} - -func TestRecordsReplace(t *testing.T) { - rs := records([]*record{ - newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }) - - var tests = []struct { - desc string - key string - rec *record - expected *record - }{ - { - "replace with existing key", - "rls-a.v2", - newRecord("rls-a.v3", releaseStub("rls-a", 3, "default", rspb.StatusSuperseded)), - newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.StatusDeployed)), - }, - { - "replace with non existing key", - "rls-a.v4", - newRecord("rls-a.v4", releaseStub("rls-a", 4, "default", rspb.StatusDeployed)), - nil, - }, - } - - for _, tt := range tests { - got := rs.Replace(tt.key, tt.rec) - if !reflect.DeepEqual(tt.expected, got) { - t.Fatalf("Expected %v, got %v", tt.expected, got) - } - } -} diff --git a/pkg/storage/driver/secrets.go b/pkg/storage/driver/secrets.go deleted file mode 100644 index 2e8530d0c..000000000 --- a/pkg/storage/driver/secrets.go +++ /dev/null @@ -1,250 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "context" - "strconv" - "strings" - "time" - - "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - kblabels "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/util/validation" - corev1 "k8s.io/client-go/kubernetes/typed/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*Secrets)(nil) - -// SecretsDriverName is the string name of the driver. -const SecretsDriverName = "Secret" - -// Secrets is a wrapper around an implementation of a kubernetes -// SecretsInterface. -type Secrets struct { - impl corev1.SecretInterface - Log func(string, ...interface{}) -} - -// NewSecrets initializes a new Secrets wrapping an implementation of -// the kubernetes SecretsInterface. -func NewSecrets(impl corev1.SecretInterface) *Secrets { - return &Secrets{ - impl: impl, - Log: func(_ string, _ ...interface{}) {}, - } -} - -// Name returns the name of the driver. -func (secrets *Secrets) Name() string { - return SecretsDriverName -} - -// Get fetches the release named by key. The corresponding release is returned -// or error if not found. -func (secrets *Secrets) Get(key string) (*rspb.Release, error) { - // fetch the secret holding the release named by key - obj, err := secrets.impl.Get(context.Background(), key, metav1.GetOptions{}) - if err != nil { - if apierrors.IsNotFound(err) { - return nil, ErrReleaseNotFound - } - return nil, errors.Wrapf(err, "get: failed to get %q", key) - } - // found the secret, decode the base64 data string - r, err := decodeRelease(string(obj.Data["release"])) - return r, errors.Wrapf(err, "get: failed to decode data %q", key) -} - -// List fetches all releases and returns the list releases such -// that filter(release) == true. An error is returned if the -// secret fails to retrieve the releases. -func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - lsel := kblabels.Set{"owner": "helm"}.AsSelector() - opts := metav1.ListOptions{LabelSelector: lsel.String()} - - list, err := secrets.impl.List(context.Background(), opts) - if err != nil { - return nil, errors.Wrap(err, "list: failed to list") - } - - var results []*rspb.Release - - // iterate over the secrets object list - // and decode each release - for _, item := range list.Items { - rls, err := decodeRelease(string(item.Data["release"])) - if err != nil { - secrets.Log("list: failed to decode release: %v: %s", item, err) - continue - } - - rls.Labels = item.ObjectMeta.Labels - - if filter(rls) { - results = append(results, rls) - } - } - return results, nil -} - -// Query fetches all releases that match the provided map of labels. -// An error is returned if the secret fails to retrieve the releases. -func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error) { - ls := kblabels.Set{} - for k, v := range labels { - if errs := validation.IsValidLabelValue(v); len(errs) != 0 { - return nil, errors.Errorf("invalid label value: %q: %s", v, strings.Join(errs, "; ")) - } - ls[k] = v - } - - opts := metav1.ListOptions{LabelSelector: ls.AsSelector().String()} - - list, err := secrets.impl.List(context.Background(), opts) - if err != nil { - return nil, errors.Wrap(err, "query: failed to query with labels") - } - - if len(list.Items) == 0 { - return nil, ErrReleaseNotFound - } - - var results []*rspb.Release - for _, item := range list.Items { - rls, err := decodeRelease(string(item.Data["release"])) - if err != nil { - secrets.Log("query: failed to decode release: %s", err) - continue - } - results = append(results, rls) - } - return results, nil -} - -// Create creates a new Secret holding the release. If the -// Secret already exists, ErrReleaseExists is returned. -func (secrets *Secrets) Create(key string, rls *rspb.Release) error { - // set labels for secrets object meta data - var lbs labels - - lbs.init() - lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new secret to hold the release - obj, err := newSecretsObject(key, rls, lbs) - if err != nil { - return errors.Wrapf(err, "create: failed to encode release %q", rls.Name) - } - // push the secret object out into the kubiverse - if _, err := secrets.impl.Create(context.Background(), obj, metav1.CreateOptions{}); err != nil { - if apierrors.IsAlreadyExists(err) { - return ErrReleaseExists - } - - return errors.Wrap(err, "create: failed to create") - } - return nil -} - -// Update updates the Secret holding the release. If not found -// the Secret is created to hold the release. -func (secrets *Secrets) Update(key string, rls *rspb.Release) error { - // set labels for secrets object meta data - var lbs labels - - lbs.init() - lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) - - // create a new secret object to hold the release - obj, err := newSecretsObject(key, rls, lbs) - if err != nil { - return errors.Wrapf(err, "update: failed to encode release %q", rls.Name) - } - // push the secret object out into the kubiverse - _, err = secrets.impl.Update(context.Background(), obj, metav1.UpdateOptions{}) - return errors.Wrap(err, "update: failed to update") -} - -// Delete deletes the Secret holding the release named by key. -func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) { - // fetch the release to check existence - if rls, err = secrets.Get(key); err != nil { - return nil, err - } - // delete the release - err = secrets.impl.Delete(context.Background(), key, metav1.DeleteOptions{}) - return rls, err -} - -// newSecretsObject constructs a kubernetes Secret object -// to store a release. Each secret data entry is the base64 -// encoded gzipped string of a release. -// -// The following labels are used within each secret: -// -// "modifiedAt" - timestamp indicating when this secret was last modified. (set in Update) -// "createdAt" - timestamp indicating when this secret was created. (set in Create) -// "version" - version of the release. -// "status" - status of the release (see pkg/release/status.go for variants) -// "owner" - owner of the secret, currently "helm". -// "name" - name of the release. -// -func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) { - const owner = "helm" - - // encode the release - s, err := encodeRelease(rls) - if err != nil { - return nil, err - } - - if lbs == nil { - lbs.init() - } - - // apply labels - lbs.set("name", rls.Name) - lbs.set("owner", owner) - lbs.set("status", rls.Info.Status.String()) - lbs.set("version", strconv.Itoa(rls.Version)) - - // create and return secret object. - // Helm 3 introduced setting the 'Type' field - // in the Kubernetes storage object. - // Helm defines the field content as follows: - // /.v - // Type field for Helm 3: helm.sh/release.v1 - // Note: Version starts at 'v1' for Helm 3 and - // should be incremented if the release object - // metadata is modified. - // This would potentially be a breaking change - // and should only happen between major versions. - return &v1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: key, - Labels: lbs.toMap(), - }, - Type: "helm.sh/release.v1", - Data: map[string][]byte{"release": []byte(s)}, - }, nil -} diff --git a/pkg/storage/driver/secrets_test.go b/pkg/storage/driver/secrets_test.go deleted file mode 100644 index d509c7b3a..000000000 --- a/pkg/storage/driver/secrets_test.go +++ /dev/null @@ -1,241 +0,0 @@ -/* -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 driver - -import ( - "encoding/base64" - "encoding/json" - "reflect" - "testing" - - v1 "k8s.io/api/core/v1" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestSecretName(t *testing.T) { - c := newTestFixtureSecrets(t) - if c.Name() != SecretsDriverName { - t.Errorf("Expected name to be %q, got %q", SecretsDriverName, c.Name()) - } -} - -func TestSecretGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // get release with key - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestUNcompressedSecretGet(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // Create a test fixture which contains an uncompressed release - secret, err := newSecretsObject(key, rel, nil) - if err != nil { - t.Fatalf("Failed to create secret: %s", err) - } - b, err := json.Marshal(rel) - if err != nil { - t.Fatalf("Failed to marshal release: %s", err) - } - secret.Data["release"] = []byte(base64.StdEncoding.EncodeToString(b)) - var mock MockSecretsInterface - mock.objects = map[string]*v1.Secret{key: secret} - secrets := NewSecrets(&mock) - - // get release with key - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %s", err) - } - // compare fetched release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestSecretList(t *testing.T) { - secrets := newTestFixtureSecrets(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - // list all deleted releases - del, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %s", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %s", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d", len(dpl)) - } - - // list all superseded releases - ssd, err := secrets.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %s", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d", len(ssd)) - } -} - -func TestSecretQuery(t *testing.T) { - secrets := newTestFixtureSecrets(t, []*rspb.Release{ - releaseStub("key-1", 1, "default", rspb.StatusUninstalled), - releaseStub("key-2", 1, "default", rspb.StatusUninstalled), - releaseStub("key-3", 1, "default", rspb.StatusDeployed), - releaseStub("key-4", 1, "default", rspb.StatusDeployed), - releaseStub("key-5", 1, "default", rspb.StatusSuperseded), - releaseStub("key-6", 1, "default", rspb.StatusSuperseded), - }...) - - rls, err := secrets.Query(map[string]string{"status": "deployed"}) - if err != nil { - t.Fatalf("Failed to query: %s", err) - } - if len(rls) != 2 { - t.Fatalf("Expected 2 results, actual %d", len(rls)) - } - - _, err = secrets.Query(map[string]string{"name": "notExist"}) - if err != ErrReleaseNotFound { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} - -func TestSecretCreate(t *testing.T) { - secrets := newTestFixtureSecrets(t) - - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - // store the release in a secret - if err := secrets.Create(key, rel); err != nil { - t.Fatalf("Failed to create release with key %q: %s", key, err) - } - - // get the release back - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // compare created release with original - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected {%v}, got {%v}", rel, got) - } -} - -func TestSecretUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // modify release status code - rel.Info.Status = rspb.StatusSuperseded - - // perform the update - if err := secrets.Update(key, rel); err != nil { - t.Fatalf("Failed to update release: %s", err) - } - - // fetch the updated release - got, err := secrets.Get(key) - if err != nil { - t.Fatalf("Failed to get release with key %q: %s", key, err) - } - - // check release has actually been updated by comparing modified fields - if rel.Info.Status != got.Info.Status { - t.Errorf("Expected status %s, got status %s", rel.Info.Status.String(), got.Info.Status.String()) - } -} - -func TestSecretDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) - - // perform the delete on a non-existing release - _, err := secrets.Delete("nonexistent") - if err != ErrReleaseNotFound { - t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err) - } - - // perform the delete - rls, err := secrets.Delete(key) - if err != nil { - t.Fatalf("Failed to delete release with key %q: %s", key, err) - } - if !reflect.DeepEqual(rel, rls) { - t.Errorf("Expected {%v}, got {%v}", rel, rls) - } - - // fetch the deleted release - _, err = secrets.Get(key) - if !reflect.DeepEqual(ErrReleaseNotFound, err) { - t.Errorf("Expected {%v}, got {%v}", ErrReleaseNotFound, err) - } -} diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go deleted file mode 100644 index c8a6ae04f..000000000 --- a/pkg/storage/driver/sql.go +++ /dev/null @@ -1,496 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "fmt" - "sort" - "time" - - "github.com/jmoiron/sqlx" - migrate "github.com/rubenv/sql-migrate" - - sq "github.com/Masterminds/squirrel" - - // Import pq for postgres dialect - _ "github.com/lib/pq" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var _ Driver = (*SQL)(nil) - -var labelMap = map[string]struct{}{ - "modifiedAt": {}, - "createdAt": {}, - "version": {}, - "status": {}, - "owner": {}, - "name": {}, -} - -const postgreSQLDialect = "postgres" - -// SQLDriverName is the string name of this driver. -const SQLDriverName = "SQL" - -const sqlReleaseTableName = "releases_v1" - -const ( - sqlReleaseTableKeyColumn = "key" - sqlReleaseTableTypeColumn = "type" - sqlReleaseTableBodyColumn = "body" - sqlReleaseTableNameColumn = "name" - sqlReleaseTableNamespaceColumn = "namespace" - sqlReleaseTableVersionColumn = "version" - sqlReleaseTableStatusColumn = "status" - sqlReleaseTableOwnerColumn = "owner" - sqlReleaseTableCreatedAtColumn = "createdAt" - sqlReleaseTableModifiedAtColumn = "modifiedAt" -) - -const ( - sqlReleaseDefaultOwner = "helm" - sqlReleaseDefaultType = "helm.sh/release.v1" -) - -// SQL is the sql storage driver implementation. -type SQL struct { - db *sqlx.DB - namespace string - statementBuilder sq.StatementBuilderType - - Log func(string, ...interface{}) -} - -// Name returns the name of the driver. -func (s *SQL) Name() string { - return SQLDriverName -} - -func (s *SQL) ensureDBSetup() error { - // Populate the database with the relations we need if they don't exist yet - migrations := &migrate.MemoryMigrationSource{ - Migrations: []*migrate.Migration{ - { - Id: "init", - Up: []string{ - fmt.Sprintf(` - CREATE TABLE %s ( - %s VARCHAR(67), - %s VARCHAR(64) NOT NULL, - %s TEXT NOT NULL, - %s VARCHAR(64) NOT NULL, - %s VARCHAR(64) NOT NULL, - %s INTEGER NOT NULL, - %s TEXT NOT NULL, - %s TEXT NOT NULL, - %s INTEGER NOT NULL, - %s INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY(%s, %s) - ); - CREATE INDEX ON %s (%s, %s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - CREATE INDEX ON %s (%s); - - GRANT ALL ON %s TO PUBLIC; - - ALTER TABLE %s ENABLE ROW LEVEL SECURITY; - `, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableName, - sqlReleaseTableVersionColumn, - sqlReleaseTableName, - sqlReleaseTableStatusColumn, - sqlReleaseTableName, - sqlReleaseTableOwnerColumn, - sqlReleaseTableName, - sqlReleaseTableCreatedAtColumn, - sqlReleaseTableName, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableName, - sqlReleaseTableName, - ), - }, - Down: []string{ - fmt.Sprintf(` - DROP TABLE %s; - `, sqlReleaseTableName), - }, - }, - }, - } - - _, err := migrate.Exec(s.db.DB, postgreSQLDialect, migrations, migrate.Up) - return err -} - -// SQLReleaseWrapper describes how Helm releases are stored in an SQL database -type SQLReleaseWrapper struct { - // The primary key, made of {release-name}.{release-version} - Key string `db:"key"` - - // See https://github.com/helm/helm/blob/c9fe3d118caec699eb2565df9838673af379ce12/pkg/storage/driver/secrets.go#L231 - Type string `db:"type"` - - // The rspb.Release body, as a base64-encoded string - Body string `db:"body"` - - // Release "labels" that can be used as filters in the storage.Query(labels map[string]string) - // we implemented. Note that allowing Helm users to filter against new dimensions will require a - // new migration to be added, and the Create and/or update functions to be updated accordingly. - Name string `db:"name"` - Namespace string `db:"namespace"` - Version int `db:"version"` - Status string `db:"status"` - Owner string `db:"owner"` - CreatedAt int `db:"createdAt"` - ModifiedAt int `db:"modifiedAt"` -} - -// NewSQL initializes a new sql driver. -func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { - db, err := sqlx.Connect(postgreSQLDialect, connectionString) - if err != nil { - return nil, err - } - - driver := &SQL{ - db: db, - Log: logger, - statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), - } - - if err := driver.ensureDBSetup(); err != nil { - return nil, err - } - - driver.namespace = namespace - - return driver, nil -} - -// Get returns the release named by key. -func (s *SQL) Get(key string) (*rspb.Release, error) { - var record SQLReleaseWrapper - - qb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - - query, args, err := qb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - // Get will return an error if the result is empty - if err := s.db.Get(&record, query, args...); err != nil { - s.Log("got SQL error when getting release %s: %v", key, err) - return nil, ErrReleaseNotFound - } - - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("get: failed to decode data %q: %v", key, err) - return nil, err - } - - return release, nil -} - -// List returns the list of all releases such that filter(release) == true -func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - sb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) - - // If a namespace was specified, we only list releases from that namespace - if s.namespace != "" { - sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - } - - query, args, err := sb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - var records = []SQLReleaseWrapper{} - if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to list: %v", err) - return nil, err - } - - var releases []*rspb.Release - for _, record := range records { - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) - continue - } - if filter(release) { - releases = append(releases, release) - } - } - - return releases, nil -} - -// Query returns the set of releases that match the provided set of labels. -func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { - sb := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName) - - keys := make([]string, 0, len(labels)) - for key := range labels { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - if _, ok := labelMap[key]; ok { - sb = sb.Where(sq.Eq{key: labels[key]}) - } else { - s.Log("unknown label %s", key) - return nil, fmt.Errorf("unknown label %s", key) - } - } - - // If a namespace was specified, we only list releases from that namespace - if s.namespace != "" { - sb = sb.Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}) - } - - // Build our query - query, args, err := sb.ToSql() - if err != nil { - s.Log("failed to build query: %v", err) - return nil, err - } - - var records = []SQLReleaseWrapper{} - if err := s.db.Select(&records, query, args...); err != nil { - s.Log("list: failed to query with labels: %v", err) - return nil, err - } - - if len(records) == 0 { - return nil, ErrReleaseNotFound - } - - var releases []*rspb.Release - for _, record := range records { - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("list: failed to decode release: %v: %v", record, err) - continue - } - releases = append(releases, release) - } - - if len(releases) == 0 { - return nil, ErrReleaseNotFound - } - - return releases, nil -} - -// Create creates a new release. -func (s *SQL) Create(key string, rls *rspb.Release) error { - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - s.namespace = namespace - - body, err := encodeRelease(rls) - if err != nil { - s.Log("failed to encode release: %v", err) - return err - } - - transaction, err := s.db.Beginx() - if err != nil { - s.Log("failed to start SQL transaction: %v", err) - return fmt.Errorf("error beginning transaction: %v", err) - } - - insertQuery, args, err := s.statementBuilder. - Insert(sqlReleaseTableName). - Columns( - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ). - Values( - key, - sqlReleaseDefaultType, - body, - rls.Name, - namespace, - int(rls.Version), - rls.Info.Status.String(), - sqlReleaseDefaultOwner, - int(time.Now().Unix()), - ).ToSql() - if err != nil { - s.Log("failed to build insert query: %v", err) - return err - } - - if _, err := transaction.Exec(insertQuery, args...); err != nil { - defer transaction.Rollback() - - selectQuery, args, buildErr := s.statementBuilder. - Select(sqlReleaseTableKeyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if buildErr != nil { - s.Log("failed to build select query: %v", buildErr) - return err - } - - var record SQLReleaseWrapper - if err := transaction.Get(&record, selectQuery, args...); err == nil { - s.Log("release %s already exists", key) - return ErrReleaseExists - } - - s.Log("failed to store release %s in SQL database: %v", key, err) - return err - } - defer transaction.Commit() - - return nil -} - -// Update updates a release. -func (s *SQL) Update(key string, rls *rspb.Release) error { - namespace := rls.Namespace - if namespace == "" { - namespace = defaultNamespace - } - s.namespace = namespace - - body, err := encodeRelease(rls) - if err != nil { - s.Log("failed to encode release: %v", err) - return err - } - - query, args, err := s.statementBuilder. - Update(sqlReleaseTableName). - Set(sqlReleaseTableBodyColumn, body). - Set(sqlReleaseTableNameColumn, rls.Name). - Set(sqlReleaseTableVersionColumn, int(rls.Version)). - Set(sqlReleaseTableStatusColumn, rls.Info.Status.String()). - Set(sqlReleaseTableOwnerColumn, sqlReleaseDefaultOwner). - Set(sqlReleaseTableModifiedAtColumn, int(time.Now().Unix())). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: namespace}). - ToSql() - - if err != nil { - s.Log("failed to build update query: %v", err) - return err - } - - if _, err := s.db.Exec(query, args...); err != nil { - s.Log("failed to update release %s in SQL database: %v", key, err) - return err - } - - return nil -} - -// Delete deletes a release or returns ErrReleaseNotFound. -func (s *SQL) Delete(key string) (*rspb.Release, error) { - transaction, err := s.db.Beginx() - if err != nil { - s.Log("failed to start SQL transaction: %v", err) - return nil, fmt.Errorf("error beginning transaction: %v", err) - } - - selectQuery, args, err := s.statementBuilder. - Select(sqlReleaseTableBodyColumn). - From(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if err != nil { - s.Log("failed to build select query: %v", err) - return nil, err - } - - var record SQLReleaseWrapper - err = transaction.Get(&record, selectQuery, args...) - if err != nil { - s.Log("release %s not found: %v", key, err) - return nil, ErrReleaseNotFound - } - - release, err := decodeRelease(record.Body) - if err != nil { - s.Log("failed to decode release %s: %v", key, err) - transaction.Rollback() - return nil, err - } - defer transaction.Commit() - - deleteQuery, args, err := s.statementBuilder. - Delete(sqlReleaseTableName). - Where(sq.Eq{sqlReleaseTableKeyColumn: key}). - Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). - ToSql() - if err != nil { - s.Log("failed to build select query: %v", err) - return nil, err - } - - _, err = transaction.Exec(deleteQuery, args...) - return release, err -} diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go deleted file mode 100644 index 87b6315b8..000000000 --- a/pkg/storage/driver/sql_test.go +++ /dev/null @@ -1,463 +0,0 @@ -/* -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 driver - -import ( - "fmt" - "reflect" - "regexp" - "testing" - "time" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - - rspb "helm.sh/helm/v3/pkg/release" -) - -func TestSQLName(t *testing.T) { - sqlDriver, _ := newTestFixtureSQL(t) - if sqlDriver.Name() != SQLDriverName { - t.Errorf("Expected name to be %s, got %s", SQLDriverName, sqlDriver.Name()) - } -} - -func TestSQLGet(t *testing.T) { - vers := int(1) - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - body, _ := encodeRelease(rel) - - sqlDriver, mock := newTestFixtureSQL(t) - - query := fmt.Sprintf( - regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(query). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - body, - ), - ).RowsWillBeClosed() - - got, err := sqlDriver.Get(key) - if err != nil { - t.Fatalf("Failed to get release: %v", err) - } - - if !reflect.DeepEqual(rel, got) { - t.Errorf("Expected release {%v}, got {%v}", rel, got) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSQLList(t *testing.T) { - body1, _ := encodeRelease(releaseStub("key-1", 1, "default", rspb.StatusUninstalled)) - body2, _ := encodeRelease(releaseStub("key-2", 1, "default", rspb.StatusUninstalled)) - body3, _ := encodeRelease(releaseStub("key-3", 1, "default", rspb.StatusDeployed)) - body4, _ := encodeRelease(releaseStub("key-4", 1, "default", rspb.StatusDeployed)) - body5, _ := encodeRelease(releaseStub("key-5", 1, "default", rspb.StatusSuperseded)) - body6, _ := encodeRelease(releaseStub("key-6", 1, "default", rspb.StatusSuperseded)) - - sqlDriver, mock := newTestFixtureSQL(t) - - for i := 0; i < 3; i++ { - query := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableOwnerColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }). - AddRow(body1). - AddRow(body2). - AddRow(body3). - AddRow(body4). - AddRow(body5). - AddRow(body6), - ).RowsWillBeClosed() - } - - // list all deleted releases - del, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusUninstalled - }) - // check - if err != nil { - t.Errorf("Failed to list deleted: %v", err) - } - if len(del) != 2 { - t.Errorf("Expected 2 deleted, got %d:\n%v\n", len(del), del) - } - - // list all deployed releases - dpl, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusDeployed - }) - // check - if err != nil { - t.Errorf("Failed to list deployed: %v", err) - } - if len(dpl) != 2 { - t.Errorf("Expected 2 deployed, got %d:\n%v\n", len(dpl), dpl) - } - - // list all superseded releases - ssd, err := sqlDriver.List(func(rel *rspb.Release) bool { - return rel.Info.Status == rspb.StatusSuperseded - }) - // check - if err != nil { - t.Errorf("Failed to list superseded: %v", err) - } - if len(ssd) != 2 { - t.Errorf("Expected 2 superseded, got %d:\n%v\n", len(ssd), ssd) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlCreate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - query := fmt.Sprintf( - "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ) - - mock.ExpectBegin() - mock. - ExpectExec(regexp.QuoteMeta(query)). - WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). - WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectCommit() - - if err := sqlDriver.Create(key, rel); err != nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlCreateAlreadyExists(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - insertQuery := fmt.Sprintf( - "INSERT INTO %s (%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableTypeColumn, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableNamespaceColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableCreatedAtColumn, - ) - - // Insert fails (primary key already exists) - mock.ExpectBegin() - mock. - ExpectExec(regexp.QuoteMeta(insertQuery)). - WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). - WillReturnError(fmt.Errorf("dialect dependent SQL error")) - - selectQuery := fmt.Sprintf( - regexp.QuoteMeta("SELECT %s FROM %s WHERE %s = $1 AND %s = $2"), - sqlReleaseTableKeyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - // Let's check that we do make sure the error is due to a release already existing - mock. - ExpectQuery(selectQuery). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableKeyColumn, - }).AddRow( - key, - ), - ).RowsWillBeClosed() - mock.ExpectRollback() - - if err := sqlDriver.Create(key, rel); err == nil { - t.Fatalf("failed to create release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlUpdate(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - sqlDriver, mock := newTestFixtureSQL(t) - body, _ := encodeRelease(rel) - - query := fmt.Sprintf( - "UPDATE %s SET %s = $1, %s = $2, %s = $3, %s = $4, %s = $5, %s = $6 WHERE %s = $7 AND %s = $8", - sqlReleaseTableName, - sqlReleaseTableBodyColumn, - sqlReleaseTableNameColumn, - sqlReleaseTableVersionColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableModifiedAtColumn, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectExec(regexp.QuoteMeta(query)). - WithArgs(body, rel.Name, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix()), key, namespace). - WillReturnResult(sqlmock.NewResult(0, 1)) - - if err := sqlDriver.Update(key, rel); err != nil { - t.Fatalf("failed to update release with key %s: %v", key, err) - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlQuery(t *testing.T) { - // Reflect actual use cases in ../storage.go - labelSetUnknown := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - "status": "unknown", - } - labelSetDeployed := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - "status": "deployed", - } - labelSetAll := map[string]string{ - "name": "smug-pigeon", - "owner": sqlReleaseDefaultOwner, - } - - supersededRelease := releaseStub("smug-pigeon", 1, "default", rspb.StatusSuperseded) - supersededReleaseBody, _ := encodeRelease(supersededRelease) - deployedRelease := releaseStub("smug-pigeon", 2, "default", rspb.StatusDeployed) - deployedReleaseBody, _ := encodeRelease(deployedRelease) - - // Let's actually start our test - sqlDriver, mock := newTestFixtureSQL(t) - - query := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableNameColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableStatusColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "unknown", "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }), - ).RowsWillBeClosed() - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "deployed", "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - deployedReleaseBody, - ), - ).RowsWillBeClosed() - - query = fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableNameColumn, - sqlReleaseTableOwnerColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectQuery(regexp.QuoteMeta(query)). - WithArgs("smug-pigeon", sqlReleaseDefaultOwner, "default"). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - supersededReleaseBody, - ).AddRow( - deployedReleaseBody, - ), - ).RowsWillBeClosed() - - _, err := sqlDriver.Query(labelSetUnknown) - if err == nil { - t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) - } else if err != ErrReleaseNotFound { - t.Fatalf("failed to query for unknown smug-pigeon release: %v", err) - } - - results, err := sqlDriver.Query(labelSetDeployed) - if err != nil { - t.Fatalf("failed to query for deployed smug-pigeon release: %v", err) - } - - for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) { - t.Errorf("Expected release {%v}, got {%v}", deployedRelease, res) - } - } - - results, err = sqlDriver.Query(labelSetAll) - if err != nil { - t.Fatalf("failed to query release history for smug-pigeon: %v", err) - } - - if len(results) != 2 { - t.Errorf("expected a resultset of size 2, got %d", len(results)) - } - - for _, res := range results { - if !reflect.DeepEqual(res, deployedRelease) && !reflect.DeepEqual(res, supersededRelease) { - t.Errorf("Expected release {%v} or {%v}, got {%v}", deployedRelease, supersededRelease, res) - } - } - - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } -} - -func TestSqlDelete(t *testing.T) { - vers := 1 - name := "smug-pigeon" - namespace := "default" - key := testKey(name, vers) - rel := releaseStub(name, vers, namespace, rspb.StatusDeployed) - - body, _ := encodeRelease(rel) - - sqlDriver, mock := newTestFixtureSQL(t) - - selectQuery := fmt.Sprintf( - "SELECT %s FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableBodyColumn, - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock.ExpectBegin() - mock. - ExpectQuery(regexp.QuoteMeta(selectQuery)). - WithArgs(key, namespace). - WillReturnRows( - mock.NewRows([]string{ - sqlReleaseTableBodyColumn, - }).AddRow( - body, - ), - ).RowsWillBeClosed() - - deleteQuery := fmt.Sprintf( - "DELETE FROM %s WHERE %s = $1 AND %s = $2", - sqlReleaseTableName, - sqlReleaseTableKeyColumn, - sqlReleaseTableNamespaceColumn, - ) - - mock. - ExpectExec(regexp.QuoteMeta(deleteQuery)). - WithArgs(key, namespace). - WillReturnResult(sqlmock.NewResult(0, 1)) - mock.ExpectCommit() - - deletedRelease, err := sqlDriver.Delete(key) - if err := mock.ExpectationsWereMet(); err != nil { - t.Errorf("sql expectations weren't met: %v", err) - } - if err != nil { - t.Fatalf("failed to delete release with key %q: %v", key, err) - } - - if !reflect.DeepEqual(rel, deletedRelease) { - t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease) - } -} diff --git a/pkg/storage/driver/util.go b/pkg/storage/driver/util.go deleted file mode 100644 index b5908e508..000000000 --- a/pkg/storage/driver/util.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -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 driver // import "helm.sh/helm/v3/pkg/storage/driver" - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "io/ioutil" - - rspb "helm.sh/helm/v3/pkg/release" -) - -var b64 = base64.StdEncoding - -var magicGzip = []byte{0x1f, 0x8b, 0x08} - -// encodeRelease encodes a release returning a base64 encoded -// gzipped string representation, or error. -func encodeRelease(rls *rspb.Release) (string, error) { - b, err := json.Marshal(rls) - if err != nil { - return "", err - } - var buf bytes.Buffer - w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) - if err != nil { - return "", err - } - if _, err = w.Write(b); err != nil { - return "", err - } - w.Close() - - return b64.EncodeToString(buf.Bytes()), nil -} - -// decodeRelease decodes the bytes of data into a release -// type. Data must contain a base64 encoded gzipped string of a -// valid release, otherwise an error is returned. -func decodeRelease(data string) (*rspb.Release, error) { - // base64 decode string - b, err := b64.DecodeString(data) - if err != nil { - return nil, err - } - - // For backwards compatibility with releases that were stored before - // compression was introduced we skip decompression if the - // gzip magic header is not found - if len(b) > 3 && bytes.Equal(b[0:3], magicGzip) { - r, err := gzip.NewReader(bytes.NewReader(b)) - if err != nil { - return nil, err - } - defer r.Close() - b2, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - b = b2 - } - - var rls rspb.Release - // unmarshal release object bytes - if err := json.Unmarshal(b, &rls); err != nil { - return nil, err - } - return &rls, nil -} diff --git a/pkg/storage/storage.go b/pkg/storage/storage.go deleted file mode 100644 index 0a18b34a0..000000000 --- a/pkg/storage/storage.go +++ /dev/null @@ -1,266 +0,0 @@ -/* -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 storage // import "helm.sh/helm/v3/pkg/storage" - -import ( - "fmt" - "strings" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" - relutil "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/storage/driver" -) - -// HelmStorageType is the type field of the Kubernetes storage object which stores the Helm release -// version. It is modified slightly replacing the '/': sh.helm/release.v1 -// Note: The version 'v1' is incremented if the release object metadata is -// modified between major releases. -// This constant is used as a prefix for the Kubernetes storage object name. -const HelmStorageType = "sh.helm.release.v1" - -// Storage represents a storage engine for a Release. -type Storage struct { - driver.Driver - - // MaxHistory specifies the maximum number of historical releases that will - // be retained, including the most recent release. Values of 0 or less are - // ignored (meaning no limits are imposed). - MaxHistory int - - Log func(string, ...interface{}) -} - -// Get retrieves the release from storage. An error is returned -// if the storage driver failed to fetch the release, or the -// release identified by the key, version pair does not exist. -func (s *Storage) Get(name string, version int) (*rspb.Release, error) { - s.Log("getting release %q", makeKey(name, version)) - return s.Driver.Get(makeKey(name, version)) -} - -// Create creates a new storage entry holding the release. An -// error is returned if the storage driver fails to store the -// release, or a release with an identical key already exists. -func (s *Storage) Create(rls *rspb.Release) error { - s.Log("creating release %q", makeKey(rls.Name, rls.Version)) - if s.MaxHistory > 0 { - // Want to make space for one more release. - if err := s.removeLeastRecent(rls.Name, s.MaxHistory-1); err != nil && - !errors.Is(err, driver.ErrReleaseNotFound) { - return err - } - } - return s.Driver.Create(makeKey(rls.Name, rls.Version), rls) -} - -// Update updates the release in storage. An error is returned if the -// storage backend fails to update the release or if the release -// does not exist. -func (s *Storage) Update(rls *rspb.Release) error { - s.Log("updating release %q", makeKey(rls.Name, rls.Version)) - return s.Driver.Update(makeKey(rls.Name, rls.Version), rls) -} - -// Delete deletes the release from storage. An error is returned if -// the storage backend fails to delete the release or if the release -// does not exist. -func (s *Storage) Delete(name string, version int) (*rspb.Release, error) { - s.Log("deleting release %q", makeKey(name, version)) - return s.Driver.Delete(makeKey(name, version)) -} - -// ListReleases returns all releases from storage. An error is returned if the -// storage backend fails to retrieve the releases. -func (s *Storage) ListReleases() ([]*rspb.Release, error) { - s.Log("listing all releases in storage") - return s.Driver.List(func(_ *rspb.Release) bool { return true }) -} - -// ListUninstalled returns all releases with Status == UNINSTALLED. An error is returned -// if the storage backend fails to retrieve the releases. -func (s *Storage) ListUninstalled() ([]*rspb.Release, error) { - s.Log("listing uninstalled releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { - return relutil.StatusFilter(rspb.StatusUninstalled).Check(rls) - }) -} - -// ListDeployed returns all releases with Status == DEPLOYED. An error is returned -// if the storage backend fails to retrieve the releases. -func (s *Storage) ListDeployed() ([]*rspb.Release, error) { - s.Log("listing all deployed releases in storage") - return s.Driver.List(func(rls *rspb.Release) bool { - return relutil.StatusFilter(rspb.StatusDeployed).Check(rls) - }) -} - -// Deployed returns the last deployed release with the provided release name, or -// returns ErrReleaseNotFound if not found. -func (s *Storage) Deployed(name string) (*rspb.Release, error) { - ls, err := s.DeployedAll(name) - if err != nil { - return nil, err - } - - if len(ls) == 0 { - return nil, driver.NewErrNoDeployedReleases(name) - } - - // If executed concurrently, Helm's database gets corrupted - // and multiple releases are DEPLOYED. Take the latest. - relutil.Reverse(ls, relutil.SortByRevision) - - return ls[0], nil -} - -// DeployedAll returns all deployed releases with the provided name, or -// returns ErrReleaseNotFound if not found. -func (s *Storage) DeployedAll(name string) ([]*rspb.Release, error) { - s.Log("getting deployed releases from %q history", name) - - ls, err := s.Driver.Query(map[string]string{ - "name": name, - "owner": "helm", - "status": "deployed", - }) - if err == nil { - return ls, nil - } - if strings.Contains(err.Error(), "not found") { - return nil, driver.NewErrNoDeployedReleases(name) - } - return nil, err -} - -// History returns the revision history for the release with the provided name, or -// returns ErrReleaseNotFound if no such release name exists. -func (s *Storage) History(name string) ([]*rspb.Release, error) { - s.Log("getting release history for %q", name) - - return s.Driver.Query(map[string]string{"name": name, "owner": "helm"}) -} - -// removeLeastRecent removes items from history until the length number of releases -// does not exceed max. -// -// We allow max to be set explicitly so that calling functions can "make space" -// for the new records they are going to write. -func (s *Storage) removeLeastRecent(name string, max int) error { - if max < 0 { - return nil - } - h, err := s.History(name) - if err != nil { - return err - } - if len(h) <= max { - return nil - } - - // We want oldest to newest - relutil.SortByRevision(h) - - lastDeployed, err := s.Deployed(name) - if err != nil && !errors.Is(err, driver.ErrNoDeployedReleases) { - return err - } - - var toDelete []*rspb.Release - for _, rel := range h { - // once we have enough releases to delete to reach the max, stop - if len(h)-len(toDelete) == max { - break - } - if lastDeployed != nil { - if rel.Version != lastDeployed.Version { - toDelete = append(toDelete, rel) - } - } else { - toDelete = append(toDelete, rel) - } - } - - // Delete as many as possible. In the case of API throughput limitations, - // multiple invocations of this function will eventually delete them all. - errs := []error{} - for _, rel := range toDelete { - err = s.deleteReleaseVersion(name, rel.Version) - if err != nil { - errs = append(errs, err) - } - } - - s.Log("Pruned %d record(s) from %s with %d error(s)", len(toDelete), name, len(errs)) - switch c := len(errs); c { - case 0: - return nil - case 1: - return errs[0] - default: - return errors.Errorf("encountered %d deletion errors. First is: %s", c, errs[0]) - } -} - -func (s *Storage) deleteReleaseVersion(name string, version int) error { - key := makeKey(name, version) - _, err := s.Delete(name, version) - if err != nil { - s.Log("error pruning %s from release history: %s", key, err) - return err - } - return nil -} - -// Last fetches the last revision of the named release. -func (s *Storage) Last(name string) (*rspb.Release, error) { - s.Log("getting last revision of %q", name) - h, err := s.History(name) - if err != nil { - return nil, err - } - if len(h) == 0 { - return nil, errors.Errorf("no revision for release %q", name) - } - - relutil.Reverse(h, relutil.SortByRevision) - return h[0], nil -} - -// makeKey concatenates the Kubernetes storage object type, a release name and version -// into a string with format:```..v```. -// The storage type is prepended to keep name uniqueness between different -// release storage types. An example of clash when not using the type: -// https://github.com/helm/helm/issues/6435. -// This key is used to uniquely identify storage objects. -func makeKey(rlsname string, version int) string { - return fmt.Sprintf("%s.%s.v%d", HelmStorageType, rlsname, version) -} - -// Init initializes a new storage backend with the driver d. -// If d is nil, the default in-memory driver is used. -func Init(d driver.Driver) *Storage { - // default driver is in memory - if d == nil { - d = driver.NewMemory() - } - return &Storage{ - Driver: d, - Log: func(_ string, _ ...interface{}) {}, - } -} diff --git a/pkg/storage/storage_test.go b/pkg/storage/storage_test.go deleted file mode 100644 index 058b077e8..000000000 --- a/pkg/storage/storage_test.go +++ /dev/null @@ -1,560 +0,0 @@ -/* -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 storage // import "helm.sh/helm/v3/pkg/storage" - -import ( - "fmt" - "reflect" - "testing" - - "github.com/pkg/errors" - - rspb "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" -) - -func TestStorageCreate(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - - // fetch the release - res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") - - // verify the fetched and created release are the same - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } -} - -func TestStorageUpdate(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - Status: rspb.StatusDeployed, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - - // modify the release - rls.Info.Status = rspb.StatusUninstalled - assertErrNil(t.Fatal, storage.Update(rls), "UpdateRelease") - - // retrieve the updated release - res, err := storage.Get(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "QueryRelease") - - // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } -} - -func TestStorageDelete(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // create fake release - rls := ReleaseTestData{ - Name: "angry-beaver", - Version: 1, - }.ToRelease() - rls2 := ReleaseTestData{ - Name: "angry-beaver", - Version: 2, - }.ToRelease() - - assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") - assertErrNil(t.Fatal, storage.Create(rls2), "StoreRelease") - - // delete the release - res, err := storage.Delete(rls.Name, rls.Version) - assertErrNil(t.Fatal, err, "DeleteRelease") - - // verify updated and fetched releases are the same. - if !reflect.DeepEqual(rls, res) { - t.Fatalf("Expected %v, got %v", rls, res) - } - - hist, err := storage.History(rls.Name) - if err != nil { - t.Errorf("unexpected error: %s", err) - } - - // We have now deleted one of the two records. - if len(hist) != 1 { - t.Errorf("expected 1 record for deleted release version, got %d", len(hist)) - } - - if hist[0].Version != 2 { - t.Errorf("Expected version to be 2, got %d", hist[0].Version) - } -} - -func TestStorageList(t *testing.T) { - // initialize storage - storage := Init(driver.NewMemory()) - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: "happy-catdog", Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.StatusDeployed}.ToRelease() - rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.StatusDeployed}.ToRelease() - rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.StatusUninstalled}.ToRelease() - rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.StatusUninstalled}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'rls0'") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'rls1'") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'rls2'") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'rls3'") - assertErrNil(t.Fatal, storage.Create(rls4), "Storing release 'rls4'") - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'rls5'") - assertErrNil(t.Fatal, storage.Create(rls6), "Storing release 'rls6'") - } - - var listTests = []struct { - Description string - NumExpected int - ListFunc func() ([]*rspb.Release, error) - }{ - {"ListDeployed", 2, storage.ListDeployed}, - {"ListReleases", 7, storage.ListReleases}, - {"ListUninstalled", 2, storage.ListUninstalled}, - } - - setup() - - for _, tt := range listTests { - list, err := tt.ListFunc() - assertErrNil(t.Fatal, err, tt.Description) - // verify the count of releases returned - if len(list) != tt.NumExpected { - t.Errorf("ListReleases(%s): expected %d, actual %d", - tt.Description, - tt.NumExpected, - len(list)) - } - } -} - -func TestStorageDeployed(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - const vers = 4 - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - rls, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } - - switch { - case rls == nil: - t.Fatalf("Release is nil") - case rls.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name) - case rls.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version) - case rls.Info.Status != rspb.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String()) - } -} - -func TestStorageDeployedWithCorruption(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - const vers = int(4) - - // setup storage with test releases - setup := func() { - // release records (notice odd order and corruption) - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - rls, err := storage.Deployed(name) - if err != nil { - t.Fatalf("Failed to query for deployed release: %s\n", err) - } - - switch { - case rls == nil: - t.Fatalf("Release is nil") - case rls.Name != name: - t.Fatalf("Expected release name %q, actual %q\n", name, rls.Name) - case rls.Version != vers: - t.Fatalf("Expected release version %d, actual %d\n", vers, rls.Version) - case rls.Info.Status != rspb.StatusDeployed: - t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.String()) - } -} - -func TestStorageHistory(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - h, err := storage.History(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } - if len(h) != 4 { - t.Fatalf("Release history (%q) is empty\n", name) - } -} - -var errMaxHistoryMockDriverSomethingHappened = errors.New("something happened") - -type MaxHistoryMockDriver struct { - Driver driver.Driver -} - -func NewMaxHistoryMockDriver(d driver.Driver) *MaxHistoryMockDriver { - return &MaxHistoryMockDriver{Driver: d} -} -func (d *MaxHistoryMockDriver) Create(key string, rls *rspb.Release) error { - return d.Driver.Create(key, rls) -} -func (d *MaxHistoryMockDriver) Update(key string, rls *rspb.Release) error { - return d.Driver.Update(key, rls) -} -func (d *MaxHistoryMockDriver) Delete(key string) (*rspb.Release, error) { - return nil, errMaxHistoryMockDriverSomethingHappened -} -func (d *MaxHistoryMockDriver) Get(key string) (*rspb.Release, error) { - return d.Driver.Get(key) -} -func (d *MaxHistoryMockDriver) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { - return d.Driver.List(filter) -} -func (d *MaxHistoryMockDriver) Query(labels map[string]string) ([]*rspb.Release, error) { - return d.Driver.Query(labels) -} -func (d *MaxHistoryMockDriver) Name() string { - return d.Driver.Name() -} - -func TestMaxHistoryErrorHandling(t *testing.T) { - //func TestStorageRemoveLeastRecentWithError(t *testing.T) { - storage := Init(NewMaxHistoryMockDriver(driver.NewMemory())) - storage.Log = t.Logf - - storage.MaxHistory = 1 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls1 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Driver.Create(makeKey(rls1.Name, rls1.Version), rls1), "Storing release 'angry-bird' (v1)") - } - setup() - - rls2 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - wantErr := errMaxHistoryMockDriverSomethingHappened - gotErr := storage.Create(rls2) - if !errors.Is(gotErr, wantErr) { - t.Fatalf("Storing release 'angry-bird' (v2) should return the error %#v, but returned %#v", wantErr, gotErr) - } -} - -func TestStorageRemoveLeastRecent(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.Log = t.Logf - - // Make sure that specifying this at the outset doesn't cause any bugs. - storage.MaxHistory = 10 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusDeployed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - setup() - - // Because we have not set a limit, we expect 4. - expect := 4 - if hist, err := storage.History(name); err != nil { - t.Fatal(err) - } else if len(hist) != expect { - t.Fatalf("expected %d items in history, got %d", expect, len(hist)) - } - - storage.MaxHistory = 3 - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusDeployed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") - - // On inserting the 5th record, we expect two records to be pruned from history. - hist, err := storage.History(name) - if err != nil { - t.Fatal(err) - } else if len(hist) != storage.MaxHistory { - for _, item := range hist { - t.Logf("%s %v", item.Name, item.Version) - } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist)) - } - - // We expect the existing records to be 3, 4, and 5. - for i, item := range hist { - v := item.Version - if expect := i + 3; v != expect { - t.Errorf("Expected release %d, got %d", expect, v) - } - } -} - -func TestStorageDoNotDeleteDeployed(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.Log = t.Logf - storage.MaxHistory = 3 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusDeployed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - setup() - - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease() - assertErrNil(t.Fatal, storage.Create(rls5), "Storing release 'angry-bird' (v5)") - - // On inserting the 5th record, we expect a total of 3 releases, but we expect version 2 - // (the only deployed release), to still exist - hist, err := storage.History(name) - if err != nil { - t.Fatal(err) - } else if len(hist) != storage.MaxHistory { - for _, item := range hist { - t.Logf("%s %v", item.Name, item.Version) - } - t.Fatalf("expected %d items in history, got %d", storage.MaxHistory, len(hist)) - } - - expectedVersions := map[int]bool{ - 2: true, - 4: true, - 5: true, - } - - for _, item := range hist { - if !expectedVersions[item.Version] { - t.Errorf("Release version %d, found when not expected", item.Version) - } - } -} - -func TestStorageLast(t *testing.T) { - storage := Init(driver.NewMemory()) - - const name = "angry-bird" - - // Set up storage with test releases. - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusSuperseded}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusSuperseded}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusSuperseded}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - } - - setup() - - h, err := storage.Last(name) - if err != nil { - t.Fatalf("Failed to query for release history (%q): %s\n", name, err) - } - - if h.Version != 4 { - t.Errorf("Expected revision 4, got %d", h.Version) - } -} - -// TestUpgradeInitiallyFailedRelease tests a case when there are no deployed release yet, but history limit has been -// reached: the has-no-deployed-releases error should not occur in such case. -func TestUpgradeInitiallyFailedReleaseWithHistoryLimit(t *testing.T) { - storage := Init(driver.NewMemory()) - storage.MaxHistory = 4 - - const name = "angry-bird" - - // setup storage with test releases - setup := func() { - // release records - rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.StatusFailed}.ToRelease() - rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.StatusFailed}.ToRelease() - rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.StatusFailed}.ToRelease() - rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.StatusFailed}.ToRelease() - - // create the release records in the storage - assertErrNil(t.Fatal, storage.Create(rls0), "Storing release 'angry-bird' (v1)") - assertErrNil(t.Fatal, storage.Create(rls1), "Storing release 'angry-bird' (v2)") - assertErrNil(t.Fatal, storage.Create(rls2), "Storing release 'angry-bird' (v3)") - assertErrNil(t.Fatal, storage.Create(rls3), "Storing release 'angry-bird' (v4)") - - hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - wantHistoryLen := 4 - if len(hist) != wantHistoryLen { - t.Fatalf("expected history of release %q to contain %d releases, got %d", name, wantHistoryLen, len(hist)) - } - } - - setup() - - rls5 := ReleaseTestData{Name: name, Version: 5, Status: rspb.StatusFailed}.ToRelease() - err := storage.Create(rls5) - if err != nil { - t.Fatalf("Failed to create a new release version: %s", err) - } - - hist, err := storage.History(name) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - for i, rel := range hist { - wantVersion := i + 2 - if rel.Version != wantVersion { - t.Fatalf("Expected history release %d version to equal %d, got %d", i+1, wantVersion, rel.Version) - } - - wantStatus := rspb.StatusFailed - if rel.Info.Status != wantStatus { - t.Fatalf("Expected history release %d status to equal %q, got %q", i+1, wantStatus, rel.Info.Status) - } - } -} - -type ReleaseTestData struct { - Name string - Version int - Manifest string - Namespace string - Status rspb.Status -} - -func (test ReleaseTestData) ToRelease() *rspb.Release { - return &rspb.Release{ - Name: test.Name, - Version: test.Version, - Manifest: test.Manifest, - Namespace: test.Namespace, - Info: &rspb.Info{Status: test.Status}, - } -} - -func assertErrNil(eh func(args ...interface{}), err error, message string) { - if err != nil { - eh(fmt.Sprintf("%s: %q", message, err)) - } -} diff --git a/pkg/strvals/doc.go b/pkg/strvals/doc.go deleted file mode 100644 index f17290587..000000000 --- a/pkg/strvals/doc.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -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 strvals provides tools for working with strval lines. - -Helm supports a compressed format for YAML settings which we call strvals. -The format is roughly like this: - - name=value,topname.subname=value - -The above is equivalent to the YAML document - - name: value - topname: - subname: value - -This package provides a parser and utilities for converting the strvals format -to other formats. -*/ -package strvals diff --git a/pkg/strvals/parser.go b/pkg/strvals/parser.go deleted file mode 100644 index 26bc0fcf2..000000000 --- a/pkg/strvals/parser.go +++ /dev/null @@ -1,549 +0,0 @@ -/* -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 strvals - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "strconv" - "strings" - "unicode" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" -) - -// ErrNotList indicates that a non-list was treated as a list. -var ErrNotList = errors.New("not a list") - -// MaxIndex is the maximum index that will be allowed by setIndex. -// The default value 65536 = 1024 * 64 -var MaxIndex = 65536 - -// ToYAML takes a string of arguments and converts to a YAML document. -func ToYAML(s string) (string, error) { - m, err := Parse(s) - if err != nil { - return "", err - } - d, err := yaml.Marshal(m) - return strings.TrimSuffix(string(d), "\n"), err -} - -// Parse parses a set line. -// -// A set line is of the form name1=value1,name2=value2 -func Parse(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newParser(scanner, vals, false) - err := t.parse() - return vals, err -} - -// ParseString parses a set line and forces a string value. -// -// A set line is of the form name1=value1,name2=value2 -func ParseString(s string) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newParser(scanner, vals, true) - err := t.parse() - return vals, err -} - -// ParseInto parses a strvals line and merges the result into dest. -// -// If the strval string has a key that exists in dest, it overwrites the -// dest version. -func ParseInto(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newParser(scanner, dest, false) - return t.parse() -} - -// ParseFile parses a set line, but its final value is loaded from the file at the path specified by the original value. -// -// A set line is of the form name1=path1,name2=path2 -// -// When the files at path1 and path2 contained "val1" and "val2" respectively, the set line is consumed as -// name1=val1,name2=val2 -func ParseFile(s string, reader RunesValueReader) (map[string]interface{}, error) { - vals := map[string]interface{}{} - scanner := bytes.NewBufferString(s) - t := newFileParser(scanner, vals, reader) - err := t.parse() - return vals, err -} - -// ParseIntoString parses a strvals line and merges the result into dest. -// -// This method always returns a string as the value. -func ParseIntoString(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newParser(scanner, dest, true) - return t.parse() -} - -// ParseJSON parses a string with format key1=val1, key2=val2, ... -// where values are json strings (null, or scalars, or arrays, or objects). -// An empty val is treated as null. -// -// If a key exists in dest, the new value overwrites the dest version. -// -func ParseJSON(s string, dest map[string]interface{}) error { - scanner := bytes.NewBufferString(s) - t := newJSONParser(scanner, dest) - return t.parse() -} - -// ParseIntoFile parses a filevals line and merges the result into dest. -// -// This method always returns a string as the value. -func ParseIntoFile(s string, dest map[string]interface{}, reader RunesValueReader) error { - scanner := bytes.NewBufferString(s) - t := newFileParser(scanner, dest, reader) - return t.parse() -} - -// RunesValueReader is a function that takes the given value (a slice of runes) -// and returns the parsed value -type RunesValueReader func([]rune) (interface{}, error) - -// parser is a simple parser that takes a strvals line and parses it into a -// map representation. -// -// where sc is the source of the original data being parsed -// where data is the final parsed data from the parses with correct types -type parser struct { - sc *bytes.Buffer - data map[string]interface{} - reader RunesValueReader - isjsonval bool -} - -func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { - stringConverter := func(rs []rune) (interface{}, error) { - return typedVal(rs, stringBool), nil - } - return &parser{sc: sc, data: data, reader: stringConverter} -} - -func newJSONParser(sc *bytes.Buffer, data map[string]interface{}) *parser { - return &parser{sc: sc, data: data, reader: nil, isjsonval: true} -} - -func newFileParser(sc *bytes.Buffer, data map[string]interface{}, reader RunesValueReader) *parser { - return &parser{sc: sc, data: data, reader: reader} -} - -func (t *parser) parse() error { - for { - err := t.key(t.data) - if err == nil { - continue - } - if err == io.EOF { - return nil - } - return err - } -} - -func runeSet(r []rune) map[rune]bool { - s := make(map[rune]bool, len(r)) - for _, rr := range r { - s[rr] = true - } - return s -} - -func (t *parser) key(data map[string]interface{}) (reterr error) { - defer func() { - if r := recover(); r != nil { - reterr = fmt.Errorf("unable to parse key: %s", r) - } - }() - stop := runeSet([]rune{'=', '[', ',', '.'}) - for { - switch k, last, err := runesUntil(t.sc, stop); { - case err != nil: - if len(k) == 0 { - return err - } - return errors.Errorf("key %q has no value", string(k)) - //set(data, string(k), "") - //return err - case last == '[': - // We are in a list index context, so we need to set an index. - i, err := t.keyIndex() - if err != nil { - return errors.Wrap(err, "error parsing index") - } - kk := string(k) - // Find or create target list - list := []interface{}{} - if _, ok := data[kk]; ok { - list = data[kk].([]interface{}) - } - - // Now we need to get the value after the ]. - list, err = t.listItem(list, i) - set(data, kk, list) - return err - case last == '=': - if t.isjsonval { - empval, err := t.emptyVal() - if err != nil { - return err - } - if empval { - set(data, string(k), nil) - return nil - } - // parse jsonvals by using Go’s JSON standard library - // Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,... - // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, - // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we - // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} - dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { - return err - } - set(data, string(k), jsonval) - if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil { - return err - } - // skip possible blanks and comma - _, err = t.emptyVal() - return err - } - //End of key. Consume =, Get value. - // FIXME: Get value list first - vl, e := t.valList() - switch e { - case nil: - set(data, string(k), vl) - return nil - case io.EOF: - set(data, string(k), "") - return e - case ErrNotList: - rs, e := t.val() - if e != nil && e != io.EOF { - return e - } - v, e := t.reader(rs) - set(data, string(k), v) - return e - default: - return e - } - case last == ',': - // No value given. Set the value to empty string. Return error. - set(data, string(k), "") - return errors.Errorf("key %q has no value (cannot end with ,)", string(k)) - case last == '.': - // First, create or find the target map. - inner := map[string]interface{}{} - if _, ok := data[string(k)]; ok { - inner = data[string(k)].(map[string]interface{}) - } - - // Recurse - e := t.key(inner) - if len(inner) == 0 { - return errors.Errorf("key map %q has no value", string(k)) - } - set(data, string(k), inner) - return e - } - } -} - -func set(data map[string]interface{}, key string, val interface{}) { - // If key is empty, don't set it. - if len(key) == 0 { - return - } - data[key] = val -} - -func setIndex(list []interface{}, index int, val interface{}) (l2 []interface{}, err error) { - // There are possible index values that are out of range on a target system - // causing a panic. This will catch the panic and return an error instead. - // The value of the index that causes a panic varies from system to system. - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("error processing index %d: %s", index, r) - } - }() - - if index < 0 { - return list, fmt.Errorf("negative %d index not allowed", index) - } - if index > MaxIndex { - return list, fmt.Errorf("index of %d is greater than maximum supported index of %d", index, MaxIndex) - } - if len(list) <= index { - newlist := make([]interface{}, index+1) - copy(newlist, list) - list = newlist - } - list[index] = val - return list, nil -} - -func (t *parser) keyIndex() (int, error) { - // First, get the key. - stop := runeSet([]rune{']'}) - v, _, err := runesUntil(t.sc, stop) - if err != nil { - return 0, err - } - // v should be the index - return strconv.Atoi(string(v)) - -} -func (t *parser) listItem(list []interface{}, i int) ([]interface{}, error) { - if i < 0 { - return list, fmt.Errorf("negative %d index not allowed", i) - } - stop := runeSet([]rune{'[', '.', '='}) - switch k, last, err := runesUntil(t.sc, stop); { - case len(k) > 0: - return list, errors.Errorf("unexpected data at end of array index: %q", k) - case err != nil: - return list, err - case last == '=': - if t.isjsonval { - empval, err := t.emptyVal() - if err != nil { - return list, err - } - if empval { - return setIndex(list, i, nil) - } - // parse jsonvals by using Go’s JSON standard library - // Decode is preferred to Unmarshal in order to parse just the json parts of the list key1=jsonval1,key2=jsonval2,... - // Since Decode has its own buffer that consumes more characters (from underlying t.sc) than the ones actually decoded, - // we invoke Decode on a separate reader built with a copy of what is left in t.sc. After Decode is executed, we - // discard in t.sc the chars of the decoded json value (the number of those characters is returned by InputOffset). - var jsonval interface{} - dec := json.NewDecoder(strings.NewReader(t.sc.String())) - if err = dec.Decode(&jsonval); err != nil { - return list, err - } - if list, err = setIndex(list, i, jsonval); err != nil { - return list, err - } - if _, err = io.CopyN(ioutil.Discard, t.sc, dec.InputOffset()); err != nil { - return list, err - } - // skip possible blanks and comma - _, err = t.emptyVal() - return list, err - } - vl, e := t.valList() - switch e { - case nil: - return setIndex(list, i, vl) - case io.EOF: - return setIndex(list, i, "") - case ErrNotList: - rs, e := t.val() - if e != nil && e != io.EOF { - return list, e - } - v, e := t.reader(rs) - if e != nil { - return list, e - } - return setIndex(list, i, v) - default: - return list, e - } - case last == '[': - // now we have a nested list. Read the index and handle. - nextI, err := t.keyIndex() - if err != nil { - return list, errors.Wrap(err, "error parsing index") - } - var crtList []interface{} - if len(list) > i { - // If nested list already exists, take the value of list to next cycle. - existed := list[i] - if existed != nil { - crtList = list[i].([]interface{}) - } - } - // Now we need to get the value after the ]. - list2, err := t.listItem(crtList, nextI) - if err != nil { - return list, err - } - return setIndex(list, i, list2) - case last == '.': - // We have a nested object. Send to t.key - inner := map[string]interface{}{} - if len(list) > i { - var ok bool - inner, ok = list[i].(map[string]interface{}) - if !ok { - // We have indices out of order. Initialize empty value. - list[i] = map[string]interface{}{} - inner = list[i].(map[string]interface{}) - } - } - - // Recurse - e := t.key(inner) - if e != nil { - return list, e - } - return setIndex(list, i, inner) - default: - return nil, errors.Errorf("parse error: unexpected token %v", last) - } -} - -// check for an empty value -// read and consume optional spaces until comma or EOF (empty val) or any other char (not empty val) -// comma and spaces are consumed, while any other char is not cosumed -func (t *parser) emptyVal() (bool, error) { - for { - r, _, e := t.sc.ReadRune() - if e == io.EOF { - return true, nil - } - if e != nil { - return false, e - } - if r == ',' { - return true, nil - } - if !unicode.IsSpace(r) { - t.sc.UnreadRune() - return false, nil - } - } -} - -func (t *parser) val() ([]rune, error) { - stop := runeSet([]rune{','}) - v, _, err := runesUntil(t.sc, stop) - return v, err -} - -func (t *parser) valList() ([]interface{}, error) { - r, _, e := t.sc.ReadRune() - if e != nil { - return []interface{}{}, e - } - - if r != '{' { - t.sc.UnreadRune() - return []interface{}{}, ErrNotList - } - - list := []interface{}{} - stop := runeSet([]rune{',', '}'}) - for { - switch rs, last, err := runesUntil(t.sc, stop); { - case err != nil: - if err == io.EOF { - err = errors.New("list must terminate with '}'") - } - return list, err - case last == '}': - // If this is followed by ',', consume it. - if r, _, e := t.sc.ReadRune(); e == nil && r != ',' { - t.sc.UnreadRune() - } - v, e := t.reader(rs) - list = append(list, v) - return list, e - case last == ',': - v, e := t.reader(rs) - if e != nil { - return list, e - } - list = append(list, v) - } - } -} - -func runesUntil(in io.RuneReader, stop map[rune]bool) ([]rune, rune, error) { - v := []rune{} - for { - switch r, _, e := in.ReadRune(); { - case e != nil: - return v, r, e - case inMap(r, stop): - return v, r, nil - case r == '\\': - next, _, e := in.ReadRune() - if e != nil { - return v, next, e - } - v = append(v, next) - default: - v = append(v, r) - } - } -} - -func inMap(k rune, m map[rune]bool) bool { - _, ok := m[k] - return ok -} - -func typedVal(v []rune, st bool) interface{} { - val := string(v) - - if st { - return val - } - - if strings.EqualFold(val, "true") { - return true - } - - if strings.EqualFold(val, "false") { - return false - } - - if strings.EqualFold(val, "null") { - return nil - } - - if strings.EqualFold(val, "0") { - return int64(0) - } - - // If this value does not start with zero, try parsing it to an int - if len(val) != 0 && val[0] != '0' { - if iv, err := strconv.ParseInt(val, 10, 64); err == nil { - return iv - } - } - - return val -} diff --git a/pkg/strvals/parser_test.go b/pkg/strvals/parser_test.go deleted file mode 100644 index f7eba7830..000000000 --- a/pkg/strvals/parser_test.go +++ /dev/null @@ -1,756 +0,0 @@ -/* -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 strvals - -import ( - "testing" - - "sigs.k8s.io/yaml" -) - -func TestSetIndex(t *testing.T) { - tests := []struct { - name string - initial []interface{} - expect []interface{} - add int - val int - err bool - }{ - { - name: "short", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 1, 2}, - add: 2, - val: 2, - err: false, - }, - { - name: "equal", - initial: []interface{}{0, 1}, - expect: []interface{}{0, 2}, - add: 1, - val: 2, - err: false, - }, - { - name: "long", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 4, 4, 5}, - add: 3, - val: 4, - err: false, - }, - { - name: "negative", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, - add: -1, - val: 4, - err: true, - }, - { - name: "large", - initial: []interface{}{0, 1, 2, 3, 4, 5}, - expect: []interface{}{0, 1, 2, 3, 4, 5}, - add: MaxIndex + 1, - val: 4, - err: true, - }, - } - - for _, tt := range tests { - got, err := setIndex(tt.initial, tt.add, tt.val) - - if err != nil && tt.err == false { - t.Fatalf("%s: Expected no error but error returned", tt.name) - } else if err == nil && tt.err == true { - t.Fatalf("%s: Expected error but no error returned", tt.name) - } - - if len(got) != len(tt.expect) { - t.Fatalf("%s: Expected length %d, got %d", tt.name, len(tt.expect), len(got)) - } - - if !tt.err { - if gg := got[tt.add].(int); gg != tt.val { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.val, gg) - } - } - - for k, v := range got { - if v != tt.expect[k] { - t.Errorf("%s, Expected value %d, got %d", tt.name, tt.expect[k], v) - } - } - } -} - -func TestParseSet(t *testing.T) { - testsString := []struct { - str string - expect map[string]interface{} - err bool - }{ - { - str: "long_int_string=1234567890", - expect: map[string]interface{}{"long_int_string": "1234567890"}, - err: false, - }, - { - str: "boolean=true", - expect: map[string]interface{}{"boolean": "true"}, - err: false, - }, - { - str: "is_null=null", - expect: map[string]interface{}{"is_null": "null"}, - err: false, - }, - { - str: "zero=0", - expect: map[string]interface{}{"zero": "0"}, - err: false, - }, - } - tests := []struct { - str string - expect map[string]interface{} - err bool - }{ - { - "name1=null,f=false,t=true", - map[string]interface{}{"name1": nil, "f": false, "t": true}, - false, - }, - { - "name1=value1", - map[string]interface{}{"name1": "value1"}, - false, - }, - { - "name1=value1,name2=value2", - map[string]interface{}{"name1": "value1", "name2": "value2"}, - false, - }, - { - "name1=value1,name2=value2,", - map[string]interface{}{"name1": "value1", "name2": "value2"}, - false, - }, - { - str: "name1=value1,,,,name2=value2,", - err: true, - }, - { - str: "name1=,name2=value2", - expect: map[string]interface{}{"name1": "", "name2": "value2"}, - }, - { - str: "leading_zeros=00009", - expect: map[string]interface{}{"leading_zeros": "00009"}, - }, - { - str: "zero_int=0", - expect: map[string]interface{}{"zero_int": 0}, - }, - { - str: "long_int=1234567890", - expect: map[string]interface{}{"long_int": 1234567890}, - }, - { - str: "boolean=true", - expect: map[string]interface{}{"boolean": true}, - }, - { - str: "is_null=null", - expect: map[string]interface{}{"is_null": nil}, - err: false, - }, - { - str: "name1,name2=", - err: true, - }, - { - str: "name1,name2=value2", - err: true, - }, - { - str: "name1,name2=value2\\", - err: true, - }, - { - str: "name1,name2", - err: true, - }, - { - "name1=one\\,two,name2=three\\,four", - map[string]interface{}{"name1": "one,two", "name2": "three,four"}, - false, - }, - { - "name1=one\\=two,name2=three\\=four", - map[string]interface{}{"name1": "one=two", "name2": "three=four"}, - false, - }, - { - "name1=one two three,name2=three two one", - map[string]interface{}{"name1": "one two three", "name2": "three two one"}, - false, - }, - { - "outer.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"inner": "value"}}, - false, - }, - { - "outer.middle.inner=value", - map[string]interface{}{"outer": map[string]interface{}{"middle": map[string]interface{}{"inner": "value"}}}, - false, - }, - { - "outer.inner1=value,outer.inner2=value2", - map[string]interface{}{"outer": map[string]interface{}{"inner1": "value", "inner2": "value2"}}, - false, - }, - { - "outer.inner1=value,outer.middle.inner=value", - map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "value", - "middle": map[string]interface{}{ - "inner": "value", - }, - }, - }, - false, - }, - { - str: "name1.name2", - err: true, - }, - { - str: "name1.name2,name1.name3", - err: true, - }, - { - str: "name1.name2=", - expect: map[string]interface{}{"name1": map[string]interface{}{"name2": ""}}, - }, - { - str: "name1.=name2", - err: true, - }, - { - str: "name1.,name2", - err: true, - }, - { - "name1={value1,value2}", - map[string]interface{}{"name1": []string{"value1", "value2"}}, - false, - }, - { - "name1={value1,value2},name2={value1,value2}", - map[string]interface{}{ - "name1": []string{"value1", "value2"}, - "name2": []string{"value1", "value2"}, - }, - false, - }, - { - "name1={1021,902}", - map[string]interface{}{"name1": []int{1021, 902}}, - false, - }, - { - "name1.name2={value1,value2}", - map[string]interface{}{"name1": map[string]interface{}{"name2": []string{"value1", "value2"}}}, - false, - }, - { - str: "name1={1021,902", - err: true, - }, - // List support - { - str: "list[0]=foo", - expect: map[string]interface{}{"list": []string{"foo"}}, - }, - { - str: "list[0].foo=bar", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar"}, - }, - }, - }, - { - str: "list[0].foo=bar,list[0].hello=world", - expect: map[string]interface{}{ - "list": []interface{}{ - map[string]interface{}{"foo": "bar", "hello": "world"}, - }, - }, - }, - { - str: "list[0].foo=bar,list[-30].hello=world", - err: true, - }, - { - str: "list[0]=foo,list[1]=bar", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, - }, - { - str: "list[0]=foo,list[1]=bar,", - expect: map[string]interface{}{"list": []string{"foo", "bar"}}, - }, - { - str: "list[0]=foo,list[3]=bar", - expect: map[string]interface{}{"list": []interface{}{"foo", nil, nil, "bar"}}, - }, - { - str: "list[0]=foo,list[-20]=bar", - err: true, - }, - { - str: "illegal[0]name.foo=bar", - err: true, - }, - { - str: "noval[0]", - expect: map[string]interface{}{"noval": []interface{}{}}, - }, - { - str: "noval[0]=", - expect: map[string]interface{}{"noval": []interface{}{""}}, - }, - { - str: "nested[0][0]=1", - expect: map[string]interface{}{"nested": []interface{}{[]interface{}{1}}}, - }, - { - str: "nested[1][1]=1", - expect: map[string]interface{}{"nested": []interface{}{nil, []interface{}{nil, 1}}}, - }, - { - str: "name1.name2[0].foo=bar,name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, - }, - }, - }, - { - str: "name1.name2[1].foo=bar,name1.name2[0].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{{"foo": "bar"}, {"foo": "bar"}}, - }, - }, - }, - { - str: "name1.name2[1].foo=bar", - expect: map[string]interface{}{ - "name1": map[string]interface{}{ - "name2": []map[string]interface{}{nil, {"foo": "bar"}}, - }, - }, - }, - { - str: "]={}].", - err: true, - }, - } - - for _, tt := range tests { - got, err := Parse(tt.str) - if err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.str, err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) - } - } - for _, tt := range testsString { - got, err := ParseString(tt.str) - if err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.str, err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.str) - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.str, y1, y2) - } - } -} - -func TestParseInto(t *testing.T) { - tests := []struct { - input string - input2 string - got map[string]interface{} - expect map[string]interface{} - err bool - }{ - { - input: "outer.inner1=value1,outer.inner3=value3,outer.inner4=4", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "value1", - "inner2": "value2", - "inner3": "value3", - "inner4": 4, - }}, - err: false, - }, - { - input: "listOuter[0][0].type=listValue", - input2: "listOuter[0][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{{map[string]string{ - "type": "listValue", - "status": "alive", - }}}, - }, - err: false, - }, - { - input: "listOuter[0][0].type=listValue", - input2: "listOuter[1][0].status=alive", - got: map[string]interface{}{}, - expect: map[string]interface{}{ - "listOuter": [][]interface{}{ - { - map[string]string{"type": "listValue"}, - }, - { - map[string]string{"status": "alive"}, - }, - }, - }, - err: false, - }, - { - input: "listOuter[0][1][0].type=listValue", - input2: "listOuter[0][0][1].status=alive", - got: map[string]interface{}{ - "listOuter": []interface{}{ - []interface{}{ - []interface{}{ - map[string]string{"exited": "old"}, - }, - }, - }, - }, - expect: map[string]interface{}{ - "listOuter": [][][]interface{}{ - { - { - map[string]string{"exited": "old"}, - map[string]string{"status": "alive"}, - }, - { - map[string]string{"type": "listValue"}, - }, - }, - }, - }, - err: false, - }, - } - for _, tt := range tests { - if err := ParseInto(tt.input, tt.got); err != nil { - t.Fatal(err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input) - } - - if tt.input2 != "" { - if err := ParseInto(tt.input2, tt.got); err != nil { - t.Fatal(err) - } - if tt.err { - t.Errorf("%s: Expected error. Got nil", tt.input2) - } - } - - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(tt.got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) - } - } -} - -func TestParseIntoString(t *testing.T) { - got := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - } - input := "outer.inner1=1,outer.inner3=3" - expect := map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "1", - "inner2": "value2", - "inner3": "3", - }, - } - - if err := ParseIntoString(input, got); err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestParseJSON(t *testing.T) { - tests := []struct { - input string - got map[string]interface{} - expect map[string]interface{} - err bool - }{ - { // set json scalars values, and replace one existing key - input: "outer.inner1=\"1\",outer.inner3=3,outer.inner4=true,outer.inner5=\"true\"", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "overwrite", - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": "1", - "inner2": "value2", - "inner3": 3, - "inner4": true, - "inner5": "true", - }, - }, - err: false, - }, - { // set json objects and arrays, and replace one existing key - input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5=[{\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ - "x": "overwrite", - }, - "inner2": "value2", - "inner3": []interface{}{ - "overwrite", - }, - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{"a": "1", "b": 2, "c": []interface{}{1, 2, 3}}, - "inner2": "value2", - "inner3": []interface{}{"new value 1", "new value 2"}, - "inner4": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, 2, 3}}, - "inner5": []interface{}{map[string]interface{}{"A": "1", "B": 2, "C": []interface{}{1, 2, 3}}}, - }, - }, - err: false, - }, - { // null assigment, and no value assigned (equivalent to null) - input: "outer.inner1=,outer.inner3={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner3.cc[1]=null", - got: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": map[string]interface{}{ - "x": "overwrite", - }, - "inner2": "value2", - }, - }, - expect: map[string]interface{}{ - "outer": map[string]interface{}{ - "inner1": nil, - "inner2": "value2", - "inner3": map[string]interface{}{"aa": "1", "bb": 2, "cc": []interface{}{1, nil, 3}}, - }, - }, - err: false, - }, - { // syntax error - input: "outer.inner1={\"a\":\"1\",\"b\":2,\"c\":[1,2,3]},outer.inner3=[\"new value 1\",\"new value 2\"],outer.inner4={\"aa\":\"1\",\"bb\":2,\"cc\":[1,2,3]},outer.inner5={\"A\":\"1\",\"B\":2,\"C\":[1,2,3]}]", - got: nil, - expect: nil, - err: true, - }, - } - for _, tt := range tests { - if err := ParseJSON(tt.input, tt.got); err != nil { - if tt.err { - continue - } - t.Fatalf("%s: %s", tt.input, err) - } - if tt.err { - t.Fatalf("%s: Expected error. Got nil", tt.input) - } - y1, err := yaml.Marshal(tt.expect) - if err != nil { - t.Fatalf("Error serializing expected value: %s", err) - } - y2, err := yaml.Marshal(tt.got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", tt.input, y1, y2) - } - } -} - -func TestParseFile(t *testing.T) { - input := "name1=path1" - expect := map[string]interface{}{ - "name1": "value1", - } - rs2v := func(rs []rune) (interface{}, error) { - v := string(rs) - if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) - return "", nil - } - return "value1", nil - } - - got, err := ParseFile(input, rs2v) - if err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestParseIntoFile(t *testing.T) { - got := map[string]interface{}{} - input := "name1=path1" - expect := map[string]interface{}{ - "name1": "value1", - } - rs2v := func(rs []rune) (interface{}, error) { - v := string(rs) - if v != "path1" { - t.Errorf("%s: runesToVal: Expected value path1, got %s", input, v) - return "", nil - } - return "value1", nil - } - - if err := ParseIntoFile(input, got, rs2v); err != nil { - t.Fatal(err) - } - - y1, err := yaml.Marshal(expect) - if err != nil { - t.Fatal(err) - } - y2, err := yaml.Marshal(got) - if err != nil { - t.Fatalf("Error serializing parsed value: %s", err) - } - - if string(y1) != string(y2) { - t.Errorf("%s: Expected:\n%s\nGot:\n%s", input, y1, y2) - } -} - -func TestToYAML(t *testing.T) { - // The TestParse does the hard part. We just verify that YAML formatting is - // happening. - o, err := ToYAML("name=value") - if err != nil { - t.Fatal(err) - } - expect := "name: value" - if o != expect { - t.Errorf("Expected %q, got %q", expect, o) - } -} diff --git a/pkg/time/time.go b/pkg/time/time.go deleted file mode 100644 index 44f3fedfb..000000000 --- a/pkg/time/time.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -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 time contains a wrapper for time.Time in the standard library and -// associated methods. This package mainly exists to workaround an issue in Go -// where the serializer doesn't omit an empty value for time: -// https://github.com/golang/go/issues/11939. As such, this can be removed if a -// proposal is ever accepted for Go -package time - -import ( - "bytes" - "time" -) - -// emptyString contains an empty JSON string value to be used as output -var emptyString = `""` - -// Time is a convenience wrapper around stdlib time, but with different -// marshalling and unmarshaling for zero values -type Time struct { - time.Time -} - -// Now returns the current time. It is a convenience wrapper around time.Now() -func Now() Time { - return Time{time.Now()} -} - -func (t Time) MarshalJSON() ([]byte, error) { - if t.Time.IsZero() { - return []byte(emptyString), nil - } - - return t.Time.MarshalJSON() -} - -func (t *Time) UnmarshalJSON(b []byte) error { - if bytes.Equal(b, []byte("null")) { - return nil - } - // If it is empty, we don't have to set anything since time.Time is not a - // pointer and will be set to the zero value - if bytes.Equal([]byte(emptyString), b) { - return nil - } - - return t.Time.UnmarshalJSON(b) -} - -func Parse(layout, value string) (Time, error) { - t, err := time.Parse(layout, value) - return Time{Time: t}, err -} -func ParseInLocation(layout, value string, loc *time.Location) (Time, error) { - t, err := time.ParseInLocation(layout, value, loc) - return Time{Time: t}, err -} - -func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time { - return Time{Time: time.Date(year, month, day, hour, min, sec, nsec, loc)} -} - -func Unix(sec int64, nsec int64) Time { return Time{Time: time.Unix(sec, nsec)} } - -func (t Time) Add(d time.Duration) Time { return Time{Time: t.Time.Add(d)} } -func (t Time) AddDate(years int, months int, days int) Time { - return Time{Time: t.Time.AddDate(years, months, days)} -} -func (t Time) After(u Time) bool { return t.Time.After(u.Time) } -func (t Time) Before(u Time) bool { return t.Time.Before(u.Time) } -func (t Time) Equal(u Time) bool { return t.Time.Equal(u.Time) } -func (t Time) In(loc *time.Location) Time { return Time{Time: t.Time.In(loc)} } -func (t Time) Local() Time { return Time{Time: t.Time.Local()} } -func (t Time) Round(d time.Duration) Time { return Time{Time: t.Time.Round(d)} } -func (t Time) Sub(u Time) time.Duration { return t.Time.Sub(u.Time) } -func (t Time) Truncate(d time.Duration) Time { return Time{Time: t.Time.Truncate(d)} } -func (t Time) UTC() Time { return Time{Time: t.Time.UTC()} } diff --git a/pkg/time/time_test.go b/pkg/time/time_test.go deleted file mode 100644 index 20f0f8e29..000000000 --- a/pkg/time/time_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -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 time - -import ( - "encoding/json" - "testing" - "time" -) - -var ( - testingTime, _ = Parse(time.RFC3339, "1977-09-02T22:04:05Z") - testingTimeString = `"1977-09-02T22:04:05Z"` -) - -func TestNonZeroValueMarshal(t *testing.T) { - res, err := json.Marshal(testingTime) - if err != nil { - t.Fatal(err) - } - if testingTimeString != string(res) { - t.Errorf("expected a marshaled value of %s, got %s", testingTimeString, res) - } -} - -func TestZeroValueMarshal(t *testing.T) { - res, err := json.Marshal(Time{}) - if err != nil { - t.Fatal(err) - } - if string(res) != emptyString { - t.Errorf("expected zero value to marshal to empty string, got %s", res) - } -} - -func TestNonZeroValueUnmarshal(t *testing.T) { - var myTime Time - err := json.Unmarshal([]byte(testingTimeString), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.Equal(testingTime) { - t.Errorf("expected time to be equal to %v, got %v", testingTime, myTime) - } -} - -func TestEmptyStringUnmarshal(t *testing.T) { - var myTime Time - err := json.Unmarshal([]byte(emptyString), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.IsZero() { - t.Errorf("expected time to be equal to zero value, got %v", myTime) - } -} - -func TestZeroValueUnmarshal(t *testing.T) { - // This test ensures that we can unmarshal any time value that was output - // with the current go default value of "0001-01-01T00:00:00Z" - var myTime Time - err := json.Unmarshal([]byte(`"0001-01-01T00:00:00Z"`), &myTime) - if err != nil { - t.Fatal(err) - } - if !myTime.IsZero() { - t.Errorf("expected time to be equal to zero value, got %v", myTime) - } -} diff --git a/pkg/uploader/chart_uploader.go b/pkg/uploader/chart_uploader.go deleted file mode 100644 index d7e940406..000000000 --- a/pkg/uploader/chart_uploader.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -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 uploader - -import ( - "fmt" - "io" - "net/url" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/pusher" - "helm.sh/helm/v3/pkg/registry" -) - -// ChartUploader handles uploading a chart. -type ChartUploader struct { - // Out is the location to write warning and info messages. - Out io.Writer - // Pusher collection for the operation - Pushers pusher.Providers - // Options provide parameters to be passed along to the Pusher being initialized. - Options []pusher.Option - // RegistryClient is a client for interacting with registries. - RegistryClient *registry.Client -} - -// UploadTo uploads a chart. Depending on the settings, it may also upload a provenance file. -func (c *ChartUploader) UploadTo(ref, remote string) error { - u, err := url.Parse(remote) - if err != nil { - return errors.Errorf("invalid chart URL format: %s", remote) - } - - if u.Scheme == "" { - return fmt.Errorf("scheme prefix missing from remote (e.g. \"%s://\")", registry.OCIScheme) - } - - p, err := c.Pushers.ByScheme(u.Scheme) - if err != nil { - return err - } - - return p.Push(ref, u.String(), c.Options...) -} diff --git a/pkg/uploader/doc.go b/pkg/uploader/doc.go deleted file mode 100644 index 45eacbbf5..000000000 --- a/pkg/uploader/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -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 uploader provides a library for uploading charts. - -This package contains tools for uploading charts to registries. -*/ -package uploader