From 54a0d10994e807f0d1b31a608171c9a159c0346f Mon Sep 17 00:00:00 2001 From: Steven Zou Date: Mon, 2 Jul 2018 19:34:04 +0800 Subject: [PATCH] Define the related handlers to handler the requests of chart server/repo add chartserver directory to put chart server code add controller to coordinate the flow add base/repo/manipulation handlers to handle requests add operator to parse more details of chart version call dep ensure --- src/Gopkg.lock | 13 +- src/Gopkg.toml | 5 +- src/chartserver/base_handler.go | 16 + src/chartserver/chart_operator.go | 12 + src/chartserver/controller.go | 62 + src/chartserver/manipulation_handler.go | 37 + src/chartserver/repo_handler.go | 27 + .../kubernetes/helm/.circleci/bootstrap.sh | 19 + .../kubernetes/helm/.circleci/config.yml | 38 + .../kubernetes/helm/.circleci/deploy.sh | 64 + .../kubernetes/helm/.circleci/test.sh | 53 + .../kubernetes/helm/.github/issue_template.md | 9 + .../github.com/kubernetes/helm/.gitignore | 12 + .../kubernetes/helm/CONTRIBUTING.md | 236 + src/vendor/github.com/kubernetes/helm/LICENSE | 202 + .../github.com/kubernetes/helm/Makefile | 143 + src/vendor/github.com/kubernetes/helm/OWNERS | 30 + .../github.com/kubernetes/helm/README.md | 73 + .../kubernetes/helm/_proto/Makefile | 58 + .../helm/_proto/hapi/chart/chart.proto | 44 + .../helm/_proto/hapi/chart/config.proto | 31 + .../helm/_proto/hapi/chart/metadata.proto | 93 + .../helm/_proto/hapi/chart/template.proto | 31 + .../helm/_proto/hapi/release/hook.proto | 58 + .../helm/_proto/hapi/release/info.proto | 37 + .../helm/_proto/hapi/release/release.proto | 53 + .../helm/_proto/hapi/release/status.proto | 61 + .../helm/_proto/hapi/release/test_run.proto | 37 + .../helm/_proto/hapi/release/test_suite.proto | 34 + .../helm/_proto/hapi/rudder/rudder.proto | 120 + .../helm/_proto/hapi/services/tiller.proto | 337 + .../helm/_proto/hapi/version/version.proto | 26 + .../kubernetes/helm/cmd/helm/completion.go | 229 + .../kubernetes/helm/cmd/helm/create.go | 105 + .../kubernetes/helm/cmd/helm/create_test.go | 164 + .../kubernetes/helm/cmd/helm/delete.go | 101 + .../kubernetes/helm/cmd/helm/delete_test.go | 73 + .../kubernetes/helm/cmd/helm/dependency.go | 276 + .../helm/cmd/helm/dependency_build.go | 85 + .../helm/cmd/helm/dependency_build_test.go | 120 + .../helm/cmd/helm/dependency_test.go | 58 + .../helm/cmd/helm/dependency_update.go | 105 + .../helm/cmd/helm/dependency_update_test.go | 273 + .../kubernetes/helm/cmd/helm/docs.go | 80 + .../kubernetes/helm/cmd/helm/fetch.go | 186 + .../kubernetes/helm/cmd/helm/fetch_test.go | 179 + .../kubernetes/helm/cmd/helm/get.go | 89 + .../kubernetes/helm/cmd/helm/get_hooks.go | 75 + .../helm/cmd/helm/get_hooks_test.go | 47 + .../kubernetes/helm/cmd/helm/get_manifest.go | 75 + .../helm/cmd/helm/get_manifest_test.go | 47 + .../kubernetes/helm/cmd/helm/get_test.go | 48 + .../kubernetes/helm/cmd/helm/get_values.go | 89 + .../helm/cmd/helm/get_values_test.go | 47 + .../kubernetes/helm/cmd/helm/helm.go | 310 + .../kubernetes/helm/cmd/helm/helm_test.go | 238 + .../kubernetes/helm/cmd/helm/history.go | 172 + .../kubernetes/helm/cmd/helm/history_test.go | 85 + .../kubernetes/helm/cmd/helm/home.go | 51 + .../kubernetes/helm/cmd/helm/init.go | 493 ++ .../kubernetes/helm/cmd/helm/init_test.go | 357 + .../kubernetes/helm/cmd/helm/init_unix.go | 29 + .../kubernetes/helm/cmd/helm/init_windows.go | 29 + .../kubernetes/helm/cmd/helm/inspect.go | 264 + .../kubernetes/helm/cmd/helm/inspect_test.go | 80 + .../kubernetes/helm/cmd/helm/install.go | 530 ++ .../kubernetes/helm/cmd/helm/install_test.go | 283 + .../helm/cmd/helm/installer/install.go | 372 + .../helm/cmd/helm/installer/install_test.go | 740 ++ .../helm/cmd/helm/installer/options.go | 164 + .../helm/cmd/helm/installer/uninstall.go | 71 + .../helm/cmd/helm/installer/uninstall_test.go | 88 + .../kubernetes/helm/cmd/helm/lint.go | 208 + .../kubernetes/helm/cmd/helm/lint_test.go | 54 + .../kubernetes/helm/cmd/helm/list.go | 254 + .../kubernetes/helm/cmd/helm/list_test.go | 128 + .../kubernetes/helm/cmd/helm/load_plugins.go | 160 + .../kubernetes/helm/cmd/helm/package.go | 237 + .../kubernetes/helm/cmd/helm/package_test.go | 253 + .../kubernetes/helm/cmd/helm/plugin.go | 72 + .../helm/cmd/helm/plugin_install.go | 92 + .../kubernetes/helm/cmd/helm/plugin_list.go | 60 + .../kubernetes/helm/cmd/helm/plugin_remove.go | 99 + .../kubernetes/helm/cmd/helm/plugin_test.go | 187 + .../kubernetes/helm/cmd/helm/plugin_update.go | 113 + .../kubernetes/helm/cmd/helm/printer.go | 82 + .../helm/cmd/helm/release_testing.go | 110 + .../helm/cmd/helm/release_testing_test.go | 106 + .../kubernetes/helm/cmd/helm/repo.go | 47 + .../kubernetes/helm/cmd/helm/repo_add.go | 117 + .../kubernetes/helm/cmd/helm/repo_add_test.go | 103 + .../kubernetes/helm/cmd/helm/repo_index.go | 105 + .../helm/cmd/helm/repo_index_test.go | 171 + .../kubernetes/helm/cmd/helm/repo_list.go | 66 + .../kubernetes/helm/cmd/helm/repo_remove.go | 92 + .../helm/cmd/helm/repo_remove_test.go | 81 + .../kubernetes/helm/cmd/helm/repo_update.go | 109 + .../helm/cmd/helm/repo_update_test.go | 106 + .../kubernetes/helm/cmd/helm/reset.go | 131 + .../kubernetes/helm/cmd/helm/reset_test.go | 170 + .../kubernetes/helm/cmd/helm/rollback.go | 107 + .../kubernetes/helm/cmd/helm/rollback_test.go | 61 + .../kubernetes/helm/cmd/helm/search.go | 162 + .../kubernetes/helm/cmd/helm/search/search.go | 236 + .../helm/cmd/helm/search/search_test.go | 301 + .../kubernetes/helm/cmd/helm/search_test.go | 97 + .../kubernetes/helm/cmd/helm/serve.go | 102 + .../kubernetes/helm/cmd/helm/status.go | 157 + .../kubernetes/helm/cmd/helm/status_test.go | 151 + .../kubernetes/helm/cmd/helm/template.go | 325 + .../kubernetes/helm/cmd/helm/template_test.go | 167 + .../helm/cmd/helm/testdata/helm-test-key.pub | Bin 0 -> 1243 bytes .../cmd/helm/testdata/helm-test-key.secret | Bin 0 -> 2545 bytes .../testdata/helmhome/plugins/args/args.sh | 2 + .../helmhome/plugins/args/plugin.yaml | 4 + .../helmhome/plugins/echo/plugin.yaml | 4 + .../testdata/helmhome/plugins/env/plugin.yaml | 4 + .../helmhome/plugins/fullenv/fullenv.sh | 10 + .../helmhome/plugins/fullenv/plugin.yaml | 4 + .../repository/cache/testing-index.yaml | 48 + .../helmhome/repository/local/index.yaml | 0 .../helmhome/repository/repositories.yaml | 6 + .../helm/cmd/helm/testdata/repositories.yaml | 6 + .../helm/testdata/testcache/foobar-index.yaml | 24 + .../helm/testdata/testcache/local-index.yaml | 27 + .../testdata/testcharts/alpine/Chart.yaml | 6 + .../helm/testdata/testcharts/alpine/README.md | 13 + .../testcharts/alpine/extra_values.yaml | 2 + .../testcharts/alpine/more_values.yaml | 2 + .../alpine/templates/alpine-pod.yaml | 27 + .../testdata/testcharts/alpine/values.yaml | 2 + .../chart-bad-requirements/.helmignore | 21 + .../chart-bad-requirements/Chart.yaml | 3 + .../charts/reqsubchart/.helmignore | 21 + .../charts/reqsubchart/Chart.yaml | 3 + .../charts/reqsubchart/values.yaml | 4 + .../chart-bad-requirements/requirements.yaml | 4 + .../chart-bad-requirements/values.yaml | 4 + .../testcharts/chart-missing-deps/.helmignore | 21 + .../testcharts/chart-missing-deps/Chart.yaml | 3 + .../charts/reqsubchart/.helmignore | 21 + .../charts/reqsubchart/Chart.yaml | 3 + .../charts/reqsubchart/values.yaml | 4 + .../chart-missing-deps/requirements.yaml | 7 + .../testcharts/chart-missing-deps/values.yaml | 4 + .../testcharts/compressedchart-0.1.0.tgz | Bin 0 -> 542 bytes .../testcharts/compressedchart-0.2.0.tgz | Bin 0 -> 540 bytes .../testcharts/compressedchart-0.3.0.tgz | Bin 0 -> 538 bytes .../compressedchart-with-hyphens-0.1.0.tgz | Bin 0 -> 548 bytes .../testcharts/decompressedchart/.helmignore | 5 + .../testcharts/decompressedchart/Chart.yaml | 3 + .../testcharts/decompressedchart/values.yaml | 4 + .../testdata/testcharts/novals/Chart.yaml | 6 + .../helm/testdata/testcharts/novals/README.md | 13 + .../novals/templates/alpine-pod.yaml | 26 + .../testdata/testcharts/reqtest-0.1.0.tgz | Bin 0 -> 911 bytes .../testdata/testcharts/reqtest/.helmignore | 21 + .../testdata/testcharts/reqtest/Chart.yaml | 3 + .../reqtest/charts/reqsubchart/.helmignore | 21 + .../reqtest/charts/reqsubchart/Chart.yaml | 3 + .../reqtest/charts/reqsubchart/values.yaml | 4 + .../reqtest/charts/reqsubchart2/.helmignore | 21 + .../reqtest/charts/reqsubchart2/Chart.yaml | 3 + .../reqtest/charts/reqsubchart2/values.yaml | 4 + .../reqtest/charts/reqsubchart3-0.2.0.tgz | Bin 0 -> 593 bytes .../testcharts/reqtest/requirements.lock | 3 + .../testcharts/reqtest/requirements.yaml | 10 + .../testdata/testcharts/reqtest/values.yaml | 4 + .../testdata/testcharts/signtest-0.1.0.tgz | Bin 0 -> 471 bytes .../testcharts/signtest-0.1.0.tgz.prov | 20 + .../testdata/testcharts/signtest/.helmignore | 5 + .../testdata/testcharts/signtest/Chart.yaml | 3 + .../testcharts/signtest/alpine/Chart.yaml | 6 + .../testcharts/signtest/alpine/README.md | 9 + .../signtest/alpine/templates/alpine-pod.yaml | 16 + .../testcharts/signtest/alpine/values.yaml | 2 + .../testcharts/signtest/templates/pod.yaml | 10 + .../testdata/testcharts/signtest/values.yaml | 0 .../cmd/helm/testdata/testserver/index.yaml | 1 + .../testserver/repository/repositories.yaml | 6 + .../kubernetes/helm/cmd/helm/upgrade.go | 246 + .../kubernetes/helm/cmd/helm/upgrade_test.go | 170 + .../kubernetes/helm/cmd/helm/verify.go | 70 + .../kubernetes/helm/cmd/helm/verify_test.go | 95 + .../kubernetes/helm/cmd/helm/version.go | 151 + .../kubernetes/helm/cmd/helm/version_test.go | 65 + .../kubernetes/helm/cmd/rudder/rudder.go | 158 + .../kubernetes/helm/cmd/tiller/probes.go | 43 + .../kubernetes/helm/cmd/tiller/probes_test.go | 58 + .../kubernetes/helm/cmd/tiller/tiller.go | 288 + .../kubernetes/helm/cmd/tiller/tiller_test.go | 47 + .../kubernetes/helm/cmd/tiller/trace.go | 58 + .../kubernetes/helm/code-of-conduct.md | 3 + .../kubernetes/helm/docs/architecture.md | 64 + .../helm/docs/chart_best_practices/README.md | 21 + .../docs/chart_best_practices/conventions.md | 59 + .../helm/docs/chart_best_practices/labels.md | 32 + .../helm/docs/chart_best_practices/pods.md | 69 + .../helm/docs/chart_best_practices/rbac.md | 63 + .../docs/chart_best_practices/requirements.md | 47 + .../docs/chart_best_practices/templates.md | 198 + .../third_party_resources.md | 38 + .../helm/docs/chart_best_practices/values.md | 155 + .../kubernetes/helm/docs/chart_repository.md | 294 + .../helm/docs/chart_repository_faq.md | 17 + .../docs/chart_repository_sync_example.md | 74 + .../chart_template_guide/accessing_files.md | 209 + .../chart_template_guide/builtin_objects.md | 35 + .../control_structures.md | 354 + .../docs/chart_template_guide/data_types.md | 14 + .../docs/chart_template_guide/debugging.md | 30 + .../functions_and_pipelines.md | 155 + .../chart_template_guide/getting_started.md | 213 + .../helm/docs/chart_template_guide/index.md | 16 + .../chart_template_guide/named_templates.md | 264 + .../docs/chart_template_guide/notes_files.md | 45 + .../subcharts_and_globals.md | 207 + .../docs/chart_template_guide/values_files.md | 132 + .../docs/chart_template_guide/variables.md | 129 + .../docs/chart_template_guide/wrapping_up.md | 20 + .../chart_template_guide/yaml_techniques.md | 349 + .../kubernetes/helm/docs/chart_tests.md | 83 + .../github.com/kubernetes/helm/docs/charts.md | 858 ++ .../kubernetes/helm/docs/charts_hooks.md | 198 + .../helm/docs/charts_tips_and_tricks.md | 250 + .../kubernetes/helm/docs/developers.md | 238 + .../kubernetes/helm/docs/examples/README.md | 19 + .../helm/docs/examples/alpine/Chart.yaml | 7 + .../helm/docs/examples/alpine/README.md | 11 + .../examples/alpine/templates/_helpers.tpl | 16 + .../examples/alpine/templates/alpine-pod.yaml | 23 + .../helm/docs/examples/alpine/values.yaml | 6 + .../helm/docs/examples/nginx/.helmignore | 5 + .../helm/docs/examples/nginx/Chart.yaml | 15 + .../helm/docs/examples/nginx/README.md | 33 + .../examples/nginx/templates/_helpers.tpl | 16 + .../examples/nginx/templates/configmap.yaml | 14 + .../examples/nginx/templates/deployment.yaml | 57 + .../nginx/templates/post-install-job.yaml | 37 + .../nginx/templates/pre-install-secret.yaml | 19 + .../nginx/templates/service-test.yaml | 18 + .../examples/nginx/templates/service.yaml | 39 + .../helm/docs/examples/nginx/values.yaml | 34 + .../kubernetes/helm/docs/glossary.md | 170 + .../kubernetes/helm/docs/helm/helm.md | 71 + .../helm/docs/helm/helm_completion.md | 38 + .../kubernetes/helm/docs/helm/helm_create.md | 57 + .../kubernetes/helm/docs/helm/helm_delete.md | 48 + .../helm/docs/helm/helm_dependency.md | 74 + .../helm/docs/helm/helm_dependency_build.md | 44 + .../helm/docs/helm/helm_dependency_list.md | 36 + .../helm/docs/helm/helm_dependency_update.md | 49 + .../kubernetes/helm/docs/helm/helm_fetch.md | 58 + .../kubernetes/helm/docs/helm/helm_get.md | 53 + .../helm/docs/helm/helm_get_hooks.md | 43 + .../helm/docs/helm/helm_get_manifest.md | 45 + .../helm/docs/helm/helm_get_values.md | 42 + .../kubernetes/helm/docs/helm/helm_history.md | 55 + .../kubernetes/helm/docs/helm/helm_home.md | 31 + .../kubernetes/helm/docs/helm/helm_init.md | 74 + .../kubernetes/helm/docs/helm/helm_inspect.md | 50 + .../helm/docs/helm/helm_inspect_chart.md | 45 + .../helm/docs/helm/helm_inspect_readme.md | 43 + .../helm/docs/helm/helm_inspect_values.md | 45 + .../kubernetes/helm/docs/helm/helm_install.md | 120 + .../kubernetes/helm/docs/helm/helm_lint.md | 45 + .../kubernetes/helm/docs/helm/helm_list.md | 76 + .../kubernetes/helm/docs/helm/helm_package.md | 50 + .../kubernetes/helm/docs/helm/helm_plugin.md | 30 + .../helm/docs/helm/helm_plugin_install.md | 39 + .../helm/docs/helm/helm_plugin_list.md | 28 + .../helm/docs/helm/helm_plugin_remove.md | 28 + .../helm/docs/helm/helm_plugin_update.md | 28 + .../kubernetes/helm/docs/helm/helm_repo.md | 35 + .../helm/docs/helm/helm_repo_add.md | 39 + .../helm/docs/helm/helm_repo_index.md | 44 + .../helm/docs/helm/helm_repo_list.md | 28 + .../helm/docs/helm/helm_repo_remove.md | 28 + .../helm/docs/helm/helm_repo_update.md | 34 + .../kubernetes/helm/docs/helm/helm_reset.md | 44 + .../helm/docs/helm/helm_rollback.md | 50 + .../kubernetes/helm/docs/helm/helm_search.md | 41 + .../kubernetes/helm/docs/helm/helm_serve.md | 49 + .../kubernetes/helm/docs/helm/helm_status.md | 49 + .../helm/docs/helm/helm_template.md | 54 + .../kubernetes/helm/docs/helm/helm_test.md | 45 + .../kubernetes/helm/docs/helm/helm_upgrade.md | 84 + .../kubernetes/helm/docs/helm/helm_verify.md | 43 + .../kubernetes/helm/docs/helm/helm_version.md | 58 + .../kubernetes/helm/docs/history.md | 29 + .../helm/docs/images/create-a-bucket.png | Bin 0 -> 51471 bytes .../docs/images/create-a-gh-page-button.png | Bin 0 -> 30225 bytes .../helm/docs/images/edit-permissions.png | Bin 0 -> 58335 bytes .../helm/docs/images/make-bucket-public.png | Bin 0 -> 15768 bytes .../kubernetes/helm/docs/images/nothing.png | Bin 0 -> 275670 bytes .../helm/docs/images/set-a-gh-page.png | Bin 0 -> 130550 bytes .../github.com/kubernetes/helm/docs/index.md | 37 + .../kubernetes/helm/docs/install.md | 347 + .../kubernetes/helm/docs/install_faq.md | 232 + .../helm/docs/kubernetes_distros.md | 51 + .../helm/docs/logos/helm_logo_transparent.png | Bin 0 -> 36682 bytes .../kubernetes/helm/docs/man/man1/helm.1 | 85 + .../helm/docs/man/man1/helm_completion.1 | 74 + .../helm/docs/man/man1/helm_create.1 | 86 + .../helm/docs/man/man1/helm_delete.1 | 93 + .../helm/docs/man/man1/helm_dependency.1 | 117 + .../docs/man/man1/helm_dependency_build.1 | 69 + .../helm/docs/man/man1/helm_dependency_list.1 | 58 + .../docs/man/man1/helm_dependency_update.1 | 78 + .../helm/docs/man/man1/helm_fetch.1 | 114 + .../kubernetes/helm/docs/man/man1/helm_get.1 | 89 + .../helm/docs/man/man1/helm_get_hooks.1 | 59 + .../helm/docs/man/man1/helm_get_manifest.1 | 61 + .../helm/docs/man/man1/helm_get_values.1 | 60 + .../helm/docs/man/man1/helm_history.1 | 97 + .../kubernetes/helm/docs/man/man1/helm_home.1 | 51 + .../kubernetes/helm/docs/man/man1/helm_init.1 | 135 + .../helm/docs/man/man1/helm_inspect.1 | 84 + .../helm/docs/man/man1/helm_inspect_chart.1 | 81 + .../helm/docs/man/man1/helm_inspect_values.1 | 81 + .../helm/docs/man/man1/helm_install.1 | 243 + .../kubernetes/helm/docs/man/man1/helm_lint.1 | 62 + .../kubernetes/helm/docs/man/man1/helm_list.1 | 151 + .../helm/docs/man/man1/helm_package.1 | 85 + .../helm/docs/man/man1/helm_plugin.1 | 50 + .../helm/docs/man/man1/helm_plugin_install.1 | 56 + .../helm/docs/man/man1/helm_plugin_list.1 | 50 + .../helm/docs/man/man1/helm_plugin_remove.1 | 50 + .../helm/docs/man/man1/helm_plugin_update.1 | 50 + .../kubernetes/helm/docs/man/man1/helm_repo.1 | 55 + .../helm/docs/man/man1/helm_repo_add.1 | 68 + .../helm/docs/man/man1/helm_repo_index.1 | 69 + .../helm/docs/man/man1/helm_repo_list.1 | 50 + .../helm/docs/man/man1/helm_repo_remove.1 | 50 + .../helm/docs/man/man1/helm_repo_update.1 | 55 + .../helm/docs/man/man1/helm_reset.1 | 82 + .../helm/docs/man/man1/helm_rollback.1 | 101 + .../helm/docs/man/man1/helm_search.1 | 68 + .../helm/docs/man/man1/helm_serve.1 | 79 + .../helm/docs/man/man1/helm_status.1 | 83 + .../kubernetes/helm/docs/man/man1/helm_test.1 | 84 + .../helm/docs/man/man1/helm_upgrade.1 | 190 + .../helm/docs/man/man1/helm_verify.1 | 65 + .../helm/docs/man/man1/helm_version.1 | 103 + .../kubernetes/helm/docs/plugins.md | 201 + .../kubernetes/helm/docs/provenance.md | 277 + .../kubernetes/helm/docs/quickstart.md | 137 + .../github.com/kubernetes/helm/docs/rbac.md | 281 + .../kubernetes/helm/docs/related.md | 87 + .../kubernetes/helm/docs/release_checklist.md | 267 + .../helm/docs/securing_installation.md | 111 + .../kubernetes/helm/docs/tiller_ssl.md | 291 + .../kubernetes/helm/docs/using_helm.md | 515 ++ .../github.com/kubernetes/helm/glide.lock | 853 ++ .../github.com/kubernetes/helm/glide.yaml | 65 + .../helm/pkg/chartutil/capabilities.go | 71 + .../helm/pkg/chartutil/capabilities_test.go | 54 + .../helm/pkg/chartutil/chartfile.go | 98 + .../helm/pkg/chartutil/chartfile_test.go | 122 + .../kubernetes/helm/pkg/chartutil/create.go | 410 + .../helm/pkg/chartutil/create_test.go | 134 + .../kubernetes/helm/pkg/chartutil/doc.go | 44 + .../kubernetes/helm/pkg/chartutil/expand.go | 84 + .../kubernetes/helm/pkg/chartutil/files.go | 236 + .../helm/pkg/chartutil/files_test.go | 217 + .../kubernetes/helm/pkg/chartutil/load.go | 288 + .../helm/pkg/chartutil/load_test.go | 253 + .../helm/pkg/chartutil/requirements.go | 456 + .../helm/pkg/chartutil/requirements_test.go | 499 ++ .../kubernetes/helm/pkg/chartutil/save.go | 219 + .../helm/pkg/chartutil/save_test.go | 114 + .../chartutil/testdata/albatross/Chart.yaml | 4 + .../chartutil/testdata/albatross/values.yaml | 4 + .../pkg/chartutil/testdata/chartfiletest.yaml | 20 + .../pkg/chartutil/testdata/coleridge.yaml | 12 + .../dependent-chart-alias/.helmignore | 1 + .../testdata/dependent-chart-alias/Chart.yaml | 17 + .../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 | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../dependent-chart-alias/docs/README.md | 1 + .../testdata/dependent-chart-alias/icon.svg | 8 + .../dependent-chart-alias/ignore/me.txt | 0 .../dependent-chart-alias/requirements.lock | 8 + .../dependent-chart-alias/requirements.yaml | 12 + .../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 | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../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 | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../templates/template.tpl | 1 + .../values.yaml | 6 + .../.helmignore | 1 + .../Chart.yaml | 17 + .../INSTALL.txt | 1 + .../LICENSE | 1 + .../README.md | 11 + .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../requirements.yaml | 7 + .../templates/template.tpl | 1 + .../values.yaml | 6 + .../.helmignore | 1 + .../Chart.yaml | 17 + .../INSTALL.txt | 1 + .../LICENSE | 1 + .../README.md | 11 + .../charts/_ignore_me | 1 + .../charts/alpine/Chart.yaml | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../docs/README.md | 1 + .../icon.svg | 8 + .../ignore/me.txt | 0 .../requirements.yaml | 4 + .../templates/template.tpl | 1 + .../values.yaml | 6 + .../pkg/chartutil/testdata/frobnitz-1.2.3.tgz | Bin 0 -> 2070 bytes .../chartutil/testdata/frobnitz/.helmignore | 1 + .../chartutil/testdata/frobnitz/Chart.yaml | 20 + .../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 | 4 + .../testdata/frobnitz/charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../frobnitz/charts/alpine/values.yaml | 2 + .../frobnitz/charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../testdata/frobnitz/docs/README.md | 1 + .../pkg/chartutil/testdata/frobnitz/icon.svg | 8 + .../chartutil/testdata/frobnitz/ignore/me.txt | 0 .../testdata/frobnitz/requirements.lock | 8 + .../testdata/frobnitz/requirements.yaml | 7 + .../testdata/frobnitz/templates/template.tpl | 1 + .../chartutil/testdata/frobnitz/values.yaml | 6 + .../testdata/frobnitz_backslash-1.2.3.tgz | Bin 0 -> 2079 bytes .../testdata/frobnitz_backslash/.helmignore | 1 + .../testdata/frobnitz_backslash/Chart.yaml | 20 + .../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 | 4 + .../charts/alpine/README.md | 9 + .../charts/alpine/charts/mast1/Chart.yaml | 4 + .../charts/alpine/charts/mast1/values.yaml | 4 + .../charts/alpine/charts/mast2-0.1.0.tgz | Bin 0 -> 325 bytes .../charts/alpine/templates/alpine-pod.yaml | 16 + .../charts/alpine/values.yaml | 2 + .../charts/mariner-4.3.2.tgz | Bin 0 -> 1034 bytes .../frobnitz_backslash/docs/README.md | 1 + .../testdata/frobnitz_backslash/icon.svg | 8 + .../testdata/frobnitz_backslash/ignore/me.txt | 0 .../frobnitz_backslash/requirements.lock | 8 + .../frobnitz_backslash/requirements.yaml | 7 + .../frobnitz_backslash/templates/template.tpl | 1 + .../testdata/frobnitz_backslash/values.yaml | 6 + .../helm/pkg/chartutil/testdata/genfrob.sh | 12 + .../pkg/chartutil/testdata/joonix/Chart.yaml | 4 + .../chartutil/testdata/joonix/charts/frobnitz | 1 + .../pkg/chartutil/testdata/mariner/Chart.yaml | 4 + .../mariner/charts/albatross-0.1.0.tgz | Bin 0 -> 347 bytes .../testdata/mariner/requirements.yaml | 4 + .../mariner/templates/placeholder.tpl | 1 + .../chartutil/testdata/mariner/values.yaml | 7 + .../pkg/chartutil/testdata/moby/Chart.yaml | 3 + .../testdata/moby/charts/pequod/Chart.yaml | 3 + .../moby/charts/pequod/charts/ahab/Chart.yaml | 3 + .../charts/pequod/charts/ahab/values.yaml | 2 + .../testdata/moby/charts/pequod/values.yaml | 2 + .../testdata/moby/charts/spouter/Chart.yaml | 3 + .../testdata/moby/charts/spouter/values.yaml | 1 + .../pkg/chartutil/testdata/moby/values.yaml | 9 + .../pkg/chartutil/testdata/subpop/Chart.yaml | 4 + .../pkg/chartutil/testdata/subpop/README.md | 18 + .../subpop/charts/subchart1/Chart.yaml | 4 + .../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/requirements.yaml | 32 + .../charts/subchart1/templates/NOTES.txt | 1 + .../charts/subchart1/templates/service.yaml | 20 + .../subpop/charts/subchart1/values.yaml | 55 + .../subpop/charts/subchart2/Chart.yaml | 4 + .../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 + .../subpop/charts/subchart2/requirements.yaml | 15 + .../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 + .../testdata/subpop/requirements.yaml | 31 + .../pkg/chartutil/testdata/subpop/values.yaml | 41 + .../helm/pkg/chartutil/transform.go | 25 + .../kubernetes/helm/pkg/chartutil/values.go | 435 + .../helm/pkg/chartutil/values_test.go | 459 + .../helm/pkg/downloader/chart_downloader.go | 357 + .../pkg/downloader/chart_downloader_test.go | 315 + .../kubernetes/helm/pkg/downloader/doc.go | 23 + .../kubernetes/helm/pkg/downloader/manager.go | 656 ++ .../helm/pkg/downloader/manager_test.go | 170 + .../pkg/downloader/testdata/helm-test-key.pub | Bin 0 -> 1243 bytes .../downloader/testdata/helm-test-key.secret | Bin 0 -> 2545 bytes .../cache/kubernetes-charts-index.yaml | 49 + .../repository/cache/local-index.yaml | 1 + .../repository/cache/malformed-index.yaml | 16 + .../cache/testing-basicauth-index.yaml | 15 + .../repository/cache/testing-https-index.yaml | 15 + .../repository/cache/testing-index.yaml | 43 + .../cache/testing-querystring-index.yaml | 16 + .../cache/testing-relative-index.yaml | 28 + ...testing-relative-trailing-slash-index.yaml | 28 + .../helmhome/repository/local/index.yaml | 0 .../helmhome/repository/repositories.yaml | 18 + .../downloader/testdata/signtest-0.1.0.tgz | Bin 0 -> 471 bytes .../testdata/signtest-0.1.0.tgz.prov | 20 + .../downloader/testdata/signtest/.helmignore | 5 + .../downloader/testdata/signtest/Chart.yaml | 3 + .../testdata/signtest/alpine/Chart.yaml | 6 + .../testdata/signtest/alpine/README.md | 9 + .../signtest/alpine/templates/alpine-pod.yaml | 16 + .../testdata/signtest/alpine/values.yaml | 2 + .../testdata/signtest/templates/pod.yaml | 10 + .../downloader/testdata/signtest/values.yaml | 0 .../kubernetes/helm/pkg/engine/doc.go | 23 + .../kubernetes/helm/pkg/engine/engine.go | 356 + .../kubernetes/helm/pkg/engine/engine_test.go | 566 ++ .../kubernetes/helm/pkg/getter/doc.go | 21 + .../kubernetes/helm/pkg/getter/getter.go | 98 + .../kubernetes/helm/pkg/getter/getter_test.go | 81 + .../kubernetes/helm/pkg/getter/httpgetter.go | 108 + .../helm/pkg/getter/httpgetter_test.go | 48 + .../helm/pkg/getter/plugingetter.go | 96 + .../helm/pkg/getter/plugingetter_test.go | 91 + .../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 + .../repository/cache/local-index.yaml | 1 + .../repository/cache/stable-index.yaml | 7852 +++++++++++++++++ .../testdata/repository/local/index.yaml | 3 + .../testdata/repository/repositories.yaml | 15 + .../kubernetes/helm/pkg/helm/client.go | 522 ++ .../kubernetes/helm/pkg/helm/client_test.go | 34 + .../helm/pkg/helm/environment/environment.go | 103 + .../pkg/helm/environment/environment_test.go | 134 + .../kubernetes/helm/pkg/helm/fake.go | 277 + .../kubernetes/helm/pkg/helm/fake_test.go | 283 + .../kubernetes/helm/pkg/helm/helm_test.go | 363 + .../helm/pkg/helm/helmpath/helmhome.go | 103 + .../pkg/helm/helmpath/helmhome_unix_test.go | 50 + .../helm/helmpath/helmhome_windows_test.go | 41 + .../kubernetes/helm/pkg/helm/interface.go | 39 + .../kubernetes/helm/pkg/helm/option.go | 444 + .../helm/pkg/helm/portforwarder/pod.go | 60 + .../pkg/helm/portforwarder/portforwarder.go | 72 + .../helm/portforwarder/portforwarder_test.go | 87 + .../kubernetes/helm/pkg/hooks/hooks.go | 67 + .../kubernetes/helm/pkg/ignore/doc.go | 67 + .../kubernetes/helm/pkg/ignore/rules.go | 221 + .../kubernetes/helm/pkg/ignore/rules_test.go | 155 + .../helm/pkg/ignore/testdata/.helmignore | 3 + .../helm/pkg/ignore/testdata/.joonix | 0 .../kubernetes/helm/pkg/ignore/testdata/a.txt | 0 .../helm/pkg/ignore/testdata/cargo/a.txt | 0 .../helm/pkg/ignore/testdata/cargo/b.txt | 0 .../helm/pkg/ignore/testdata/cargo/c.txt | 0 .../helm/pkg/ignore/testdata/helm.txt | 0 .../helm/pkg/ignore/testdata/mast/a.txt | 0 .../helm/pkg/ignore/testdata/mast/b.txt | 0 .../helm/pkg/ignore/testdata/mast/c.txt | 0 .../helm/pkg/ignore/testdata/rudder.txt | 0 .../pkg/ignore/testdata/templates/.dotfile | 0 .../helm/pkg/ignore/testdata/tiller.txt | 0 .../kubernetes/helm/pkg/kube/client.go | 744 ++ .../kubernetes/helm/pkg/kube/client_test.go | 546 ++ .../kubernetes/helm/pkg/kube/config.go | 32 + .../kubernetes/helm/pkg/kube/log.go | 30 + .../kubernetes/helm/pkg/kube/namespace.go | 46 + .../helm/pkg/kube/namespace_test.go | 37 + .../kubernetes/helm/pkg/kube/result.go | 87 + .../kubernetes/helm/pkg/kube/result_test.go | 58 + .../kubernetes/helm/pkg/kube/tunnel.go | 122 + .../kubernetes/helm/pkg/kube/tunnel_test.go | 31 + .../kubernetes/helm/pkg/kube/wait.go | 266 + .../kubernetes/helm/pkg/lint/lint.go | 36 + .../kubernetes/helm/pkg/lint/lint_test.go | 98 + .../helm/pkg/lint/rules/chartfile.go | 172 + .../helm/pkg/lint/rules/chartfile_test.go | 249 + .../helm/pkg/lint/rules/template.go | 165 + .../helm/pkg/lint/rules/template_test.go | 83 + .../lint/rules/testdata/albatross/Chart.yaml | 4 + .../testdata/albatross/templates/_helpers.tpl | 16 + .../testdata/albatross/templates/fail.yaml | 1 + .../testdata/albatross/templates/svc.yaml | 20 + .../lint/rules/testdata/albatross/values.yaml | 1 + .../rules/testdata/badchartfile/Chart.yaml | 3 + .../rules/testdata/badchartfile/values.yaml | 1 + .../rules/testdata/badvaluesfile/Chart.yaml | 5 + .../templates/badvaluesfile.yaml | 2 + .../rules/testdata/badvaluesfile/values.yaml | 2 + .../lint/rules/testdata/goodone/Chart.yaml | 4 + .../testdata/goodone/templates/goodone.yaml | 2 + .../lint/rules/testdata/goodone/values.yaml | 1 + .../kubernetes/helm/pkg/lint/rules/values.go | 55 + .../kubernetes/helm/pkg/lint/support/doc.go | 22 + .../helm/pkg/lint/support/message.go | 76 + .../helm/pkg/lint/support/message_test.go | 79 + .../kubernetes/helm/pkg/plugin/cache/cache.go | 74 + .../kubernetes/helm/pkg/plugin/hooks.go | 35 + .../helm/pkg/plugin/installer/base.go | 48 + .../helm/pkg/plugin/installer/doc.go | 17 + .../pkg/plugin/installer/http_installer.go | 208 + .../plugin/installer/http_installer_test.go | 189 + .../helm/pkg/plugin/installer/installer.go | 116 + .../pkg/plugin/installer/local_installer.go | 56 + .../plugin/installer/local_installer_test.go | 64 + .../pkg/plugin/installer/vcs_installer.go | 175 + .../plugin/installer/vcs_installer_test.go | 203 + .../kubernetes/helm/pkg/plugin/plugin.go | 200 + .../kubernetes/helm/pkg/plugin/plugin_test.go | 153 + .../testdata/plugdir/downloader/plugin.yaml | 11 + .../plugin/testdata/plugdir/echo/plugin.yaml | 8 + .../plugin/testdata/plugdir/hello/hello.sh | 13 + .../plugin/testdata/plugdir/hello/plugin.yaml | 11 + .../helm/pkg/proto/hapi/chart/chart.pb.go | 119 + .../helm/pkg/proto/hapi/chart/config.pb.go | 78 + .../helm/pkg/proto/hapi/chart/metadata.pb.go | 276 + .../helm/pkg/proto/hapi/chart/template.pb.go | 60 + .../helm/pkg/proto/hapi/release/hook.pb.go | 231 + .../helm/pkg/proto/hapi/release/info.pb.go | 90 + .../helm/pkg/proto/hapi/release/release.pb.go | 124 + .../helm/pkg/proto/hapi/release/status.pb.go | 141 + .../pkg/proto/hapi/release/test_run.pb.go | 118 + .../pkg/proto/hapi/release/test_suite.pb.go | 73 + .../helm/pkg/proto/hapi/rudder/rudder.pb.go | 722 ++ .../helm/pkg/proto/hapi/services/tiller.pb.go | 1449 +++ .../helm/pkg/proto/hapi/version/version.pb.go | 81 + .../kubernetes/helm/pkg/provenance/doc.go | 37 + .../kubernetes/helm/pkg/provenance/sign.go | 409 + .../helm/pkg/provenance/sign_test.go | 311 + .../provenance/testdata/hashtest-1.2.3.tgz | Bin 0 -> 465 bytes .../pkg/provenance/testdata/hashtest.sha256 | 1 + .../provenance/testdata/hashtest/.helmignore | 5 + .../provenance/testdata/hashtest/Chart.yaml | 3 + .../provenance/testdata/hashtest/values.yaml | 4 + .../testdata/helm-password-key.secret | Bin 0 -> 2562 bytes .../pkg/provenance/testdata/helm-test-key.pub | Bin 0 -> 1243 bytes .../provenance/testdata/helm-test-key.secret | Bin 0 -> 2545 bytes .../pkg/provenance/testdata/msgblock.yaml | 7 + .../pkg/provenance/testdata/msgblock.yaml.asc | 21 + .../testdata/msgblock.yaml.tampered | 21 + .../pkg/provenance/testdata/regen-hashtest.sh | 3 + .../helm/pkg/releasetesting/environment.go | 122 + .../pkg/releasetesting/environment_test.go | 182 + .../helm/pkg/releasetesting/test_suite.go | 193 + .../pkg/releasetesting/test_suite_test.go | 343 + .../kubernetes/helm/pkg/releaseutil/filter.go | 78 + .../helm/pkg/releaseutil/filter_test.go | 59 + .../helm/pkg/releaseutil/manifest.go | 59 + .../helm/pkg/releaseutil/manifest_test.go | 61 + .../kubernetes/helm/pkg/releaseutil/sorter.go | 77 + .../helm/pkg/releaseutil/sorter_test.go | 82 + .../kubernetes/helm/pkg/repo/chartrepo.go | 274 + .../helm/pkg/repo/chartrepo_test.go | 297 + .../kubernetes/helm/pkg/repo/doc.go | 93 + .../kubernetes/helm/pkg/repo/index.go | 329 + .../kubernetes/helm/pkg/repo/index_test.go | 352 + .../kubernetes/helm/pkg/repo/local.go | 137 + .../kubernetes/helm/pkg/repo/local_test.go | 67 + .../kubernetes/helm/pkg/repo/repo.go | 150 + .../kubernetes/helm/pkg/repo/repo_test.go | 227 + .../kubernetes/helm/pkg/repo/repotest/doc.go | 20 + .../helm/pkg/repo/repotest/server.go | 172 + .../helm/pkg/repo/repotest/server_test.go | 127 + .../repotest/testdata/examplechart-0.1.0.tgz | Bin 0 -> 558 bytes .../testdata/examplechart/.helmignore | 21 + .../repotest/testdata/examplechart/Chart.yaml | 3 + .../testdata/examplechart/values.yaml | 4 + .../repo/testdata/local-index-unordered.yaml | 48 + .../helm/pkg/repo/testdata/local-index.yaml | 48 + .../pkg/repo/testdata/old-repositories.yaml | 3 + .../helm/pkg/repo/testdata/repositories.yaml | 8 + .../testdata/repository/frobnitz-1.2.3.tgz | Bin 0 -> 1165 bytes .../testdata/repository/sprocket-1.1.0.tgz | Bin 0 -> 814 bytes .../testdata/repository/sprocket-1.2.0.tgz | Bin 0 -> 847 bytes .../repository/universe/zarthal-1.0.0.tgz | Bin 0 -> 1121 bytes .../helm/pkg/repo/testdata/server/index.yaml | 39 + .../helm/pkg/repo/testdata/server/test.txt | 1 + .../pkg/repo/testdata/unversioned-index.yaml | 64 + .../kubernetes/helm/pkg/resolver/resolver.go | 153 + .../helm/pkg/resolver/resolver_test.go | 171 + .../cache/kubernetes-charts-index.yaml | 49 + .../kubernetes/helm/pkg/rudder/client.go | 91 + .../helm/pkg/storage/driver/cfgmaps.go | 258 + .../helm/pkg/storage/driver/cfgmaps_test.go | 186 + .../helm/pkg/storage/driver/driver.go | 82 + .../helm/pkg/storage/driver/labels.go | 48 + .../helm/pkg/storage/driver/labels_test.go | 49 + .../helm/pkg/storage/driver/memory.go | 176 + .../helm/pkg/storage/driver/memory_test.go | 196 + .../helm/pkg/storage/driver/mock_test.go | 222 + .../helm/pkg/storage/driver/records.go | 135 + .../helm/pkg/storage/driver/records_test.go | 112 + .../helm/pkg/storage/driver/secrets.go | 258 + .../helm/pkg/storage/driver/secrets_test.go | 186 + .../helm/pkg/storage/driver/util.go | 85 + .../kubernetes/helm/pkg/storage/storage.go | 244 + .../helm/pkg/storage/storage_test.go | 350 + .../kubernetes/helm/pkg/strvals/doc.go | 32 + .../kubernetes/helm/pkg/strvals/parser.go | 340 + .../helm/pkg/strvals/parser_test.go | 372 + .../kubernetes/helm/pkg/sympath/walk.go | 115 + .../kubernetes/helm/pkg/sympath/walk_test.go | 134 + .../pkg/tiller/environment/environment.go | 227 + .../tiller/environment/environment_test.go | 110 + .../kubernetes/helm/pkg/tiller/hook_sorter.go | 53 + .../helm/pkg/tiller/hook_sorter_test.go | 73 + .../kubernetes/helm/pkg/tiller/hooks.go | 233 + .../kubernetes/helm/pkg/tiller/hooks_test.go | 246 + .../kubernetes/helm/pkg/tiller/kind_sorter.go | 148 + .../helm/pkg/tiller/kind_sorter_test.go | 217 + .../helm/pkg/tiller/release_content.go | 39 + .../helm/pkg/tiller/release_content_test.go | 42 + .../helm/pkg/tiller/release_history.go | 54 + .../helm/pkg/tiller/release_history_test.go | 115 + .../helm/pkg/tiller/release_install.go | 225 + .../helm/pkg/tiller/release_install_test.go | 500 ++ .../helm/pkg/tiller/release_list.go | 178 + .../helm/pkg/tiller/release_list_test.go | 233 + .../helm/pkg/tiller/release_modules.go | 183 + .../helm/pkg/tiller/release_rollback.go | 163 + .../helm/pkg/tiller/release_rollback_test.go | 254 + .../helm/pkg/tiller/release_server.go | 444 + .../helm/pkg/tiller/release_server_test.go | 811 ++ .../helm/pkg/tiller/release_status.go | 77 + .../helm/pkg/tiller/release_status_test.go | 65 + .../helm/pkg/tiller/release_testing.go | 72 + .../helm/pkg/tiller/release_testing_test.go | 36 + .../helm/pkg/tiller/release_uninstall.go | 128 + .../helm/pkg/tiller/release_uninstall_test.go | 178 + .../helm/pkg/tiller/release_update.go | 293 + .../helm/pkg/tiller/release_update_test.go | 446 + .../helm/pkg/tiller/release_version.go | 30 + .../helm/pkg/tiller/resource_policy.go | 77 + .../kubernetes/helm/pkg/tiller/server.go | 96 + .../kubernetes/helm/pkg/timeconv/doc.go | 23 + .../kubernetes/helm/pkg/timeconv/timeconv.go | 58 + .../helm/pkg/timeconv/timeconv_test.go | 62 + .../kubernetes/helm/pkg/tlsutil/cfg.go | 82 + .../kubernetes/helm/pkg/tlsutil/tls.go | 71 + .../helm/pkg/tlsutil/tlsutil_test.go | 83 + .../kubernetes/helm/pkg/urlutil/urlutil.go | 87 + .../helm/pkg/urlutil/urlutil_test.go | 77 + .../kubernetes/helm/pkg/version/compatible.go | 65 + .../helm/pkg/version/compatible_test.go | 66 + .../kubernetes/helm/pkg/version/doc.go | 18 + .../kubernetes/helm/pkg/version/version.go | 54 + .../helm/pkg/version/version_test.go | 47 + .../kubernetes/helm/rootfs/Dockerfile | 26 + .../helm/rootfs/Dockerfile.experimental | 26 + .../kubernetes/helm/rootfs/Dockerfile.rudder | 25 + .../kubernetes/helm/rootfs/README.md | 27 + .../kubernetes/helm/scripts/completions.bash | 1677 ++++ .../kubernetes/helm/scripts/coverage.sh | 53 + .../github.com/kubernetes/helm/scripts/get | 232 + .../kubernetes/helm/scripts/sync-repo.sh | 82 + .../kubernetes/helm/scripts/update-docs.sh | 54 + .../kubernetes/helm/scripts/util.sh | 58 + .../kubernetes/helm/scripts/validate-go.sh | 52 + .../helm/scripts/validate-license.sh | 37 + .../kubernetes/helm/scripts/verify-docs.sh | 55 + .../kubernetes/helm/testdata/ca.pem | 35 + .../kubernetes/helm/testdata/crt.pem | 29 + .../kubernetes/helm/testdata/key.pem | 51 + .../github.com/kubernetes/helm/versioning.mk | 57 + 839 files changed, 77397 insertions(+), 3 deletions(-) create mode 100644 src/chartserver/base_handler.go create mode 100644 src/chartserver/chart_operator.go create mode 100644 src/chartserver/controller.go create mode 100644 src/chartserver/manipulation_handler.go create mode 100644 src/chartserver/repo_handler.go create mode 100755 src/vendor/github.com/kubernetes/helm/.circleci/bootstrap.sh create mode 100644 src/vendor/github.com/kubernetes/helm/.circleci/config.yml create mode 100755 src/vendor/github.com/kubernetes/helm/.circleci/deploy.sh create mode 100755 src/vendor/github.com/kubernetes/helm/.circleci/test.sh create mode 100644 src/vendor/github.com/kubernetes/helm/.github/issue_template.md create mode 100644 src/vendor/github.com/kubernetes/helm/.gitignore create mode 100644 src/vendor/github.com/kubernetes/helm/CONTRIBUTING.md create mode 100644 src/vendor/github.com/kubernetes/helm/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/Makefile create mode 100644 src/vendor/github.com/kubernetes/helm/OWNERS create mode 100644 src/vendor/github.com/kubernetes/helm/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/Makefile create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/chart.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/config.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/metadata.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/template.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/hook.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/info.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/release.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/status.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_run.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_suite.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/rudder/rudder.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/services/tiller.proto create mode 100644 src/vendor/github.com/kubernetes/helm/_proto/hapi/version/version.proto create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/completion.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/create.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/create_test.go create mode 100755 src/vendor/github.com/kubernetes/helm/cmd/helm/delete.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/delete_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/docs.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/fetch.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/fetch_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_values.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/get_values_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/helm.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/helm_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/history.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/history_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/home.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/init.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/init_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/init_unix.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/init_windows.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/inspect.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/inspect_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/install.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/install_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/installer/options.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/lint.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/lint_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/list.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/list_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/load_plugins.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/package.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/package_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_install.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_list.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_remove.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_update.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/printer.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_list.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/reset.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/reset_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/rollback.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/rollback_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/search.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/search/search.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/search/search_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/search_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/serve.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/status.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/status_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/template.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/template_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.pub create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.secret create mode 100755 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/args/args.sh create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/args/plugin.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/echo/plugin.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/env/plugin.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/fullenv/fullenv.sh create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/plugins/fullenv/plugin.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/repository/cache/testing-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/repository/local/index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helmhome/repository/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcache/foobar-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcache/local-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/extra_values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/more_values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/charts/reqsubchart/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-bad-requirements/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/charts/reqsubchart/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/chart-missing-deps/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.2.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.3.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz create mode 100755 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.lock create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/templates/pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/repository/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/verify.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/verify_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/version.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/helm/version_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/rudder/rudder.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/tiller/probes.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/tiller/probes_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/cmd/tiller/trace.go create mode 100644 src/vendor/github.com/kubernetes/helm/code-of-conduct.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/architecture.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/conventions.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/labels.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/pods.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/rbac.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/requirements.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/templates.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/third_party_resources.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/values.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_repository.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_repository_faq.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_repository_sync_example.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/accessing_files.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/builtin_objects.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/control_structures.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/data_types.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/debugging.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/functions_and_pipelines.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/getting_started.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/index.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/named_templates.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/notes_files.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/subcharts_and_globals.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/values_files.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/variables.md create mode 100755 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/wrapping_up.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/yaml_techniques.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/chart_tests.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/charts.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/charts_hooks.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/charts_tips_and_tricks.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/developers.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/_helpers.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/_helpers.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/configmap.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/deployment.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/post-install-job.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/pre-install-secret.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service-test.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/examples/nginx/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/docs/glossary.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_completion.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_create.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_delete.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_build.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_list.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_update.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_fetch.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_get.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_hooks.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_manifest.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_values.md create mode 100755 src/vendor/github.com/kubernetes/helm/docs/helm/helm_history.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_home.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_init.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_chart.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_readme.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_values.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_install.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_lint.md create mode 100755 src/vendor/github.com/kubernetes/helm/docs/helm/helm_list.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_package.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_install.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_list.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_remove.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_update.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_add.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_index.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_list.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_remove.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_update.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_reset.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_rollback.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_search.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_serve.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_status.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_template.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_test.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_upgrade.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_verify.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/helm/helm_version.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/history.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/create-a-bucket.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/create-a-gh-page-button.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/edit-permissions.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/make-bucket-public.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/nothing.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/images/set-a-gh-page.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/index.md create mode 100755 src/vendor/github.com/kubernetes/helm/docs/install.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/install_faq.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/kubernetes_distros.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/logos/helm_logo_transparent.png create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_completion.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_create.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_delete.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_build.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_list.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_update.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_fetch.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_hooks.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_manifest.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_values.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_history.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_home.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_init.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_chart.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_values.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_install.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_lint.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_list.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_package.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_install.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_list.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_remove.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_update.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_add.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_index.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_list.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_remove.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_update.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_reset.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_rollback.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_search.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_serve.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_status.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_test.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_upgrade.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_verify.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_version.1 create mode 100644 src/vendor/github.com/kubernetes/helm/docs/plugins.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/provenance.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/quickstart.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/rbac.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/related.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/release_checklist.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/securing_installation.md create mode 100644 src/vendor/github.com/kubernetes/helm/docs/tiller_ssl.md create mode 100755 src/vendor/github.com/kubernetes/helm/docs/using_helm.md create mode 100644 src/vendor/github.com/kubernetes/helm/glide.lock create mode 100644 src/vendor/github.com/kubernetes/helm/glide.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile.go create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/create.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/create_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/expand.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/files.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/files_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/load.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/load_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/save.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/save_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/chartfiletest.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/coleridge.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/docs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/icon.svg create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/docs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/icon.svg create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/docs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/icon.svg create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/docs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/icon.svg create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz-1.2.3.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/INSTALL.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/LICENSE create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/_ignore_me create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/charts/mast2-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/docs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/icon.svg create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/ignore/me.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.lock create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/templates/template.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/.helmignore create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/Chart.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/INSTALL.txt create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/README.md create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/_ignore_me create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/Chart.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/README.md create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/values.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/docs/README.md create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/icon.svg create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/ignore/me.txt create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.lock create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/templates/template.tpl create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/values.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/genfrob.sh create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/Chart.yaml create mode 120000 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/charts/frobnitz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/templates/placeholder.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/requirements.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/transform.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/values.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/chartutil/values_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/manager.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/manager_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.pub create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.secret create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml create mode 120000 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/local-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/malformed-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-basicauth-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-https-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-querystring-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/cache/testing-relative-trailing-slash-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/local/index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helmhome/repository/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest-0.1.0.tgz.prov create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/templates/pod.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/engine/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/engine/engine.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/engine/engine_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/getter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/getter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter_test.go create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/get.sh create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/plugin.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/get.sh create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/plugin.yaml create mode 120000 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/local-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/stable-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/local/index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/client.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/client_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/fake.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/fake_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/helm_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_unix_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_windows_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/interface.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/option.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/pod.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/hooks/hooks.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/rules.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/rules_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.joonix create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/a.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/a.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/b.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/c.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/helm.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/a.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/b.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/c.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/rudder.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/templates/.dotfile create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/tiller.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/client.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/client_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/config.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/log.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/namespace.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/namespace_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/result.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/result_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/kube/wait.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/lint.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/lint_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/fail.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/svc.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/templates/goodone.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/rules/values.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/support/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/support/message.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/lint/support/message_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/cache/cache.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/hooks.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/base.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/installer.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/downloader/plugin.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/echo/plugin.yaml create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/hello.sh create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/plugin.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/chart.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/config.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/metadata.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/template.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/hook.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/info.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/release.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/status.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_run.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_suite.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/rudder/rudder.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/services/tiller.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/version/version.pb.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/sign.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/sign_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest-1.2.3.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest.sha256 create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/helm-password-key.secret create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/helm-test-key.pub create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/helm-test-key.secret create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/msgblock.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/msgblock.yaml.asc create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/msgblock.yaml.tampered create mode 100755 src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/regen-hashtest.sh create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/index.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/index_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/local.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/local_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repo.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repo_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart/.helmignore create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart/Chart.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart/values.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/local-index-unordered.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/local-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/old-repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repositories.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/frobnitz-1.2.3.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/sprocket-1.1.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/sprocket-1.2.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/test.txt create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/unversioned-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/rudder/client.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/driver.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/mock_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/driver/util.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/storage.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/storage/storage_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/strvals/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/strvals/parser.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/strvals/parser_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/sympath/walk.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/sympath/walk_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_modules.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/release_version.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/resource_policy.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tiller/server.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/timeconv/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tlsutil/cfg.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tls.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tlsutil_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/version/compatible.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/version/compatible_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/version/doc.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/version/version.go create mode 100644 src/vendor/github.com/kubernetes/helm/pkg/version/version_test.go create mode 100644 src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile create mode 100644 src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.experimental create mode 100644 src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.rudder create mode 100644 src/vendor/github.com/kubernetes/helm/rootfs/README.md create mode 100644 src/vendor/github.com/kubernetes/helm/scripts/completions.bash create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/coverage.sh create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/get create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/sync-repo.sh create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/update-docs.sh create mode 100644 src/vendor/github.com/kubernetes/helm/scripts/util.sh create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/validate-go.sh create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/validate-license.sh create mode 100755 src/vendor/github.com/kubernetes/helm/scripts/verify-docs.sh create mode 100644 src/vendor/github.com/kubernetes/helm/testdata/ca.pem create mode 100644 src/vendor/github.com/kubernetes/helm/testdata/crt.pem create mode 100644 src/vendor/github.com/kubernetes/helm/testdata/key.pem create mode 100644 src/vendor/github.com/kubernetes/helm/versioning.mk diff --git a/src/Gopkg.lock b/src/Gopkg.lock index f0f977ac3..a8a6542cc 100644 --- a/src/Gopkg.lock +++ b/src/Gopkg.lock @@ -148,7 +148,10 @@ [[projects]] name = "github.com/golang/protobuf" - packages = ["proto"] + packages = [ + "proto", + "ptypes/any" + ] revision = "130e6b02ab059e7b717a096f397c5b60111cae74" [[projects]] @@ -175,6 +178,12 @@ revision = "7f08801859139f86dfafd1c296e2cba9a80d292e" version = "v1.6.0" +[[projects]] + name = "github.com/kubernetes/helm" + packages = ["pkg/proto/hapi/chart"] + revision = "20adb27c7c5868466912eebdf6664e7390ebe710" + version = "v2.9.1" + [[projects]] branch = "master" name = "github.com/lib/pq" @@ -287,6 +296,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a53e62d0a95585213a5c0519e5c02c570201189b9332d9dfca730d5671619dab" + inputs-digest = "d6cb7ad53bb52b7243e16b20774f160c24ea003285cf7b6c4db782bca6f1e3c8" solver-name = "gps-cdcl" solver-version = 1 diff --git a/src/Gopkg.toml b/src/Gopkg.toml index d59e66f8a..18c43b068 100644 --- a/src/Gopkg.toml +++ b/src/Gopkg.toml @@ -80,7 +80,10 @@ ignored = ["github.com/vmware/harbor/tests*"] name = "github.com/garyburd/redigo" version = "=1.6.0" - [[constraint]] name = "github.com/golang-migrate/migrate" version = "=3.3.0" + +[[constraint]] + name = "github.com/kubernetes/helm" + version = "=2.9.1" \ No newline at end of file diff --git a/src/chartserver/base_handler.go b/src/chartserver/base_handler.go new file mode 100644 index 000000000..f8a088d37 --- /dev/null +++ b/src/chartserver/base_handler.go @@ -0,0 +1,16 @@ +package chartserver + +import ( + "net/http" + "net/http/httputil" +) + +//BaseHandler defines the handlers related with the chart server itself. +type BaseHandler struct { + //Proxy used to to transfer the traffic of requests + //It's mainly used to talk to the backend chart server + trafficProxy *httputil.ReverseProxy +} + +//GetHealthStatus will return the health status of the backend chart repository server +func (bh *BaseHandler) GetHealthStatus(w http.ResponseWriter, req *http.Request) {} diff --git a/src/chartserver/chart_operator.go b/src/chartserver/chart_operator.go new file mode 100644 index 000000000..2feeed708 --- /dev/null +++ b/src/chartserver/chart_operator.go @@ -0,0 +1,12 @@ +package chartserver + +import "github.com/kubernetes/helm/pkg/proto/hapi/chart" + +//ChartOperator is designed to process the contents of +//the specified chart version to get more details +type ChartOperator struct{} + +//GetChartDetails parse the details from the provided content bytes +func (cho *ChartOperator) GetChartDetails(content []byte) *chart.Chart { + return nil +} diff --git a/src/chartserver/controller.go b/src/chartserver/controller.go new file mode 100644 index 000000000..84b7cdf18 --- /dev/null +++ b/src/chartserver/controller.go @@ -0,0 +1,62 @@ +package chartserver + +import ( + "errors" + "net/http/httputil" + "net/url" +) + +//Controller is used to handle flows of related requests based on the corresponding handlers +//A reverse proxy will be created and managed to proxy the related traffics between API and +//backend chart server +type Controller struct { + //The access endpoint of the backend chart repository server + backendServerAddr *url.URL + + //To cover the server info and status requests + baseHandler *BaseHandler + + //To cover the chart repository requests + repositoryHandler *RepositoryHandler + + //To cover all the manipulation requests + manipulationHandler *ManipulationHandler +} + +//NewController is constructor of the chartserver.Controller +func NewController(backendServer *url.URL) (*Controller, error) { + if backendServer == nil { + return nil, errors.New("failed to create chartserver.Controller: backend sever address is required") + } + + //Currently, no customization requirements needed, so let's use the simple proxy here now + proxy := httputil.NewSingleHostReverseProxy(backendServer) + + //Initialize chart operator for use + operator := &ChartOperator{} + + return &Controller{ + backendServerAddr: backendServer, + baseHandler: &BaseHandler{proxy}, + repositoryHandler: &RepositoryHandler{proxy}, + manipulationHandler: &ManipulationHandler{ + trafficProxy: proxy, + chartOperator: operator, + }, + }, nil +} + +//GetBaseHandler returns the reference of BaseHandler +func (c *Controller) GetBaseHandler() *BaseHandler { + return c.baseHandler +} + +//GetRepositoryHandler returns the reference of RepositoryHandler +func (c *Controller) GetRepositoryHandler() *RepositoryHandler { + return c.repositoryHandler +} + +//GetManipulationHandler returns the reference of ManipulationHandler +func (c *Controller) GetManipulationHandler() *ManipulationHandler { + return c.manipulationHandler +} diff --git a/src/chartserver/manipulation_handler.go b/src/chartserver/manipulation_handler.go new file mode 100644 index 000000000..207bf9be8 --- /dev/null +++ b/src/chartserver/manipulation_handler.go @@ -0,0 +1,37 @@ +package chartserver + +import ( + "net/http" + "net/http/httputil" +) + +//ManipulationHandler includes all the handler methods for the purpose of manipulating the +//chart repository +type ManipulationHandler struct { + //Proxy used to to transfer the traffic of requests + //It's mainly used to talk to the backend chart server + trafficProxy *httputil.ReverseProxy + + //Parse and process the chart version to provide required info data + chartOperator *ChartOperator +} + +//ListCharts lists all the charts under the specified namespace +func (mh *ManipulationHandler) ListCharts(w http.ResponseWriter, req *http.Request) {} + +//GetChart returns all the chart versions under the specified chart +func (mh *ManipulationHandler) GetChart(w http.ResponseWriter, req *http.Request) {} + +//GetChartVersion get the specified version for one chart +//This handler should return the details of the chart version, +//maybe including metadata,dependencies and values etc. +func (mh *ManipulationHandler) GetChartVersion(w http.ResponseWriter, req *http.Request) {} + +//UploadChartVersion will save the new version of the chart to the backend storage +func (mh *ManipulationHandler) UploadChartVersion(w http.ResponseWriter, req *http.Request) {} + +//UploadProvenanceFile will save the provenance file of the chart to the backend storage +func (mh *ManipulationHandler) UploadProvenanceFile(w http.ResponseWriter, req *http.Request) {} + +//DeleteChartVersion will delete the specified version of the chart +func (mh *ManipulationHandler) DeleteChartVersion(w http.ResponseWriter, req *http.Request) {} diff --git a/src/chartserver/repo_handler.go b/src/chartserver/repo_handler.go new file mode 100644 index 000000000..37a6f7561 --- /dev/null +++ b/src/chartserver/repo_handler.go @@ -0,0 +1,27 @@ +package chartserver + +import ( + "net/http" + "net/http/httputil" +) + +//RepositoryHandler defines all the handlers to handle the requests related with chart repository +//e.g: index.yaml and downloading chart objects +type RepositoryHandler struct { + //Proxy used to to transfer the traffic of requests + //It's mainly used to talk to the backend chart server + trafficProxy *httputil.ReverseProxy +} + +//GetIndexFileWithNS will read the index.yaml data under the specified namespace +func (rh *RepositoryHandler) GetIndexFileWithNS(w http.ResponseWriter, req *http.Request) { +} + +//GetIndexFile will read the index.yaml under all namespaces and merge them as a single one +//Please be aware that, to support this function, the backend chart repository server should +//enable multi-tenancies +func (rh *RepositoryHandler) GetIndexFile(w http.ResponseWriter, req *http.Request) {} + +//DownloadChartObject will download the stored chart object to the client +//e.g: helm install +func (rh *RepositoryHandler) DownloadChartObject(w http.ResponseWriter, req *http.Request) {} diff --git a/src/vendor/github.com/kubernetes/helm/.circleci/bootstrap.sh b/src/vendor/github.com/kubernetes/helm/.circleci/bootstrap.sh new file mode 100755 index 000000000..978464efe --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.circleci/bootstrap.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. +set -euo pipefail + +apt-get update -y && apt-get install -yq zip +make bootstrap diff --git a/src/vendor/github.com/kubernetes/helm/.circleci/config.yml b/src/vendor/github.com/kubernetes/helm/.circleci/config.yml new file mode 100644 index 000000000..df9786d82 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.circleci/config.yml @@ -0,0 +1,38 @@ +version: 2 +jobs: + build: + working_directory: /go/src/k8s.io/helm + parallelism: 3 + docker: + - image: golang:1.10 + environment: + PROJECT_NAME: "kubernetes-helm" + steps: + - checkout + - setup_remote_docker: + version: 17.09.0-ce + - restore_cache: + keys: + - glide-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }} + - glide- # used as a fall through if the checksum fails to find exact entry + - run: + name: install dependencies + command: .circleci/bootstrap.sh + - save_cache: + key: glide-{{ checksum "glide.yaml" }}-{{ checksum "glide.lock" }} + paths: + - /root/.glide # Where the glide cache is stored + - run: + name: test + command: .circleci/test.sh + - deploy: + name: deploy + command: .circleci/deploy.sh +workflows: + version: 2 + build: + jobs: + - build: + filters: + tags: + only: /.*/ diff --git a/src/vendor/github.com/kubernetes/helm/.circleci/deploy.sh b/src/vendor/github.com/kubernetes/helm/.circleci/deploy.sh new file mode 100755 index 000000000..6ad91109d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.circleci/deploy.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. +set -euo pipefail + +# Skip on pull request builds +if [[ -n "${CIRCLE_PR_NUMBER:-}" ]]; then + exit +fi + +: ${GCLOUD_SERVICE_KEY:?"GCLOUD_SERVICE_KEY environment variable is not set"} +: ${PROJECT_NAME:?"PROJECT_NAME environment variable is not set"} + +VERSION= +if [[ -n "${CIRCLE_TAG:-}" ]]; then + VERSION="${CIRCLE_TAG}" +elif [[ "${CIRCLE_BRANCH:-}" == "master" ]]; then + VERSION="canary" +else + echo "Skipping deploy step; this is neither master or a tag" + exit +fi + +echo "Install docker client" +VER="17.09.0-ce" +curl -L -o /tmp/docker-$VER.tgz https://download.docker.com/linux/static/stable/x86_64/docker-$VER.tgz +tar -xz -C /tmp -f /tmp/docker-$VER.tgz +mv /tmp/docker/* /usr/bin + +echo "Install gcloud components" +export CLOUDSDK_CORE_DISABLE_PROMPTS=1 +curl https://sdk.cloud.google.com | bash +${HOME}/google-cloud-sdk/bin/gcloud --quiet components update + +echo "Configuring gcloud authentication" +echo "${GCLOUD_SERVICE_KEY}" | base64 --decode > "${HOME}/gcloud-service-key.json" +${HOME}/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file "${HOME}/gcloud-service-key.json" +${HOME}/google-cloud-sdk/bin/gcloud config set project "${PROJECT_NAME}" +docker login -u _json_key -p "$(cat ${HOME}/gcloud-service-key.json)" https://gcr.io + +echo "Building the tiller image" +make docker-build VERSION="${VERSION}" + +echo "Pushing image to gcr.io" +docker push "gcr.io/kubernetes-helm/tiller:${VERSION}" + +echo "Building helm binaries" +make build-cross +make dist checksum VERSION="${VERSION}" + +echo "Pushing binaries to gs bucket" +${HOME}/google-cloud-sdk/bin/gsutil cp ./_dist/* "gs://${PROJECT_NAME}" diff --git a/src/vendor/github.com/kubernetes/helm/.circleci/test.sh b/src/vendor/github.com/kubernetes/helm/.circleci/test.sh new file mode 100755 index 000000000..e0faf9c18 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.circleci/test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. + +# Bash 'Strict Mode' +# http://redsymbol.net/articles/unofficial-bash-strict-mode +set -euo pipefail +IFS=$'\n\t' + +HELM_ROOT="${BASH_SOURCE[0]%/*}/.." +cd "$HELM_ROOT" + +run_unit_test() { + if [[ "${CIRCLE_BRANCH-}" == "master" ]]; then + echo "Running unit tests with coverage'" + ./scripts/coverage.sh --coveralls + else + echo "Running 'unit tests'" + make test-unit + fi +} + +run_style_check() { + echo "Running 'make test-style'" + make test-style +} + +run_docs_check() { + echo "Running 'make verify-docs'" + make verify-docs +} + +# Build to ensure packages are compiled +echo "Running 'make build'" +make build + +case "${CIRCLE_NODE_INDEX-0}" in + 0) run_unit_test ;; + 1) run_style_check ;; + 2) run_docs_check ;; +esac diff --git a/src/vendor/github.com/kubernetes/helm/.github/issue_template.md b/src/vendor/github.com/kubernetes/helm/.github/issue_template.md new file mode 100644 index 000000000..48f48e5b6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.github/issue_template.md @@ -0,0 +1,9 @@ + + +Output of `helm version`: + +Output of `kubectl version`: + +Cloud Provider/Platform (AKS, GKE, Minikube etc.): + + diff --git a/src/vendor/github.com/kubernetes/helm/.gitignore b/src/vendor/github.com/kubernetes/helm/.gitignore new file mode 100644 index 000000000..b94f87b26 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +.coverage/ +.vimrc +.vscode/ +_dist/ +_proto/*.pb.go +bin/ +rootfs/tiller +rootfs/rudder +vendor/ +*.exe +.idea/ diff --git a/src/vendor/github.com/kubernetes/helm/CONTRIBUTING.md b/src/vendor/github.com/kubernetes/helm/CONTRIBUTING.md new file mode 100644 index 000000000..34b4f8b16 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/CONTRIBUTING.md @@ -0,0 +1,236 @@ +# Contributing Guidelines + +The Kubernetes Helm project accepts contributions via GitHub pull requests. This document outlines the process to help get your contribution accepted. + +## Reporting a Security Issue + +Most of the time, when you find a bug in Helm, it should be reported +using [GitHub issues](https://github.com/kubernetes/helm/issues). However, if +you are reporting a _security vulnerability_, please email a report to +[helm-security@deis.com](mailto:helm-security@deis.com). This will give +us a chance to try to fix the issue before it is exploited in the wild. + +## Contributor License Agreements + +We'd love to accept your patches! Before we can take them, we have to jump a +couple of legal hurdles. + +The Cloud Native Computing Foundation (CNCF) CLA [must be signed](https://github.com/kubernetes/community/blob/master/CLA.md) by all contributors. +Please fill out either the individual or corporate Contributor License +Agreement (CLA). + +Once you are CLA'ed, we'll be able to accept your pull requests. For any issues that you face during this process, +please add a comment [here](https://github.com/kubernetes/kubernetes/issues/27796) explaining the issue and we will help get it sorted out. + +***NOTE***: Only original source code from you and other people that have +signed the CLA can be accepted into the repository. This policy does not +apply to [third_party](third_party/) and [vendor](vendor/). + +## Support Channels + +Whether you are a user or contributor, official support channels include: + +- GitHub [issues](https://github.com/kubernetes/helm/issues/new) +- Slack: #Helm room in the [Kubernetes Slack](http://slack.kubernetes.io/) + +Before opening a new issue or submitting a new pull request, it's helpful to search the project - it's likely that another user has already reported the issue you're facing, or it's a known issue that we're already aware of. + +## Milestones + +We use milestones to track progress of releases. There are also 2 special milestones +used for helping us keep work organized: `Upcoming - Minor` and `Upcoming - Major` + +`Upcoming - Minor` is used for keeping track of issues that aren't assigned to a specific +release but could easily be addressed in a minor release. `Upcoming - Major` keeps track +of issues that will need to be addressed in a major release. For example, if the current +version is `2.2.0` an issue/PR could fall in to one of 4 different active milestones: +`2.2.1`, `2.3.0`, `Upcoming - Minor`, or `Upcoming - Major`. If an issue pertains to a +specific upcoming bug or minor release, it would go into `2.2.1` or `2.3.0`. If the issue/PR +does not have a specific milestone yet, but it is likely that it will land in a `2.X` release, +it should go into `Upcoming - Minor`. If the issue/PR is a large functionality add or change +and/or it breaks compatibility, then it should be added to the `Upcoming - Major` milestone. +An issue that we are not sure we will be doing will not be added to any milestone. + +A milestone (and hence release) is considered done when all outstanding issues/PRs have been closed or moved to another milestone. + +## Semver + +Helm maintains a strong commitment to backward compatibility. All of our changes to protocols and formats are backward compatible from Helm 2.0 until Helm 3.0. No features, flags, or commands are removed or substantially modified (other than bug fixes). + +We also try very hard to not change publicly accessible Go library definitions inside of the `pkg/` directory of our source code. + +For a quick summary of our backward compatibility guidelines for releases between 2.0 and 3.0: + +- Protobuf and gRPC changes MUST be backward compatible. +- Command line commands, flags, and arguments MUST be backward compatible +- File formats (such as Chart.yaml, repositories.yaml, and requirements.yaml) MUST be backward compatible +- Any chart that worked on a previous version of Helm MUST work on a new version of Helm (barring the cases where (a) Kubernetes itself changed, and (b) the chart worked because it exploited a bug) +- Chart repository functionality MUST be backward compatible +- Go libraries inside of `pkg/` SHOULD remain backward compatible (though code inside of `cmd/` may be changed from release to release without notice). + +## Issues + +Issues are used as the primary method for tracking anything to do with the Helm project. + +### Issue Types + +There are 4 types of issues (each with their own corresponding [label](#labels)): +- Question: These are support or functionality inquiries that we want to have a record of for +future reference. Generally these are questions that are too complex or large to store in the +Slack channel or have particular interest to the community as a whole. Depending on the discussion, +these can turn into "Feature" or "Bug" issues. +- Proposal: Used for items (like this one) that propose a new ideas or functionality that require +a larger community discussion. This allows for feedback from others in the community before a +feature is actually developed. This is not needed for small additions. Final word on whether or +not a feature needs a proposal is up to the core maintainers. All issues that are proposals should +both have a label and an issue title of "Proposal: [the rest of the title]." A proposal can become +a "Feature" and does not require a milestone. +- Features: These track specific feature requests and ideas until they are complete. They can evolve +from a "Proposal" or can be submitted individually depending on the size. +- Bugs: These track bugs with the code or problems with the documentation (i.e. missing or incomplete) + +### Issue Lifecycle + +The issue lifecycle is mainly driven by the core maintainers, but is good information for those +contributing to Helm. All issue types follow the same general lifecycle. Differences are noted below. +1. Issue creation +2. Triage + - The maintainer in charge of triaging will apply the proper labels for the issue. This + includes labels for priority, type, and metadata (such as "starter"). The only issue + priority we will be tracking is whether or not the issue is "critical." If additional + levels are needed in the future, we will add them. + - (If needed) Clean up the title to succinctly and clearly state the issue. Also ensure + that proposals are prefaced with "Proposal". + - Add the issue to the correct milestone. If any questions come up, don't worry about + adding the issue to a milestone until the questions are answered. + - We attempt to do this process at least once per work day. +3. Discussion + - "Feature" and "Bug" issues should be connected to the PR that resolves it. + - Whoever is working on a "Feature" or "Bug" issue (whether a maintainer or someone from + the community), should either assign the issue to them self or make a comment in the issue + saying that they are taking it. + - "Proposal" and "Question" issues should stay open until resolved or if they have not been + active for more than 30 days. This will help keep the issue queue to a manageable size and + reduce noise. Should the issue need to stay open, the `keep open` label can be added. +4. Issue closure + +## How to Contribute a Patch + +1. If you haven't already done so, sign a Contributor License Agreement (see details above). +2. Fork the desired repo, develop and test your code changes. +3. Submit a pull request. + +Coding conventions and standards are explained in the official developer docs: +https://github.com/kubernetes/helm/blob/master/docs/developers.md + +The next section contains more information on the workflow followed for PRs + +## Pull Requests + +Like any good open source project, we use Pull Requests to track code changes + +### PR Lifecycle + +1. PR creation + - We more than welcome PRs that are currently in progress. They are a great way to keep track of + important work that is in-flight, but useful for others to see. If a PR is a work in progress, + it **must** be prefaced with "WIP: [title]". Once the PR is ready for review, remove "WIP" from + the title. + - It is preferred, but not required, to have a PR tied to a specific issue. +2. Triage + - The maintainer in charge of triaging will apply the proper labels for the issue. This should + include at least a size label, `bug` or `feature`, and `awaiting review` once all labels are applied. + See the [Labels section](#labels) for full details on the definitions of labels + - Add the PR to the correct milestone. This should be the same as the issue the PR closes. +3. Assigning reviews + - Once a review has the `awaiting review` label, maintainers will review them as schedule permits. + The maintainer who takes the issue should self-request a review. + - Reviews from others in the community, especially those who have encountered a bug or have + requested a feature, are highly encouraged, but not required. Maintainer reviews **are** required + before any merge + - Any PR with the `size/large` label requires 2 review approvals from maintainers before it can be + merged. Those with `size/medium` are per the judgement of the maintainers +4. Reviewing/Discussion + - Once a maintainer begins reviewing a PR, they will remove the `awaiting review` label and add + the `in progress` label so the person submitting knows that it is being worked on. This is + especially helpful when the review may take awhile. + - All reviews will be completed using Github review tool. + - A "Comment" review should be used when there are questions about the code that should be + answered, but that don't involve code changes. This type of review does not count as approval. + - A "Changes Requested" review indicates that changes to the code need to be made before they will be merged. + - Reviewers should update labels as needed (such as `needs rebase`) +5. Address comments by answering questions or changing code +6. Merge or close + - PRs should stay open until merged or if they have not been active for more than 30 days. + This will help keep the PR queue to a manageable size and reduce noise. Should the PR need + to stay open (like in the case of a WIP), the `keep open` label can be added. + - If the owner of the PR is listed in `OWNERS`, that user **must** merge their own PRs + or explicitly request another OWNER do that for them. + - If the owner of a PR is _not_ listed in `OWNERS`, any core committer may + merge the PR once it is approved. + +#### Documentation PRs + +Documentation PRs will follow the same lifecycle as other PRs. They will also be labeled with the +`docs` label. For documentation, special attention will be paid to spelling, grammar, and clarity +(whereas those things don't matter *as* much for comments in code). + +## The Triager + +Each week, one of the core maintainers will serve as the designated "triager" starting after the +public standup meetings on Thursday. This person will be in charge triaging new PRs and issues +throughout the work week. + +## Labels + +The following tables define all label types used for Helm. It is split up by category. + +### Common + +| Label | Description | +| ----- | ----------- | +| `bug` | Marks an issue as a bug or a PR as a bugfix | +| `critical` | Marks an issue or PR as critical. This means that addressing the PR or issue is top priority and will be handled first by maintainers | +| `docs` | Indicates the issue or PR is a documentation change | +| `duplicate` | Indicates that the issue or PR is a duplicate of another | +| `feature` | Marks the issue as a feature request or a PR as a feature implementation | +| `keep open` | Denotes that the issue or PR should be kept open past 30 days of inactivity | +| `refactor` | Indicates that the issue is a code refactor and is not fixing a bug or adding additional functionality | + +### Issue Specific + +| Label | Description | +| ----- | ----------- | +| `help wanted` | This issue is one the core maintainers cannot get to right now and would appreciate help with | +| `proposal` | This issue is a proposal | +| `question/support` | This issue is a support request or question | +| `starter` | This issue is a good for someone new to contributing to Helm | +| `wont fix` | The issue has been discussed and will not be implemented (or accepted in the case of a proposal) | + +### PR Specific + +| Label | Description | +| ----- | ----------- | +| `awaiting review` | The PR has been triaged and is ready for someone to review | +| `breaking` | The PR has breaking changes (such as API changes) | +| `cncf-cla: no` | The PR submitter has **not** signed the project CLA. | +| `cncf-cla: yes` | The PR submitter has signed the project CLA. This is required to merge. | +| `in progress` | Indicates that a maintainer is looking at the PR, even if no review has been posted yet | +| `needs pick` | Indicates that the PR needs to be picked into a feature branch (generally bugfix branches). Once it has been, the `picked` label should be applied and this one removed | +| `needs rebase` | A helper label used to indicate that the PR needs to be rebased before it can be merged. Used for easy filtering | +| `picked` | This PR has been picked into a feature branch | + +#### Size labels + +Size labels are used to indicate how "dangerous" a PR is. The guidelines below are used to assign the +labels, but ultimately this can be changed by the maintainers. For example, even if a PR only makes +30 lines of changes in 1 file, but it changes key functionality, it will likely be labeled as `size/large` +because it requires sign off from multiple people. Conversely, a PR that adds a small feature, but requires +another 150 lines of tests to cover all cases, could be labeled as `size/small` even though the number +lines is greater than defined below. + +| Label | Description | +| ----- | ----------- | +| `size/small` | Anything less than or equal to 4 files and 150 lines. Only small amounts of manual testing may be required | +| `size/medium` | Anything greater than `size/small` and less than or equal to 8 files and 300 lines. Manual validation should be required. | +| `size/large` | Anything greater than `size/medium`. This should be thoroughly tested before merging and always requires 2 approvals. This also should be applied to anything that is a significant logic change. | diff --git a/src/vendor/github.com/kubernetes/helm/LICENSE b/src/vendor/github.com/kubernetes/helm/LICENSE new file mode 100644 index 000000000..21c57fae2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 The Kubernetes Authors All Rights Reserved + + 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. diff --git a/src/vendor/github.com/kubernetes/helm/Makefile b/src/vendor/github.com/kubernetes/helm/Makefile new file mode 100644 index 000000000..709cc301a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/Makefile @@ -0,0 +1,143 @@ +DOCKER_REGISTRY ?= gcr.io +IMAGE_PREFIX ?= kubernetes-helm +SHORT_NAME ?= tiller +SHORT_NAME_RUDDER ?= rudder +TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le windows/amd64 +DIST_DIRS = find * -type d -exec +APP = helm + +# go option +GO ?= go +PKG := $(shell glide novendor) +TAGS := +TESTS := . +TESTFLAGS := +LDFLAGS := -w -s +GOFLAGS := +BINDIR := $(CURDIR)/bin +BINARIES := helm tiller + +# Required for globs to work correctly +SHELL=/bin/bash + +.PHONY: all +all: build + +.PHONY: build +build: + GOBIN=$(BINDIR) $(GO) install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/... + +# usage: make clean build-cross dist APP=helm|tiller VERSION=v2.0.0-alpha.3 +.PHONY: build-cross +build-cross: LDFLAGS += -extldflags "-static" +build-cross: + CGO_ENABLED=0 gox -parallel=3 -output="_dist/{{.OS}}-{{.Arch}}/{{.Dir}}" -osarch='$(TARGETS)' $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/$(APP) + +.PHONY: dist +dist: + ( \ + cd _dist && \ + $(DIST_DIRS) cp ../LICENSE {} \; && \ + $(DIST_DIRS) cp ../README.md {} \; && \ + $(DIST_DIRS) tar -zcf helm-${VERSION}-{}.tar.gz {} \; && \ + $(DIST_DIRS) zip -r helm-${VERSION}-{}.zip {} \; \ + ) + +.PHONY: checksum +checksum: + for f in _dist/*.{gz,zip} ; do \ + shasum -a 256 "$${f}" | awk '{print $$1}' > "$${f}.sha256" ; \ + done + +.PHONY: check-docker +check-docker: + @if [ -z $$(which docker) ]; then \ + echo "Missing \`docker\` client which is required for development"; \ + exit 2; \ + fi + +.PHONY: docker-binary +docker-binary: BINDIR = ./rootfs +docker-binary: GOFLAGS += -a -installsuffix cgo +docker-binary: + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build -o $(BINDIR)/tiller $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/tiller + +.PHONY: docker-build +docker-build: check-docker docker-binary + docker build --rm -t ${IMAGE} rootfs + docker tag ${IMAGE} ${MUTABLE_IMAGE} + +.PHONY: docker-binary-rudder +docker-binary-rudder: BINDIR = ./rootfs +docker-binary-rudder: GOFLAGS += -a -installsuffix cgo +docker-binary-rudder: + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 $(GO) build -o $(BINDIR)/rudder $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' k8s.io/helm/cmd/rudder + +.PHONY: docker-build-experimental +docker-build-experimental: check-docker docker-binary docker-binary-rudder + docker build --rm -t ${IMAGE} rootfs -f rootfs/Dockerfile.experimental + docker tag ${IMAGE} ${MUTABLE_IMAGE} + docker build --rm -t ${IMAGE_RUDDER} rootfs -f rootfs/Dockerfile.rudder + docker tag ${IMAGE_RUDDER} ${MUTABLE_IMAGE_RUDDER} + +.PHONY: test +test: build +test: TESTFLAGS += -race -v +test: test-style +test: test-unit + +.PHONY: test-unit +test-unit: + @echo + @echo "==> Running unit tests <==" + HELM_HOME=/no/such/dir $(GO) test $(GOFLAGS) -run $(TESTS) $(PKG) $(TESTFLAGS) + +.PHONY: test-style +test-style: + @scripts/validate-go.sh + @scripts/validate-license.sh + +.PHONY: protoc +protoc: + $(MAKE) -C _proto/ all + +.PHONY: docs +docs: build + @scripts/update-docs.sh + +.PHONY: verify-docs +verify-docs: build + @scripts/verify-docs.sh + +.PHONY: clean +clean: + @rm -rf $(BINDIR) ./rootfs/tiller ./_dist + +.PHONY: coverage +coverage: + @scripts/coverage.sh + +HAS_GLIDE := $(shell command -v glide;) +HAS_GOX := $(shell command -v gox;) +HAS_GIT := $(shell command -v git;) +HAS_HG := $(shell command -v hg;) + +.PHONY: bootstrap +bootstrap: +ifndef HAS_GLIDE + go get -u github.com/Masterminds/glide +endif +ifndef HAS_GOX + go get -u github.com/mitchellh/gox +endif + +ifndef HAS_GIT + $(error You must install Git) +endif +ifndef HAS_HG + $(error You must install Mercurial) +endif + glide install --strip-vendor + go build -o bin/protoc-gen-go ./vendor/github.com/golang/protobuf/protoc-gen-go + +include versioning.mk diff --git a/src/vendor/github.com/kubernetes/helm/OWNERS b/src/vendor/github.com/kubernetes/helm/OWNERS new file mode 100644 index 000000000..32b26efa2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/OWNERS @@ -0,0 +1,30 @@ +maintainers: + - adamreese + - bacongobbler + - jascott1 + - mattfarina + - michelleN + - nebril + - prydonius + - SlickNik + - technosophos + - thomastaylor312 + - viglesiasce +reviewers: + - adamreese + - bacongobbler + - fibonacci1729 + - jascott1 + - mattfarina + - michelleN + - migmartri + - nebril + - prydonius + - SlickNik + - technosophos + - thomastaylor312 + - viglesiasce +emeritus: + - migmartri + - seh + - vaikas-google diff --git a/src/vendor/github.com/kubernetes/helm/README.md b/src/vendor/github.com/kubernetes/helm/README.md new file mode 100644 index 000000000..1038ff9db --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/README.md @@ -0,0 +1,73 @@ +# Kubernetes Helm + +[![CircleCI](https://circleci.com/gh/kubernetes/helm.svg?style=svg)](https://circleci.com/gh/kubernetes/helm) +[![Go Report Card](https://goreportcard.com/badge/github.com/kubernetes/helm)](https://goreportcard.com/report/github.com/kubernetes/helm) +[![GoDoc](https://godoc.org/github.com/kubernetes/helm?status.svg)](https://godoc.org/github.com/kubernetes/helm) + +Helm is a tool for managing Kubernetes charts. Charts are packages of +pre-configured Kubernetes resources. + +Use Helm to: + +- Find and use [popular software packaged as Kubernetes charts](https://github.com/kubernetes/charts) +- Share your own applications as Kubernetes charts +- Create reproducible builds of your Kubernetes applications +- Intelligently manage your Kubernetes manifest files +- Manage releases of Helm packages + +## Helm in a Handbasket + +Helm is a tool that streamlines installing and managing Kubernetes applications. +Think of it like apt/yum/homebrew for Kubernetes. + +- Helm has two parts: a client (`helm`) and a server (`tiller`) +- Tiller runs inside of your Kubernetes cluster, and manages releases (installations) + of your charts. +- Helm runs on your laptop, CI/CD, or wherever you want it to run. +- Charts are Helm packages that contain at least two things: + - A description of the package (`Chart.yaml`) + - One or more templates, which contain Kubernetes manifest files +- Charts can be stored on disk, or fetched from remote chart repositories + (like Debian or RedHat packages) + +## Install + +Binary downloads of the Helm client can be found at the following links: + +- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-darwin-amd64.tar.gz) +- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-linux-amd64.tar.gz) +- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-linux-386.tar.gz) +- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.9.1-windows-amd64.tar.gz) + +Unpack the `helm` binary and add it to your PATH and you are good to go! +macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. + +To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide). + +See the [installation guide](https://docs.helm.sh/using_helm/#installing-helm) for more options, +including installing pre-releases. + +## Docs + +Get started with the [Quick Start guide](https://docs.helm.sh/using_helm/#quickstart-guide) or plunge into the [complete documentation](https://docs.helm.sh) + +## Roadmap + +The [Helm roadmap uses Github milestones](https://github.com/kubernetes/helm/milestones) to track the progress of the project. + +## Community, discussion, contribution, and support + +You can reach the Helm community and developers via the following channels: + +- [Kubernetes Slack](http://slack.k8s.io): + - #helm-users + - #helm-dev + - #charts +- Mailing Lists: + - [Helm Mailing List](https://lists.cncf.io/g/cncf-kubernetes-helm) + - [Kubernetes SIG Apps Mailing List](https://groups.google.com/forum/#!forum/kubernetes-sig-apps) +- Developer Call: Thursdays at 9:30-10:00 Pacific. [https://zoom.us/j/4526666954](https://zoom.us/j/4526666954) + +### Code of conduct + +Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md). diff --git a/src/vendor/github.com/kubernetes/helm/_proto/Makefile b/src/vendor/github.com/kubernetes/helm/_proto/Makefile new file mode 100644 index 000000000..39f72d441 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/Makefile @@ -0,0 +1,58 @@ +space := $(empty) $(empty) +comma := , +empty := + +import_path = k8s.io/helm/pkg/proto/hapi + +dst = ../pkg/proto +target = go +plugins = grpc + +chart_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(chart_pkg),$(addprefix M,$(chart_pbs)))) +chart_pbs = $(sort $(wildcard hapi/chart/*.proto)) +chart_pkg = chart + +release_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(release_pkg),$(addprefix M,$(release_pbs)))) +release_pbs = $(sort $(wildcard hapi/release/*.proto)) +release_pkg = release + +services_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(services_pkg),$(addprefix M,$(services_pbs)))) +services_pbs = $(sort $(wildcard hapi/services/*.proto)) +services_pkg = services + +rudder_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(rudder_pkg),$(addprefix M,$(rudder_pbs)))) +rudder_pbs = $(sort $(wildcard hapi/rudder/*.proto)) +rudder_pkg = rudder + +version_ias = $(subst $(space),$(comma),$(addsuffix =$(import_path)/$(version_pkg),$(addprefix M,$(version_pbs)))) +version_pbs = $(sort $(wildcard hapi/version/*.proto)) +version_pkg = version + +google_deps = Mgoogle/protobuf/timestamp.proto=github.com/golang/protobuf/ptypes/timestamp,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any + +.PHONY: all +all: chart release services rudder version + +.PHONY: chart +chart: + PATH=../bin:$(PATH) protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias):$(dst) $(chart_pbs) + +.PHONY: release +release: + PATH=../bin:$(PATH) protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias),$(version_ias):$(dst) $(release_pbs) + +.PHONY: services +services: + PATH=../bin:$(PATH) protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias),$(version_ias),$(release_ias):$(dst) $(services_pbs) + +.PHONY: rudder +rudder: + PATH=../bin:$(PATH) protoc --$(target)_out=plugins=$(plugins),$(google_deps),$(chart_ias),$(version_ias),$(release_ias):$(dst) $(rudder_pbs) + +.PHONY: version +version: + PATH=../bin:$(PATH) protoc --$(target)_out=plugins=$(plugins),$(google_deps):$(dst) $(version_pbs) + +.PHONY: clean +clean: + @rm -rf $(dst)/hapi 2>/dev/null diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/chart.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/chart.proto new file mode 100644 index 000000000..9b838fd1a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/chart.proto @@ -0,0 +1,44 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.chart; + +import "hapi/chart/config.proto"; +import "hapi/chart/metadata.proto"; +import "hapi/chart/template.proto"; +import "google/protobuf/any.proto"; + +option go_package = "chart"; + +// Chart is a helm package that contains metadata, a default config, zero or more +// optionally parameterizable templates, and zero or more charts (dependencies). +message Chart { + // Contents of the Chartfile. + hapi.chart.Metadata metadata = 1; + + // Templates for this chart. + repeated hapi.chart.Template templates = 2; + + // Charts that this chart depends on. + repeated Chart dependencies = 3; + + // Default config for this template. + hapi.chart.Config values = 4; + + // Miscellaneous files in a chart archive, + // e.g. README, LICENSE, etc. + repeated google.protobuf.Any files = 5; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/config.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/config.proto new file mode 100644 index 000000000..a1404476b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/config.proto @@ -0,0 +1,31 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// Config supplies values to the parametrizable templates of a chart. +message Config { + string raw = 1; + + map values = 2; +} + +// Value describes a configuration value as a string. +message Value { + string value = 1; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/metadata.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/metadata.proto new file mode 100644 index 000000000..49d6a217a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/metadata.proto @@ -0,0 +1,93 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// Maintainer describes a Chart maintainer. +message Maintainer { + // Name is a user name or organization name + string name = 1; + + // Email is an optional email address to contact the named maintainer + string email = 2; + + // Url is an optional URL to an address for the named maintainer + string url = 3; +} + +// Metadata for a Chart file. This models the structure of a Chart.yaml file. +// +// Spec: https://k8s.io/helm/blob/master/docs/design/chart_format.md#the-chart-file +message Metadata { + enum Engine { + UNKNOWN = 0; + GOTPL = 1; + } + // The name of the chart + string name = 1; + + // The URL to a relevant project page, git repo, or contact person + string home = 2; + + // Source is the URL to the source code of this chart + repeated string sources = 3; + + // A SemVer 2 conformant version string of the chart + string version = 4; + + // A one-sentence description of the chart + string description = 5; + + // A list of string keywords + repeated string keywords = 6; + + // A list of name and URL/email address combinations for the maintainer(s) + repeated Maintainer maintainers = 7; + + // The name of the template engine to use. Defaults to 'gotpl'. + string engine = 8; + + // The URL to an icon file. + string icon = 9; + + // The API Version of this chart. + string apiVersion = 10; + + // The condition to check to enable chart + string condition = 11; + + // The tags to check to enable chart + string tags = 12; + + // The version of the application enclosed inside of this chart. + string appVersion = 13; + + // Whether or not this chart is deprecated + bool deprecated = 14; + + // TillerVersion is a SemVer constraints on what version of Tiller is required. + // See SemVer ranges here: https://github.com/Masterminds/semver#basic-comparisons + string tillerVersion = 15; + + // Annotations are additional mappings uninterpreted by Tiller, + // made available for inspection by other applications. + map annotations = 16; + + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + string kubeVersion = 17; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/template.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/template.proto new file mode 100644 index 000000000..d13543ea9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/chart/template.proto @@ -0,0 +1,31 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.chart; + +option go_package = "chart"; + +// Template represents a template as a name/value pair. +// +// By convention, name is a relative path within the scope of the chart's +// base directory. +message Template { + // Name is the path-like name of the template. + string name = 1; + + // Data is the template as byte data. + bytes data = 2; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/hook.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/hook.proto new file mode 100644 index 000000000..f0332ecb8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/hook.proto @@ -0,0 +1,58 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; + +option go_package = "release"; + +// Hook defines a hook object. +message Hook { + enum Event { + UNKNOWN = 0; + PRE_INSTALL = 1; + POST_INSTALL = 2; + PRE_DELETE = 3; + POST_DELETE = 4; + PRE_UPGRADE = 5; + POST_UPGRADE = 6; + PRE_ROLLBACK = 7; + POST_ROLLBACK = 8; + RELEASE_TEST_SUCCESS = 9; + RELEASE_TEST_FAILURE = 10; + } + enum DeletePolicy { + SUCCEEDED = 0; + FAILED = 1; + BEFORE_HOOK_CREATION = 2; + } + string name = 1; + // Kind is the Kubernetes kind. + string kind = 2; + // Path is the chart-relative path to the template. + string path = 3; + // Manifest is the manifest contents. + string manifest = 4; + // Events are the events that this hook fires on. + repeated Event events = 5; + // LastRun indicates the date/time this was last run. + google.protobuf.Timestamp last_run = 6; + // Weight indicates the sort order for execution among similar Hook type + int32 weight = 7; + // DeletePolicies are the policies that indicate when to delete the hook + repeated DeletePolicy delete_policies = 8; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/info.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/info.proto new file mode 100644 index 000000000..e23175d3d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/info.proto @@ -0,0 +1,37 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; +import "hapi/release/status.proto"; + +option go_package = "release"; + +// Info describes release information. +message Info { + Status status = 1; + + google.protobuf.Timestamp first_deployed = 2; + + google.protobuf.Timestamp last_deployed = 3; + + // Deleted tracks when this object was deleted. + google.protobuf.Timestamp deleted = 4; + + // Description is human-friendly "log entry" about this release. + string Description = 5; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/release.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/release.proto new file mode 100644 index 000000000..4a6afa0fe --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/release.proto @@ -0,0 +1,53 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "hapi/release/hook.proto"; +import "hapi/release/info.proto"; +import "hapi/chart/config.proto"; +import "hapi/chart/chart.proto"; + +option go_package = "release"; + +// Release describes a deployment of a chart, together with the chart +// and the variables used to deploy that chart. +message Release { + // Name is the name of the release + string name = 1; + + // Info provides information about a release + hapi.release.Info info = 2; + + // Chart is the chart that was released. + hapi.chart.Chart chart = 3; + + // Config is the set of extra Values added to the chart. + // These values override the default values inside of the chart. + hapi.chart.Config config = 4; + + // Manifest is the string representation of the rendered template. + string manifest = 5; + + // Hooks are all of the hooks declared for this release. + repeated hapi.release.Hook hooks = 6; + + // Version is an int32 which represents the version of the release. + int32 version = 7; + + // Namespace is the kubernetes namespace of the release. + string namespace = 8; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/status.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/status.proto new file mode 100644 index 000000000..ef28a233c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/status.proto @@ -0,0 +1,61 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "hapi/release/test_suite.proto"; + +import "google/protobuf/any.proto"; + +option go_package = "release"; + +// Status defines the status of a release. +message Status { + enum Code { + // Status_UNKNOWN indicates that a release is in an uncertain state. + UNKNOWN = 0; + // Status_DEPLOYED indicates that the release has been pushed to Kubernetes. + DEPLOYED = 1; + // Status_DELETED indicates that a release has been deleted from Kubermetes. + DELETED = 2; + // Status_SUPERSEDED indicates that this release object is outdated and a newer one exists. + SUPERSEDED = 3; + // Status_FAILED indicates that the release was not successfully deployed. + FAILED = 4; + // Status_DELETING indicates that a delete operation is underway. + DELETING = 5; + // Status_PENDING_INSTALL indicates that an install operation is underway. + PENDING_INSTALL = 6; + // Status_PENDING_UPGRADE indicates that an upgrade operation is underway. + PENDING_UPGRADE = 7; + // Status_PENDING_ROLLBACK indicates that an rollback operation is underway. + PENDING_ROLLBACK = 8; + } + + Code code = 1; + + // Deprecated + // google.protobuf.Any details = 2; + + // Cluster resources as kubectl would print them. + string resources = 3; + + // Contains the rendered templates/NOTES.txt if available + string notes = 4; + + // LastTestSuiteRun provides results on the last test run on a release + hapi.release.TestSuite last_test_suite_run = 5; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_run.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_run.proto new file mode 100644 index 000000000..60734ae03 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_run.proto @@ -0,0 +1,37 @@ + +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; + +option go_package = "release"; + +message TestRun { + enum Status { + UNKNOWN = 0; + SUCCESS = 1; + FAILURE = 2; + RUNNING = 3; + } + + string name = 1; + Status status = 2; + string info = 3; + google.protobuf.Timestamp started_at = 4; + google.protobuf.Timestamp completed_at = 5; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_suite.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_suite.proto new file mode 100644 index 000000000..2f6feb08c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/release/test_suite.proto @@ -0,0 +1,34 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.release; + +import "google/protobuf/timestamp.proto"; +import "hapi/release/test_run.proto"; + +option go_package = "release"; + +// TestSuite comprises of the last run of the pre-defined test suite of a release version +message TestSuite { + // StartedAt indicates the date/time this test suite was kicked off + google.protobuf.Timestamp started_at = 1; + + // CompletedAt indicates the date/time this test suite was completed + google.protobuf.Timestamp completed_at = 2; + + // Results are the results of each segment of the test + repeated hapi.release.TestRun results = 3; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/rudder/rudder.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/rudder/rudder.proto new file mode 100644 index 000000000..3a37c9e99 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/rudder/rudder.proto @@ -0,0 +1,120 @@ +// Copyright 2017 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.services.rudder; + +import "hapi/release/info.proto"; +import "hapi/release/release.proto"; + +option go_package = "rudder"; + +service ReleaseModuleService { + rpc Version(VersionReleaseRequest) returns (VersionReleaseResponse) { + } + + // InstallRelease requests installation of a chart as a new release. + rpc InstallRelease(InstallReleaseRequest) returns (InstallReleaseResponse) { + } + + // DeleteRelease requests deletion of a named release. + rpc DeleteRelease(DeleteReleaseRequest) returns (DeleteReleaseResponse) { + } + + // RollbackRelease rolls back a release to a previous version. + rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) { + } + + // UpgradeRelease updates release content. + rpc UpgradeRelease(UpgradeReleaseRequest) returns (UpgradeReleaseResponse) { + } + + // ReleaseStatus retrieves release status. + rpc ReleaseStatus(ReleaseStatusRequest) returns (ReleaseStatusResponse) { + } +} + +message Result { + enum Status { + // No status set + UNKNOWN = 0; + // Operation was successful + SUCCESS = 1; + // Operation had no results (e.g. upgrade identical, rollback to same, delete non-existent) + UNCHANGED = 2; + // Operation failed + ERROR = 3; + } + string info = 1; + repeated string log = 2; +} + +message VersionReleaseRequest { +} + +message VersionReleaseResponse { + string name = 1; // The canonical name of the release module + string version = 2; // The version of the release module +} + +message InstallReleaseRequest { + hapi.release.Release release = 1; +} +message InstallReleaseResponse { + hapi.release.Release release = 1; + Result result = 2; +} + +message DeleteReleaseRequest { + hapi.release.Release release = 1; +} +message DeleteReleaseResponse { + hapi.release.Release release = 1; + Result result = 2; +} + +message UpgradeReleaseRequest{ + hapi.release.Release current = 1; + hapi.release.Release target = 2; + int64 Timeout = 3; + bool Wait = 4; + bool Recreate = 5; + bool Force = 6; +} +message UpgradeReleaseResponse{ + hapi.release.Release release = 1; + Result result = 2; +} + +message RollbackReleaseRequest{ + hapi.release.Release current = 1; + hapi.release.Release target = 2; + int64 Timeout = 3; + bool Wait = 4; + bool Recreate = 5; + bool Force = 6; +} +message RollbackReleaseResponse{ + hapi.release.Release release = 1; + Result result = 2; +} + +message ReleaseStatusRequest{ + hapi.release.Release release = 1; +} +message ReleaseStatusResponse{ + hapi.release.Release release = 1; + hapi.release.Info info = 2; +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/services/tiller.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/services/tiller.proto new file mode 100644 index 000000000..5897676ab --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/services/tiller.proto @@ -0,0 +1,337 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.services.tiller; + +import "hapi/chart/chart.proto"; +import "hapi/chart/config.proto"; +import "hapi/release/release.proto"; +import "hapi/release/info.proto"; +import "hapi/release/test_run.proto"; +import "hapi/release/status.proto"; +import "hapi/version/version.proto"; + +option go_package = "services"; + +// ReleaseService is the service that a helm application uses to mutate, +// query, and manage releases. +// +// Release: A named installation composed of a chart and +// config. At any given time a release has one +// chart and one config. +// +// Config: A config is a YAML file that supplies values +// to the parametrizable templates of a chart. +// +// Chart: A chart is a helm package that contains +// metadata, a default config, zero or more +// optionally parameterizable templates, and +// zero or more charts (dependencies). +service ReleaseService { + // ListReleases retrieves release history. + // TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + rpc ListReleases(ListReleasesRequest) returns (stream ListReleasesResponse) { + } + + // GetReleasesStatus retrieves status information for the specified release. + rpc GetReleaseStatus(GetReleaseStatusRequest) returns (GetReleaseStatusResponse) { + } + + // GetReleaseContent retrieves the release content (chart + value) for the specified release. + rpc GetReleaseContent(GetReleaseContentRequest) returns (GetReleaseContentResponse) { + } + + // UpdateRelease updates release content. + rpc UpdateRelease(UpdateReleaseRequest) returns (UpdateReleaseResponse) { + } + + // InstallRelease requests installation of a chart as a new release. + rpc InstallRelease(InstallReleaseRequest) returns (InstallReleaseResponse) { + } + + // UninstallRelease requests deletion of a named release. + rpc UninstallRelease(UninstallReleaseRequest) returns (UninstallReleaseResponse) { + } + + // GetVersion returns the current version of the server. + rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) { + } + + // RollbackRelease rolls back a release to a previous version. + rpc RollbackRelease(RollbackReleaseRequest) returns (RollbackReleaseResponse) { + } + + // ReleaseHistory retrieves a releasse's history. + rpc GetHistory(GetHistoryRequest) returns (GetHistoryResponse) { + } + + // RunReleaseTest executes the tests defined of a named release + rpc RunReleaseTest(TestReleaseRequest) returns (stream TestReleaseResponse) { + } +} + +// ListReleasesRequest requests a list of releases. +// +// Releases can be retrieved in chunks by setting limit and offset. +// +// Releases can be sorted according to a few pre-determined sort stategies. +message ListReleasesRequest { + // Limit is the maximum number of releases to be returned. + int64 limit = 1; + + // Offset is the last release name that was seen. The next listing + // operation will start with the name after this one. + // Example: If list one returns albert, bernie, carl, and sets 'next: dennis'. + // dennis is the offset. Supplying 'dennis' for the next request should + // cause the next batch to return a set of results starting with 'dennis'. + string offset = 2; + + // SortBy is the sort field that the ListReleases server should sort data before returning. + ListSort.SortBy sort_by = 3; + + // Filter is a regular expression used to filter which releases should be listed. + // + // Anything that matches the regexp will be included in the results. + string filter = 4; + + // SortOrder is the ordering directive used for sorting. + ListSort.SortOrder sort_order = 5; + + repeated hapi.release.Status.Code status_codes = 6; + // Namespace is the filter to select releases only from a specific namespace. + string namespace = 7; +} + +// ListSort defines sorting fields on a release list. +message ListSort{ + // SortBy defines sort operations. + enum SortBy { + UNKNOWN = 0; + NAME = 1; + LAST_RELEASED = 2; + } + + // SortOrder defines sort orders to augment sorting operations. + enum SortOrder { + ASC = 0; + DESC = 1; + } +} + +// ListReleasesResponse is a list of releases. +message ListReleasesResponse { + // Count is the expected total number of releases to be returned. + int64 count = 1; + + // Next is the name of the next release. If this is other than an empty + // string, it means there are more results. + string next = 2; + + // Total is the total number of queryable releases. + int64 total = 3; + + // Releases is the list of found release objects. + repeated hapi.release.Release releases = 4; +} + +// GetReleaseStatusRequest is a request to get the status of a release. +message GetReleaseStatusRequest { + // Name is the name of the release + string name = 1; + // Version is the version of the release + int32 version = 2; +} + +// GetReleaseStatusResponse is the response indicating the status of the named release. +message GetReleaseStatusResponse { + // Name is the name of the release. + string name = 1; + + // Info contains information about the release. + hapi.release.Info info = 2; + + // Namespace the release was released into + string namespace = 3; +} + +// GetReleaseContentRequest is a request to get the contents of a release. +message GetReleaseContentRequest { + // The name of the release + string name = 1; + // Version is the version of the release + int32 version = 2; +} + +// GetReleaseContentResponse is a response containing the contents of a release. +message GetReleaseContentResponse { + // The release content + hapi.release.Release release = 1; +} + +// UpdateReleaseRequest updates a release. +message UpdateReleaseRequest { + // The name of the release + string name = 1; + // Chart is the protobuf representation of a chart. + hapi.chart.Chart chart = 2; + // Values is a string containing (unparsed) YAML values. + hapi.chart.Config values = 3; + // dry_run, if true, will run through the release logic, but neither create + bool dry_run = 4; + // DisableHooks causes the server to skip running any hooks for the upgrade. + bool disable_hooks = 5; + // Performs pods restart for resources if applicable + bool recreate = 6; + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 7; + // ResetValues will cause Tiller to ignore stored values, resetting to default values. + bool reset_values = 8; + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + bool wait = 9; + // ReuseValues will cause Tiller to reuse the values from the last release. + // This is ignored if reset_values is set. + bool reuse_values = 10; + // Force resource update through delete/recreate if needed. + bool force = 11; +} + +// UpdateReleaseResponse is the response to an update request. +message UpdateReleaseResponse { + hapi.release.Release release = 1; +} + +message RollbackReleaseRequest { + // The name of the release + string name = 1; + // dry_run, if true, will run through the release logic but no create + bool dry_run = 2; + // DisableHooks causes the server to skip running any hooks for the rollback + bool disable_hooks = 3; + // Version is the version of the release to deploy. + int32 version = 4; + // Performs pods restart for resources if applicable + bool recreate = 5; + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 6; + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + bool wait = 7; + // Force resource update through delete/recreate if needed. + bool force = 8; +} + +// RollbackReleaseResponse is the response to an update request. +message RollbackReleaseResponse { + hapi.release.Release release = 1; +} + +// InstallReleaseRequest is the request for an installation of a chart. +message InstallReleaseRequest { + // Chart is the protobuf representation of a chart. + hapi.chart.Chart chart = 1; + // Values is a string containing (unparsed) YAML values. + hapi.chart.Config values = 2; + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + bool dry_run = 3; + + // Name is the candidate release name. This must be unique to the + // namespace, otherwise the server will return an error. If it is not + // supplied, the server will autogenerate one. + string name = 4; + + // DisableHooks causes the server to skip running any hooks for the install. + bool disable_hooks = 5; + + // Namepace is the kubernetes namespace of the release. + string namespace = 6; + + // ReuseName requests that Tiller re-uses a name, instead of erroring out. + bool reuse_name = 7; + + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 8; + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + bool wait = 9; +} + +// InstallReleaseResponse is the response from a release installation. +message InstallReleaseResponse { + hapi.release.Release release = 1; +} + +// UninstallReleaseRequest represents a request to uninstall a named release. +message UninstallReleaseRequest { + // Name is the name of the release to delete. + string name = 1; + // DisableHooks causes the server to skip running any hooks for the uninstall. + bool disable_hooks = 2; + // Purge removes the release from the store and make its name free for later use. + bool purge = 3; + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 4; +} + +// UninstallReleaseResponse represents a successful response to an uninstall request. +message UninstallReleaseResponse { + // Release is the release that was marked deleted. + hapi.release.Release release = 1; + // Info is an uninstall message + string info = 2; +} + +// GetVersionRequest requests for version information. +message GetVersionRequest { +} + +message GetVersionResponse { + hapi.version.Version Version = 1; +} + +// GetHistoryRequest requests a release's history. +message GetHistoryRequest { + // The name of the release. + string name = 1; + // The maximum number of releases to include. + int32 max = 2; +} + +// GetHistoryResponse is received in response to a GetHistory rpc. +message GetHistoryResponse { + repeated hapi.release.Release releases = 1; +} + +// TestReleaseRequest is a request to get the status of a release. +message TestReleaseRequest { + // Name is the name of the release + string name = 1; + // timeout specifies the max amount of time any kubernetes client command can run. + int64 timeout = 2; + // cleanup specifies whether or not to attempt pod deletion after test completes + bool cleanup = 3; +} + +// TestReleaseResponse represents a message from executing a test +message TestReleaseResponse { + string msg = 1; + hapi.release.TestRun.Status status = 2; + +} diff --git a/src/vendor/github.com/kubernetes/helm/_proto/hapi/version/version.proto b/src/vendor/github.com/kubernetes/helm/_proto/hapi/version/version.proto new file mode 100644 index 000000000..0ae0985b7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/_proto/hapi/version/version.proto @@ -0,0 +1,26 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// +// 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. + +syntax = "proto3"; + +package hapi.version; + +option go_package = "version"; + +message Version { + // Sem ver string for the version + string sem_ver = 1; + string git_commit = 2; + string git_tree_state = 3; +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/completion.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/completion.go new file mode 100644 index 000000000..b1cd04140 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/completion.go @@ -0,0 +1,229 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "fmt" + "io" + + "github.com/spf13/cobra" +) + +const completionDesc = ` +Generate autocompletions script for Helm for the specified shell (bash or zsh). + +This command can generate shell autocompletions. e.g. + + $ helm completion bash + +Can be sourced as such + + $ source <(helm completion bash) +` + +var ( + completionShells = map[string]func(out io.Writer, cmd *cobra.Command) error{ + "bash": runCompletionBash, + "zsh": runCompletionZsh, + } +) + +func newCompletionCmd(out io.Writer) *cobra.Command { + shells := []string{} + for s := range completionShells { + shells = append(shells, s) + } + + cmd := &cobra.Command{ + Use: "completion SHELL", + Short: "Generate autocompletions script for the specified shell (bash or zsh)", + Long: completionDesc, + RunE: func(cmd *cobra.Command, args []string) error { + return runCompletion(out, cmd, args) + }, + ValidArgs: shells, + } + + return cmd +} + +func runCompletion(out io.Writer, cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("shell not specified") + } + if len(args) > 1 { + return fmt.Errorf("too many arguments, expected only the shell type") + } + run, found := completionShells[args[0]] + if !found { + return fmt.Errorf("unsupported shell type %q", args[0]) + } + + return run(out, cmd) +} + +func runCompletionBash(out io.Writer, cmd *cobra.Command) error { + return cmd.Root().GenBashCompletion(out) +} + +func runCompletionZsh(out io.Writer, cmd *cobra.Command) error { + zshInitialization := ` +__helm_bash_source() { + alias shopt=':' + alias _expand=_bash_expand + alias _complete=_bash_comp + emulate -L sh + setopt kshglob noshglob braceexpand + source "$@" +} +__helm_type() { + # -t is not supported by zsh + if [ "$1" == "-t" ]; then + shift + # fake Bash 4 to disable "complete -o nospace". Instead + # "compopt +-o nospace" is used in the code to toggle trailing + # spaces. We don't support that, but leave trailing spaces on + # all the time + if [ "$1" = "__helm_compopt" ]; then + echo builtin + return 0 + fi + fi + type "$@" +} +__helm_compgen() { + local completions w + completions=( $(compgen "$@") ) || return $? + # filter by given word as prefix + while [[ "$1" = -* && "$1" != -- ]]; do + shift + shift + done + if [[ "$1" == -- ]]; then + shift + fi + for w in "${completions[@]}"; do + if [[ "${w}" = "$1"* ]]; then + echo "${w}" + fi + done +} +__helm_compopt() { + true # don't do anything. Not supported by bashcompinit in zsh +} +__helm_declare() { + if [ "$1" == "-F" ]; then + whence -w "$@" + else + builtin declare "$@" + fi +} +__helm_ltrim_colon_completions() +{ + if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then + # Remove colon-word prefix from COMPREPLY items + local colon_word=${1%${1##*:}} + local i=${#COMPREPLY[*]} + while [[ $((--i)) -ge 0 ]]; do + COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"} + done + fi +} +__helm_get_comp_words_by_ref() { + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[${COMP_CWORD}-1]}" + words=("${COMP_WORDS[@]}") + cword=("${COMP_CWORD[@]}") +} +__helm_filedir() { + local RET OLD_IFS w qw + __debug "_filedir $@ cur=$cur" + if [[ "$1" = \~* ]]; then + # somehow does not work. Maybe, zsh does not call this at all + eval echo "$1" + return 0 + fi + OLD_IFS="$IFS" + IFS=$'\n' + if [ "$1" = "-d" ]; then + shift + RET=( $(compgen -d) ) + else + RET=( $(compgen -f) ) + fi + IFS="$OLD_IFS" + IFS="," __debug "RET=${RET[@]} len=${#RET[@]}" + for w in ${RET[@]}; do + if [[ ! "${w}" = "${cur}"* ]]; then + continue + fi + if eval "[[ \"\${w}\" = *.$1 || -d \"\${w}\" ]]"; then + qw="$(__helm_quote "${w}")" + if [ -d "${w}" ]; then + COMPREPLY+=("${qw}/") + else + COMPREPLY+=("${qw}") + fi + fi + done +} +__helm_quote() { + if [[ $1 == \'* || $1 == \"* ]]; then + # Leave out first character + printf %q "${1:1}" + else + printf %q "$1" + fi +} +autoload -U +X bashcompinit && bashcompinit +# use word boundary patterns for BSD or GNU sed +LWORD='[[:<:]]' +RWORD='[[:>:]]' +if sed --help 2>&1 | grep -q GNU; then + LWORD='\<' + RWORD='\>' +fi +__helm_convert_bash_to_zsh() { + sed \ + -e 's/declare -F/whence -w/' \ + -e 's/_get_comp_words_by_ref "\$@"/_get_comp_words_by_ref "\$*"/' \ + -e 's/local \([a-zA-Z0-9_]*\)=/local \1; \1=/' \ + -e 's/flags+=("\(--.*\)=")/flags+=("\1"); two_word_flags+=("\1")/' \ + -e 's/must_have_one_flag+=("\(--.*\)=")/must_have_one_flag+=("\1")/' \ + -e "s/${LWORD}_filedir${RWORD}/__helm_filedir/g" \ + -e "s/${LWORD}_get_comp_words_by_ref${RWORD}/__helm_get_comp_words_by_ref/g" \ + -e "s/${LWORD}__ltrim_colon_completions${RWORD}/__helm_ltrim_colon_completions/g" \ + -e "s/${LWORD}compgen${RWORD}/__helm_compgen/g" \ + -e "s/${LWORD}compopt${RWORD}/__helm_compopt/g" \ + -e "s/${LWORD}declare${RWORD}/__helm_declare/g" \ + -e "s/\\\$(type${RWORD}/\$(__helm_type/g" \ + <<'BASH_COMPLETION_EOF' +` + out.Write([]byte(zshInitialization)) + + buf := new(bytes.Buffer) + cmd.Root().GenBashCompletion(buf) + out.Write(buf.Bytes()) + + zshTail := ` +BASH_COMPLETION_EOF +} +__helm_bash_source <(__helm_convert_bash_to_zsh) +` + out.Write([]byte(zshTail)) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/create.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/create.go new file mode 100644 index 000000000..556ff171d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/create.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "path/filepath" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const createDesc = ` +This command creates a chart directory along with the common files and +directories used in a chart. + +For example, 'helm create foo' will create a directory structure that looks +something like this: + + foo/ + | + |- .helmignore # Contains patterns to ignore when packaging Helm charts. + | + |- Chart.yaml # Information about your chart + | + |- values.yaml # The default values for your templates + | + |- charts/ # Charts that this chart depends on + | + |- templates/ # The template files + +'helm create' takes a path for an argument. If directories in the given path +do not exist, Helm will attempt to create them as it goes. If the given +destination exists and there are files in that directory, conflicting files +will be overwritten, but other files will be left alone. +` + +type createCmd struct { + home helmpath.Home + name string + out io.Writer + starter string +} + +func newCreateCmd(out io.Writer) *cobra.Command { + cc := &createCmd{out: out} + + cmd := &cobra.Command{ + Use: "create NAME", + Short: "create a new chart with the given name", + Long: createDesc, + RunE: func(cmd *cobra.Command, args []string) error { + cc.home = settings.Home + if len(args) == 0 { + return errors.New("the name of the new chart is required") + } + cc.name = args[0] + return cc.run() + }, + } + + cmd.Flags().StringVarP(&cc.starter, "starter", "p", "", "the named Helm starter scaffold") + return cmd +} + +func (c *createCmd) run() error { + fmt.Fprintf(c.out, "Creating %s\n", c.name) + + chartname := filepath.Base(c.name) + cfile := &chart.Metadata{ + Name: chartname, + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + AppVersion: "1.0", + ApiVersion: chartutil.ApiVersionV1, + } + + if c.starter != "" { + // Create from the starter + lstarter := filepath.Join(c.home.Starters(), c.starter) + return chartutil.CreateFrom(cfile, filepath.Dir(c.name), lstarter) + } + + _, err := chartutil.Create(cfile, filepath.Dir(c.name)) + return err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/create_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/create_test.go new file mode 100644 index 000000000..84ccebece --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/create_test.go @@ -0,0 +1,164 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestCreateCmd(t *testing.T) { + cname := "testchart" + // Make a temp dir + tdir, err := ioutil.TempDir("", "helm-create-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + // CD into it + pwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(tdir); err != nil { + t.Fatal(err) + } + defer os.Chdir(pwd) + + // Run a create + cmd := newCreateCmd(ioutil.Discard) + if err := cmd.RunE(cmd, []string{cname}); err != nil { + t.Errorf("Failed to run create: %s", err) + return + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + c, err := chartutil.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + if c.Metadata.Name != cname { + t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) + } + if c.Metadata.ApiVersion != chartutil.ApiVersionV1 { + t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion) + } +} + +func TestCreateStarterCmd(t *testing.T) { + cname := "testchart" + // Make a temp dir + tdir, err := ioutil.TempDir("", "helm-create-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + thome, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(thome.String()) + cleanup() + }() + + settings.Home = thome + + // Create a starter. + starterchart := filepath.Join(thome.String(), "starters") + os.Mkdir(starterchart, 0755) + if dest, err := chartutil.Create(&chart.Metadata{Name: "starterchart"}, starterchart); err != nil { + t.Fatalf("Could not create chart: %s", err) + } else { + t.Logf("Created %s", dest) + } + tplpath := filepath.Join(starterchart, "starterchart", "templates", "foo.tpl") + if err := ioutil.WriteFile(tplpath, []byte("test"), 0755); err != nil { + t.Fatalf("Could not write template: %s", err) + } + + // CD into it + pwd, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + if err := os.Chdir(tdir); err != nil { + t.Fatal(err) + } + defer os.Chdir(pwd) + + // Run a create + cmd := newCreateCmd(ioutil.Discard) + cmd.ParseFlags([]string{"--starter", "starterchart"}) + if err := cmd.RunE(cmd, []string{cname}); err != nil { + t.Errorf("Failed to run create: %s", err) + return + } + + // Test that the chart is there + if fi, err := os.Stat(cname); err != nil { + t.Fatalf("no chart directory: %s", err) + } else if !fi.IsDir() { + t.Fatalf("chart is not directory") + } + + c, err := chartutil.LoadDir(cname) + if err != nil { + t.Fatal(err) + } + + if c.Metadata.Name != cname { + t.Errorf("Expected %q name, got %q", cname, c.Metadata.Name) + } + if c.Metadata.ApiVersion != chartutil.ApiVersionV1 { + t.Errorf("Wrong API version: %q", c.Metadata.ApiVersion) + } + + if l := len(c.Templates); l != 6 { + t.Errorf("Expected 5 templates, got %d", l) + } + + found := false + for _, tpl := range c.Templates { + if tpl.Name == "templates/foo.tpl" { + found = true + data := tpl.Data + if string(data) != "test" { + t.Errorf("Expected template 'test', got %q", string(data)) + } + } + } + if !found { + t.Error("Did not find foo.tpl") + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/delete.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/delete.go new file mode 100755 index 000000000..e0ac8c4f6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/delete.go @@ -0,0 +1,101 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +const deleteDesc = ` +This command takes a release name, and then deletes the release from Kubernetes. +It removes all of the resources associated with the last release of the chart. + +Use the '--dry-run' flag to see which releases will be deleted without actually +deleting them. +` + +type deleteCmd struct { + name string + dryRun bool + disableHooks bool + purge bool + timeout int64 + + out io.Writer + client helm.Interface +} + +func newDeleteCmd(c helm.Interface, out io.Writer) *cobra.Command { + del := &deleteCmd{ + out: out, + client: c, + } + + cmd := &cobra.Command{ + Use: "delete [flags] RELEASE_NAME [...]", + Aliases: []string{"del"}, + SuggestFor: []string{"remove", "rm"}, + Short: "given a release name, delete the release from Kubernetes", + Long: deleteDesc, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("command 'delete' requires a release name") + } + del.client = ensureHelmClient(del.client) + + for i := 0; i < len(args); i++ { + del.name = args[i] + if err := del.run(); err != nil { + return err + } + + fmt.Fprintf(out, "release \"%s\" deleted\n", del.name) + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVar(&del.dryRun, "dry-run", false, "simulate a delete") + f.BoolVar(&del.disableHooks, "no-hooks", false, "prevent hooks from running during deletion") + f.BoolVar(&del.purge, "purge", false, "remove the release from the store and make its name free for later use") + f.Int64Var(&del.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") + + return cmd +} + +func (d *deleteCmd) run() error { + opts := []helm.DeleteOption{ + helm.DeleteDryRun(d.dryRun), + helm.DeleteDisableHooks(d.disableHooks), + helm.DeletePurge(d.purge), + helm.DeleteTimeout(d.timeout), + } + res, err := d.client.DeleteRelease(d.name, opts...) + if res != nil && res.Info != "" { + fmt.Fprintln(d.out, res.Info) + } + + return prettyError(err) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/delete_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/delete_test.go new file mode 100644 index 000000000..829d17906 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/delete_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestDelete(t *testing.T) { + + tests := []releaseCase{ + { + name: "basic delete", + args: []string{"aeneas"}, + flags: []string{}, + expected: "", // Output of a delete is an empty string and exit 0. + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, + }, + { + name: "delete with timeout", + args: []string{"aeneas"}, + flags: []string{"--timeout", "120"}, + expected: "", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, + }, + { + name: "delete without hooks", + args: []string{"aeneas"}, + flags: []string{"--no-hooks"}, + expected: "", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, + }, + { + name: "purge", + args: []string{"aeneas"}, + flags: []string{"--purge"}, + expected: "", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, + }, + { + name: "delete without release", + args: []string{}, + err: true, + }, + } + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newDeleteCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency.go new file mode 100644 index 000000000..231659691 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency.go @@ -0,0 +1,276 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/Masterminds/semver" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" +) + +const dependencyDesc = ` +Manage the dependencies of a chart. + +Helm charts store their dependencies in 'charts/'. For chart developers, it is +often easier to manage a single dependency file ('requirements.yaml') +which declares all dependencies. + +The dependency commands operate on that file, making it easy to synchronize +between the desired dependencies and the actual dependencies stored in the +'charts/' directory. + +A 'requirements.yaml' file is a YAML file in which developers can declare chart +dependencies, along with the location of the chart and the desired version. +For example, this requirements file declares two dependencies: + + # requirements.yaml + dependencies: + - name: nginx + version: "1.2.3" + repository: "https://example.com/charts" + - name: memcached + version: "3.2.1" + repository: "https://another.example.com/charts" + +The 'name' should be the name of a chart, where that name must match the name +in that chart's 'Chart.yaml' file. + +The 'version' field should contain a semantic version or version range. + +The 'repository' URL should point to a Chart Repository. Helm expects that by +appending '/index.yaml' to the URL, it should be able to retrieve the chart +repository's index. Note: 'repository' can be an alias. The alias must start +with 'alias:' or '@'. + +Starting from 2.2.0, repository can be defined as the path to the directory of +the dependency charts stored locally. The path should start with a prefix of +"file://". For example, + + # requirements.yaml + dependencies: + - name: nginx + version: "1.2.3" + repository: "file://../dependency_chart/nginx" + +If the dependency chart is retrieved locally, it is not required to have the +repository added to helm by "helm add repo". Version matching is also supported +for this case. +` + +const dependencyListDesc = ` +List all of the dependencies declared in a chart. + +This can take chart archives and chart directories as input. It will not alter +the contents of a chart. + +This will produce an error if the chart cannot be loaded. It will emit a warning +if it cannot find a requirements.yaml. +` + +func newDependencyCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "dependency update|build|list", + Aliases: []string{"dep", "dependencies"}, + Short: "manage a chart's dependencies", + Long: dependencyDesc, + } + + cmd.AddCommand(newDependencyListCmd(out)) + cmd.AddCommand(newDependencyUpdateCmd(out)) + cmd.AddCommand(newDependencyBuildCmd(out)) + + return cmd +} + +type dependencyListCmd struct { + out io.Writer + chartpath string +} + +func newDependencyListCmd(out io.Writer) *cobra.Command { + dlc := &dependencyListCmd{out: out} + + cmd := &cobra.Command{ + Use: "list [flags] CHART", + Aliases: []string{"ls"}, + Short: "list the dependencies for the given chart", + Long: dependencyListDesc, + RunE: func(cmd *cobra.Command, args []string) error { + cp := "." + if len(args) > 0 { + cp = args[0] + } + + var err error + dlc.chartpath, err = filepath.Abs(cp) + if err != nil { + return err + } + return dlc.run() + }, + } + return cmd +} + +func (l *dependencyListCmd) run() error { + c, err := chartutil.Load(l.chartpath) + if err != nil { + return err + } + + r, err := chartutil.LoadRequirements(c) + if err != nil { + if err == chartutil.ErrRequirementsNotFound { + fmt.Fprintf(l.out, "WARNING: no requirements at %s/charts\n", l.chartpath) + return nil + } + return err + } + + l.printRequirements(r, l.out) + fmt.Fprintln(l.out) + l.printMissing(r) + return nil +} + +func (l *dependencyListCmd) dependencyStatus(dep *chartutil.Dependency) string { + filename := fmt.Sprintf("%s-%s.tgz", dep.Name, "*") + archives, err := filepath.Glob(filepath.Join(l.chartpath, "charts", filename)) + if err != nil { + return "bad pattern" + } else if len(archives) > 1 { + return "too many matches" + } else if len(archives) == 1 { + archive := archives[0] + if _, err := os.Stat(archive); err == nil { + c, err := chartutil.Load(archive) + if err != nil { + return "corrupt" + } + if c.Metadata.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 "ok" + } + return "wrong version" + } + return "ok" + } + } + + folder := filepath.Join(l.chartpath, "charts", dep.Name) + if fi, err := os.Stat(folder); err != nil { + return "missing" + } else if !fi.IsDir() { + return "mispackaged" + } + + c, err := chartutil.Load(folder) + if err != nil { + return "corrupt" + } + + if c.Metadata.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 "unpacked" + } + return "wrong version" + } + + return "unpacked" +} + +// printRequirements prints all of the requirements in the yaml file. +func (l *dependencyListCmd) printRequirements(reqs *chartutil.Requirements, out io.Writer) { + table := uitable.New() + table.MaxColWidth = 80 + table.AddRow("NAME", "VERSION", "REPOSITORY", "STATUS") + for _, row := range reqs.Dependencies { + table.AddRow(row.Name, row.Version, row.Repository, l.dependencyStatus(row)) + } + fmt.Fprintln(out, table) +} + +// printMissing prints warnings about charts that are present on disk, but are not in the requirements. +func (l *dependencyListCmd) printMissing(reqs *chartutil.Requirements) { + folder := filepath.Join(l.chartpath, "charts/*") + files, err := filepath.Glob(folder) + if err != nil { + fmt.Fprintln(l.out, err) + return + } + + for _, f := range files { + fi, err := os.Stat(f) + if err != nil { + fmt.Fprintf(l.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 := chartutil.Load(f) + if err != nil { + fmt.Fprintf(l.out, "WARNING: %q is not a chart.\n", f) + continue + } + found := false + for _, d := range reqs.Dependencies { + if d.Name == c.Metadata.Name { + found = true + break + } + } + if !found { + fmt.Fprintf(l.out, "WARNING: %q is not in requirements.yaml.\n", f) + } + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build.go new file mode 100644 index 000000000..ec5fd14b7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build.go @@ -0,0 +1,85 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" +) + +const dependencyBuildDesc = ` +Build out the charts/ directory from the requirements.lock file. + +Build is used to reconstruct a chart's dependencies to the state specified in +the lock file. This will not re-negotiate dependencies, as 'helm dependency update' +does. + +If no lock file is found, 'helm dependency build' will mirror the behavior +of 'helm dependency update'. +` + +type dependencyBuildCmd struct { + out io.Writer + chartpath string + verify bool + keyring string + helmhome helmpath.Home +} + +func newDependencyBuildCmd(out io.Writer) *cobra.Command { + dbc := &dependencyBuildCmd{out: out} + + cmd := &cobra.Command{ + Use: "build [flags] CHART", + Short: "rebuild the charts/ directory based on the requirements.lock file", + Long: dependencyBuildDesc, + RunE: func(cmd *cobra.Command, args []string) error { + dbc.helmhome = settings.Home + dbc.chartpath = "." + + if len(args) > 0 { + dbc.chartpath = args[0] + } + return dbc.run() + }, + } + + f := cmd.Flags() + f.BoolVar(&dbc.verify, "verify", false, "verify the packages against signatures") + f.StringVar(&dbc.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + + return cmd +} + +func (d *dependencyBuildCmd) run() error { + man := &downloader.Manager{ + Out: d.out, + ChartPath: d.chartpath, + HelmHome: d.helmhome, + Keyring: d.keyring, + Getters: getter.All(settings), + } + if d.verify { + man.Verify = downloader.VerifyIfPossible + } + + return man.Build() +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build_test.go new file mode 100644 index 000000000..2d7c88654 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_build_test.go @@ -0,0 +1,120 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +func TestDependencyBuildCmd(t *testing.T) { + hh, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(hh.String()) + cleanup() + }() + + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + _, err = srv.CopyCharts("testdata/testcharts/*.tgz") + if err != nil { + t.Fatal(err) + } + + chartname := "depbuild" + if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out := bytes.NewBuffer(nil) + dbc := &dependencyBuildCmd{out: out} + dbc.helmhome = helmpath.Home(hh) + dbc.chartpath = filepath.Join(hh.String(), chartname) + + // In the first pass, we basically want the same results as an update. + if err := dbc.run(); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + output := out.String() + if !strings.Contains(output, `update from the "test" chart repository`) { + t.Errorf("Repo did not get updated\n%s", output) + } + + // Make sure the actual file got downloaded. + expect := filepath.Join(hh.String(), chartname, "charts/reqtest-0.1.0.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatal(err) + } + + // In the second pass, we want to remove the chart's request dependency, + // then see if it restores from the lock. + lockfile := filepath.Join(hh.String(), chartname, "requirements.lock") + if _, err := os.Stat(lockfile); err != nil { + t.Fatal(err) + } + if err := os.RemoveAll(expect); err != nil { + t.Fatal(err) + } + + if err := dbc.run(); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + // Now repeat the test that the dependency exists. + expect = filepath.Join(hh.String(), chartname, "charts/reqtest-0.1.0.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatal(err) + } + + // Make sure that build is also fetching the correct version. + hash, err := provenance.DigestFile(expect) + if err != nil { + t.Fatal(err) + } + + i, err := repo.LoadIndexFile(dbc.helmhome.CacheIndex("test")) + if err != nil { + t.Fatal(err) + } + + reqver := i.Entries["reqtest"][0] + if h := reqver.Digest; h != hash { + t.Errorf("Failed hash match: expected %s, got %s", hash, h) + } + if v := reqver.Version; v != "0.1.0" { + t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_test.go new file mode 100644 index 000000000..cc4519147 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +func TestDependencyListCmd(t *testing.T) { + + tests := []releaseCase{ + { + name: "No such chart", + args: []string{"/no/such/chart"}, + err: true, + }, + { + name: "No requirements.yaml", + args: []string{"testdata/testcharts/alpine"}, + expected: "WARNING: no requirements at ", + }, + { + name: "Requirements in chart dir", + args: []string{"testdata/testcharts/reqtest"}, + expected: "NAME \tVERSION\tREPOSITORY \tSTATUS \n" + + "reqsubchart \t0.1.0 \thttps://example.com/charts\tunpacked\n" + + "reqsubchart2\t0.2.0 \thttps://example.com/charts\tunpacked\n" + + "reqsubchart3\t>=0.1.0\thttps://example.com/charts\tok \n\n", + }, + { + name: "Requirements in chart archive", + args: []string{"testdata/testcharts/reqtest-0.1.0.tgz"}, + expected: "NAME \tVERSION\tREPOSITORY \tSTATUS \nreqsubchart \t0.1.0 \thttps://example.com/charts\tmissing\nreqsubchart2\t0.2.0 \thttps://example.com/charts\tmissing\n", + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newDependencyListCmd(out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update.go new file mode 100644 index 000000000..d6a998639 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "io" + "path/filepath" + + "github.com/spf13/cobra" + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" +) + +const dependencyUpDesc = ` +Update the on-disk dependencies to mirror the requirements.yaml file. + +This command verifies that the required charts, as expressed in 'requirements.yaml', +are present in 'charts/' and are at an acceptable version. It will pull down +the latest charts that satisfy the dependencies, and clean up old dependencies. + +On successful update, this will generate a lock file that can be used to +rebuild the requirements to an exact version. + +Dependencies are not required to be represented in 'requirements.yaml'. For that +reason, an update command will not remove charts unless they are (a) present +in the requirements.yaml file, but (b) at the wrong version. +` + +// dependencyUpdateCmd describes a 'helm dependency update' +type dependencyUpdateCmd struct { + out io.Writer + chartpath string + helmhome helmpath.Home + verify bool + keyring string + skipRefresh bool +} + +// newDependencyUpdateCmd creates a new dependency update command. +func newDependencyUpdateCmd(out io.Writer) *cobra.Command { + duc := &dependencyUpdateCmd{out: out} + + cmd := &cobra.Command{ + Use: "update [flags] CHART", + Aliases: []string{"up"}, + Short: "update charts/ based on the contents of requirements.yaml", + Long: dependencyUpDesc, + RunE: func(cmd *cobra.Command, args []string) error { + cp := "." + if len(args) > 0 { + cp = args[0] + } + + var err error + duc.chartpath, err = filepath.Abs(cp) + if err != nil { + return err + } + + duc.helmhome = settings.Home + + return duc.run() + }, + } + + f := cmd.Flags() + f.BoolVar(&duc.verify, "verify", false, "verify the packages against signatures") + f.StringVar(&duc.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + f.BoolVar(&duc.skipRefresh, "skip-refresh", false, "do not refresh the local repository cache") + + return cmd +} + +// run runs the full dependency update process. +func (d *dependencyUpdateCmd) run() error { + man := &downloader.Manager{ + Out: d.out, + ChartPath: d.chartpath, + HelmHome: d.helmhome, + Keyring: d.keyring, + SkipUpdate: d.skipRefresh, + Getters: getter.All(settings), + } + if d.verify { + man.Verify = downloader.VerifyAlways + } + if settings.Debug { + man.Debug = true + } + return man.Update() +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update_test.go new file mode 100644 index 000000000..e29cb35de --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/dependency_update_test.go @@ -0,0 +1,273 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +func TestDependencyUpdateCmd(t *testing.T) { + hh, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(hh.String()) + cleanup() + }() + + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "depup" + if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out := bytes.NewBuffer(nil) + duc := &dependencyUpdateCmd{out: out} + duc.helmhome = helmpath.Home(hh) + duc.chartpath = filepath.Join(hh.String(), chartname) + + if err := duc.run(); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + output := out.String() + // This is written directly to stdout, so we have to capture as is. + if !strings.Contains(output, `update from the "test" chart repository`) { + t.Errorf("Repo did not get updated\n%s", output) + } + + // Make sure the actual file got downloaded. + expect := filepath.Join(hh.String(), chartname, "charts/reqtest-0.1.0.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatal(err) + } + + hash, err := provenance.DigestFile(expect) + if err != nil { + t.Fatal(err) + } + + i, err := repo.LoadIndexFile(duc.helmhome.CacheIndex("test")) + if err != nil { + t.Fatal(err) + } + + reqver := i.Entries["reqtest"][0] + if h := reqver.Digest; h != hash { + t.Errorf("Failed hash match: expected %s, got %s", hash, h) + } + + // Now change the dependencies and update. This verifies that on update, + // old dependencies are cleansed and new dependencies are added. + reqfile := &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "reqtest", Version: "0.1.0", Repository: srv.URL()}, + {Name: "compressedchart", Version: "0.3.0", Repository: srv.URL()}, + }, + } + dir := filepath.Join(hh.String(), chartname) + if err := writeRequirements(dir, reqfile); err != nil { + t.Fatal(err) + } + if err := duc.run(); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + // In this second run, we should see compressedchart-0.3.0.tgz, and not + // the 0.1.0 version. + expect = filepath.Join(hh.String(), chartname, "charts/compressedchart-0.3.0.tgz") + if _, err := os.Stat(expect); err != nil { + t.Fatalf("Expected %q: %s", expect, err) + } + dontExpect := filepath.Join(hh.String(), chartname, "charts/compressedchart-0.1.0.tgz") + if _, err := os.Stat(dontExpect); err == nil { + t.Fatalf("Unexpected %q", dontExpect) + } +} + +func TestDependencyUpdateCmd_SkipRefresh(t *testing.T) { + hh, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(hh.String()) + cleanup() + }() + + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "depup" + if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out := bytes.NewBuffer(nil) + duc := &dependencyUpdateCmd{out: out} + duc.helmhome = helmpath.Home(hh) + duc.chartpath = filepath.Join(hh.String(), chartname) + duc.skipRefresh = true + + if err := duc.run(); err == nil { + t.Fatal("Expected failure to find the repo with skipRefresh") + } + + output := out.String() + // This is written directly to stdout, so we have to capture as is. + if strings.Contains(output, `update from the "test" chart repository`) { + t.Errorf("Repo was unexpectedly updated\n%s", output) + } +} + +func TestDependencyUpdateCmd_DontDeleteOldChartsOnError(t *testing.T) { + hh, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(hh.String()) + cleanup() + }() + + settings.Home = hh + + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + copied, err := srv.CopyCharts("testdata/testcharts/*.tgz") + if err != nil { + t.Fatal(err) + } + t.Logf("Copied charts:\n%s", strings.Join(copied, "\n")) + t.Logf("Listening on directory %s", srv.Root()) + + chartname := "depupdelete" + if err := createTestingChart(hh.String(), chartname, srv.URL()); err != nil { + t.Fatal(err) + } + + out := bytes.NewBuffer(nil) + duc := &dependencyUpdateCmd{out: out} + duc.helmhome = helmpath.Home(hh) + duc.chartpath = filepath.Join(hh.String(), chartname) + + if err := duc.run(); err != nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal(err) + } + + // Chart repo is down + srv.Stop() + + if err := duc.run(); err == nil { + output := out.String() + t.Logf("Output: %s", output) + t.Fatal("Expected error, got nil") + } + + // Make sure charts dir still has dependencies + files, err := ioutil.ReadDir(filepath.Join(duc.chartpath, "charts")) + if err != nil { + t.Fatal(err) + } + dependencies := []string{"compressedchart-0.1.0.tgz", "reqtest-0.1.0.tgz"} + + if len(dependencies) != len(files) { + t.Fatalf("Expected %d chart dependencies, got %d", len(dependencies), len(files)) + } + for index, file := range files { + if dependencies[index] != file.Name() { + t.Fatalf("Chart dependency %s not matching %s", dependencies[index], file.Name()) + } + } + + // Make sure tmpcharts is deleted + if _, err := os.Stat(filepath.Join(duc.chartpath, "tmpcharts")); !os.IsNotExist(err) { + t.Fatalf("tmpcharts dir still exists") + } +} + +// createTestingChart creates a basic chart that depends on reqtest-0.1.0 +// +// The baseURL can be used to point to a particular repository server. +func createTestingChart(dest, name, baseURL string) error { + cfile := &chart.Metadata{ + Name: name, + Version: "1.2.3", + } + dir := filepath.Join(dest, name) + _, err := chartutil.Create(cfile, dest) + if err != nil { + return err + } + req := &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "reqtest", Version: "0.1.0", Repository: baseURL}, + {Name: "compressedchart", Version: "0.1.0", Repository: baseURL}, + }, + } + return writeRequirements(dir, req) +} + +func writeRequirements(dir string, req *chartutil.Requirements) error { + data, err := yaml.Marshal(req) + if err != nil { + return err + } + + return ioutil.WriteFile(filepath.Join(dir, "requirements.yaml"), data, 0655) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/docs.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/docs.go new file mode 100644 index 000000000..e5b9f7521 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/docs.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +const docsDesc = ` +Generate documentation files for Helm. + +This command can generate documentation for Helm in the following formats: + +- Markdown +- Man pages + +It can also generate bash autocompletions. + + $ helm docs markdown -dir mydocs/ +` + +type docsCmd struct { + out io.Writer + dest string + docTypeString string + topCmd *cobra.Command +} + +func newDocsCmd(out io.Writer) *cobra.Command { + dc := &docsCmd{out: out} + + cmd := &cobra.Command{ + Use: "docs", + Short: "Generate documentation as markdown or man pages", + Long: docsDesc, + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + dc.topCmd = cmd.Root() + return dc.run() + }, + } + + f := cmd.Flags() + f.StringVar(&dc.dest, "dir", "./", "directory to which documentation is written") + f.StringVar(&dc.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)") + + return cmd +} + +func (d *docsCmd) run() error { + switch d.docTypeString { + case "markdown", "mdown", "md": + return doc.GenMarkdownTree(d.topCmd, d.dest) + case "man": + manHdr := &doc.GenManHeader{Title: "HELM", Section: "1"} + return doc.GenManTree(d.topCmd, manHdr, d.dest) + case "bash": + return d.topCmd.GenBashCompletionFile(filepath.Join(d.dest, "completions.bash")) + default: + return fmt.Errorf("unknown doc type %q. Try 'markdown' or 'man'", d.docTypeString) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch.go new file mode 100644 index 000000000..069f57eff --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch.go @@ -0,0 +1,186 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/spf13/cobra" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/repo" +) + +const fetchDesc = ` +Retrieve a package from a package repository, and download it locally. + +This is useful for fetching packages to inspect, modify, or repackage. It can +also be used to perform cryptographic verification of a chart without installing +the chart. + +There are options for unpacking the chart after download. This will create a +directory for the chart and uncompress into that directory. + +If the --verify flag is specified, the requested chart MUST have a provenance +file, and MUST pass the verification process. Failure in any part of this will +result in an error, and the chart will not be saved locally. +` + +type fetchCmd struct { + untar bool + untardir string + chartRef string + destdir string + version string + repoURL string + username string + password string + + verify bool + verifyLater bool + keyring string + + certFile string + keyFile string + caFile string + + devel bool + + out io.Writer +} + +func newFetchCmd(out io.Writer) *cobra.Command { + fch := &fetchCmd{out: out} + + cmd := &cobra.Command{ + Use: "fetch [flags] [chart URL | repo/chartname] [...]", + Short: "download a chart from a repository and (optionally) unpack it in local directory", + Long: fetchDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return fmt.Errorf("need at least one argument, url or repo/name of the chart") + } + + if fch.version == "" && fch.devel { + debug("setting version to >0.0.0-0") + fch.version = ">0.0.0-0" + } + + for i := 0; i < len(args); i++ { + fch.chartRef = args[i] + if err := fch.run(); err != nil { + return err + } + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVar(&fch.untar, "untar", false, "if set to true, will untar the chart after downloading it") + f.StringVar(&fch.untardir, "untardir", ".", "if untar is specified, this flag specifies the name of the directory into which the chart is expanded") + f.BoolVar(&fch.verify, "verify", false, "verify the package against its signature") + f.BoolVar(&fch.verifyLater, "prov", false, "fetch the provenance file, but don't perform verification") + f.StringVar(&fch.version, "version", "", "specific version of a chart. Without this, the latest version is fetched") + f.StringVar(&fch.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + f.StringVarP(&fch.destdir, "destination", "d", ".", "location to write the chart. If this and tardir are specified, tardir is appended to this") + f.StringVar(&fch.repoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&fch.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&fch.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&fch.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&fch.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") + f.StringVar(&fch.username, "username", "", "chart repository username") + f.StringVar(&fch.password, "password", "", "chart repository password") + + return cmd +} + +func (f *fetchCmd) run() error { + c := downloader.ChartDownloader{ + HelmHome: settings.Home, + Out: f.out, + Keyring: f.keyring, + Verify: downloader.VerifyNever, + Getters: getter.All(settings), + Username: f.username, + Password: f.password, + } + + if f.verify { + c.Verify = downloader.VerifyAlways + } else if f.verifyLater { + c.Verify = downloader.VerifyLater + } + + // If untar is set, we fetch to a tempdir, then untar and copy after + // verification. + dest := f.destdir + if f.untar { + var err error + dest, err = ioutil.TempDir("", "helm-") + if err != nil { + return fmt.Errorf("Failed to untar: %s", err) + } + defer os.RemoveAll(dest) + } + + if f.repoURL != "" { + chartURL, err := repo.FindChartInAuthRepoURL(f.repoURL, f.username, f.password, f.chartRef, f.version, f.certFile, f.keyFile, f.caFile, getter.All(settings)) + if err != nil { + return err + } + f.chartRef = chartURL + } + + saved, v, err := c.DownloadTo(f.chartRef, f.version, dest) + if err != nil { + return err + } + + if f.verify { + fmt.Fprintf(f.out, "Verification: %v\n", v) + } + + // After verification, untar the chart into the requested directory. + if f.untar { + ud := f.untardir + if !filepath.IsAbs(ud) { + ud = filepath.Join(f.destdir, ud) + } + if fi, err := os.Stat(ud); err != nil { + if err := os.MkdirAll(ud, 0755); err != nil { + return fmt.Errorf("Failed to untar (mkdir): %s", err) + } + + } else if !fi.IsDir() { + return fmt.Errorf("Failed to untar: %s is not a directory", ud) + } + + return chartutil.ExpandFile(ud, saved) + } + return nil +} + +// defaultKeyring returns the expanded path to the default keyring. +func defaultKeyring() string { + return os.ExpandEnv("$HOME/.gnupg/pubring.gpg") +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch_test.go new file mode 100644 index 000000000..13247ee99 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/fetch_test.go @@ -0,0 +1,179 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" + "testing" + + "k8s.io/helm/pkg/repo/repotest" +) + +func TestFetchCmd(t *testing.T) { + hh, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(hh.String()) + cleanup() + }() + srv := repotest.NewServer(hh.String()) + defer srv.Stop() + + settings.Home = hh + + // all flags will get "--home=TMDIR -d outdir" appended. + tests := []struct { + name string + chart string + flags []string + fail bool + failExpect string + expectFile string + expectDir bool + expectVerify bool + }{ + { + name: "Basic chart fetch", + chart: "test/signtest", + expectFile: "./signtest-0.1.0.tgz", + }, + { + name: "Chart fetch with version", + chart: "test/signtest", + flags: []string{"--version", "0.1.0"}, + expectFile: "./signtest-0.1.0.tgz", + }, + { + name: "Fail chart fetch with non-existent version", + chart: "test/signtest", + flags: []string{"--version", "99.1.0"}, + fail: true, + failExpect: "no such chart", + }, + { + name: "Fail fetching non-existent chart", + chart: "test/nosuchthing", + failExpect: "Failed to fetch", + fail: true, + }, + { + name: "Fetch and verify", + chart: "test/signtest", + flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"}, + expectFile: "./signtest-0.1.0.tgz", + expectVerify: true, + }, + { + name: "Fetch and fail verify", + chart: "test/reqtest", + flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub"}, + failExpect: "Failed to fetch provenance", + fail: true, + }, + { + name: "Fetch and untar", + chart: "test/signtest", + flags: []string{"--untar", "--untardir", "signtest"}, + expectFile: "./signtest", + expectDir: true, + }, + { + name: "Fetch, verify, untar", + chart: "test/signtest", + flags: []string{"--verify", "--keyring", "testdata/helm-test-key.pub", "--untar", "--untardir", "signtest"}, + expectFile: "./signtest", + expectDir: true, + expectVerify: true, + }, + { + name: "Chart fetch using repo URL", + chart: "signtest", + expectFile: "./signtest-0.1.0.tgz", + flags: []string{"--repo", srv.URL()}, + }, + { + name: "Fail fetching non-existent chart on repo URL", + chart: "someChart", + flags: []string{"--repo", srv.URL()}, + failExpect: "Failed to fetch chart", + fail: true, + }, + { + name: "Specific version chart fetch using repo URL", + chart: "signtest", + expectFile: "./signtest-0.1.0.tgz", + flags: []string{"--repo", srv.URL(), "--version", "0.1.0"}, + }, + { + name: "Specific version chart fetch using repo URL", + chart: "signtest", + flags: []string{"--repo", srv.URL(), "--version", "0.2.0"}, + failExpect: "Failed to fetch chart version", + fail: true, + }, + } + + if _, err := srv.CopyCharts("testdata/testcharts/*.tgz*"); err != nil { + t.Fatal(err) + } + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + outdir := filepath.Join(hh.String(), "testout") + os.RemoveAll(outdir) + os.Mkdir(outdir, 0755) + + buf := bytes.NewBuffer(nil) + cmd := newFetchCmd(buf) + tt.flags = append(tt.flags, "-d", outdir) + cmd.ParseFlags(tt.flags) + if err := cmd.RunE(cmd, []string{tt.chart}); err != nil { + if tt.fail { + continue + } + t.Errorf("%q reported error: %s", tt.name, err) + continue + } + if tt.expectVerify { + pointerAddressPattern := "0[xX][A-Fa-f0-9]+" + sha256Pattern := "[A-Fa-f0-9]{64}" + verificationRegex := regexp.MustCompile( + fmt.Sprintf("Verification: &{%s sha256:%s signtest-0.1.0.tgz}\n", pointerAddressPattern, sha256Pattern)) + if !verificationRegex.MatchString(buf.String()) { + t.Errorf("%q: expected match for regex %s, got %s", tt.name, verificationRegex, buf.String()) + } + } + + ef := filepath.Join(outdir, tt.expectFile) + fi, err := os.Stat(ef) + if err != nil { + t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err) + } + if fi.IsDir() != tt.expectDir { + t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get.go new file mode 100644 index 000000000..a2eb1d137 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get.go @@ -0,0 +1,89 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +var getHelp = ` +This command shows the details of a named release. + +It can be used to get extended information about the release, including: + + - The values used to generate the release + - The chart used to generate the release + - The generated manifest file + +By default, this prints a human readable collection of information about the +chart, the supplied values, and the generated manifest file. +` + +var errReleaseRequired = errors.New("release name is required") + +type getCmd struct { + release string + out io.Writer + client helm.Interface + version int32 +} + +func newGetCmd(client helm.Interface, out io.Writer) *cobra.Command { + get := &getCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "get [flags] RELEASE_NAME", + Short: "download a named release", + Long: getHelp, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + get.release = args[0] + if get.client == nil { + get.client = newClient() + } + return get.run() + }, + } + + cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") + + cmd.AddCommand(addFlagsTLS(newGetValuesCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetManifestCmd(nil, out))) + cmd.AddCommand(addFlagsTLS(newGetHooksCmd(nil, out))) + + return cmd +} + +// getCmd is the command that implements 'helm get' +func (g *getCmd) run() error { + res, err := g.client.ReleaseContent(g.release, helm.ContentReleaseVersion(g.version)) + if err != nil { + return prettyError(err) + } + return printRelease(g.out, res.Release) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks.go new file mode 100644 index 000000000..1b6f2f8fe --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks.go @@ -0,0 +1,75 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +const getHooksHelp = ` +This command downloads hooks for a given release. + +Hooks are formatted in YAML and separated by the YAML '---\n' separator. +` + +type getHooksCmd struct { + release string + out io.Writer + client helm.Interface + version int32 +} + +func newGetHooksCmd(client helm.Interface, out io.Writer) *cobra.Command { + ghc := &getHooksCmd{ + out: out, + client: client, + } + cmd := &cobra.Command{ + Use: "hooks [flags] RELEASE_NAME", + Short: "download all hooks for a named release", + Long: getHooksHelp, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + ghc.release = args[0] + ghc.client = ensureHelmClient(ghc.client) + return ghc.run() + }, + } + cmd.Flags().Int32Var(&ghc.version, "revision", 0, "get the named release with revision") + return cmd +} + +func (g *getHooksCmd) run() error { + res, err := g.client.ReleaseContent(g.release, helm.ContentReleaseVersion(g.version)) + if err != nil { + fmt.Fprintln(g.out, g.release) + return prettyError(err) + } + + for _, hook := range res.Release.Hooks { + fmt.Fprintf(g.out, "---\n# %s\n%s", hook.Name, hook.Manifest) + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks_test.go new file mode 100644 index 000000000..e578c2533 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_hooks_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestGetHooks(t *testing.T) { + tests := []releaseCase{ + { + name: "get hooks with release", + args: []string{"aeneas"}, + expected: helm.MockHookTemplate, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"})}, + }, + { + name: "get hooks without args", + args: []string{}, + err: true, + }, + } + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newGetHooksCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest.go new file mode 100644 index 000000000..c01febfb4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest.go @@ -0,0 +1,75 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +var getManifestHelp = ` +This command fetches the generated manifest for a given release. + +A manifest is a YAML-encoded representation of the Kubernetes resources that +were generated from this release's chart(s). If a chart is dependent on other +charts, those resources will also be included in the manifest. +` + +type getManifestCmd struct { + release string + out io.Writer + client helm.Interface + version int32 +} + +func newGetManifestCmd(client helm.Interface, out io.Writer) *cobra.Command { + get := &getManifestCmd{ + out: out, + client: client, + } + cmd := &cobra.Command{ + Use: "manifest [flags] RELEASE_NAME", + Short: "download the manifest for a named release", + Long: getManifestHelp, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + get.release = args[0] + get.client = ensureHelmClient(get.client) + return get.run() + }, + } + + cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") + return cmd +} + +// getManifest implements 'helm get manifest' +func (g *getManifestCmd) run() error { + res, err := g.client.ReleaseContent(g.release, helm.ContentReleaseVersion(g.version)) + if err != nil { + return prettyError(err) + } + fmt.Fprintln(g.out, res.Release.Manifest) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest_test.go new file mode 100644 index 000000000..286b5628a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_manifest_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestGetManifest(t *testing.T) { + tests := []releaseCase{ + { + name: "get manifest with release", + args: []string{"juno"}, + expected: helm.MockManifest, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"}), + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "juno"})}, + }, + { + name: "get manifest without args", + args: []string{}, + err: true, + }, + } + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newGetManifestCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_test.go new file mode 100644 index 000000000..a6e72987a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestGetCmd(t *testing.T) { + tests := []releaseCase{ + { + name: "get with a release", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + args: []string{"thomas-guide"}, + expected: "REVISION: 1\nRELEASED: (.*)\nCHART: foo-0.1.0-beta.1\nUSER-SUPPLIED VALUES:\nname: \"value\"\nCOMPUTED VALUES:\nname: value\n\nHOOKS:\n---\n# pre-install-hook\n" + helm.MockHookTemplate + "\nMANIFEST:", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, + }, + { + name: "get requires release name arg", + err: true, + }, + } + + cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newGetCmd(c, out) + } + runReleaseCases(t, tests, cmd) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values.go new file mode 100644 index 000000000..b6ce648e5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values.go @@ -0,0 +1,89 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm" +) + +var getValuesHelp = ` +This command downloads a values file for a given release. +` + +type getValuesCmd struct { + release string + allValues bool + out io.Writer + client helm.Interface + version int32 +} + +func newGetValuesCmd(client helm.Interface, out io.Writer) *cobra.Command { + get := &getValuesCmd{ + out: out, + client: client, + } + cmd := &cobra.Command{ + Use: "values [flags] RELEASE_NAME", + Short: "download the values file for a named release", + Long: getValuesHelp, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + get.release = args[0] + get.client = ensureHelmClient(get.client) + return get.run() + }, + } + + cmd.Flags().Int32Var(&get.version, "revision", 0, "get the named release with revision") + cmd.Flags().BoolVarP(&get.allValues, "all", "a", false, "dump all (computed) values") + return cmd +} + +// getValues implements 'helm get values' +func (g *getValuesCmd) run() error { + res, err := g.client.ReleaseContent(g.release, helm.ContentReleaseVersion(g.version)) + if err != nil { + return prettyError(err) + } + + // If the user wants all values, compute the values and return. + if g.allValues { + cfg, err := chartutil.CoalesceValues(res.Release.Chart, res.Release.Config) + if err != nil { + return err + } + cfgStr, err := cfg.YAML() + if err != nil { + return err + } + fmt.Fprintln(g.out, cfgStr) + return nil + } + + fmt.Fprintln(g.out, res.Release.Config.Raw) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values_test.go new file mode 100644 index 000000000..30b2ba4b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/get_values_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestGetValuesCmd(t *testing.T) { + tests := []releaseCase{ + { + name: "get values with a release", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + args: []string{"thomas-guide"}, + expected: "name: \"value\"", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"})}, + }, + { + name: "get values requires release name arg", + err: true, + }, + } + cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newGetValuesCmd(c, out) + } + runReleaseCases(t, tests, cmd) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/helm.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/helm.go new file mode 100644 index 000000000..4c7ca9290 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/helm.go @@ -0,0 +1,310 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main // import "k8s.io/helm/cmd/helm" + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/spf13/cobra" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + // Import to initialize client auth plugins. + _ "k8s.io/client-go/plugin/pkg/client/auth" + "k8s.io/helm/pkg/helm" + helm_env "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/helm/portforwarder" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/tlsutil" +) + +var ( + tlsCaCertFile string // path to TLS CA certificate file + tlsCertFile string // path to TLS certificate file + tlsKeyFile string // path to TLS key file + tlsVerify bool // enable TLS and verify remote certificates + tlsEnable bool // enable TLS + + tlsCaCertDefault = "$HELM_HOME/ca.pem" + tlsCertDefault = "$HELM_HOME/cert.pem" + tlsKeyDefault = "$HELM_HOME/key.pem" + + tillerTunnel *kube.Tunnel + settings helm_env.EnvSettings +) + +var globalUsage = `The Kubernetes package manager + +To begin working with Helm, run the 'helm init' command: + + $ helm init + +This will install Tiller to your running Kubernetes cluster. +It will also set up any necessary local configuration. + +Common actions from this point include: + +- helm search: search for charts +- helm fetch: download a chart to your local directory to view +- helm install: upload the chart to Kubernetes +- helm list: list releases of charts + +Environment: + $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm + $HELM_HOST set an alternative Tiller host. The format is host:port + $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. + $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system") + $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") +` + +func newRootCmd(args []string) *cobra.Command { + cmd := &cobra.Command{ + Use: "helm", + Short: "The Helm package manager for Kubernetes.", + Long: globalUsage, + SilenceUsage: true, + PersistentPreRun: func(*cobra.Command, []string) { + tlsCaCertFile = os.ExpandEnv(tlsCaCertFile) + tlsCertFile = os.ExpandEnv(tlsCertFile) + tlsKeyFile = os.ExpandEnv(tlsKeyFile) + }, + PersistentPostRun: func(*cobra.Command, []string) { + teardown() + }, + } + flags := cmd.PersistentFlags() + + settings.AddFlags(flags) + + out := cmd.OutOrStdout() + + cmd.AddCommand( + // chart commands + newCreateCmd(out), + newDependencyCmd(out), + newFetchCmd(out), + newInspectCmd(out), + newLintCmd(out), + newPackageCmd(out), + newRepoCmd(out), + newSearchCmd(out), + newServeCmd(out), + newVerifyCmd(out), + + // release commands + addFlagsTLS(newDeleteCmd(nil, out)), + addFlagsTLS(newGetCmd(nil, out)), + addFlagsTLS(newHistoryCmd(nil, out)), + addFlagsTLS(newInstallCmd(nil, out)), + addFlagsTLS(newListCmd(nil, out)), + addFlagsTLS(newRollbackCmd(nil, out)), + addFlagsTLS(newStatusCmd(nil, out)), + addFlagsTLS(newUpgradeCmd(nil, out)), + + addFlagsTLS(newReleaseTestCmd(nil, out)), + addFlagsTLS(newResetCmd(nil, out)), + addFlagsTLS(newVersionCmd(nil, out)), + + newCompletionCmd(out), + newHomeCmd(out), + newInitCmd(out), + newPluginCmd(out), + newTemplateCmd(out), + + // Hidden documentation generator command: 'helm docs' + newDocsCmd(out), + + // Deprecated + markDeprecated(newRepoUpdateCmd(out), "use 'helm repo update'\n"), + ) + + flags.Parse(args) + + // set defaults from environment + settings.Init(flags) + + // Find and add plugins + loadPlugins(cmd, out) + + return cmd +} + +func init() { + // Tell gRPC not to log to console. + grpclog.SetLogger(log.New(ioutil.Discard, "", log.LstdFlags)) +} + +func main() { + cmd := newRootCmd(os.Args[1:]) + if err := cmd.Execute(); err != nil { + os.Exit(1) + } +} + +func markDeprecated(cmd *cobra.Command, notice string) *cobra.Command { + cmd.Deprecated = notice + return cmd +} + +func setupConnection() error { + if settings.TillerHost == "" { + config, client, err := getKubeClient(settings.KubeContext) + if err != nil { + return err + } + + tunnel, err := portforwarder.New(settings.TillerNamespace, client, config) + if err != nil { + return err + } + + settings.TillerHost = fmt.Sprintf("127.0.0.1:%d", tunnel.Local) + debug("Created tunnel using local port: '%d'\n", tunnel.Local) + } + + // Set up the gRPC config. + debug("SERVER: %q\n", settings.TillerHost) + + // Plugin support. + return nil +} + +func teardown() { + if tillerTunnel != nil { + tillerTunnel.Close() + } +} + +func checkArgsLength(argsReceived int, requiredArgs ...string) error { + expectedNum := len(requiredArgs) + if argsReceived != expectedNum { + arg := "arguments" + if expectedNum == 1 { + arg = "argument" + } + return fmt.Errorf("This command needs %v %s: %s", expectedNum, arg, strings.Join(requiredArgs, ", ")) + } + return nil +} + +// prettyError unwraps or rewrites certain errors to make them more user-friendly. +func prettyError(err error) error { + // Add this check can prevent the object creation if err is nil. + if err == nil { + return nil + } + // If it's grpc's error, make it more user-friendly. + if s, ok := status.FromError(err); ok { + return fmt.Errorf(s.Message()) + } + // Else return the original error. + return err +} + +// configForContext creates a Kubernetes REST client configuration for a given kubeconfig context. +func configForContext(context string) (*rest.Config, error) { + config, err := kube.GetConfig(context).ClientConfig() + if err != nil { + return nil, fmt.Errorf("could not get Kubernetes config for context %q: %s", context, err) + } + return config, nil +} + +// getKubeClient creates a Kubernetes config and client for a given kubeconfig context. +func getKubeClient(context string) (*rest.Config, kubernetes.Interface, error) { + config, err := configForContext(context) + if err != nil { + return nil, nil, err + } + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, nil, fmt.Errorf("could not get Kubernetes client: %s", err) + } + return config, client, nil +} + +// getInternalKubeClient creates a Kubernetes config and an "internal" client for a given kubeconfig context. +// +// Prefer the similar getKubeClient if you don't need to use such an internal client. +func getInternalKubeClient(context string) (internalclientset.Interface, error) { + config, err := configForContext(context) + if err != nil { + return nil, err + } + client, err := internalclientset.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("could not get Kubernetes client: %s", err) + } + return client, nil +} + +// ensureHelmClient returns a new helm client impl. if h is not nil. +func ensureHelmClient(h helm.Interface) helm.Interface { + if h != nil { + return h + } + return newClient() +} + +func newClient() helm.Interface { + options := []helm.Option{helm.Host(settings.TillerHost), helm.ConnectTimeout(settings.TillerConnectionTimeout)} + + if tlsVerify || tlsEnable { + if tlsCaCertFile == "" { + tlsCaCertFile = settings.Home.TLSCaCert() + } + if tlsCertFile == "" { + tlsCertFile = settings.Home.TLSCert() + } + if tlsKeyFile == "" { + tlsKeyFile = settings.Home.TLSKey() + } + debug("Key=%q, Cert=%q, CA=%q\n", tlsKeyFile, tlsCertFile, tlsCaCertFile) + tlsopts := tlsutil.Options{KeyFile: tlsKeyFile, CertFile: tlsCertFile, InsecureSkipVerify: true} + if tlsVerify { + tlsopts.CaCertFile = tlsCaCertFile + tlsopts.InsecureSkipVerify = false + } + tlscfg, err := tlsutil.ClientConfig(tlsopts) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(2) + } + options = append(options, helm.WithTLS(tlscfg)) + } + return helm.NewClient(options...) +} + +// addFlagsTLS adds the flags for supporting client side TLS to the +// helm command (only those that invoke communicate to Tiller.) +func addFlagsTLS(cmd *cobra.Command) *cobra.Command { + + // add flags + cmd.Flags().StringVar(&tlsCaCertFile, "tls-ca-cert", tlsCaCertDefault, "path to TLS CA certificate file") + cmd.Flags().StringVar(&tlsCertFile, "tls-cert", tlsCertDefault, "path to TLS certificate file") + cmd.Flags().StringVar(&tlsKeyFile, "tls-key", tlsKeyDefault, "path to TLS key file") + cmd.Flags().BoolVar(&tlsVerify, "tls-verify", false, "enable TLS for request and verify remote") + cmd.Flags().BoolVar(&tlsEnable, "tls", false, "enable TLS for request") + return cmd +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/helm_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/helm_test.go new file mode 100644 index 000000000..c95caa75f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/helm_test.go @@ -0,0 +1,238 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/repo" +) + +// releaseCmd is a command that works with a FakeClient +type releaseCmd func(c *helm.FakeClient, out io.Writer) *cobra.Command + +// runReleaseCases runs a set of release cases through the given releaseCmd. +func runReleaseCases(t *testing.T, tests []releaseCase, rcmd releaseCmd) { + var buf bytes.Buffer + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &helm.FakeClient{Rels: tt.rels} + cmd := rcmd(c, &buf) + cmd.ParseFlags(tt.flags) + err := cmd.RunE(cmd, tt.args) + if (err != nil) != tt.err { + t.Errorf("expected error, got '%v'", err) + } + re := regexp.MustCompile(tt.expected) + if !re.Match(buf.Bytes()) { + t.Errorf("expected\n%q\ngot\n%q", tt.expected, buf.String()) + } + buf.Reset() + }) + } +} + +// releaseCase describes a test case that works with releases. +type releaseCase struct { + name string + args []string + flags []string + // expected is the string to be matched. This supports regular expressions. + expected string + err bool + resp *release.Release + // Rels are the available releases at the start of the test. + rels []*release.Release +} + +// tempHelmHome sets up a Helm Home in a temp dir. +// +// This does not clean up the directory. You must do that yourself. +// You must also set helmHome yourself. +func tempHelmHome(t *testing.T) (helmpath.Home, error) { + oldhome := settings.Home + dir, err := ioutil.TempDir("", "helm_home-") + if err != nil { + return helmpath.Home("n/"), err + } + + settings.Home = helmpath.Home(dir) + if err := ensureTestHome(settings.Home, t); err != nil { + return helmpath.Home("n/"), err + } + settings.Home = oldhome + return helmpath.Home(dir), nil +} + +// ensureTestHome creates a home directory like ensureHome, but without remote references. +// +// t is used only for logging. +func ensureTestHome(home helmpath.Home, t *testing.T) error { + configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Plugins(), home.Starters()} + for _, p := range configDirectories { + if fi, err := os.Stat(p); err != nil { + if err := os.MkdirAll(p, 0755); err != nil { + return fmt.Errorf("Could not create %s: %s", p, err) + } + } else if !fi.IsDir() { + return fmt.Errorf("%s must be a directory", p) + } + } + + repoFile := home.RepositoryFile() + if fi, err := os.Stat(repoFile); err != nil { + rf := repo.NewRepoFile() + rf.Add(&repo.Entry{ + Name: "charts", + URL: "http://example.com/foo", + Cache: "charts-index.yaml", + }, &repo.Entry{ + Name: "local", + URL: "http://localhost.com:7743/foo", + Cache: "local-index.yaml", + }) + if err := rf.WriteFile(repoFile, 0644); err != nil { + return err + } + } else if fi.IsDir() { + return fmt.Errorf("%s must be a file, not a directory", repoFile) + } + if r, err := repo.LoadRepositoriesFile(repoFile); err == repo.ErrRepoOutOfDate { + t.Log("Updating repository file format...") + if err := r.WriteFile(repoFile, 0644); err != nil { + return err + } + } + + localRepoIndexFile := home.LocalRepository(localRepositoryIndexFile) + if fi, err := os.Stat(localRepoIndexFile); err != nil { + i := repo.NewIndexFile() + if err := i.WriteFile(localRepoIndexFile, 0644); err != nil { + return err + } + + //TODO: take this out and replace with helm update functionality + os.Symlink(localRepoIndexFile, home.CacheIndex("local")) + } else if fi.IsDir() { + return fmt.Errorf("%s must be a file, not a directory", localRepoIndexFile) + } + + t.Logf("$HELM_HOME has been configured at %s.\n", settings.Home.String()) + return nil + +} + +func TestRootCmd(t *testing.T) { + cleanup := resetEnv() + defer cleanup() + + tests := []struct { + name string + args []string + envars map[string]string + home string + }{ + { + name: "defaults", + args: []string{"home"}, + home: filepath.Join(os.Getenv("HOME"), "/.helm"), + }, + { + name: "with --home set", + args: []string{"--home", "/foo"}, + home: "/foo", + }, + { + name: "subcommands with --home set", + args: []string{"home", "--home", "/foo"}, + home: "/foo", + }, + { + name: "with $HELM_HOME set", + args: []string{"home"}, + envars: map[string]string{"HELM_HOME": "/bar"}, + home: "/bar", + }, + { + name: "subcommands with $HELM_HOME set", + args: []string{"home"}, + envars: map[string]string{"HELM_HOME": "/bar"}, + home: "/bar", + }, + { + name: "with $HELM_HOME and --home set", + args: []string{"home", "--home", "/foo"}, + envars: map[string]string{"HELM_HOME": "/bar"}, + home: "/foo", + }, + } + + // ensure not set locally + os.Unsetenv("HELM_HOME") + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + defer os.Unsetenv("HELM_HOME") + + for k, v := range tt.envars { + os.Setenv(k, v) + } + + cmd := newRootCmd(tt.args) + cmd.SetOutput(ioutil.Discard) + cmd.SetArgs(tt.args) + cmd.Run = func(*cobra.Command, []string) {} + if err := cmd.Execute(); err != nil { + t.Errorf("unexpected error: %s", err) + } + + if settings.Home.String() != tt.home { + t.Errorf("expected home %q, got %q", tt.home, settings.Home) + } + homeFlag := cmd.Flag("home").Value.String() + homeFlag = os.ExpandEnv(homeFlag) + if homeFlag != tt.home { + t.Errorf("expected home %q, got %q", tt.home, homeFlag) + } + }) + } +} + +func resetEnv() func() { + origSettings := settings + origEnv := os.Environ() + return func() { + settings = origSettings + for _, pair := range origEnv { + kv := strings.SplitN(pair, "=", 2) + os.Setenv(kv[0], kv[1]) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/history.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/history.go new file mode 100644 index 000000000..e6366d31d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/history.go @@ -0,0 +1,172 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/ghodss/yaml" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/timeconv" +) + +type releaseInfo struct { + Revision int32 `json:"revision"` + Updated string `json:"updated"` + Status string `json:"status"` + Chart string `json:"chart"` + Description string `json:"description"` +} + +type releaseHistory []releaseInfo + +var historyHelp = ` +History prints historical revisions for a given release. + +A default maximum of 256 revisions will be returned. Setting '--max' +configures the maximum length of the revision list returned. + +The historical release set is printed as a formatted table, e.g: + + $ helm history angry-bird --max=4 + REVISION UPDATED STATUS CHART DESCRIPTION + 1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Initial install + 2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Upgraded successfully + 3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Rolled back to 2 + 4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0 Upgraded successfully +` + +type historyCmd struct { + max int32 + rls string + out io.Writer + helmc helm.Interface + colWidth uint + outputFormat string +} + +func newHistoryCmd(c helm.Interface, w io.Writer) *cobra.Command { + his := &historyCmd{out: w, helmc: c} + + cmd := &cobra.Command{ + Use: "history [flags] RELEASE_NAME", + Long: historyHelp, + Short: "fetch release history", + Aliases: []string{"hist"}, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + switch { + case len(args) == 0: + return errReleaseRequired + case his.helmc == nil: + his.helmc = newClient() + } + his.rls = args[0] + return his.run() + }, + } + + f := cmd.Flags() + f.Int32Var(&his.max, "max", 256, "maximum number of revision to include in history") + f.UintVar(&his.colWidth, "col-width", 60, "specifies the max column width of output") + f.StringVarP(&his.outputFormat, "output", "o", "table", "prints the output in the specified format (json|table|yaml)") + + return cmd +} + +func (cmd *historyCmd) run() error { + r, err := cmd.helmc.ReleaseHistory(cmd.rls, helm.WithMaxHistory(cmd.max)) + if err != nil { + return prettyError(err) + } + if len(r.Releases) == 0 { + return nil + } + + releaseHistory := getReleaseHistory(r.Releases) + + var history []byte + var formattingError error + + switch cmd.outputFormat { + case "yaml": + history, formattingError = yaml.Marshal(releaseHistory) + case "json": + history, formattingError = json.Marshal(releaseHistory) + case "table": + history = formatAsTable(releaseHistory, cmd.colWidth) + default: + return fmt.Errorf("unknown output format %q", cmd.outputFormat) + } + + if formattingError != nil { + return prettyError(formattingError) + } + + fmt.Fprintln(cmd.out, string(history)) + return nil +} + +func getReleaseHistory(rls []*release.Release) (history releaseHistory) { + for i := len(rls) - 1; i >= 0; i-- { + r := rls[i] + c := formatChartname(r.Chart) + t := timeconv.String(r.Info.LastDeployed) + s := r.Info.Status.Code.String() + v := r.Version + d := r.Info.Description + + rInfo := releaseInfo{ + Revision: v, + Updated: t, + Status: s, + Chart: c, + Description: d, + } + history = append(history, rInfo) + } + + return history +} + +func formatAsTable(releases releaseHistory, colWidth uint) []byte { + tbl := uitable.New() + + tbl.MaxColWidth = colWidth + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION") + for i := 0; i <= len(releases)-1; i++ { + r := releases[i] + tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description) + } + return tbl.Bytes() +} + +func formatChartname(c *chart.Chart) string { + if c == nil || c.Metadata == nil { + // This is an edge case that has happened in prod, though we don't + // know how: https://github.com/kubernetes/helm/issues/1347 + return "MISSING" + } + return fmt.Sprintf("%s-%s", c.Metadata.Name, c.Metadata.Version) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/history_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/history_test.go new file mode 100644 index 000000000..fef433742 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/history_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + rpb "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestHistoryCmd(t *testing.T) { + mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release { + return helm.ReleaseMock(&helm.MockReleaseOptions{ + Name: name, + Version: vers, + StatusCode: code, + }) + } + + tests := []releaseCase{ + { + name: "get history for release", + args: []string{"angry-bird"}, + rels: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + mk("angry-bird", 2, rpb.Status_SUPERSEDED), + mk("angry-bird", 1, rpb.Status_SUPERSEDED), + }, + expected: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n1 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n2 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n", + }, + { + name: "get history with max limit set", + args: []string{"angry-bird"}, + flags: []string{"--max", "2"}, + rels: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + }, + expected: "REVISION\tUPDATED \tSTATUS \tCHART \tDESCRIPTION \n3 \t(.*)\tSUPERSEDED\tfoo-0.1.0-beta.1\tRelease mock\n4 \t(.*)\tDEPLOYED \tfoo-0.1.0-beta.1\tRelease mock\n", + }, + { + name: "get history with yaml output format", + args: []string{"angry-bird"}, + flags: []string{"--output", "yaml"}, + rels: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + }, + expected: "- chart: foo-0.1.0-beta.1\n description: Release mock\n revision: 3\n status: SUPERSEDED\n updated: (.*)\n- chart: foo-0.1.0-beta.1\n description: Release mock\n revision: 4\n status: DEPLOYED\n updated: (.*)\n\n", + }, + { + name: "get history with json output format", + args: []string{"angry-bird"}, + flags: []string{"--output", "json"}, + rels: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + }, + expected: `[{"revision":3,"updated":".*","status":"SUPERSEDED","chart":"foo\-0.1.0-beta.1","description":"Release mock"},{"revision":4,"updated":".*","status":"DEPLOYED","chart":"foo\-0.1.0-beta.1","description":"Release mock"}]\n`, + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newHistoryCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/home.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/home.go new file mode 100644 index 000000000..e96edd7a1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/home.go @@ -0,0 +1,51 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" +) + +var longHomeHelp = ` +This command displays the location of HELM_HOME. This is where +any helm configuration files live. +` + +func newHomeCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "home", + Short: "displays the location of HELM_HOME", + Long: longHomeHelp, + Run: func(cmd *cobra.Command, args []string) { + h := settings.Home + fmt.Fprintln(out, h) + if settings.Debug { + fmt.Fprintf(out, "Repository: %s\n", h.Repository()) + fmt.Fprintf(out, "RepositoryFile: %s\n", h.RepositoryFile()) + fmt.Fprintf(out, "Cache: %s\n", h.Cache()) + fmt.Fprintf(out, "Stable CacheIndex: %s\n", h.CacheIndex("stable")) + fmt.Fprintf(out, "Starters: %s\n", h.Starters()) + fmt.Fprintf(out, "LocalRepository: %s\n", h.LocalRepository()) + fmt.Fprintf(out, "Plugins: %s\n", h.Plugins()) + } + }, + } + return cmd +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/init.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/init.go new file mode 100644 index 000000000..d368945ec --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/init.go @@ -0,0 +1,493 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/spf13/cobra" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/kubernetes" + + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/helm/cmd/helm/installer" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/helm/portforwarder" + "k8s.io/helm/pkg/repo" +) + +const initDesc = ` +This command installs Tiller (the Helm server-side component) onto your +Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). + +As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters +by reading $KUBECONFIG (default '~/.kube/config') and using the default context. + +To set up just a local environment, use '--client-only'. That will configure +$HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller +deployment. + +When installing Tiller, 'helm init' will attempt to install the latest released +version. You can specify an alternative image with '--tiller-image'. For those +frequently working on the latest code, the flag '--canary-image' will install +the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub +repository on the master branch). + +To dump a manifest containing the Tiller deployment YAML, combine the +'--dry-run' and '--debug' flags. +` + +const ( + stableRepository = "stable" + localRepository = "local" + localRepositoryIndexFile = "index.yaml" +) + +var ( + stableRepositoryURL = "https://kubernetes-charts.storage.googleapis.com" + // This is the IPv4 loopback, not localhost, because we have to force IPv4 + // for Dockerized Helm: https://github.com/kubernetes/helm/issues/1410 + localRepositoryURL = "http://127.0.0.1:8879/charts" +) + +type initCmd struct { + image string + clientOnly bool + canary bool + upgrade bool + namespace string + dryRun bool + forceUpgrade bool + skipRefresh bool + out io.Writer + client helm.Interface + home helmpath.Home + opts installer.Options + kubeClient kubernetes.Interface + serviceAccount string + maxHistory int + replicas int + wait bool +} + +func newInitCmd(out io.Writer) *cobra.Command { + i := &initCmd{out: out} + + cmd := &cobra.Command{ + Use: "init", + Short: "initialize Helm on both client and server", + Long: initDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return errors.New("This command does not accept arguments") + } + i.namespace = settings.TillerNamespace + i.home = settings.Home + i.client = ensureHelmClient(i.client) + + return i.run() + }, + } + + f := cmd.Flags() + f.StringVarP(&i.image, "tiller-image", "i", "", "override Tiller image") + f.BoolVar(&i.canary, "canary-image", false, "use the canary Tiller image") + f.BoolVar(&i.upgrade, "upgrade", false, "upgrade if Tiller is already installed") + f.BoolVar(&i.forceUpgrade, "force-upgrade", false, "force upgrade of Tiller to the current helm version") + f.BoolVarP(&i.clientOnly, "client-only", "c", false, "if set does not install Tiller") + f.BoolVar(&i.dryRun, "dry-run", false, "do not install local or remote") + f.BoolVar(&i.skipRefresh, "skip-refresh", false, "do not refresh (download) the local repository cache") + f.BoolVar(&i.wait, "wait", false, "block until Tiller is running and ready to receive requests") + + f.BoolVar(&tlsEnable, "tiller-tls", false, "install Tiller with TLS enabled") + f.BoolVar(&tlsVerify, "tiller-tls-verify", false, "install Tiller with TLS enabled and to verify remote certificates") + f.StringVar(&tlsKeyFile, "tiller-tls-key", "", "path to TLS key file to install with Tiller") + f.StringVar(&tlsCertFile, "tiller-tls-cert", "", "path to TLS certificate file to install with Tiller") + f.StringVar(&tlsCaCertFile, "tls-ca-cert", "", "path to CA root certificate") + + f.StringVar(&stableRepositoryURL, "stable-repo-url", stableRepositoryURL, "URL for stable repository") + f.StringVar(&localRepositoryURL, "local-repo-url", localRepositoryURL, "URL for local repository") + + f.BoolVar(&i.opts.EnableHostNetwork, "net-host", false, "install Tiller with net=host") + f.StringVar(&i.serviceAccount, "service-account", "", "name of service account") + f.IntVar(&i.maxHistory, "history-max", 0, "limit the maximum number of revisions saved per release. Use 0 for no limit.") + f.IntVar(&i.replicas, "replicas", 1, "amount of tiller instances to run on the cluster") + + f.StringVar(&i.opts.NodeSelectors, "node-selectors", "", "labels to specify the node on which Tiller is installed (app=tiller,helm=rocks)") + f.VarP(&i.opts.Output, "output", "o", "skip installation and output Tiller's manifest in specified format (json or yaml)") + f.StringArrayVar(&i.opts.Values, "override", []string{}, "override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2)") + + return cmd +} + +// tlsOptions sanitizes the tls flags as well as checks for the existence of required +// tls files indicated by those flags, if any. +func (i *initCmd) tlsOptions() error { + i.opts.EnableTLS = tlsEnable || tlsVerify + i.opts.VerifyTLS = tlsVerify + + if i.opts.EnableTLS { + missing := func(file string) bool { + _, err := os.Stat(file) + return os.IsNotExist(err) + } + if i.opts.TLSKeyFile = tlsKeyFile; i.opts.TLSKeyFile == "" || missing(i.opts.TLSKeyFile) { + return errors.New("missing required TLS key file") + } + if i.opts.TLSCertFile = tlsCertFile; i.opts.TLSCertFile == "" || missing(i.opts.TLSCertFile) { + return errors.New("missing required TLS certificate file") + } + if i.opts.VerifyTLS { + if i.opts.TLSCaCertFile = tlsCaCertFile; i.opts.TLSCaCertFile == "" || missing(i.opts.TLSCaCertFile) { + return errors.New("missing required TLS CA file") + } + } + } + return nil +} + +// run initializes local config and installs Tiller to Kubernetes cluster. +func (i *initCmd) run() error { + if err := i.tlsOptions(); err != nil { + return err + } + i.opts.Namespace = i.namespace + i.opts.UseCanary = i.canary + i.opts.ImageSpec = i.image + i.opts.ForceUpgrade = i.forceUpgrade + i.opts.ServiceAccount = i.serviceAccount + i.opts.MaxHistory = i.maxHistory + i.opts.Replicas = i.replicas + + writeYAMLManifest := func(apiVersion, kind, body string, first, last bool) error { + w := i.out + if !first { + // YAML starting document boundary marker + if _, err := fmt.Fprintln(w, "---"); err != nil { + return err + } + } + if _, err := fmt.Fprintln(w, "apiVersion:", apiVersion); err != nil { + return err + } + if _, err := fmt.Fprintln(w, "kind:", kind); err != nil { + return err + } + if _, err := fmt.Fprint(w, body); err != nil { + return err + } + if !last { + return nil + } + // YAML ending document boundary marker + _, err := fmt.Fprintln(w, "...") + return err + } + if len(i.opts.Output) > 0 { + var body string + var err error + const tm = `{"apiVersion":"extensions/v1beta1","kind":"Deployment",` + if body, err = installer.DeploymentManifest(&i.opts); err != nil { + return err + } + switch i.opts.Output.String() { + case "json": + var out bytes.Buffer + jsonb, err := yaml.ToJSON([]byte(body)) + if err != nil { + return err + } + buf := bytes.NewBuffer(make([]byte, 0, len(tm)+len(jsonb)-1)) + buf.WriteString(tm) + // Drop the opening object delimiter ('{'). + buf.Write(jsonb[1:]) + if err := json.Indent(&out, buf.Bytes(), "", " "); err != nil { + return err + } + if _, err = i.out.Write(out.Bytes()); err != nil { + return err + } + + return nil + case "yaml": + if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { + return err + } + return nil + default: + return fmt.Errorf("unknown output format: %q", i.opts.Output) + } + } + if settings.Debug { + + var body string + var err error + + // write Deployment manifest + if body, err = installer.DeploymentManifest(&i.opts); err != nil { + return err + } + if err := writeYAMLManifest("extensions/v1beta1", "Deployment", body, true, false); err != nil { + return err + } + + // write Service manifest + if body, err = installer.ServiceManifest(i.namespace); err != nil { + return err + } + if err := writeYAMLManifest("v1", "Service", body, false, !i.opts.EnableTLS); err != nil { + return err + } + + // write Secret manifest + if i.opts.EnableTLS { + if body, err = installer.SecretManifest(&i.opts); err != nil { + return err + } + if err := writeYAMLManifest("v1", "Secret", body, false, true); err != nil { + return err + } + } + } + + if i.dryRun { + return nil + } + + if err := ensureDirectories(i.home, i.out); err != nil { + return err + } + if err := ensureDefaultRepos(i.home, i.out, i.skipRefresh); err != nil { + return err + } + if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil { + return err + } + fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", settings.Home) + + if !i.clientOnly { + if i.kubeClient == nil { + _, c, err := getKubeClient(settings.KubeContext) + if err != nil { + return fmt.Errorf("could not get kubernetes client: %s", err) + } + i.kubeClient = c + } + if err := installer.Install(i.kubeClient, &i.opts); err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("error installing: %s", err) + } + if i.upgrade { + if err := installer.Upgrade(i.kubeClient, &i.opts); err != nil { + return fmt.Errorf("error when upgrading: %s", err) + } + if err := i.ping(); err != nil { + return err + } + fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been upgraded to the current version.") + } else { + fmt.Fprintln(i.out, "Warning: Tiller is already installed in the cluster.\n"+ + "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)") + } + } else { + fmt.Fprintln(i.out, "\nTiller (the Helm server-side component) has been installed into your Kubernetes Cluster.\n\n"+ + "Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy.\n"+ + "For more information on securing your installation see: https://docs.helm.sh/using_helm/#securing-your-helm-installation") + } + if err := i.ping(); err != nil { + return err + } + } else { + fmt.Fprintln(i.out, "Not installing Tiller due to 'client-only' flag having been set") + } + + fmt.Fprintln(i.out, "Happy Helming!") + return nil +} + +func (i *initCmd) ping() error { + if i.wait { + _, kubeClient, err := getKubeClient(settings.KubeContext) + if err != nil { + return err + } + if !watchTillerUntilReady(settings.TillerNamespace, kubeClient, settings.TillerConnectionTimeout) { + return fmt.Errorf("tiller was not found. polling deadline exceeded") + } + + // establish a connection to Tiller now that we've effectively guaranteed it's available + if err := setupConnection(); err != nil { + return err + } + i.client = newClient() + if err := i.client.PingTiller(); err != nil { + return fmt.Errorf("could not ping Tiller: %s", err) + } + } + + return nil +} + +// ensureDirectories checks to see if $HELM_HOME exists. +// +// If $HELM_HOME does not exist, this function will create it. +func ensureDirectories(home helmpath.Home, out io.Writer) error { + configDirectories := []string{ + home.String(), + home.Repository(), + home.Cache(), + home.LocalRepository(), + home.Plugins(), + home.Starters(), + home.Archive(), + } + for _, p := range configDirectories { + if fi, err := os.Stat(p); err != nil { + fmt.Fprintf(out, "Creating %s \n", p) + if err := os.MkdirAll(p, 0755); err != nil { + return fmt.Errorf("Could not create %s: %s", p, err) + } + } else if !fi.IsDir() { + return fmt.Errorf("%s must be a directory", p) + } + } + + return nil +} + +func ensureDefaultRepos(home helmpath.Home, out io.Writer, skipRefresh bool) error { + repoFile := home.RepositoryFile() + if fi, err := os.Stat(repoFile); err != nil { + fmt.Fprintf(out, "Creating %s \n", repoFile) + f := repo.NewRepoFile() + sr, err := initStableRepo(home.CacheIndex(stableRepository), out, skipRefresh, home) + if err != nil { + return err + } + lr, err := initLocalRepo(home.LocalRepository(localRepositoryIndexFile), home.CacheIndex("local"), out, home) + if err != nil { + return err + } + f.Add(sr) + f.Add(lr) + if err := f.WriteFile(repoFile, 0644); err != nil { + return err + } + } else if fi.IsDir() { + return fmt.Errorf("%s must be a file, not a directory", repoFile) + } + return nil +} + +func initStableRepo(cacheFile string, out io.Writer, skipRefresh bool, home helmpath.Home) (*repo.Entry, error) { + fmt.Fprintf(out, "Adding %s repo with URL: %s \n", stableRepository, stableRepositoryURL) + c := repo.Entry{ + Name: stableRepository, + URL: stableRepositoryURL, + Cache: cacheFile, + } + r, err := repo.NewChartRepository(&c, getter.All(settings)) + if err != nil { + return nil, err + } + + if skipRefresh { + return &c, nil + } + + // In this case, the cacheFile is always absolute. So passing empty string + // is safe. + if err := r.DownloadIndexFile(""); err != nil { + return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error()) + } + + return &c, nil +} + +func initLocalRepo(indexFile, cacheFile string, out io.Writer, home helmpath.Home) (*repo.Entry, error) { + if fi, err := os.Stat(indexFile); err != nil { + fmt.Fprintf(out, "Adding %s repo with URL: %s \n", localRepository, localRepositoryURL) + i := repo.NewIndexFile() + if err := i.WriteFile(indexFile, 0644); err != nil { + return nil, err + } + + //TODO: take this out and replace with helm update functionality + if err := createLink(indexFile, cacheFile, home); err != nil { + return nil, err + } + } else if fi.IsDir() { + return nil, fmt.Errorf("%s must be a file, not a directory", indexFile) + } + + return &repo.Entry{ + Name: localRepository, + URL: localRepositoryURL, + Cache: cacheFile, + }, nil +} + +func ensureRepoFileFormat(file string, out io.Writer) error { + r, err := repo.LoadRepositoriesFile(file) + if err == repo.ErrRepoOutOfDate { + fmt.Fprintln(out, "Updating repository file format...") + if err := r.WriteFile(file, 0644); err != nil { + return err + } + } + + return nil +} + +// watchTillerUntilReady waits for the tiller pod to become available. This is useful in situations where we +// want to wait before we call New(). +// +// Returns true if it exists. If the timeout was reached and it could not find the pod, it returns false. +func watchTillerUntilReady(namespace string, client kubernetes.Interface, timeout int64) bool { + deadlinePollingChan := time.NewTimer(time.Duration(timeout) * time.Second).C + checkTillerPodTicker := time.NewTicker(500 * time.Millisecond) + doneChan := make(chan bool) + + defer checkTillerPodTicker.Stop() + + go func() { + for range checkTillerPodTicker.C { + _, err := portforwarder.GetTillerPodName(client.CoreV1(), namespace) + if err == nil { + doneChan <- true + break + } + } + }() + + for { + select { + case <-deadlinePollingChan: + return false + case <-doneChan: + return true + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/init_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_test.go new file mode 100644 index 000000000..6a5767fca --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_test.go @@ -0,0 +1,357 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + + "github.com/ghodss/yaml" + + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + testcore "k8s.io/client-go/testing" + + "encoding/json" + + "k8s.io/helm/cmd/helm/installer" + "k8s.io/helm/pkg/helm/helmpath" +) + +func TestInitCmd(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(home) + + var buf bytes.Buffer + fc := fake.NewSimpleClientset() + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("expected error: %v", err) + } + actions := fc.Actions() + if len(actions) != 2 { + t.Errorf("Expected 2 actions, got %d", len(actions)) + } + if !actions[0].Matches("create", "deployments") { + t.Errorf("unexpected action: %v, expected create deployment", actions[0]) + } + if !actions[1].Matches("create", "services") { + t.Errorf("unexpected action: %v, expected create service", actions[1]) + } + expected := "Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster." + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } +} + +func TestInitCmd_exists(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(home) + + var buf bytes.Buffer + fc := fake.NewSimpleClientset(&v1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: v1.NamespaceDefault, + Name: "tiller-deploy", + }, + }) + fc.PrependReactor("*", "*", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewAlreadyExists(v1.Resource("deployments"), "1") + }) + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("expected error: %v", err) + } + expected := "Warning: Tiller is already installed in the cluster.\n" + + "(Use --client-only to suppress this message, or --upgrade to upgrade Tiller to the current version.)" + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } +} + +func TestInitCmd_clientOnly(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(home) + + var buf bytes.Buffer + fc := fake.NewSimpleClientset() + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + clientOnly: true, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(fc.Actions()) != 0 { + t.Error("expected client call") + } + expected := "Not installing Tiller due to 'client-only' flag having been set" + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } +} + +func TestInitCmd_dryRun(t *testing.T) { + // This is purely defensive in this case. + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.Remove(home) + cleanup() + }() + + settings.Debug = true + + var buf bytes.Buffer + fc := fake.NewSimpleClientset() + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + clientOnly: true, + dryRun: true, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Fatal(err) + } + if got := len(fc.Actions()); got != 0 { + t.Errorf("expected no server calls, got %d", got) + } + + docs := bytes.Split(buf.Bytes(), []byte("\n---")) + if got, want := len(docs), 2; got != want { + t.Fatalf("Expected document count of %d, got %d", want, got) + } + for _, doc := range docs { + var y map[string]interface{} + if err := yaml.Unmarshal(doc, &y); err != nil { + t.Errorf("Expected parseable YAML, got %q\n\t%s", doc, err) + } + } +} + +func TestEnsureHome(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(home) + + b := bytes.NewBuffer(nil) + hh := helmpath.Home(home) + settings.Home = hh + if err := ensureDirectories(hh, b); err != nil { + t.Error(err) + } + if err := ensureDefaultRepos(hh, b, false); err != nil { + t.Error(err) + } + if err := ensureDefaultRepos(hh, b, true); err != nil { + t.Error(err) + } + if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil { + t.Error(err) + } + + expectedDirs := []string{hh.String(), hh.Repository(), hh.Cache(), hh.LocalRepository()} + for _, dir := range expectedDirs { + if fi, err := os.Stat(dir); err != nil { + t.Errorf("%s", err) + } else if !fi.IsDir() { + t.Errorf("%s is not a directory", fi) + } + } + + if fi, err := os.Stat(hh.RepositoryFile()); err != nil { + t.Error(err) + } else if fi.IsDir() { + t.Errorf("%s should not be a directory", fi) + } + + if fi, err := os.Stat(hh.LocalRepository(localRepositoryIndexFile)); err != nil { + t.Errorf("%s", err) + } else if fi.IsDir() { + t.Errorf("%s should not be a directory", fi) + } +} + +func TestInitCmd_tlsOptions(t *testing.T) { + const testDir = "../../testdata" + + // tls certificates in testDir + var ( + testCaCertFile = filepath.Join(testDir, "ca.pem") + testCertFile = filepath.Join(testDir, "crt.pem") + testKeyFile = filepath.Join(testDir, "key.pem") + ) + + // these tests verify the effects of permuting the "--tls" and "--tls-verify" flags + // and the install options yieled as a result of (*initCmd).tlsOptions() + // during helm init. + var tests = []struct { + certFile string + keyFile string + caFile string + enable bool + verify bool + describe string + }{ + { // --tls and --tls-verify specified (--tls=true,--tls-verify=true) + certFile: testCertFile, + keyFile: testKeyFile, + caFile: testCaCertFile, + enable: true, + verify: true, + describe: "--tls and --tls-verify specified (--tls=true,--tls-verify=true)", + }, + { // --tls-verify implies --tls (--tls=false,--tls-verify=true) + certFile: testCertFile, + keyFile: testKeyFile, + caFile: testCaCertFile, + enable: false, + verify: true, + describe: "--tls-verify implies --tls (--tls=false,--tls-verify=true)", + }, + { // no --tls-verify (--tls=true,--tls-verify=false) + certFile: testCertFile, + keyFile: testKeyFile, + caFile: "", + enable: true, + verify: false, + describe: "no --tls-verify (--tls=true,--tls-verify=false)", + }, + { // tls is disabled (--tls=false,--tls-verify=false) + certFile: "", + keyFile: "", + caFile: "", + enable: false, + verify: false, + describe: "tls is disabled (--tls=false,--tls-verify=false)", + }, + } + + for _, tt := range tests { + // emulate tls file specific flags + tlsCaCertFile, tlsCertFile, tlsKeyFile = tt.caFile, tt.certFile, tt.keyFile + + // emulate tls enable/verify flags + tlsEnable, tlsVerify = tt.enable, tt.verify + + cmd := &initCmd{} + if err := cmd.tlsOptions(); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // expected result options + expect := installer.Options{ + TLSCaCertFile: tt.caFile, + TLSCertFile: tt.certFile, + TLSKeyFile: tt.keyFile, + VerifyTLS: tt.verify, + EnableTLS: tt.enable || tt.verify, + } + + if !reflect.DeepEqual(cmd.opts, expect) { + t.Errorf("%s: got %#+v, want %#+v", tt.describe, cmd.opts, expect) + } + } +} + +// TestInitCmd_output tests that init -o formats are unmarshal-able +func TestInitCmd_output(t *testing.T) { + // This is purely defensive in this case. + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + dbg := settings.Debug + settings.Debug = true + defer func() { + os.Remove(home) + settings.Debug = dbg + }() + fc := fake.NewSimpleClientset() + tests := []struct { + expectF func([]byte, interface{}) error + expectName string + }{ + { + json.Unmarshal, + "json", + }, + { + yaml.Unmarshal, + "yaml", + }, + } + for _, s := range tests { + var buf bytes.Buffer + cmd := &initCmd{ + out: &buf, + home: helmpath.Home(home), + kubeClient: fc, + opts: installer.Options{Output: installer.OutputFormat(s.expectName)}, + namespace: v1.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Fatal(err) + } + if got := len(fc.Actions()); got != 0 { + t.Errorf("expected no server calls, got %d", got) + } + d := &v1beta1.Deployment{} + if err = s.expectF(buf.Bytes(), &d); err != nil { + t.Errorf("error unmarshalling init %s output %s %s", s.expectName, err, buf.String()) + } + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/init_unix.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_unix.go new file mode 100644 index 000000000..1117dd487 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_unix.go @@ -0,0 +1,29 @@ +// +build !windows + +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "os" + + "k8s.io/helm/pkg/helm/helmpath" +) + +func createLink(indexFile, cacheFile string, home helmpath.Home) error { + return os.Symlink(indexFile, cacheFile) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/init_windows.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_windows.go new file mode 100644 index 000000000..be17bccda --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/init_windows.go @@ -0,0 +1,29 @@ +// +build windows + +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "os" + + "k8s.io/helm/pkg/helm/helmpath" +) + +func createLink(indexFile, cacheFile string, home helmpath.Home) error { + return os.Link(indexFile, cacheFile) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect.go new file mode 100644 index 000000000..999856959 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect.go @@ -0,0 +1,264 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "strings" + + "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/any" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/kubernetes/pkg/util/slice" +) + +const inspectDesc = ` +This command inspects a chart and displays information. It takes a chart reference +('stable/drupal'), a full path to a directory or packaged chart, or a URL. + +Inspect prints the contents of the Chart.yaml file and the values.yaml file. +` + +const inspectValuesDesc = ` +This command inspects a chart (directory, file, or URL) and displays the contents +of the values.yaml file +` + +const inspectChartDesc = ` +This command inspects a chart (directory, file, or URL) and displays the contents +of the Charts.yaml file +` + +const readmeChartDesc = ` +This command inspects a chart (directory, file, or URL) and displays the contents +of the README file +` + +type inspectCmd struct { + chartpath string + output string + verify bool + keyring string + out io.Writer + version string + repoURL string + username string + password string + + certFile string + keyFile string + caFile string +} + +const ( + chartOnly = "chart" + valuesOnly = "values" + readmeOnly = "readme" + all = "all" +) + +var readmeFileNames = []string{"readme.md", "readme.txt", "readme"} + +func newInspectCmd(out io.Writer) *cobra.Command { + insp := &inspectCmd{ + out: out, + output: all, + } + + inspectCommand := &cobra.Command{ + Use: "inspect [CHART]", + Short: "inspect a chart", + Long: inspectDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "chart name"); err != nil { + return err + } + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, + insp.certFile, insp.keyFile, insp.caFile) + if err != nil { + return err + } + insp.chartpath = cp + return insp.run() + }, + } + + valuesSubCmd := &cobra.Command{ + Use: "values [CHART]", + Short: "shows inspect values", + Long: inspectValuesDesc, + RunE: func(cmd *cobra.Command, args []string) error { + insp.output = valuesOnly + if err := checkArgsLength(len(args), "chart name"); err != nil { + return err + } + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, + insp.certFile, insp.keyFile, insp.caFile) + if err != nil { + return err + } + insp.chartpath = cp + return insp.run() + }, + } + + chartSubCmd := &cobra.Command{ + Use: "chart [CHART]", + Short: "shows inspect chart", + Long: inspectChartDesc, + RunE: func(cmd *cobra.Command, args []string) error { + insp.output = chartOnly + if err := checkArgsLength(len(args), "chart name"); err != nil { + return err + } + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, + insp.certFile, insp.keyFile, insp.caFile) + if err != nil { + return err + } + insp.chartpath = cp + return insp.run() + }, + } + + readmeSubCmd := &cobra.Command{ + Use: "readme [CHART]", + Short: "shows inspect readme", + Long: readmeChartDesc, + RunE: func(cmd *cobra.Command, args []string) error { + insp.output = readmeOnly + if err := checkArgsLength(len(args), "chart name"); err != nil { + return err + } + cp, err := locateChartPath(insp.repoURL, insp.username, insp.password, args[0], insp.version, insp.verify, insp.keyring, + insp.certFile, insp.keyFile, insp.caFile) + if err != nil { + return err + } + insp.chartpath = cp + return insp.run() + }, + } + + cmds := []*cobra.Command{inspectCommand, readmeSubCmd, valuesSubCmd, chartSubCmd} + vflag := "verify" + vdesc := "verify the provenance data for this chart" + for _, subCmd := range cmds { + subCmd.Flags().BoolVar(&insp.verify, vflag, false, vdesc) + } + + kflag := "keyring" + kdesc := "path to the keyring containing public verification keys" + kdefault := defaultKeyring() + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.keyring, kflag, kdefault, kdesc) + } + + verflag := "version" + verdesc := "version of the chart. By default, the newest chart is shown" + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.version, verflag, "", verdesc) + } + + repoURL := "repo" + repoURLdesc := "chart repository url where to locate the requested chart" + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.repoURL, repoURL, "", repoURLdesc) + } + + username := "username" + usernamedesc := "chart repository username where to locate the requested chart" + inspectCommand.Flags().StringVar(&insp.username, username, "", usernamedesc) + valuesSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc) + chartSubCmd.Flags().StringVar(&insp.username, username, "", usernamedesc) + + password := "password" + passworddesc := "chart repository password where to locate the requested chart" + inspectCommand.Flags().StringVar(&insp.password, password, "", passworddesc) + valuesSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) + chartSubCmd.Flags().StringVar(&insp.password, password, "", passworddesc) + + certFile := "cert-file" + certFiledesc := "verify certificates of HTTPS-enabled servers using this CA bundle" + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.certFile, certFile, "", certFiledesc) + } + + keyFile := "key-file" + keyFiledesc := "identify HTTPS client using this SSL key file" + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.keyFile, keyFile, "", keyFiledesc) + } + + caFile := "ca-file" + caFiledesc := "chart repository url where to locate the requested chart" + for _, subCmd := range cmds { + subCmd.Flags().StringVar(&insp.caFile, caFile, "", caFiledesc) + } + + for _, subCmd := range cmds[1:] { + inspectCommand.AddCommand(subCmd) + } + + return inspectCommand +} + +func (i *inspectCmd) run() error { + chrt, err := chartutil.Load(i.chartpath) + if err != nil { + return err + } + cf, err := yaml.Marshal(chrt.Metadata) + if err != nil { + return err + } + + if i.output == chartOnly || i.output == all { + fmt.Fprintln(i.out, string(cf)) + } + + if (i.output == valuesOnly || i.output == all) && chrt.Values != nil { + if i.output == all { + fmt.Fprintln(i.out, "---") + } + fmt.Fprintln(i.out, chrt.Values.Raw) + } + + if i.output == readmeOnly || i.output == all { + if i.output == all { + fmt.Fprintln(i.out, "---") + } + readme := findReadme(chrt.Files) + if readme == nil { + return nil + } + fmt.Fprintln(i.out, string(readme.Value)) + } + return nil +} + +func findReadme(files []*any.Any) (file *any.Any) { + for _, file := range files { + if slice.ContainsString(readmeFileNames, strings.ToLower(file.TypeUrl), nil) { + return file + } + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect_test.go new file mode 100644 index 000000000..44d71fbbd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/inspect_test.go @@ -0,0 +1,80 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "io/ioutil" + "strings" + "testing" +) + +func TestInspect(t *testing.T) { + b := bytes.NewBuffer(nil) + + insp := &inspectCmd{ + chartpath: "testdata/testcharts/alpine", + output: all, + out: b, + } + insp.run() + + // Load the data from the textfixture directly. + cdata, err := ioutil.ReadFile("testdata/testcharts/alpine/Chart.yaml") + if err != nil { + t.Fatal(err) + } + data, err := ioutil.ReadFile("testdata/testcharts/alpine/values.yaml") + if err != nil { + t.Fatal(err) + } + readmeData, err := ioutil.ReadFile("testdata/testcharts/alpine/README.md") + if err != nil { + t.Fatal(err) + } + parts := strings.SplitN(b.String(), "---", 3) + if len(parts) != 3 { + t.Fatalf("Expected 2 parts, got %d", len(parts)) + } + + expect := []string{ + strings.Replace(strings.TrimSpace(string(cdata)), "\r", "", -1), + strings.Replace(strings.TrimSpace(string(data)), "\r", "", -1), + strings.Replace(strings.TrimSpace(string(readmeData)), "\r", "", -1), + } + + // Problem: ghodss/yaml doesn't marshal into struct order. To solve, we + // have to carefully craft the Chart.yaml to match. + for i, got := range parts { + got = strings.Replace(strings.TrimSpace(got), "\r", "", -1) + if got != expect[i] { + t.Errorf("Expected\n%q\nGot\n%q\n", expect[i], got) + } + } + + // Regression tests for missing values. See issue #1024. + b.Reset() + insp = &inspectCmd{ + chartpath: "testdata/testcharts/novals", + output: "values", + out: b, + } + insp.run() + if b.Len() != 0 { + t.Errorf("expected empty values buffer, got %q", b.String()) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/install.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/install.go new file mode 100644 index 000000000..55e34f405 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/install.go @@ -0,0 +1,530 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/Masterminds/sprig" + "github.com/ghodss/yaml" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/strvals" +) + +const installDesc = ` +This command installs a chart archive. + +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force +a string value use '--set-string'. + + $ helm install -f myvalues.yaml ./redis + +or + + $ helm install --set name=prod ./redis + +or + + $ helm install --set-string long_int=1234567890 ./redis + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm install -f myvalues.yaml -f override.yaml ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm install --set foo=bar --set foo=newbar ./redis + + +To check the generated manifests of a release without installing the chart, +the '--debug' and '--dry-run' flags can be combined. This will still require a +round-trip to the Tiller server. + +If --verify is set, the chart MUST have a provenance file, and the provenance +file MUST pass all verification steps. + +There are five different ways you can express the chart you want to install: + +1. By chart reference: helm install stable/mariadb +2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz +3. By path to an unpacked chart directory: helm install ./nginx +4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz +5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx + +CHART REFERENCES + +A chart reference is a convenient way of reference a chart in a chart repository. + +When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local +configuration for a chart repository named 'stable', and will then look for a +chart in that repository whose name is 'mariadb'. It will install the latest +version of that chart unless you also supply a version number with the +'--version' flag. + +To see the list of chart repositories, use 'helm repo list'. To search for +charts in a repository, use 'helm search'. +` + +type installCmd struct { + name string + namespace string + valueFiles valueFiles + chartPath string + dryRun bool + disableHooks bool + replace bool + verify bool + keyring string + out io.Writer + client helm.Interface + values []string + stringValues []string + nameTemplate string + version string + timeout int64 + wait bool + repoURL string + username string + password string + devel bool + depUp bool + + certFile string + keyFile string + caFile string +} + +type valueFiles []string + +func (v *valueFiles) String() string { + return fmt.Sprint(*v) +} + +func (v *valueFiles) Type() string { + return "valueFiles" +} + +func (v *valueFiles) Set(value string) error { + for _, filePath := range strings.Split(value, ",") { + *v = append(*v, filePath) + } + return nil +} + +func newInstallCmd(c helm.Interface, out io.Writer) *cobra.Command { + inst := &installCmd{ + out: out, + client: c, + } + + cmd := &cobra.Command{ + Use: "install [CHART]", + Short: "install a chart archive", + Long: installDesc, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "chart name"); err != nil { + return err + } + + debug("Original chart version: %q", inst.version) + if inst.version == "" && inst.devel { + debug("setting version to >0.0.0-0") + inst.version = ">0.0.0-0" + } + + cp, err := locateChartPath(inst.repoURL, inst.username, inst.password, args[0], inst.version, inst.verify, inst.keyring, + inst.certFile, inst.keyFile, inst.caFile) + if err != nil { + return err + } + inst.chartPath = cp + inst.client = ensureHelmClient(inst.client) + return inst.run() + }, + } + + f := cmd.Flags() + f.VarP(&inst.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") + f.StringVarP(&inst.name, "name", "n", "", "release name. If unspecified, it will autogenerate one for you") + f.StringVar(&inst.namespace, "namespace", "", "namespace to install the release into. Defaults to the current kube config namespace.") + f.BoolVar(&inst.dryRun, "dry-run", false, "simulate an install") + f.BoolVar(&inst.disableHooks, "no-hooks", false, "prevent hooks from running during install") + f.BoolVar(&inst.replace, "replace", false, "re-use the given name, even if that name is already used. This is unsafe in production") + f.StringArrayVar(&inst.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&inst.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringVar(&inst.nameTemplate, "name-template", "", "specify template used to name the release") + f.BoolVar(&inst.verify, "verify", false, "verify the package before installing it") + f.StringVar(&inst.keyring, "keyring", defaultKeyring(), "location of public keys used for verification") + f.StringVar(&inst.version, "version", "", "specify the exact chart version to install. If this is not specified, the latest version is installed") + f.Int64Var(&inst.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&inst.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.StringVar(&inst.repoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&inst.username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&inst.password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&inst.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&inst.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&inst.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&inst.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") + f.BoolVar(&inst.depUp, "dep-up", false, "run helm dependency update before installing the chart") + + return cmd +} + +func (i *installCmd) run() error { + debug("CHART PATH: %s\n", i.chartPath) + + if i.namespace == "" { + i.namespace = defaultNamespace() + } + + rawVals, err := vals(i.valueFiles, i.values, i.stringValues) + if err != nil { + return err + } + + // If template is specified, try to run the template. + if i.nameTemplate != "" { + i.name, err = generateName(i.nameTemplate) + if err != nil { + return err + } + // Print the final name so the user knows what the final name of the release is. + fmt.Printf("FINAL NAME: %s\n", i.name) + } + + // Check chart requirements to make sure all dependencies are present in /charts + chartRequested, err := chartutil.Load(i.chartPath) + if err != nil { + return prettyError(err) + } + + if req, err := chartutil.LoadRequirements(chartRequested); err == nil { + // If checkDependencies returns an error, we have unfulfilled dependencies. + // As of Helm 2.4.0, this is treated as a stopping condition: + // https://github.com/kubernetes/helm/issues/2209 + if err := checkDependencies(chartRequested, req); err != nil { + if i.depUp { + man := &downloader.Manager{ + Out: i.out, + ChartPath: i.chartPath, + HelmHome: settings.Home, + Keyring: defaultKeyring(), + SkipUpdate: false, + Getters: getter.All(settings), + } + if err := man.Update(); err != nil { + return prettyError(err) + } + } else { + return prettyError(err) + } + + } + } else if err != chartutil.ErrRequirementsNotFound { + return fmt.Errorf("cannot load requirements: %v", err) + } + + res, err := i.client.InstallReleaseFromChart( + chartRequested, + i.namespace, + helm.ValueOverrides(rawVals), + helm.ReleaseName(i.name), + helm.InstallDryRun(i.dryRun), + helm.InstallReuseName(i.replace), + helm.InstallDisableHooks(i.disableHooks), + helm.InstallTimeout(i.timeout), + helm.InstallWait(i.wait)) + if err != nil { + return prettyError(err) + } + + rel := res.GetRelease() + if rel == nil { + return nil + } + i.printRelease(rel) + + // If this is a dry run, we can't display status. + if i.dryRun { + return nil + } + + // Print the status like status command does + status, err := i.client.ReleaseStatus(rel.Name) + if err != nil { + return prettyError(err) + } + PrintStatus(i.out, status) + return nil +} + +// Merges source and destination map, preferring values from the source map +func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { + for k, v := range src { + // If the key doesn't exist already, then just set the key to that value + if _, exists := dest[k]; !exists { + dest[k] = v + continue + } + nextMap, ok := v.(map[string]interface{}) + // If it isn't another map, overwrite the value + if !ok { + dest[k] = v + continue + } + // If the key doesn't exist already, then just set the key to that value + if _, exists := dest[k]; !exists { + dest[k] = nextMap + continue + } + // Edge case: If the key exists in the destination, but isn't a map + destMap, isMap := dest[k].(map[string]interface{}) + // If the source map has a map for this key, prefer it + if !isMap { + dest[k] = v + continue + } + // If we got to this point, it is a map in both, so merge them + dest[k] = mergeValues(destMap, nextMap) + } + return dest +} + +// vals merges values from files specified via -f/--values and +// directly via --set or --set-string, marshaling them to YAML +func vals(valueFiles valueFiles, values []string, stringValues []string) ([]byte, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range valueFiles { + currentMap := map[string]interface{}{} + + var bytes []byte + var err error + if strings.TrimSpace(filePath) == "-" { + bytes, err = ioutil.ReadAll(os.Stdin) + } else { + bytes, err = readFile(filePath) + } + + if err != nil { + return []byte{}, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) + } + // Merge with the previous map + base = mergeValues(base, currentMap) + } + + // User specified a value via --set + for _, value := range values { + if err := strvals.ParseInto(value, base); err != nil { + return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) + } + } + + // User specified a value via --set-string + for _, value := range stringValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err) + } + } + + return yaml.Marshal(base) +} + +// printRelease prints info about a release if the Debug is true. +func (i *installCmd) printRelease(rel *release.Release) { + if rel == nil { + return + } + // TODO: Switch to text/template like everything else. + fmt.Fprintf(i.out, "NAME: %s\n", rel.Name) + if settings.Debug { + printRelease(i.out, rel) + } +} + +// locateChartPath 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: +// - current working directory +// - if path is absolute or begins with '.', error out here +// - chart repos in $HELM_HOME +// - URL +// +// If 'verify' is true, this will attempt to also verify the chart. +func locateChartPath(repoURL, username, password, name, version string, verify bool, keyring, + certFile, keyFile, caFile string) (string, error) { + name = strings.TrimSpace(name) + version = strings.TrimSpace(version) + if fi, err := os.Stat(name); err == nil { + abs, err := filepath.Abs(name) + if err != nil { + return abs, err + } + if verify { + if fi.IsDir() { + return "", errors.New("cannot verify a directory") + } + if _, err := downloader.VerifyChart(abs, keyring); err != nil { + return "", err + } + } + return abs, nil + } + if filepath.IsAbs(name) || strings.HasPrefix(name, ".") { + return name, fmt.Errorf("path %q not found", name) + } + + crepo := filepath.Join(settings.Home.Repository(), name) + if _, err := os.Stat(crepo); err == nil { + return filepath.Abs(crepo) + } + + dl := downloader.ChartDownloader{ + HelmHome: settings.Home, + Out: os.Stdout, + Keyring: keyring, + Getters: getter.All(settings), + Username: username, + Password: password, + } + if verify { + dl.Verify = downloader.VerifyAlways + } + if repoURL != "" { + chartURL, err := repo.FindChartInAuthRepoURL(repoURL, username, password, name, version, + certFile, keyFile, caFile, getter.All(settings)) + if err != nil { + return "", err + } + name = chartURL + } + + if _, err := os.Stat(settings.Home.Archive()); os.IsNotExist(err) { + os.MkdirAll(settings.Home.Archive(), 0744) + } + + filename, _, err := dl.DownloadTo(name, version, settings.Home.Archive()) + if err == nil { + lname, err := filepath.Abs(filename) + if err != nil { + return filename, err + } + debug("Fetched %s to %s\n", name, filename) + return lname, nil + } else if settings.Debug { + return filename, err + } + + return filename, fmt.Errorf("failed to download %q (hint: running `helm repo update` may help)", name) +} + +func generateName(nameTemplate string) (string, error) { + t, err := template.New("name-template").Funcs(sprig.TxtFuncMap()).Parse(nameTemplate) + if err != nil { + return "", err + } + var b bytes.Buffer + err = t.Execute(&b, nil) + if err != nil { + return "", err + } + return b.String(), nil +} + +func defaultNamespace() string { + if ns, _, err := kube.GetConfig(settings.KubeContext).Namespace(); err == nil { + return ns + } + return "default" +} + +func checkDependencies(ch *chart.Chart, reqs *chartutil.Requirements) error { + missing := []string{} + + deps := ch.GetDependencies() + for _, r := range reqs.Dependencies { + found := false + for _, d := range deps { + if d.Metadata.Name == r.Name { + found = true + break + } + } + if !found { + missing = append(missing, r.Name) + } + } + + if len(missing) > 0 { + return fmt.Errorf("found in requirements.yaml, but missing in charts/ directory: %s", strings.Join(missing, ", ")) + } + return nil +} + +//readFile load a file from the local directory or a remote file with a url. +func readFile(filePath string) ([]byte, error) { + u, _ := url.Parse(filePath) + p := getter.All(settings) + + // FIXME: maybe someone handle other protocols like ftp. + getterConstructor, err := p.ByScheme(u.Scheme) + + if err != nil { + return ioutil.ReadFile(filePath) + } + + getter, err := getterConstructor(filePath, "", "", "") + if err != nil { + return []byte{}, err + } + data, err := getter.Get(filePath) + return data.Bytes(), err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/install_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/install_test.go new file mode 100644 index 000000000..aa828c6ce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/install_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/spf13/cobra" + "k8s.io/helm/pkg/helm" +) + +func TestInstall(t *testing.T) { + tests := []releaseCase{ + // Install, base case + { + name: "basic install", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name aeneas", " "), + expected: "aeneas", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + }, + // Install, no hooks + { + name: "install without hooks", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name aeneas --no-hooks", " "), + expected: "aeneas", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + }, + // Install, values from cli + { + name: "install with values", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name virgil --set foo=bar", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), + expected: "virgil", + }, + // Install, values from cli via multiple --set + { + name: "install with multiple values", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name virgil --set foo=bar --set bar=foo", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), + expected: "virgil", + }, + // Install, values from yaml + { + name: "install with values", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name virgil -f testdata/testcharts/alpine/extra_values.yaml", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), + expected: "virgil", + }, + // Install, values from multiple yaml + { + name: "install with values", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name virgil -f testdata/testcharts/alpine/extra_values.yaml -f testdata/testcharts/alpine/more_values.yaml", " "), + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "virgil"}), + expected: "virgil", + }, + // Install, no charts + { + name: "install with no chart specified", + args: []string{}, + err: true, + }, + // Install, re-use name + { + name: "install and replace release", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name aeneas --replace", " "), + expected: "aeneas", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "aeneas"}), + }, + // Install, with timeout + { + name: "install with a timeout", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name foobar --timeout 120", " "), + expected: "foobar", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "foobar"}), + }, + // Install, with wait + { + name: "install with a wait", + args: []string{"testdata/testcharts/alpine"}, + flags: strings.Split("--name apollo --wait", " "), + expected: "apollo", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "apollo"}), + }, + // Install, using the name-template + { + name: "install with name-template", + args: []string{"testdata/testcharts/alpine"}, + flags: []string{"--name-template", "{{upper \"foobar\"}}"}, + expected: "FOOBAR", + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "FOOBAR"}), + }, + // Install, perform chart verification along the way. + { + name: "install with verification, missing provenance", + args: []string{"testdata/testcharts/compressedchart-0.1.0.tgz"}, + flags: strings.Split("--verify --keyring testdata/helm-test-key.pub", " "), + err: true, + }, + { + name: "install with verification, directory instead of file", + args: []string{"testdata/testcharts/signtest"}, + flags: strings.Split("--verify --keyring testdata/helm-test-key.pub", " "), + err: true, + }, + { + name: "install with verification, valid", + args: []string{"testdata/testcharts/signtest-0.1.0.tgz"}, + flags: strings.Split("--verify --keyring testdata/helm-test-key.pub", " "), + }, + // Install, chart with missing dependencies in /charts + { + name: "install chart with missing dependencies", + args: []string{"testdata/testcharts/chart-missing-deps"}, + err: true, + }, + // Install, chart with bad requirements.yaml in /charts + { + name: "install chart with bad requirements.yaml", + args: []string{"testdata/testcharts/chart-bad-requirements"}, + err: true, + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newInstallCmd(c, out) + }) +} + +type nameTemplateTestCase struct { + tpl string + expected string + expectedErrorStr string +} + +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-{{randInt}}", + expected: "", + expectedErrorStr: "function \"randInt\" not defined", + }, + // Invalid template + { + tpl: "foobar-{{", + expected: "", + expectedErrorStr: "unexpected unclosed action", + }, + } + + for _, tc := range testCases { + + n, err := generateName(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 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 := mergeValues(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 = mergeValues(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 = mergeValues(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 = mergeValues(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) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install.go new file mode 100644 index 000000000..fc81fa26b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install.go @@ -0,0 +1,372 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/cmd/helm/installer" + +import ( + "errors" + "fmt" + "io/ioutil" + "strings" + + "github.com/Masterminds/semver" + "github.com/ghodss/yaml" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/kubernetes" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + extensionsclient "k8s.io/client-go/kubernetes/typed/extensions/v1beta1" + "k8s.io/helm/pkg/version" + + "k8s.io/helm/pkg/chartutil" +) + +// Install uses Kubernetes client to install Tiller. +// +// Returns an error if the command failed. +func Install(client kubernetes.Interface, opts *Options) error { + if err := createDeployment(client.ExtensionsV1beta1(), opts); err != nil { + return err + } + if err := createService(client.CoreV1(), opts.Namespace); err != nil { + return err + } + if opts.tls() { + if err := createSecret(client.CoreV1(), opts); err != nil { + return err + } + } + return nil +} + +// Upgrade uses Kubernetes client to upgrade Tiller to current version. +// +// Returns an error if the command failed. +func Upgrade(client kubernetes.Interface, opts *Options) error { + obj, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Get(deploymentName, metav1.GetOptions{}) + if err != nil { + return err + } + tillerImage := obj.Spec.Template.Spec.Containers[0].Image + if semverCompare(tillerImage) == -1 && !opts.ForceUpgrade { + return errors.New("current Tiller version is newer, use --force-upgrade to downgrade") + } + obj.Spec.Template.Spec.Containers[0].Image = opts.selectImage() + obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = opts.pullPolicy() + obj.Spec.Template.Spec.ServiceAccountName = opts.ServiceAccount + if _, err := client.ExtensionsV1beta1().Deployments(opts.Namespace).Update(obj); err != nil { + return err + } + // If the service does not exists that would mean we are upgrading from a Tiller version + // that didn't deploy the service, so install it. + _, err = client.CoreV1().Services(opts.Namespace).Get(serviceName, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return createService(client.CoreV1(), opts.Namespace) + } + return err +} + +// semverCompare returns whether the client's version is older, equal or newer than the given image's version. +func semverCompare(image string) int { + split := strings.Split(image, ":") + if len(split) < 2 { + // If we don't know the version, we consider the client version newer. + return 1 + } + tillerVersion, err := semver.NewVersion(split[1]) + if err != nil { + // same thing with unparsable tiller versions (e.g. canary releases). + return 1 + } + clientVersion, err := semver.NewVersion(version.Version) + if err != nil { + // aaaaaand same thing with unparsable helm versions (e.g. canary releases). + return 1 + } + return clientVersion.Compare(tillerVersion) +} + +// createDeployment creates the Tiller Deployment resource. +func createDeployment(client extensionsclient.DeploymentsGetter, opts *Options) error { + obj, err := deployment(opts) + if err != nil { + return err + } + _, err = client.Deployments(obj.Namespace).Create(obj) + return err + +} + +// deployment gets the deployment object that installs Tiller. +func deployment(opts *Options) (*v1beta1.Deployment, error) { + return generateDeployment(opts) +} + +// createService creates the Tiller service resource +func createService(client corev1.ServicesGetter, namespace string) error { + obj := service(namespace) + _, err := client.Services(obj.Namespace).Create(obj) + return err +} + +// service gets the service object that installs Tiller. +func service(namespace string) *v1.Service { + return generateService(namespace) +} + +// DeploymentManifest gets the manifest (as a string) that describes the Tiller Deployment +// resource. +func DeploymentManifest(opts *Options) (string, error) { + obj, err := deployment(opts) + if err != nil { + return "", err + } + buf, err := yaml.Marshal(obj) + return string(buf), err +} + +// ServiceManifest gets the manifest (as a string) that describes the Tiller Service +// resource. +func ServiceManifest(namespace string) (string, error) { + obj := service(namespace) + buf, err := yaml.Marshal(obj) + return string(buf), err +} + +func generateLabels(labels map[string]string) map[string]string { + labels["app"] = "helm" + return labels +} + +// parseNodeSelectors parses a comma delimited list of key=values pairs into a map. +func parseNodeSelectorsInto(labels string, m map[string]string) error { + kv := strings.Split(labels, ",") + for _, v := range kv { + el := strings.Split(v, "=") + if len(el) == 2 { + m[el[0]] = el[1] + } else { + return fmt.Errorf("invalid nodeSelector label: %q", kv) + } + } + return nil +} +func generateDeployment(opts *Options) (*v1beta1.Deployment, error) { + labels := generateLabels(map[string]string{"name": "tiller"}) + nodeSelectors := map[string]string{} + if len(opts.NodeSelectors) > 0 { + err := parseNodeSelectorsInto(opts.NodeSelectors, nodeSelectors) + if err != nil { + return nil, err + } + } + d := &v1beta1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: opts.Namespace, + Name: deploymentName, + Labels: labels, + }, + Spec: v1beta1.DeploymentSpec{ + Replicas: opts.getReplicas(), + Template: v1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: v1.PodSpec{ + ServiceAccountName: opts.ServiceAccount, + Containers: []v1.Container{ + { + Name: "tiller", + Image: opts.selectImage(), + ImagePullPolicy: opts.pullPolicy(), + Ports: []v1.ContainerPort{ + {ContainerPort: 44134, Name: "tiller"}, + {ContainerPort: 44135, Name: "http"}, + }, + Env: []v1.EnvVar{ + {Name: "TILLER_NAMESPACE", Value: opts.Namespace}, + {Name: "TILLER_HISTORY_MAX", Value: fmt.Sprintf("%d", opts.MaxHistory)}, + }, + LivenessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/liveness", + Port: intstr.FromInt(44135), + }, + }, + InitialDelaySeconds: 1, + TimeoutSeconds: 1, + }, + ReadinessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Path: "/readiness", + Port: intstr.FromInt(44135), + }, + }, + InitialDelaySeconds: 1, + TimeoutSeconds: 1, + }, + }, + }, + HostNetwork: opts.EnableHostNetwork, + NodeSelector: nodeSelectors, + }, + }, + }, + } + + if opts.tls() { + const certsDir = "/etc/certs" + + var tlsVerify, tlsEnable = "", "1" + if opts.VerifyTLS { + tlsVerify = "1" + } + + // Mount secret to "/etc/certs" + d.Spec.Template.Spec.Containers[0].VolumeMounts = append(d.Spec.Template.Spec.Containers[0].VolumeMounts, v1.VolumeMount{ + Name: "tiller-certs", + ReadOnly: true, + MountPath: certsDir, + }) + // Add environment variable required for enabling TLS + d.Spec.Template.Spec.Containers[0].Env = append(d.Spec.Template.Spec.Containers[0].Env, []v1.EnvVar{ + {Name: "TILLER_TLS_VERIFY", Value: tlsVerify}, + {Name: "TILLER_TLS_ENABLE", Value: tlsEnable}, + {Name: "TILLER_TLS_CERTS", Value: certsDir}, + }...) + // Add secret volume to deployment + d.Spec.Template.Spec.Volumes = append(d.Spec.Template.Spec.Volumes, v1.Volume{ + Name: "tiller-certs", + VolumeSource: v1.VolumeSource{ + Secret: &v1.SecretVolumeSource{ + SecretName: "tiller-secret", + }, + }, + }) + } + // if --override values were specified, ultimately convert values and deployment to maps, + // merge them and convert back to Deployment + if len(opts.Values) > 0 { + // base deployment struct + var dd v1beta1.Deployment + // get YAML from original deployment + dy, err := yaml.Marshal(d) + if err != nil { + return nil, fmt.Errorf("Error marshalling base Tiller Deployment: %s", err) + } + // convert deployment YAML to values + dv, err := chartutil.ReadValues(dy) + if err != nil { + return nil, fmt.Errorf("Error converting Deployment manifest: %s ", err) + } + dm := dv.AsMap() + // merge --set values into our map + sm, err := opts.valuesMap(dm) + if err != nil { + return nil, fmt.Errorf("Error merging --set values into Deployment manifest") + } + finalY, err := yaml.Marshal(sm) + if err != nil { + return nil, fmt.Errorf("Error marshalling merged map to YAML: %s ", err) + } + // convert merged values back into deployment + err = yaml.Unmarshal(finalY, &dd) + if err != nil { + return nil, fmt.Errorf("Error unmarshalling Values to Deployment manifest: %s ", err) + } + d = &dd + } + + return d, nil +} + +func generateService(namespace string) *v1.Service { + labels := generateLabels(map[string]string{"name": "tiller"}) + s := &v1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: serviceName, + Labels: labels, + }, + Spec: v1.ServiceSpec{ + Type: v1.ServiceTypeClusterIP, + Ports: []v1.ServicePort{ + { + Name: "tiller", + Port: 44134, + TargetPort: intstr.FromString("tiller"), + }, + }, + Selector: labels, + }, + } + return s +} + +// SecretManifest gets the manifest (as a string) that describes the Tiller Secret resource. +func SecretManifest(opts *Options) (string, error) { + o, err := generateSecret(opts) + if err != nil { + return "", err + } + buf, err := yaml.Marshal(o) + return string(buf), err +} + +// createSecret creates the Tiller secret resource. +func createSecret(client corev1.SecretsGetter, opts *Options) error { + o, err := generateSecret(opts) + if err != nil { + return err + } + _, err = client.Secrets(o.Namespace).Create(o) + return err +} + +// generateSecret builds the secret object that hold Tiller secrets. +func generateSecret(opts *Options) (*v1.Secret, error) { + + labels := generateLabels(map[string]string{"name": "tiller"}) + secret := &v1.Secret{ + Type: v1.SecretTypeOpaque, + Data: make(map[string][]byte), + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Labels: labels, + Namespace: opts.Namespace, + }, + } + var err error + if secret.Data["tls.key"], err = read(opts.TLSKeyFile); err != nil { + return nil, err + } + if secret.Data["tls.crt"], err = read(opts.TLSCertFile); err != nil { + return nil, err + } + if opts.VerifyTLS { + if secret.Data["ca.crt"], err = read(opts.TLSCaCertFile); err != nil { + return nil, err + } + } + return secret, nil +} + +func read(path string) (b []byte, err error) { return ioutil.ReadFile(path) } diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install_test.go new file mode 100644 index 000000000..dbb7143e3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/install_test.go @@ -0,0 +1,740 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/cmd/helm/installer" + +import ( + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/ghodss/yaml" + "k8s.io/api/core/v1" + "k8s.io/api/extensions/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/fake" + testcore "k8s.io/client-go/testing" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/version" +) + +func TestDeploymentManifest(t *testing.T) { + tests := []struct { + name string + image string + canary bool + expect string + imagePullPolicy v1.PullPolicy + }{ + {"default", "", false, "gcr.io/kubernetes-helm/tiller:" + version.Version, "IfNotPresent"}, + {"canary", "example.com/tiller", true, "gcr.io/kubernetes-helm/tiller:canary", "Always"}, + {"custom", "example.com/tiller:latest", false, "example.com/tiller:latest", "IfNotPresent"}, + } + + for _, tt := range tests { + o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary}) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + var dep v1beta1.Deployment + if err := yaml.Unmarshal([]byte(o), &dep); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + if got := dep.Spec.Template.Spec.Containers[0].Image; got != tt.expect { + t.Errorf("%s: expected image %q, got %q", tt.name, tt.expect, got) + } + + if got := dep.Spec.Template.Spec.Containers[0].ImagePullPolicy; got != tt.imagePullPolicy { + t.Errorf("%s: expected imagePullPolicy %q, got %q", tt.name, tt.imagePullPolicy, got) + } + + if got := dep.Spec.Template.Spec.Containers[0].Env[0].Value; got != v1.NamespaceDefault { + t.Errorf("%s: expected namespace %q, got %q", tt.name, v1.NamespaceDefault, got) + } + } +} + +func TestDeploymentManifestForServiceAccount(t *testing.T) { + tests := []struct { + name string + image string + canary bool + expect string + imagePullPolicy v1.PullPolicy + serviceAccount string + }{ + {"withSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", "service-account"}, + {"withoutSA", "", false, "gcr.io/kubernetes-helm/tiller:latest", "IfNotPresent", ""}, + } + for _, tt := range tests { + o, err := DeploymentManifest(&Options{Namespace: v1.NamespaceDefault, ImageSpec: tt.image, UseCanary: tt.canary, ServiceAccount: tt.serviceAccount}) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + var d v1beta1.Deployment + if err := yaml.Unmarshal([]byte(o), &d); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + if got := d.Spec.Template.Spec.ServiceAccountName; got != tt.serviceAccount { + t.Errorf("%s: expected service account value %q, got %q", tt.name, tt.serviceAccount, got) + } + } +} + +func TestDeploymentManifest_WithTLS(t *testing.T) { + tests := []struct { + opts Options + name string + enable string + verify string + }{ + { + Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: true}, + "tls enable (true), tls verify (true)", + "1", + "1", + }, + { + Options{Namespace: v1.NamespaceDefault, EnableTLS: true, VerifyTLS: false}, + "tls enable (true), tls verify (false)", + "1", + "", + }, + { + Options{Namespace: v1.NamespaceDefault, EnableTLS: false, VerifyTLS: true}, + "tls enable (false), tls verify (true)", + "1", + "1", + }, + } + for _, tt := range tests { + o, err := DeploymentManifest(&tt.opts) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + var d v1beta1.Deployment + if err := yaml.Unmarshal([]byte(o), &d); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + // verify environment variable in deployment reflect the use of tls being enabled. + if got := d.Spec.Template.Spec.Containers[0].Env[2].Value; got != tt.verify { + t.Errorf("%s: expected tls verify env value %q, got %q", tt.name, tt.verify, got) + } + if got := d.Spec.Template.Spec.Containers[0].Env[3].Value; got != tt.enable { + t.Errorf("%s: expected tls enable env value %q, got %q", tt.name, tt.enable, got) + } + } +} + +func TestServiceManifest(t *testing.T) { + o, err := ServiceManifest(v1.NamespaceDefault) + if err != nil { + t.Fatalf("error %q", err) + } + var svc v1.Service + if err := yaml.Unmarshal([]byte(o), &svc); err != nil { + t.Fatalf("error %q", err) + } + + if got := svc.ObjectMeta.Namespace; got != v1.NamespaceDefault { + t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) + } +} + +func TestSecretManifest(t *testing.T) { + o, err := SecretManifest(&Options{ + VerifyTLS: true, + EnableTLS: true, + Namespace: v1.NamespaceDefault, + TLSKeyFile: tlsTestFile(t, "key.pem"), + TLSCertFile: tlsTestFile(t, "crt.pem"), + TLSCaCertFile: tlsTestFile(t, "ca.pem"), + }) + + if err != nil { + t.Fatalf("error %q", err) + } + + var obj v1.Secret + if err := yaml.Unmarshal([]byte(o), &obj); err != nil { + t.Fatalf("error %q", err) + } + + if got := obj.ObjectMeta.Namespace; got != v1.NamespaceDefault { + t.Errorf("expected namespace %s, got %s", v1.NamespaceDefault, got) + } + if _, ok := obj.Data["tls.key"]; !ok { + t.Errorf("missing 'tls.key' in generated secret object") + } + if _, ok := obj.Data["tls.crt"]; !ok { + t.Errorf("missing 'tls.crt' in generated secret object") + } + if _, ok := obj.Data["ca.crt"]; !ok { + t.Errorf("missing 'ca.crt' in generated secret object") + } +} + +func TestInstall(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + + fc := &fake.Clientset{} + fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment) + l := obj.GetLabels() + if reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + ports := len(obj.Spec.Template.Spec.Containers[0].Ports) + if ports != 2 { + t.Errorf("expected ports = 2, got '%d'", ports) + } + replicas := obj.Spec.Replicas + if int(*replicas) != 1 { + t.Errorf("expected replicas = 1, got '%d'", replicas) + } + return true, obj, nil + }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1.Service) + l := obj.GetLabels() + if reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + n := obj.ObjectMeta.Namespace + if n != v1.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n) + } + return true, obj, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image} + if err := Install(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions)) + } +} + +func TestInstallHA(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + + fc := &fake.Clientset{} + fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment) + replicas := obj.Spec.Replicas + if int(*replicas) != 2 { + t.Errorf("expected replicas = 2, got '%d'", replicas) + } + return true, obj, nil + }) + + opts := &Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: image, + Replicas: 2, + } + if err := Install(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } +} + +func TestInstall_WithTLS(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + name := "tiller-secret" + + fc := &fake.Clientset{} + fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment) + l := obj.GetLabels() + if reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + return true, obj, nil + }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1.Service) + l := obj.GetLabels() + if reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + n := obj.ObjectMeta.Namespace + if n != v1.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n) + } + return true, obj, nil + }) + fc.AddReactor("create", "secrets", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1.Secret) + if l := obj.GetLabels(); reflect.DeepEqual(l, map[string]string{"app": "helm"}) { + t.Errorf("expected labels = '', got '%s'", l) + } + if n := obj.ObjectMeta.Namespace; n != v1.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n) + } + if s := obj.ObjectMeta.Name; s != name { + t.Errorf("expected name = '%s', got '%s'", name, s) + } + if _, ok := obj.Data["tls.key"]; !ok { + t.Errorf("missing 'tls.key' in generated secret object") + } + if _, ok := obj.Data["tls.crt"]; !ok { + t.Errorf("missing 'tls.crt' in generated secret object") + } + if _, ok := obj.Data["ca.crt"]; !ok { + t.Errorf("missing 'ca.crt' in generated secret object") + } + return true, obj, nil + }) + + opts := &Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: image, + EnableTLS: true, + VerifyTLS: true, + TLSKeyFile: tlsTestFile(t, "key.pem"), + TLSCertFile: tlsTestFile(t, "crt.pem"), + TLSCaCertFile: tlsTestFile(t, "ca.pem"), + } + + if err := Install(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func TestInstall_canary(t *testing.T) { + fc := &fake.Clientset{} + fc.AddReactor("create", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != "gcr.io/kubernetes-helm/tiller:canary" { + t.Errorf("expected canary image, got '%s'", i) + } + return true, obj, nil + }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1.Service) + return true, obj, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, UseCanary: true} + if err := Install(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 2 { + t.Errorf("unexpected actions: %v, expected 2 actions got %d", actions, len(actions)) + } +} + +func TestUpgrade(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + serviceAccount := "newServiceAccount" + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:v1.0.0", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount} + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func TestUpgrade_serviceNotFound(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace", + UseCanary: false, + }) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(v1.Resource("services"), "1") + }) + fc.AddReactor("create", "services", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.CreateAction).GetObject().(*v1.Service) + n := obj.ObjectMeta.Namespace + if n != v1.NamespaceDefault { + t.Errorf("expected namespace = '%s', got '%s'", v1.NamespaceDefault, n) + } + return true, obj, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image} + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 4 { + t.Errorf("unexpected actions: %v, expected 4 actions got %d", actions, len(actions)) + } +} + +func TestUgrade_newerVersion(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + serviceAccount := "newServiceAccount" + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:v100.5.0", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) + + opts := &Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: image, + ServiceAccount: serviceAccount, + ForceUpgrade: false, + } + if err := Upgrade(fc, opts); err == nil { + t.Errorf("Expected error because the deployed version is newer") + } + + if actions := fc.Actions(); len(actions) != 1 { + t.Errorf("unexpected actions: %v, expected 1 action got %d", actions, len(actions)) + } + + opts = &Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: image, + ServiceAccount: serviceAccount, + ForceUpgrade: true, + } + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 4 { + t.Errorf("unexpected actions: %v, expected 4 action got %d", actions, len(actions)) + } +} + +func TestUpgrade_identical(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + serviceAccount := "newServiceAccount" + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:v2.0.0", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount} + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func TestUpgrade_canaryClient(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:canary" + serviceAccount := "newServiceAccount" + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:v1.0.0", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount} + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func TestUpgrade_canaryServer(t *testing.T) { + image := "gcr.io/kubernetes-helm/tiller:v2.0.0" + serviceAccount := "newServiceAccount" + existingDeployment, _ := deployment(&Options{ + Namespace: v1.NamespaceDefault, + ImageSpec: "imageToReplace:canary", + ServiceAccount: "serviceAccountToReplace", + UseCanary: false, + }) + existingService := service(v1.NamespaceDefault) + + fc := &fake.Clientset{} + fc.AddReactor("get", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingDeployment, nil + }) + fc.AddReactor("update", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + obj := action.(testcore.UpdateAction).GetObject().(*v1beta1.Deployment) + i := obj.Spec.Template.Spec.Containers[0].Image + if i != image { + t.Errorf("expected image = '%s', got '%s'", image, i) + } + sa := obj.Spec.Template.Spec.ServiceAccountName + if sa != serviceAccount { + t.Errorf("expected serviceAccountName = '%s', got '%s'", serviceAccount, sa) + } + return true, obj, nil + }) + fc.AddReactor("get", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, existingService, nil + }) + + opts := &Options{Namespace: v1.NamespaceDefault, ImageSpec: image, ServiceAccount: serviceAccount} + if err := Upgrade(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 3 { + t.Errorf("unexpected actions: %v, expected 3 actions got %d", actions, len(actions)) + } +} + +func tlsTestFile(t *testing.T, path string) string { + const tlsTestDir = "../../../testdata" + path = filepath.Join(tlsTestDir, path) + if _, err := os.Stat(path); os.IsNotExist(err) { + t.Fatalf("tls test file %s does not exist", path) + } + return path +} +func TestDeploymentManifest_WithNodeSelectors(t *testing.T) { + tests := []struct { + opts Options + name string + expect map[string]interface{} + }{ + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller"}, + "nodeSelector app=tiller", + map[string]interface{}{"app": "tiller"}, + }, + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,helm=rocks"}, + "nodeSelector app=tiller, helm=rocks", + map[string]interface{}{"app": "tiller", "helm": "rocks"}, + }, + // note: nodeSelector key and value are strings + { + Options{Namespace: v1.NamespaceDefault, NodeSelectors: "app=tiller,minCoolness=1"}, + "nodeSelector app=tiller, helm=rocks", + map[string]interface{}{"app": "tiller", "minCoolness": "1"}, + }, + } + for _, tt := range tests { + o, err := DeploymentManifest(&tt.opts) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + + var d v1beta1.Deployment + if err := yaml.Unmarshal([]byte(o), &d); err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + // Verify that environment variables in Deployment reflect the use of TLS being enabled. + got := d.Spec.Template.Spec.NodeSelector + for k, v := range tt.expect { + if got[k] != v { + t.Errorf("%s: expected nodeSelector value %q, got %q", tt.name, tt.expect, got) + } + } + } +} +func TestDeploymentManifest_WithSetValues(t *testing.T) { + tests := []struct { + opts Options + name string + expectPath string + expect interface{} + }{ + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.nodeselector.app=tiller"}}, + "setValues spec.template.spec.nodeSelector.app=tiller", + "spec.template.spec.nodeSelector.app", + "tiller", + }, + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.replicas=2"}}, + "setValues spec.replicas=2", + "spec.replicas", + 2, + }, + { + Options{Namespace: v1.NamespaceDefault, Values: []string{"spec.template.spec.activedeadlineseconds=120"}}, + "setValues spec.template.spec.activedeadlineseconds=120", + "spec.template.spec.activeDeadlineSeconds", + 120, + }, + } + for _, tt := range tests { + o, err := DeploymentManifest(&tt.opts) + if err != nil { + t.Fatalf("%s: error %q", tt.name, err) + } + values, err := chartutil.ReadValues([]byte(o)) + if err != nil { + t.Errorf("Error converting Deployment manifest to Values: %s", err) + } + // path value + pv, err := values.PathValue(tt.expectPath) + if err != nil { + t.Errorf("Error retrieving path value from Deployment Values: %s", err) + } + + // convert our expected value to match the result type for comparison + ev := tt.expect + switch pvt := pv.(type) { + case float64: + floatType := reflect.TypeOf(float64(0)) + v := reflect.ValueOf(ev) + v = reflect.Indirect(v) + if !v.Type().ConvertibleTo(floatType) { + t.Fatalf("Error converting expected value %v to float64", v.Type()) + } + fv := v.Convert(floatType) + if fv.Float() != pvt { + t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv) + } + default: + if pv != tt.expect { + t.Errorf("%s: expected value %q, got %q", tt.name, tt.expect, pv) + } + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/options.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/options.go new file mode 100644 index 000000000..13cf43dcc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/options.go @@ -0,0 +1,164 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/cmd/helm/installer" + +import ( + "fmt" + + "k8s.io/api/core/v1" + "k8s.io/helm/pkg/strvals" + "k8s.io/helm/pkg/version" +) + +const defaultImage = "gcr.io/kubernetes-helm/tiller" + +// Options control how to install Tiller into a cluster, upgrade, and uninstall Tiller from a cluster. +type Options struct { + // EnableTLS instructs Tiller to serve with TLS enabled. + // + // Implied by VerifyTLS. If set the TLSKey and TLSCert are required. + EnableTLS bool + + // VerifyTLS instructs Tiller to serve with TLS enabled verify remote certificates. + // + // If set TLSKey, TLSCert, TLSCaCert are required. + VerifyTLS bool + + // UseCanary indicates that Tiller should deploy using the latest Tiller image. + UseCanary bool + + // Namespace is the Kubernetes namespace to use to deploy Tiller. + Namespace string + + // ServiceAccount is the Kubernetes service account to add to Tiller. + ServiceAccount string + + // Force allows to force upgrading tiller if deployed version is greater than current version + ForceUpgrade bool + + // ImageSpec indentifies the image Tiller will use when deployed. + // + // Valid if and only if UseCanary is false. + ImageSpec string + + // TLSKeyFile identifies the file containing the pem encoded TLS private + // key Tiller should use. + // + // Required and valid if and only if EnableTLS or VerifyTLS is set. + TLSKeyFile string + + // TLSCertFile identifies the file containing the pem encoded TLS + // certificate Tiller should use. + // + // Required and valid if and only if EnableTLS or VerifyTLS is set. + TLSCertFile string + + // TLSCaCertFile identifies the file containing the pem encoded TLS CA + // certificate Tiller should use to verify remotes certificates. + // + // Required and valid if and only if VerifyTLS is set. + TLSCaCertFile string + + // EnableHostNetwork installs Tiller with net=host. + EnableHostNetwork bool + + // MaxHistory sets the maximum number of release versions stored per release. + // + // Less than or equal to zero means no limit. + MaxHistory int + + // Replicas sets the amount of Tiller replicas to start + // + // Less than or equals to 1 means 1. + Replicas int + + // NodeSelectors determine which nodes Tiller can land on. + NodeSelectors string + + // Output dumps the Tiller manifest in the specified format (e.g. JSON) but skips Helm/Tiller installation. + Output OutputFormat + + // Set merges additional values into the Tiller Deployment manifest. + Values []string +} + +func (opts *Options) selectImage() string { + switch { + case opts.UseCanary: + return defaultImage + ":canary" + case opts.ImageSpec == "": + return fmt.Sprintf("%s:%s", defaultImage, version.Version) + default: + return opts.ImageSpec + } +} + +func (opts *Options) pullPolicy() v1.PullPolicy { + if opts.UseCanary { + return v1.PullAlways + } + return v1.PullIfNotPresent +} + +func (opts *Options) getReplicas() *int32 { + replicas := int32(1) + if opts.Replicas > 1 { + replicas = int32(opts.Replicas) + } + return &replicas +} + +func (opts *Options) tls() bool { return opts.EnableTLS || opts.VerifyTLS } + +// valuesMap returns user set values in map format +func (opts *Options) valuesMap(m map[string]interface{}) (map[string]interface{}, error) { + for _, skv := range opts.Values { + if err := strvals.ParseInto(skv, m); err != nil { + return nil, err + } + } + return m, nil +} + +// OutputFormat defines valid values for init output (json, yaml) +type OutputFormat string + +// String returns the string value of the OutputFormat +func (f *OutputFormat) String() string { + return string(*f) +} + +// Type returns the string value of the OutputFormat +func (f *OutputFormat) Type() string { + return "OutputFormat" +} + +const ( + fmtJSON OutputFormat = "json" + fmtYAML OutputFormat = "yaml" +) + +// Set validates and sets the value of the OutputFormat +func (f *OutputFormat) Set(s string) error { + for _, of := range []OutputFormat{fmtJSON, fmtYAML} { + if s == string(of) { + *f = of + return nil + } + } + return fmt.Errorf("unknown output format %q", s) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall.go new file mode 100644 index 000000000..818827ddb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall.go @@ -0,0 +1,71 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/cmd/helm/installer" + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + "k8s.io/kubernetes/pkg/kubectl" +) + +const ( + deploymentName = "tiller-deploy" + serviceName = "tiller-deploy" + secretName = "tiller-secret" +) + +// Uninstall uses Kubernetes client to uninstall Tiller. +func Uninstall(client internalclientset.Interface, opts *Options) error { + if err := deleteService(client.Core(), opts.Namespace); err != nil { + return err + } + if err := deleteDeployment(client, opts.Namespace); err != nil { + return err + } + return deleteSecret(client.Core(), opts.Namespace) +} + +// deleteService deletes the Tiller Service resource +func deleteService(client coreclient.ServicesGetter, namespace string) error { + err := client.Services(namespace).Delete(serviceName, &metav1.DeleteOptions{}) + return ingoreNotFound(err) +} + +// deleteDeployment deletes the Tiller Deployment resource +// We need to use the reaper instead of the kube API because GC for deployment dependents +// is not yet supported at the k8s server level (<= 1.5) +func deleteDeployment(client internalclientset.Interface, namespace string) error { + reaper, _ := kubectl.ReaperFor(extensions.Kind("Deployment"), client) + err := reaper.Stop(namespace, deploymentName, 0, nil) + return ingoreNotFound(err) +} + +// deleteSecret deletes the Tiller Secret resource +func deleteSecret(client coreclient.SecretsGetter, namespace string) error { + err := client.Secrets(namespace).Delete(secretName, &metav1.DeleteOptions{}) + return ingoreNotFound(err) +} + +func ingoreNotFound(err error) error { + if apierrors.IsNotFound(err) { + return nil + } + return err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall_test.go new file mode 100644 index 000000000..91b257d47 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/installer/uninstall_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/cmd/helm/installer" + +import ( + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + testcore "k8s.io/client-go/testing" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" +) + +func TestUninstall(t *testing.T) { + fc := &fake.Clientset{} + opts := &Options{Namespace: core.NamespaceDefault} + if err := Uninstall(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 7 { + t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) + } +} + +func TestUninstall_serviceNotFound(t *testing.T) { + fc := &fake.Clientset{} + fc.AddReactor("delete", "services", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(schema.GroupResource{Resource: "services"}, "1") + }) + + opts := &Options{Namespace: core.NamespaceDefault} + if err := Uninstall(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 7 { + t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) + } +} + +func TestUninstall_deploymentNotFound(t *testing.T) { + fc := &fake.Clientset{} + fc.AddReactor("delete", "deployments", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(core.Resource("deployments"), "1") + }) + + opts := &Options{Namespace: core.NamespaceDefault} + if err := Uninstall(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 7 { + t.Errorf("unexpected actions: %v, expected 7 actions got %d", actions, len(actions)) + } +} + +func TestUninstall_secretNotFound(t *testing.T) { + fc := &fake.Clientset{} + fc.AddReactor("delete", "secrets", func(action testcore.Action) (bool, runtime.Object, error) { + return true, nil, apierrors.NewNotFound(core.Resource("secrets"), "1") + }) + + opts := &Options{Namespace: core.NamespaceDefault} + if err := Uninstall(fc, opts); err != nil { + t.Errorf("unexpected error: %#+v", err) + } + + if actions := fc.Actions(); len(actions) != 7 { + t.Errorf("unexpected actions: %v, expect 7 actions got %d", actions, len(actions)) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/lint.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/lint.go new file mode 100644 index 000000000..63f11c062 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/lint.go @@ -0,0 +1,208 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/lint" + "k8s.io/helm/pkg/lint/support" + "k8s.io/helm/pkg/strvals" +) + +var longLintHelp = ` +This command takes a path to a chart and runs a series of tests to verify that +the chart is well-formed. + +If the linter encounters things that will cause the chart to fail installation, +it will emit [ERROR] messages. If it encounters issues that break with convention +or recommendation, it will emit [WARNING] messages. +` + +type lintCmd struct { + valueFiles valueFiles + values []string + sValues []string + namespace string + strict bool + paths []string + out io.Writer +} + +func newLintCmd(out io.Writer) *cobra.Command { + l := &lintCmd{ + paths: []string{"."}, + out: out, + } + cmd := &cobra.Command{ + Use: "lint [flags] PATH", + Short: "examines a chart for possible issues", + Long: longLintHelp, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + l.paths = args + } + return l.run() + }, + } + + cmd.Flags().VarP(&l.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") + cmd.Flags().StringArrayVar(&l.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + cmd.Flags().StringArrayVar(&l.sValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + cmd.Flags().StringVar(&l.namespace, "namespace", "default", "namespace to install the release into (only used if --install is set)") + cmd.Flags().BoolVar(&l.strict, "strict", false, "fail on lint warnings") + + return cmd +} + +var errLintNoChart = errors.New("No chart found for linting (missing Chart.yaml)") + +func (l *lintCmd) run() error { + var lowestTolerance int + if l.strict { + lowestTolerance = support.WarningSev + } else { + lowestTolerance = support.ErrorSev + } + + // Get the raw values + rvals, err := l.vals() + if err != nil { + return err + } + + var total int + var failures int + for _, path := range l.paths { + if linter, err := lintChart(path, rvals, l.namespace, l.strict); err != nil { + fmt.Println("==> Skipping", path) + fmt.Println(err) + if err == errLintNoChart { + failures = failures + 1 + } + } else { + fmt.Println("==> Linting", path) + + if len(linter.Messages) == 0 { + fmt.Println("Lint OK") + } + + for _, msg := range linter.Messages { + fmt.Println(msg) + } + + total = total + 1 + if linter.HighestSeverity >= lowestTolerance { + failures = failures + 1 + } + } + fmt.Println("") + } + + msg := fmt.Sprintf("%d chart(s) linted", total) + if failures > 0 { + return fmt.Errorf("%s, %d chart(s) failed", msg, failures) + } + + fmt.Fprintf(l.out, "%s, no failures\n", msg) + + return nil +} + +func lintChart(path string, vals []byte, namespace string, strict bool) (support.Linter, error) { + var chartPath string + linter := support.Linter{} + + if strings.HasSuffix(path, ".tgz") { + tempDir, err := ioutil.TempDir("", "helm-lint") + if err != nil { + return linter, err + } + defer os.RemoveAll(tempDir) + + file, err := os.Open(path) + if err != nil { + return linter, err + } + defer file.Close() + + if err = chartutil.Expand(tempDir, file); err != nil { + return linter, err + } + + lastHyphenIndex := strings.LastIndex(filepath.Base(path), "-") + if lastHyphenIndex <= 0 { + return linter, fmt.Errorf("unable to parse chart archive %q, missing '-'", filepath.Base(path)) + } + base := filepath.Base(path)[:lastHyphenIndex] + chartPath = filepath.Join(tempDir, base) + } else { + chartPath = path + } + + // Guard: Error out of this is not a chart. + if _, err := os.Stat(filepath.Join(chartPath, "Chart.yaml")); err != nil { + return linter, errLintNoChart + } + + return lint.All(chartPath, vals, namespace, strict), nil +} + +func (l *lintCmd) vals() ([]byte, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range l.valueFiles { + currentMap := map[string]interface{}{} + bytes, err := ioutil.ReadFile(filePath) + if err != nil { + return []byte{}, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err) + } + // Merge with the previous map + base = mergeValues(base, currentMap) + } + + // User specified a value via --set + for _, value := range l.values { + if err := strvals.ParseInto(value, base); err != nil { + return []byte{}, fmt.Errorf("failed parsing --set data: %s", err) + } + } + + // User specified a value via --set-string + for _, value := range l.sValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return []byte{}, fmt.Errorf("failed parsing --set-string data: %s", err) + } + } + + return yaml.Marshal(base) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/lint_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/lint_test.go new file mode 100644 index 000000000..973af9b63 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/lint_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "testing" +) + +var ( + values = []byte{} + namespace = "testNamespace" + strict = false + archivedChartPath = "testdata/testcharts/compressedchart-0.1.0.tgz" + archivedChartPathWithHyphens = "testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz" + invalidArchivedChartPath = "testdata/testcharts/invalidcompressedchart0.1.0.tgz" + chartDirPath = "testdata/testcharts/decompressedchart/" + chartMissingManifest = "testdata/testcharts/chart-missing-manifest" +) + +func TestLintChart(t *testing.T) { + if _, err := lintChart(chartDirPath, values, namespace, strict); err != nil { + t.Errorf("%s", err) + } + + if _, err := lintChart(archivedChartPath, values, namespace, strict); err != nil { + t.Errorf("%s", err) + } + + if _, err := lintChart(archivedChartPathWithHyphens, values, namespace, strict); err != nil { + t.Errorf("%s", err) + } + + if _, err := lintChart(invalidArchivedChartPath, values, namespace, strict); err == nil { + t.Errorf("Expected a chart parsing error") + } + + if _, err := lintChart(chartMissingManifest, values, namespace, strict); err == nil { + t.Errorf("Expected a chart parsing error") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/list.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/list.go new file mode 100644 index 000000000..4332ceb21 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/list.go @@ -0,0 +1,254 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "strings" + + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +var listHelp = ` +This command lists all of the releases. + +By default, it lists only releases that are deployed or failed. Flags like +'--deleted' and '--all' will alter this behavior. Such flags can be combined: +'--deleted --failed'. + +By default, items are sorted alphabetically. Use the '-d' flag to sort by +release date. + +If an argument is provided, it will be treated as a filter. Filters are +regular expressions (Perl compatible) that are applied to the list of releases. +Only items that match the filter will be returned. + + $ helm list 'ara[a-z]+' + NAME UPDATED CHART + maudlin-arachnid Mon May 9 16:07:08 2016 alpine-0.1.0 + +If no results are found, 'helm list' will exit 0, but with no output (or in +the case of no '-q' flag, only headers). + +By default, up to 256 items may be returned. To limit this, use the '--max' flag. +Setting '--max' to 0 will not return all results. Rather, it will return the +server's default, which may be much higher than 256. Pairing the '--max' +flag with the '--offset' flag allows you to page through results. +` + +type listCmd struct { + filter string + short bool + limit int + offset string + byDate bool + sortDesc bool + out io.Writer + all bool + deleted bool + deleting bool + deployed bool + failed bool + namespace string + superseded bool + pending bool + client helm.Interface + colWidth uint +} + +func newListCmd(client helm.Interface, out io.Writer) *cobra.Command { + list := &listCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "list [flags] [FILTER]", + Short: "list releases", + Long: listHelp, + Aliases: []string{"ls"}, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + list.filter = strings.Join(args, " ") + } + if list.client == nil { + list.client = newClient() + } + return list.run() + }, + } + + f := cmd.Flags() + f.BoolVarP(&list.short, "short", "q", false, "output short (quiet) listing format") + f.BoolVarP(&list.byDate, "date", "d", false, "sort by release date") + f.BoolVarP(&list.sortDesc, "reverse", "r", false, "reverse the sort order") + f.IntVarP(&list.limit, "max", "m", 256, "maximum number of releases to fetch") + f.StringVarP(&list.offset, "offset", "o", "", "next release name in the list, used to offset from start value") + f.BoolVarP(&list.all, "all", "a", false, "show all releases, not just the ones marked DEPLOYED") + f.BoolVar(&list.deleted, "deleted", false, "show deleted releases") + f.BoolVar(&list.deleting, "deleting", false, "show releases that are currently being deleted") + f.BoolVar(&list.deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled") + f.BoolVar(&list.failed, "failed", false, "show failed releases") + f.BoolVar(&list.pending, "pending", false, "show pending releases") + f.StringVar(&list.namespace, "namespace", "", "show releases within a specific namespace") + f.UintVar(&list.colWidth, "col-width", 60, "specifies the max column width of output") + + // TODO: Do we want this as a feature of 'helm list'? + //f.BoolVar(&list.superseded, "history", true, "show historical releases") + + return cmd +} + +func (l *listCmd) run() error { + sortBy := services.ListSort_NAME + if l.byDate { + sortBy = services.ListSort_LAST_RELEASED + } + + sortOrder := services.ListSort_ASC + if l.sortDesc { + sortOrder = services.ListSort_DESC + } + + stats := l.statusCodes() + + res, err := l.client.ListReleases( + helm.ReleaseListLimit(l.limit), + helm.ReleaseListOffset(l.offset), + helm.ReleaseListFilter(l.filter), + helm.ReleaseListSort(int32(sortBy)), + helm.ReleaseListOrder(int32(sortOrder)), + helm.ReleaseListStatuses(stats), + helm.ReleaseListNamespace(l.namespace), + ) + + if err != nil { + return prettyError(err) + } + + if len(res.GetReleases()) == 0 { + return nil + } + + if res.Next != "" && !l.short { + fmt.Fprintf(l.out, "\tnext: %s\n", res.Next) + } + + rels := filterList(res.Releases) + + if l.short { + for _, r := range rels { + fmt.Fprintln(l.out, r.Name) + } + return nil + } + fmt.Fprintln(l.out, formatList(rels, l.colWidth)) + return nil +} + +// filterList returns a list scrubbed of old releases. +func filterList(rels []*release.Release) []*release.Release { + idx := map[string]int32{} + + for _, r := range rels { + name, version := r.GetName(), r.GetVersion() + if max, ok := idx[name]; ok { + // check if we have a greater version already + if max > version { + continue + } + } + idx[name] = version + } + + uniq := make([]*release.Release, 0, len(idx)) + for _, r := range rels { + if idx[r.GetName()] == r.GetVersion() { + uniq = append(uniq, r) + } + } + return uniq +} + +// statusCodes gets the list of status codes that are to be included in the results. +func (l *listCmd) statusCodes() []release.Status_Code { + if l.all { + return []release.Status_Code{ + release.Status_UNKNOWN, + release.Status_DEPLOYED, + release.Status_DELETED, + release.Status_DELETING, + release.Status_FAILED, + release.Status_PENDING_INSTALL, + release.Status_PENDING_UPGRADE, + release.Status_PENDING_ROLLBACK, + } + } + status := []release.Status_Code{} + if l.deployed { + status = append(status, release.Status_DEPLOYED) + } + if l.deleted { + status = append(status, release.Status_DELETED) + } + if l.deleting { + status = append(status, release.Status_DELETING) + } + if l.failed { + status = append(status, release.Status_FAILED) + } + if l.superseded { + status = append(status, release.Status_SUPERSEDED) + } + if l.pending { + status = append(status, release.Status_PENDING_INSTALL, release.Status_PENDING_UPGRADE, release.Status_PENDING_ROLLBACK) + } + + // Default case. + if len(status) == 0 { + status = append(status, release.Status_DEPLOYED, release.Status_FAILED) + } + return status +} + +func formatList(rels []*release.Release, colWidth uint) string { + table := uitable.New() + + table.MaxColWidth = colWidth + table.AddRow("NAME", "REVISION", "UPDATED", "STATUS", "CHART", "NAMESPACE") + for _, r := range rels { + md := r.GetChart().GetMetadata() + c := fmt.Sprintf("%s-%s", md.GetName(), md.GetVersion()) + t := "-" + if tspb := r.GetInfo().GetLastDeployed(); tspb != nil { + t = timeconv.String(tspb) + } + s := r.GetInfo().GetStatus().GetCode().String() + v := r.GetVersion() + n := r.GetNamespace() + table.AddRow(r.GetName(), v, t, s, c, n) + } + return table.String() +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/list_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/list_test.go new file mode 100644 index 000000000..d853fa6b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/list_test.go @@ -0,0 +1,128 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestListCmd(t *testing.T) { + tests := []releaseCase{ + { + name: "with a release", + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + }, + expected: "thomas-guide", + }, + { + name: "list", + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas"}), + }, + expected: "NAME \tREVISION\tUPDATED \tSTATUS \tCHART \tNAMESPACE\natlas\t1 \t(.*)\tDEPLOYED\tfoo-0.1.0-beta.1\tdefault \n", + }, + { + name: "list, one deployed, one failed", + flags: []string{"-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + expected: "thomas-guide\natlas-guide", + }, + { + name: "with a release, multiple flags", + flags: []string{"--deleted", "--deployed", "--failed", "-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + // Note: We're really only testing that the flags parsed correctly. Which results are returned + // depends on the backend. And until pkg/helm is done, we can't mock this. + expected: "thomas-guide\natlas-guide", + }, + { + name: "with a release, multiple flags", + flags: []string{"--all", "-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETED}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + // See note on previous test. + expected: "thomas-guide\natlas-guide", + }, + { + name: "with a release, multiple flags, deleting", + flags: []string{"--all", "-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_DELETING}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + // See note on previous test. + expected: "thomas-guide\natlas-guide", + }, + { + name: "namespace defined, multiple flags", + flags: []string{"--all", "-q", "--namespace test123"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", Namespace: "test123"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", Namespace: "test321"}), + }, + // See note on previous test. + expected: "thomas-guide", + }, + { + name: "with a pending release, multiple flags", + flags: []string{"--all", "-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + expected: "thomas-guide\natlas-guide", + }, + { + name: "with a pending release, pending flag", + flags: []string{"--pending", "-q"}, + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_PENDING_INSTALL}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "wild-idea", StatusCode: release.Status_PENDING_UPGRADE}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-maps", StatusCode: release.Status_PENDING_ROLLBACK}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + }, + expected: "thomas-guide\nwild-idea\ncrazy-maps", + }, + { + name: "with old releases", + rels: []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide"}), + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "thomas-guide", StatusCode: release.Status_FAILED}), + }, + expected: "thomas-guide", + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newListCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/load_plugins.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/load_plugins.go new file mode 100644 index 000000000..ef24e7883 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/load_plugins.go @@ -0,0 +1,160 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/plugin" +) + +// loadPlugins loads plugins into the command list. +// +// This follows a different pattern than the other commands because it has +// to inspect its environment and then add commands to the base command +// as it finds them. +func loadPlugins(baseCmd *cobra.Command, out io.Writer) { + + // If HELM_NO_PLUGINS is set to 1, do not load plugins. + if os.Getenv("HELM_NO_PLUGINS") == "1" { + return + } + + // debug("HELM_PLUGIN_DIRS=%s", settings.PluginDirs()) + found, err := findPlugins(settings.PluginDirs()) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err) + return + } + + processParent := func(cmd *cobra.Command, args []string) ([]string, error) { + k, u := manuallyProcessArgs(args) + if err := cmd.Parent().ParseFlags(k); err != nil { + return nil, err + } + return u, nil + } + + // Now we create commands for all of these. + for _, plug := range found { + plug := plug + md := plug.Metadata + if md.Usage == "" { + md.Usage = fmt.Sprintf("the %q plugin", md.Name) + } + + c := &cobra.Command{ + Use: md.Name, + Short: md.Usage, + Long: md.Description, + RunE: func(cmd *cobra.Command, args []string) error { + u, err := processParent(cmd, args) + if err != nil { + return err + } + + // Call setupEnv before PrepareCommand because + // PrepareCommand uses os.ExpandEnv and expects the + // setupEnv vars. + plugin.SetupPluginEnv(settings, md.Name, plug.Dir) + main, argv := plug.PrepareCommand(u) + + prog := exec.Command(main, argv...) + prog.Env = os.Environ() + prog.Stdin = os.Stdin + prog.Stdout = out + prog.Stderr = os.Stderr + if err := prog.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + os.Stderr.Write(eerr.Stderr) + return fmt.Errorf("plugin %q exited with error", md.Name) + } + return err + } + return nil + }, + // This passes all the flags to the subcommand. + DisableFlagParsing: true, + } + + if md.UseTunnel { + c.PreRunE = func(cmd *cobra.Command, args []string) error { + // Parse the parent flag, but not the local flags. + if _, err := processParent(cmd, args); err != nil { + return err + } + return setupConnection() + } + } + + // TODO: Make sure a command with this name does not already exist. + baseCmd.AddCommand(c) + } +} + +// manuallyProcessArgs processes an arg array, removing special args. +// +// Returns two sets of args: known and unknown (in that order) +func manuallyProcessArgs(args []string) ([]string, []string) { + known := []string{} + unknown := []string{} + kvargs := []string{"--host", "--kube-context", "--home", "--tiller-namespace"} + knownArg := func(a string) bool { + for _, pre := range kvargs { + if strings.HasPrefix(a, pre+"=") { + return true + } + } + return false + } + for i := 0; i < len(args); i++ { + switch a := args[i]; a { + case "--debug": + known = append(known, a) + case "--host", "--kube-context", "--home": + known = append(known, a, args[i+1]) + i++ + default: + if knownArg(a) { + known = append(known, a) + continue + } + unknown = append(unknown, a) + } + } + return known, unknown +} + +// findPlugins returns a list of YAML files that describe plugins. +func findPlugins(plugdirs string) ([]*plugin.Plugin, error) { + found := []*plugin.Plugin{} + // Let's get all UNIXy and allow path separators + for _, p := range filepath.SplitList(plugdirs) { + matches, err := plugin.LoadAll(p) + if err != nil { + return matches, err + } + found = append(found, matches...) + } + return found, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/package.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/package.go new file mode 100644 index 000000000..ed44382c7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/package.go @@ -0,0 +1,237 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "syscall" + + "github.com/Masterminds/semver" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/downloader" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" +) + +const packageDesc = ` +This command packages a chart into a versioned chart archive file. If a path +is given, this will look at that path for a chart (which must contain a +Chart.yaml file) and then package that directory. + +If no path is given, this will look in the present working directory for a +Chart.yaml file, and (if found) build the current directory into a chart. + +Versioned chart archives are used by Helm package repositories. +` + +type packageCmd struct { + save bool + sign bool + path string + key string + keyring string + version string + appVersion string + destination string + dependencyUpdate bool + + out io.Writer + home helmpath.Home +} + +func newPackageCmd(out io.Writer) *cobra.Command { + pkg := &packageCmd{out: out} + + cmd := &cobra.Command{ + Use: "package [flags] [CHART_PATH] [...]", + Short: "package a chart directory into a chart archive", + Long: packageDesc, + RunE: func(cmd *cobra.Command, args []string) error { + pkg.home = settings.Home + if len(args) == 0 { + return fmt.Errorf("need at least one argument, the path to the chart") + } + if pkg.sign { + if pkg.key == "" { + return errors.New("--key is required for signing a package") + } + if pkg.keyring == "" { + return errors.New("--keyring is required for signing a package") + } + } + for i := 0; i < len(args); i++ { + pkg.path = args[i] + if err := pkg.run(); err != nil { + return err + } + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVar(&pkg.save, "save", true, "save packaged chart to local chart repository") + f.BoolVar(&pkg.sign, "sign", false, "use a PGP private key to sign this package") + f.StringVar(&pkg.key, "key", "", "name of the key to use when signing. Used if --sign is true") + f.StringVar(&pkg.keyring, "keyring", defaultKeyring(), "location of a public keyring") + f.StringVar(&pkg.version, "version", "", "set the version on the chart to this semver version") + f.StringVar(&pkg.appVersion, "app-version", "", "set the appVersion on the chart to this version") + f.StringVarP(&pkg.destination, "destination", "d", ".", "location to write the chart.") + f.BoolVarP(&pkg.dependencyUpdate, "dependency-update", "u", false, `update dependencies from "requirements.yaml" to dir "charts/" before packaging`) + + return cmd +} + +func (p *packageCmd) run() error { + path, err := filepath.Abs(p.path) + if err != nil { + return err + } + + if p.dependencyUpdate { + downloadManager := &downloader.Manager{ + Out: p.out, + ChartPath: path, + HelmHome: settings.Home, + Keyring: p.keyring, + Getters: getter.All(settings), + Debug: settings.Debug, + } + + if err := downloadManager.Update(); err != nil { + return err + } + } + + ch, err := chartutil.LoadDir(path) + if err != nil { + return err + } + + // If version is set, modify the version. + if len(p.version) != 0 { + if err := setVersion(ch, p.version); err != nil { + return err + } + debug("Setting version to %s", p.version) + } + + if p.appVersion != "" { + ch.Metadata.AppVersion = p.appVersion + debug("Setting appVersion to %s", p.appVersion) + } + + if filepath.Base(path) != ch.Metadata.Name { + return fmt.Errorf("directory name (%s) and Chart.yaml name (%s) must match", filepath.Base(path), ch.Metadata.Name) + } + + if reqs, err := chartutil.LoadRequirements(ch); err == nil { + if err := checkDependencies(ch, reqs); err != nil { + return err + } + } else { + if err != chartutil.ErrRequirementsNotFound { + 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 { + fmt.Fprintf(p.out, "Successfully packaged chart and saved it to: %s\n", name) + } else { + return fmt.Errorf("Failed to save: %s", err) + } + + // Save to $HELM_HOME/local directory. This is second, because we don't want + // the case where we saved here, but didn't save to the default destination. + if p.save { + lr := p.home.LocalRepository() + if err := repo.AddChartToLocalRepo(ch, lr); err != nil { + return err + } + debug("Successfully saved %s to %s\n", name, lr) + } + + if p.sign { + err = p.clearsign(name) + } + + return err +} + +func setVersion(ch *chart.Chart, ver string) error { + // Verify that version is a SemVer, and error out if it is not. + if _, err := semver.NewVersion(ver); err != nil { + return err + } + + // Set the version field on the chart. + ch.Metadata.Version = ver + return nil +} + +func (p *packageCmd) clearsign(filename string) error { + // Load keyring + signer, err := provenance.NewFromKeyring(p.keyring, p.key) + if err != nil { + return err + } + + if err := signer.DecryptKey(promptUser); err != nil { + return err + } + + sig, err := signer.ClearSign(filename) + if err != nil { + return err + } + + debug(sig) + + return ioutil.WriteFile(filename+".prov", []byte(sig), 0755) +} + +// promptUser implements provenance.PassphraseFetcher +func promptUser(name string) ([]byte, error) { + fmt.Printf("Password for key %q > ", name) + pw, err := terminal.ReadPassword(int(syscall.Stdin)) + fmt.Println() + return pw, err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/package_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/package_test.go new file mode 100644 index 000000000..4404586e0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/package_test.go @@ -0,0 +1,253 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestSetVersion(t *testing.T) { + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "prow", + Version: "0.0.1", + }, + } + expect := "1.2.3-beta.5" + if err := setVersion(c, expect); err != nil { + t.Fatal(err) + } + + if c.Metadata.Version != expect { + t.Errorf("Expected %q, got %q", expect, c.Metadata.Version) + } + + if err := setVersion(c, "monkeyface"); err == nil { + t.Error("Expected bogus version to return an error.") + } +} + +func TestPackage(t *testing.T) { + + tests := []struct { + name string + flags map[string]string + args []string + expect string + hasfile string + err bool + }{ + { + name: "package without chart path", + args: []string{}, + flags: map[string]string{}, + expect: "need at least one argument, the path to the chart", + err: true, + }, + { + name: "package --sign, no --key", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"sign": "1"}, + expect: "key is required for signing a package", + err: true, + }, + { + name: "package --sign, no --keyring", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"sign": "1", "key": "nosuchkey", "keyring": ""}, + expect: "keyring is required for signing a package", + err: true, + }, + { + name: "package testdata/testcharts/alpine, no save", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"save": "0"}, + expect: "", + hasfile: "alpine-0.1.0.tgz", + }, + { + name: "package testdata/testcharts/alpine", + args: []string{"testdata/testcharts/alpine"}, + expect: "", + hasfile: "alpine-0.1.0.tgz", + }, + { + name: "package --destination toot", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"destination": "toot"}, + expect: "", + hasfile: "toot/alpine-0.1.0.tgz", + }, + { + name: "package --destination does-not-exist", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"destination": "does-not-exist"}, + expect: "stat does-not-exist: no such file or directory", + err: true, + }, + { + name: "package --sign --key=KEY --keyring=KEYRING testdata/testcharts/alpine", + args: []string{"testdata/testcharts/alpine"}, + flags: map[string]string{"sign": "1", "keyring": "testdata/helm-test-key.secret", "key": "helm-test"}, + expect: "", + hasfile: "alpine-0.1.0.tgz", + }, + { + name: "package testdata/testcharts/chart-missing-deps", + args: []string{"testdata/testcharts/chart-missing-deps"}, + hasfile: "chart-missing-deps-0.1.0.tgz", + err: true, + }, + } + + // Because these tests are destructive, we run them in a tempdir. + origDir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + tmp, err := ioutil.TempDir("", "helm-package-test-") + if err != nil { + t.Fatal(err) + } + + t.Logf("Running tests in %s", tmp) + if err := os.Chdir(tmp); err != nil { + t.Fatal(err) + } + + if err := os.Mkdir("toot", 0777); err != nil { + t.Fatal(err) + } + + ensureTestHome(helmpath.Home(tmp), t) + cleanup := resetEnv() + defer func() { + os.Chdir(origDir) + os.RemoveAll(tmp) + cleanup() + }() + + settings.Home = helmpath.Home(tmp) + + for _, tt := range tests { + buf := bytes.NewBuffer(nil) + c := newPackageCmd(buf) + + // This is an unfortunate byproduct of the tmpdir + if v, ok := tt.flags["keyring"]; ok && len(v) > 0 { + tt.flags["keyring"] = filepath.Join(origDir, v) + } + + setFlags(c, tt.flags) + re := regexp.MustCompile(tt.expect) + + adjustedArgs := make([]string, len(tt.args)) + for i, f := range tt.args { + adjustedArgs[i] = filepath.Join(origDir, f) + } + + err := c.RunE(c, adjustedArgs) + if err != nil { + if tt.err && re.MatchString(err.Error()) { + continue + } + t.Errorf("%q: expected error %q, got %q", tt.name, tt.expect, err) + continue + } + + if !re.Match(buf.Bytes()) { + t.Errorf("%q: expected output %q, got %q", tt.name, tt.expect, buf.String()) + } + + if len(tt.hasfile) > 0 { + if fi, err := os.Stat(tt.hasfile); err != nil { + t.Errorf("%q: expected file %q, got err %q", tt.name, tt.hasfile, err) + } else if fi.Size() == 0 { + t.Errorf("%q: file %q has zero bytes.", tt.name, tt.hasfile) + } + } + + if v, ok := tt.flags["sign"]; ok && v == "1" { + if fi, err := os.Stat(tt.hasfile + ".prov"); err != nil { + t.Errorf("%q: expected provenance file", tt.name) + } else if fi.Size() == 0 { + t.Errorf("%q: provenance file is empty", tt.name) + } + } + } +} + +func TestSetAppVersion(t *testing.T) { + var ch *chart.Chart + expectedAppVersion := "app-version-foo" + tmp, _ := ioutil.TempDir("", "helm-package-app-version-") + + thome, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + cleanup := resetEnv() + defer func() { + os.RemoveAll(tmp) + os.RemoveAll(thome.String()) + cleanup() + }() + + settings.Home = helmpath.Home(thome) + + c := newPackageCmd(&bytes.Buffer{}) + flags := map[string]string{ + "destination": tmp, + "app-version": expectedAppVersion, + } + setFlags(c, flags) + err = c.RunE(c, []string{"testdata/testcharts/alpine"}) + if err != nil { + t.Errorf("unexpected error %q", err) + } + + chartPath := filepath.Join(tmp, "alpine-0.1.0.tgz") + if fi, err := os.Stat(chartPath); err != nil { + t.Errorf("expected file %q, got err %q", chartPath, err) + } else if fi.Size() == 0 { + t.Errorf("file %q has zero bytes.", chartPath) + } + ch, err = chartutil.Load(chartPath) + if err != nil { + t.Errorf("unexpected error loading packaged chart: %v", err) + } + if ch.Metadata.AppVersion != expectedAppVersion { + t.Errorf("expected app-version %q, found %q", expectedAppVersion, ch.Metadata.AppVersion) + } +} + +func setFlags(cmd *cobra.Command, flags map[string]string) { + dest := cmd.Flags() + for f, v := range flags { + dest.Set(f, v) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin.go new file mode 100644 index 000000000..cf0b02f09 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + "os" + "os/exec" + + "k8s.io/helm/pkg/plugin" + + "github.com/spf13/cobra" +) + +const pluginHelp = ` +Manage client-side Helm plugins. +` + +func newPluginCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "plugin", + Short: "add, list, or remove Helm plugins", + Long: pluginHelp, + } + cmd.AddCommand( + newPluginInstallCmd(out), + newPluginListCmd(out), + newPluginRemoveCmd(out), + newPluginUpdateCmd(out), + ) + return cmd +} + +// runHook will execute a plugin hook. +func runHook(p *plugin.Plugin, event string) error { + hook := p.Metadata.Hooks.Get(event) + if hook == "" { + return nil + } + + prog := exec.Command("sh", "-c", hook) + // TODO make this work on windows + // I think its ... ¯\_(ツ)_/¯ + // prog := exec.Command("cmd", "/C", p.Metadata.Hooks.Install()) + + debug("running %s hook: %s", event, prog) + + plugin.SetupPluginEnv(settings, p.Metadata.Name, p.Dir) + prog.Stdout, prog.Stderr = os.Stdout, os.Stderr + if err := prog.Run(); err != nil { + if eerr, ok := err.(*exec.ExitError); ok { + os.Stderr.Write(eerr.Stderr) + return fmt.Errorf("plugin %s hook for %q exited with error", event, p.Metadata.Name) + } + return err + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_install.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_install.go new file mode 100644 index 000000000..f36178808 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_install.go @@ -0,0 +1,92 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + "k8s.io/helm/pkg/plugin/installer" + + "github.com/spf13/cobra" +) + +type pluginInstallCmd struct { + source string + version string + home helmpath.Home + out io.Writer +} + +const pluginInstallDesc = ` +This command allows you to install a plugin from a url to a VCS repo or a local path. + +Example usage: + $ helm plugin install https://github.com/technosophos/helm-template +` + +func newPluginInstallCmd(out io.Writer) *cobra.Command { + pcmd := &pluginInstallCmd{out: out} + cmd := &cobra.Command{ + Use: "install [options] ...", + Short: "install one or more Helm plugins", + Long: pluginInstallDesc, + PreRunE: func(cmd *cobra.Command, args []string) error { + return pcmd.complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return pcmd.run() + }, + } + cmd.Flags().StringVar(&pcmd.version, "version", "", "specify a version constraint. If this is not specified, the latest version is installed") + return cmd +} + +func (pcmd *pluginInstallCmd) complete(args []string) error { + if err := checkArgsLength(len(args), "plugin"); err != nil { + return err + } + pcmd.source = args[0] + pcmd.home = settings.Home + return nil +} + +func (pcmd *pluginInstallCmd) run() error { + installer.Debug = settings.Debug + + i, err := installer.NewForSource(pcmd.source, pcmd.version, pcmd.home) + if err != nil { + return err + } + if err := installer.Install(i); err != nil { + return err + } + + debug("loading plugin from %s", i.Path()) + p, err := plugin.LoadDir(i.Path()) + if err != nil { + return err + } + + if err := runHook(p, plugin.Install); err != nil { + return err + } + + fmt.Fprintf(pcmd.out, "Installed plugin: %s\n", p.Metadata.Name) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_list.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_list.go new file mode 100644 index 000000000..e7618f38a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_list.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + + "k8s.io/helm/pkg/helm/helmpath" + + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +type pluginListCmd struct { + home helmpath.Home + out io.Writer +} + +func newPluginListCmd(out io.Writer) *cobra.Command { + pcmd := &pluginListCmd{out: out} + cmd := &cobra.Command{ + Use: "list", + Short: "list installed Helm plugins", + RunE: func(cmd *cobra.Command, args []string) error { + pcmd.home = settings.Home + return pcmd.run() + }, + } + return cmd +} + +func (pcmd *pluginListCmd) run() error { + debug("pluginDirs: %s", settings.PluginDirs()) + plugins, err := findPlugins(settings.PluginDirs()) + if err != nil { + return err + } + + table := uitable.New() + table.AddRow("NAME", "VERSION", "DESCRIPTION") + for _, p := range plugins { + table.AddRow(p.Metadata.Name, p.Metadata.Version, p.Metadata.Description) + } + fmt.Fprintln(pcmd.out, table) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_remove.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_remove.go new file mode 100644 index 000000000..ec1154734 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_remove.go @@ -0,0 +1,99 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "errors" + "fmt" + "io" + "os" + "strings" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + + "github.com/spf13/cobra" +) + +type pluginRemoveCmd struct { + names []string + home helmpath.Home + out io.Writer +} + +func newPluginRemoveCmd(out io.Writer) *cobra.Command { + pcmd := &pluginRemoveCmd{out: out} + cmd := &cobra.Command{ + Use: "remove ...", + Short: "remove one or more Helm plugins", + PreRunE: func(cmd *cobra.Command, args []string) error { + return pcmd.complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return pcmd.run() + }, + } + return cmd +} + +func (pcmd *pluginRemoveCmd) complete(args []string) error { + if len(args) == 0 { + return errors.New("please provide plugin name to remove") + } + pcmd.names = args + pcmd.home = settings.Home + return nil +} + +func (pcmd *pluginRemoveCmd) run() error { + debug("loading installed plugins from %s", settings.PluginDirs()) + plugins, err := findPlugins(settings.PluginDirs()) + if err != nil { + return err + } + var errorPlugins []string + for _, name := range pcmd.names { + if found := findPlugin(plugins, name); found != nil { + if err := removePlugin(found); err != nil { + errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to remove plugin %s, got error (%v)", name, err)) + } else { + fmt.Fprintf(pcmd.out, "Removed plugin: %s\n", name) + } + } else { + errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name)) + } + } + if len(errorPlugins) > 0 { + return fmt.Errorf(strings.Join(errorPlugins, "\n")) + } + return nil +} + +func removePlugin(p *plugin.Plugin) error { + if err := os.Remove(p.Dir); err != nil { + return err + } + return runHook(p, plugin.Delete) +} + +func findPlugin(plugins []*plugin.Plugin, name string) *plugin.Plugin { + for _, p := range plugins { + if p.Metadata.Name == name { + return p + } + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_test.go new file mode 100644 index 000000000..2a4a0e9aa --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_test.go @@ -0,0 +1,187 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + + "github.com/spf13/cobra" +) + +func TestManuallyProcessArgs(t *testing.T) { + input := []string{ + "--debug", + "--foo", "bar", + "--host", "example.com", + "--kube-context", "test1", + "--home=/tmp", + "--tiller-namespace=hello", + "command", + } + + expectKnown := []string{ + "--debug", "--host", "example.com", "--kube-context", "test1", "--home=/tmp", "--tiller-namespace=hello", + } + + expectUnknown := []string{ + "--foo", "bar", "command", + } + + known, unknown := manuallyProcessArgs(input) + + for i, k := range known { + if k != expectKnown[i] { + t.Errorf("expected known flag %d to be %q, got %q", i, expectKnown[i], k) + } + } + for i, k := range unknown { + if k != expectUnknown[i] { + t.Errorf("expected unknown flag %d to be %q, got %q", i, expectUnknown[i], k) + } + } + +} + +func TestLoadPlugins(t *testing.T) { + cleanup := resetEnv() + defer cleanup() + + settings.Home = "testdata/helmhome" + + os.Setenv("HELM_HOME", settings.Home.String()) + hh := settings.Home + + out := bytes.NewBuffer(nil) + cmd := &cobra.Command{} + loadPlugins(cmd, out) + + envs := strings.Join([]string{ + "fullenv", + hh.Plugins() + "/fullenv", + hh.Plugins(), + hh.String(), + hh.Repository(), + hh.RepositoryFile(), + hh.Cache(), + hh.LocalRepository(), + os.Args[0], + }, "\n") + + // Test that the YAML file was correctly converted to a command. + tests := []struct { + use string + short string + long string + expect string + args []string + }{ + {"args", "echo args", "This echos args", "-a -b -c\n", []string{"-a", "-b", "-c"}}, + {"echo", "echo stuff", "This echos stuff", "hello\n", []string{}}, + {"env", "env stuff", "show the env", hh.String() + "\n", []string{}}, + {"fullenv", "show env vars", "show all env vars", envs + "\n", []string{}}, + } + + plugins := cmd.Commands() + + if len(plugins) != len(tests) { + t.Fatalf("Expected %d plugins, got %d", len(tests), len(plugins)) + } + + for i := 0; i < len(plugins); i++ { + out.Reset() + tt := tests[i] + pp := plugins[i] + if pp.Use != tt.use { + t.Errorf("%d: Expected Use=%q, got %q", i, tt.use, pp.Use) + } + if pp.Short != tt.short { + t.Errorf("%d: Expected Use=%q, got %q", i, tt.short, pp.Short) + } + if pp.Long != tt.long { + t.Errorf("%d: Expected Use=%q, got %q", i, tt.long, pp.Long) + } + + // Currently, plugins assume a Linux subsystem. Skip the execution + // tests until this is fixed + if runtime.GOOS != "windows" { + if err := pp.RunE(pp, tt.args); err != nil { + t.Errorf("Error running %s: %s", tt.use, err) + } + if out.String() != tt.expect { + t.Errorf("Expected %s to output:\n%s\ngot\n%s", tt.use, tt.expect, out.String()) + } + } + } +} + +func TestLoadPlugins_HelmNoPlugins(t *testing.T) { + cleanup := resetEnv() + defer cleanup() + + settings.Home = "testdata/helmhome" + + os.Setenv("HELM_NO_PLUGINS", "1") + + out := bytes.NewBuffer(nil) + cmd := &cobra.Command{} + loadPlugins(cmd, out) + plugins := cmd.Commands() + + if len(plugins) != 0 { + t.Fatalf("Expected 0 plugins, got %d", len(plugins)) + } +} + +func TestSetupEnv(t *testing.T) { + name := "pequod" + settings.Home = helmpath.Home("testdata/helmhome") + base := filepath.Join(settings.Home.Plugins(), name) + settings.Debug = true + defer func() { + settings.Debug = false + }() + + plugin.SetupPluginEnv(settings, name, base) + for _, tt := range []struct { + name string + expect string + }{ + {"HELM_PLUGIN_NAME", name}, + {"HELM_PLUGIN_DIR", base}, + {"HELM_PLUGIN", settings.Home.Plugins()}, + {"HELM_DEBUG", "1"}, + {"HELM_HOME", settings.Home.String()}, + {"HELM_PATH_REPOSITORY", settings.Home.Repository()}, + {"HELM_PATH_REPOSITORY_FILE", settings.Home.RepositoryFile()}, + {"HELM_PATH_CACHE", settings.Home.Cache()}, + {"HELM_PATH_LOCAL_REPOSITORY", settings.Home.LocalRepository()}, + {"HELM_PATH_STARTER", settings.Home.Starters()}, + {"TILLER_HOST", settings.TillerHost}, + {"TILLER_NAMESPACE", settings.TillerNamespace}, + } { + if got := os.Getenv(tt.name); got != tt.expect { + t.Errorf("Expected $%s=%q, got %q", tt.name, tt.expect, got) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_update.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_update.go new file mode 100644 index 000000000..d3778764d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/plugin_update.go @@ -0,0 +1,113 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 main + +import ( + "errors" + "fmt" + "io" + "path/filepath" + "strings" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin" + "k8s.io/helm/pkg/plugin/installer" + + "github.com/spf13/cobra" +) + +type pluginUpdateCmd struct { + names []string + home helmpath.Home + out io.Writer +} + +func newPluginUpdateCmd(out io.Writer) *cobra.Command { + pcmd := &pluginUpdateCmd{out: out} + cmd := &cobra.Command{ + Use: "update ...", + Short: "update one or more Helm plugins", + PreRunE: func(cmd *cobra.Command, args []string) error { + return pcmd.complete(args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return pcmd.run() + }, + } + return cmd +} + +func (pcmd *pluginUpdateCmd) complete(args []string) error { + if len(args) == 0 { + return errors.New("please provide plugin name to update") + } + pcmd.names = args + pcmd.home = settings.Home + return nil +} + +func (pcmd *pluginUpdateCmd) run() error { + installer.Debug = settings.Debug + debug("loading installed plugins from %s", settings.PluginDirs()) + plugins, err := findPlugins(settings.PluginDirs()) + if err != nil { + return err + } + var errorPlugins []string + + for _, name := range pcmd.names { + if found := findPlugin(plugins, name); found != nil { + if err := updatePlugin(found, pcmd.home); err != nil { + errorPlugins = append(errorPlugins, fmt.Sprintf("Failed to update plugin %s, got error (%v)", name, err)) + } else { + fmt.Fprintf(pcmd.out, "Updated plugin: %s\n", name) + } + } else { + errorPlugins = append(errorPlugins, fmt.Sprintf("Plugin: %s not found", name)) + } + } + if len(errorPlugins) > 0 { + return fmt.Errorf(strings.Join(errorPlugins, "\n")) + } + return nil +} + +func updatePlugin(p *plugin.Plugin, home helmpath.Home) error { + exactLocation, err := filepath.EvalSymlinks(p.Dir) + if err != nil { + return err + } + absExactLocation, err := filepath.Abs(exactLocation) + if err != nil { + return err + } + + i, err := installer.FindSource(absExactLocation, home) + if err != nil { + return err + } + if err := installer.Update(i); err != nil { + return err + } + + debug("loading plugin from %s", i.Path()) + updatedPlugin, err := plugin.LoadDir(i.Path()) + if err != nil { + return err + } + + return runHook(updatedPlugin, plugin.Update) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/printer.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/printer.go new file mode 100644 index 000000000..ebb24bf7d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/printer.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "text/template" + "time" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/timeconv" +) + +var printReleaseTemplate = `REVISION: {{.Release.Version}} +RELEASED: {{.ReleaseDate}} +CHART: {{.Release.Chart.Metadata.Name}}-{{.Release.Chart.Metadata.Version}} +USER-SUPPLIED VALUES: +{{.Release.Config.Raw}} +COMPUTED VALUES: +{{.ComputedValues}} +HOOKS: +{{- range .Release.Hooks }} +--- +# {{.Name}} +{{.Manifest}} +{{- end }} +MANIFEST: +{{.Release.Manifest}} +` + +func printRelease(out io.Writer, rel *release.Release) error { + if rel == nil { + return nil + } + + cfg, err := chartutil.CoalesceValues(rel.Chart, rel.Config) + if err != nil { + return err + } + cfgStr, err := cfg.YAML() + if err != nil { + return err + } + + data := map[string]interface{}{ + "Release": rel, + "ComputedValues": cfgStr, + "ReleaseDate": timeconv.Format(rel.Info.LastDeployed, time.ANSIC), + } + return tpl(printReleaseTemplate, data, out) +} + +func tpl(t string, vals map[string]interface{}, out io.Writer) error { + tt, err := template.New("_").Parse(t) + if err != nil { + return err + } + return tt.Execute(out, vals) +} + +func debug(format string, args ...interface{}) { + if settings.Debug { + format = fmt.Sprintf("[debug] %s\n", format) + fmt.Printf(format, args...) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing.go new file mode 100644 index 000000000..bdfa87a60 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing.go @@ -0,0 +1,110 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +const releaseTestDesc = ` +The test command runs the tests for a release. + +The argument this command takes is the name of a deployed release. +The tests to be run are defined in the chart that was installed. +` + +type releaseTestCmd struct { + name string + out io.Writer + client helm.Interface + timeout int64 + cleanup bool +} + +func newReleaseTestCmd(c helm.Interface, out io.Writer) *cobra.Command { + rlsTest := &releaseTestCmd{ + out: out, + client: c, + } + + cmd := &cobra.Command{ + Use: "test [RELEASE]", + Short: "test a release", + Long: releaseTestDesc, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "release name"); err != nil { + return err + } + + rlsTest.name = args[0] + rlsTest.client = ensureHelmClient(rlsTest.client) + return rlsTest.run() + }, + } + + f := cmd.Flags() + f.Int64Var(&rlsTest.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&rlsTest.cleanup, "cleanup", false, "delete test pods upon completion") + + return cmd +} + +func (t *releaseTestCmd) run() (err error) { + c, errc := t.client.RunReleaseTest( + t.name, + helm.ReleaseTestTimeout(t.timeout), + helm.ReleaseTestCleanup(t.cleanup), + ) + testErr := &testErr{} + + for { + select { + case err := <-errc: + if prettyError(err) == nil && testErr.failed > 0 { + return testErr.Error() + } + return prettyError(err) + case res, ok := <-c: + if !ok { + break + } + + if res.Status == release.TestRun_FAILURE { + testErr.failed++ + } + + fmt.Fprintf(t.out, res.Msg+"\n") + + } + } + +} + +type testErr struct { + failed int +} + +func (err *testErr) Error() error { + return fmt.Errorf("%v test(s) failed", err.failed) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing_test.go new file mode 100644 index 000000000..02ab1883c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/release_testing_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestReleaseTesting(t *testing.T) { + tests := []struct { + name string + args []string + flags []string + responses map[string]release.TestRun_Status + fail bool + }{ + { + name: "basic test", + args: []string{"example-release"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"PASSED: green lights everywhere": release.TestRun_SUCCESS}, + fail: false, + }, + { + name: "test failure", + args: []string{"example-fail"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"FAILURE: red lights everywhere": release.TestRun_FAILURE}, + fail: true, + }, + { + name: "test unknown", + args: []string{"example-unknown"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"UNKNOWN: yellow lights everywhere": release.TestRun_UNKNOWN}, + fail: false, + }, + { + name: "test error", + args: []string{"example-error"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"ERROR: yellow lights everywhere": release.TestRun_FAILURE}, + fail: true, + }, + { + name: "test running", + args: []string{"example-running"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{"RUNNING: things are happpeningggg": release.TestRun_RUNNING}, + fail: false, + }, + { + name: "multiple tests example", + args: []string{"example-suite"}, + flags: []string{}, + responses: map[string]release.TestRun_Status{ + "RUNNING: things are happpeningggg": release.TestRun_RUNNING, + "PASSED: party time": release.TestRun_SUCCESS, + "RUNNING: things are happening again": release.TestRun_RUNNING, + "FAILURE: good thing u checked :)": release.TestRun_FAILURE, + "RUNNING: things are happpeningggg yet again": release.TestRun_RUNNING, + "PASSED: feel free to party again": release.TestRun_SUCCESS}, + fail: true, + }, + } + + for _, tt := range tests { + c := &helm.FakeClient{Responses: tt.responses} + + buf := bytes.NewBuffer(nil) + cmd := newReleaseTestCmd(c, buf) + cmd.ParseFlags(tt.flags) + + err := cmd.RunE(cmd, tt.args) + if err == nil && tt.fail { + t.Errorf("%q did not fail but should have failed", tt.name) + } + + if err != nil { + if tt.fail { + continue + } else { + t.Errorf("%q reported error: %s", tt.name, err) + } + } + + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo.go new file mode 100644 index 000000000..8acc762e2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + + "github.com/spf13/cobra" +) + +var repoHelm = ` +This command consists of multiple subcommands to interact with chart repositories. + +It can be used to add, remove, list, and index chart repositories. +Example usage: + $ helm repo add [NAME] [REPO_URL] +` + +func newRepoCmd(out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "repo [FLAGS] add|remove|list|index|update [ARGS]", + Short: "add, list, remove, update, and index chart repositories", + Long: repoHelm, + } + + cmd.AddCommand(newRepoAddCmd(out)) + cmd.AddCommand(newRepoListCmd(out)) + cmd.AddCommand(newRepoRemoveCmd(out)) + cmd.AddCommand(newRepoIndexCmd(out)) + cmd.AddCommand(newRepoUpdateCmd(out)) + + return cmd +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add.go new file mode 100644 index 000000000..77a64cc89 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add.go @@ -0,0 +1,117 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +type repoAddCmd struct { + name string + url string + username string + password string + home helmpath.Home + noupdate bool + + certFile string + keyFile string + caFile string + + out io.Writer +} + +func newRepoAddCmd(out io.Writer) *cobra.Command { + add := &repoAddCmd{out: out} + + cmd := &cobra.Command{ + Use: "add [flags] [NAME] [URL]", + Short: "add a chart repository", + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "name for the chart repository", "the url of the chart repository"); err != nil { + return err + } + + add.name = args[0] + add.url = args[1] + add.home = settings.Home + + return add.run() + }, + } + + f := cmd.Flags() + f.StringVar(&add.username, "username", "", "chart repository username") + f.StringVar(&add.password, "password", "", "chart repository password") + f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered") + f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&add.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + + return cmd +} + +func (a *repoAddCmd) run() error { + if err := addRepository(a.name, a.url, a.username, a.password, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil { + return err + } + fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name) + return nil +} + +func addRepository(name, url, username, password string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error { + f, err := repo.LoadRepositoriesFile(home.RepositoryFile()) + if err != nil { + return err + } + + if noUpdate && f.Has(name) { + return fmt.Errorf("repository name (%s) already exists, please specify a different name", name) + } + + cif := home.CacheIndex(name) + c := repo.Entry{ + Name: name, + Cache: cif, + URL: url, + Username: username, + Password: password, + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + } + + r, err := repo.NewChartRepository(&c, getter.All(settings)) + if err != nil { + return err + } + + if err := r.DownloadIndexFile(home.Cache()); err != nil { + return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error()) + } + + f.Update(&c) + + return f.WriteFile(home.RepositoryFile(), 0644) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add_test.go new file mode 100644 index 000000000..157b1cc5b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_add_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "os" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +var testName = "test-name" + +func TestRepoAddCmd(t *testing.T) { + srv, thome, err := repotest.NewTempServer("testdata/testserver/*.*") + if err != nil { + t.Fatal(err) + } + + cleanup := resetEnv() + defer func() { + srv.Stop() + os.RemoveAll(thome.String()) + cleanup() + }() + if err := ensureTestHome(thome, t); err != nil { + t.Fatal(err) + } + + settings.Home = thome + + tests := []releaseCase{ + { + name: "add a repository", + args: []string{testName, srv.URL()}, + expected: "\"" + testName + "\" has been added to your repositories", + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newRepoAddCmd(out) + }) +} + +func TestRepoAdd(t *testing.T) { + ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*") + if err != nil { + t.Fatal(err) + } + + cleanup := resetEnv() + hh := thome + defer func() { + ts.Stop() + os.RemoveAll(thome.String()) + cleanup() + }() + if err := ensureTestHome(hh, t); err != nil { + t.Fatal(err) + } + + settings.Home = thome + + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil { + t.Error(err) + } + + f, err := repo.LoadRepositoriesFile(hh.RepositoryFile()) + if err != nil { + t.Error(err) + } + + if !f.Has(testName) { + t.Errorf("%s was not successfully inserted into %s", testName, hh.RepositoryFile()) + } + + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil { + t.Errorf("Repository was not updated: %s", err) + } + + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", false); err != nil { + t.Errorf("Duplicate repository name was added") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index.go new file mode 100644 index 000000000..540057eb8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index.go @@ -0,0 +1,105 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/repo" +) + +const repoIndexDesc = ` +Read the current directory and generate an index file based on the charts found. + +This tool is used for creating an 'index.yaml' file for a chart repository. To +set an absolute URL to the charts, use '--url' flag. + +To merge the generated index with an existing index file, use the '--merge' +flag. In this case, the charts found in the current directory will be merged +into the existing index, with local charts taking priority over existing charts. +` + +type repoIndexCmd struct { + dir string + url string + out io.Writer + merge string +} + +func newRepoIndexCmd(out io.Writer) *cobra.Command { + index := &repoIndexCmd{out: out} + + cmd := &cobra.Command{ + Use: "index [flags] [DIR]", + Short: "generate an index file given a directory containing packaged charts", + Long: repoIndexDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "path to a directory"); err != nil { + return err + } + + index.dir = args[0] + + return index.run() + }, + } + + f := cmd.Flags() + f.StringVar(&index.url, "url", "", "url of chart repository") + f.StringVar(&index.merge, "merge", "", "merge the generated index into the given index") + + return cmd +} + +func (i *repoIndexCmd) run() error { + path, err := filepath.Abs(i.dir) + if err != nil { + return err + } + + return index(path, i.url, i.merge) +} + +func index(dir, url, mergeTo string) error { + out := filepath.Join(dir, "index.yaml") + + i, err := repo.IndexDirectory(dir, url) + if err != nil { + return err + } + if mergeTo != "" { + // if index.yaml is missing then create an empty one to merge into + var i2 *repo.IndexFile + if _, err := os.Stat(mergeTo); os.IsNotExist(err) { + i2 = repo.NewIndexFile() + i2.WriteFile(mergeTo, 0755) + } else { + i2, err = repo.LoadIndexFile(mergeTo) + if err != nil { + return fmt.Errorf("Merge failed: %s", err) + } + } + i.Merge(i2) + } + i.SortEntries() + return i.WriteFile(out, 0755) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index_test.go new file mode 100644 index 000000000..4d6313f6c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_index_test.go @@ -0,0 +1,171 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/repo" +) + +func TestRepoIndexCmd(t *testing.T) { + + dir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + comp := filepath.Join(dir, "compressedchart-0.1.0.tgz") + if err := linkOrCopy("testdata/testcharts/compressedchart-0.1.0.tgz", comp); err != nil { + t.Fatal(err) + } + comp2 := filepath.Join(dir, "compressedchart-0.2.0.tgz") + if err := linkOrCopy("testdata/testcharts/compressedchart-0.2.0.tgz", comp2); err != nil { + t.Fatal(err) + } + + buf := bytes.NewBuffer(nil) + c := newRepoIndexCmd(buf) + + if err := c.RunE(c, []string{dir}); err != nil { + t.Error(err) + } + + destIndex := filepath.Join(dir, "index.yaml") + + index, err := repo.LoadIndexFile(destIndex) + if err != nil { + t.Fatal(err) + } + + if len(index.Entries) != 1 { + t.Errorf("expected 1 entry, got %d: %#v", len(index.Entries), index.Entries) + } + + vs := index.Entries["compressedchart"] + if len(vs) != 2 { + t.Errorf("expected 2 versions, got %d: %#v", len(vs), vs) + } + + expectedVersion := "0.2.0" + if vs[0].Version != expectedVersion { + t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) + } + + // Test with `--merge` + + // Remove first two charts. + if err := os.Remove(comp); err != nil { + t.Fatal(err) + } + if err := os.Remove(comp2); err != nil { + t.Fatal(err) + } + // Add a new chart and a new version of an existing chart + if err := linkOrCopy("testdata/testcharts/reqtest-0.1.0.tgz", filepath.Join(dir, "reqtest-0.1.0.tgz")); err != nil { + t.Fatal(err) + } + if err := linkOrCopy("testdata/testcharts/compressedchart-0.3.0.tgz", filepath.Join(dir, "compressedchart-0.3.0.tgz")); err != nil { + t.Fatal(err) + } + + c.ParseFlags([]string{"--merge", destIndex}) + if err := c.RunE(c, []string{dir}); err != nil { + t.Error(err) + } + + index, err = repo.LoadIndexFile(destIndex) + if err != nil { + t.Fatal(err) + } + + if len(index.Entries) != 2 { + t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) + } + + vs = index.Entries["compressedchart"] + if len(vs) != 3 { + t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs) + } + + expectedVersion = "0.3.0" + if vs[0].Version != expectedVersion { + t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) + } + + // test that index.yaml gets generated on merge even when it doesn't exist + if err := os.Remove(destIndex); err != nil { + t.Fatal(err) + } + + c.ParseFlags([]string{"--merge", destIndex}) + if err := c.RunE(c, []string{dir}); err != nil { + t.Error(err) + } + + _, err = repo.LoadIndexFile(destIndex) + if err != nil { + t.Fatal(err) + } + + // verify it didn't create an empty index.yaml and the merged happened + if len(index.Entries) != 2 { + t.Errorf("expected 2 entries, got %d: %#v", len(index.Entries), index.Entries) + } + + vs = index.Entries["compressedchart"] + if len(vs) != 3 { + t.Errorf("expected 3 versions, got %d: %#v", len(vs), vs) + } + + expectedVersion = "0.3.0" + if vs[0].Version != expectedVersion { + t.Errorf("expected %q, got %q", expectedVersion, vs[0].Version) + } +} + +func linkOrCopy(old, new string) error { + if err := os.Link(old, new); err != nil { + return copyFile(old, new) + } + + return nil +} + +func copyFile(dst, src string) error { + i, err := os.Open(dst) + if err != nil { + return err + } + defer i.Close() + + o, err := os.Create(src) + if err != nil { + return err + } + defer o.Close() + + _, err = io.Copy(o, i) + + return err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_list.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_list.go new file mode 100644 index 000000000..0f795b2b0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_list.go @@ -0,0 +1,66 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +type repoListCmd struct { + out io.Writer + home helmpath.Home +} + +func newRepoListCmd(out io.Writer) *cobra.Command { + list := &repoListCmd{out: out} + + cmd := &cobra.Command{ + Use: "list [flags]", + Short: "list chart repositories", + RunE: func(cmd *cobra.Command, args []string) error { + list.home = settings.Home + return list.run() + }, + } + + return cmd +} + +func (a *repoListCmd) run() error { + f, err := repo.LoadRepositoriesFile(a.home.RepositoryFile()) + if err != nil { + return err + } + if len(f.Repositories) == 0 { + return errors.New("no repositories to show") + } + table := uitable.New() + table.AddRow("NAME", "URL") + for _, re := range f.Repositories { + table.AddRow(re.Name, re.URL) + } + fmt.Fprintln(a.out, table) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove.go new file mode 100644 index 000000000..201ee9ca8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove.go @@ -0,0 +1,92 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +type repoRemoveCmd struct { + out io.Writer + name string + home helmpath.Home +} + +func newRepoRemoveCmd(out io.Writer) *cobra.Command { + remove := &repoRemoveCmd{out: out} + + cmd := &cobra.Command{ + Use: "remove [flags] [NAME]", + Aliases: []string{"rm"}, + Short: "remove a chart repository", + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "name of chart repository"); err != nil { + return err + } + remove.name = args[0] + remove.home = settings.Home + + return remove.run() + }, + } + + return cmd +} + +func (r *repoRemoveCmd) run() error { + return removeRepoLine(r.out, r.name, r.home) +} + +func removeRepoLine(out io.Writer, name string, home helmpath.Home) error { + repoFile := home.RepositoryFile() + r, err := repo.LoadRepositoriesFile(repoFile) + if err != nil { + return err + } + + if !r.Remove(name) { + return fmt.Errorf("no repo named %q found", name) + } + if err := r.WriteFile(repoFile, 0644); err != nil { + return err + } + + if err := removeRepoCache(name, home); err != nil { + return err + } + + fmt.Fprintf(out, "%q has been removed from your repositories\n", name) + + return nil +} + +func removeRepoCache(name string, home helmpath.Home) error { + if _, err := os.Stat(home.CacheIndex(name)); err == nil { + err = os.Remove(home.CacheIndex(name)) + if err != nil { + return err + } + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove_test.go new file mode 100644 index 000000000..174a44495 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_remove_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "os" + "strings" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +func TestRepoRemove(t *testing.T) { + ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*") + if err != nil { + t.Fatal(err) + } + + hh := helmpath.Home(thome) + cleanup := resetEnv() + defer func() { + ts.Stop() + os.RemoveAll(thome.String()) + cleanup() + }() + if err := ensureTestHome(hh, t); err != nil { + t.Fatal(err) + } + + settings.Home = thome + + b := bytes.NewBuffer(nil) + + if err := removeRepoLine(b, testName, hh); err == nil { + t.Errorf("Expected error removing %s, but did not get one.", testName) + } + if err := addRepository(testName, ts.URL(), "", "", hh, "", "", "", true); err != nil { + t.Error(err) + } + + mf, _ := os.Create(hh.CacheIndex(testName)) + mf.Close() + + b.Reset() + if err := removeRepoLine(b, testName, hh); err != nil { + t.Errorf("Error removing %s from repositories", testName) + } + if !strings.Contains(b.String(), "has been removed") { + t.Errorf("Unexpected output: %s", b.String()) + } + + if _, err := os.Stat(hh.CacheIndex(testName)); err == nil { + t.Errorf("Error cache file was not removed for repository %s", testName) + } + + f, err := repo.LoadRepositoriesFile(hh.RepositoryFile()) + if err != nil { + t.Error(err) + } + + if f.Has(testName) { + t.Errorf("%s was not successfully removed from repositories list", testName) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update.go new file mode 100644 index 000000000..51e5c0868 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update.go @@ -0,0 +1,109 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "sync" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +const updateDesc = ` +Update gets the latest information about charts from the respective chart repositories. +Information is cached locally, where it is used by commands like 'helm search'. + +'helm update' is the deprecated form of 'helm repo update'. It will be removed in +future releases. +` + +var errNoRepositories = errors.New("no repositories found. You must add one before updating") + +type repoUpdateCmd struct { + update func([]*repo.ChartRepository, io.Writer, helmpath.Home) + home helmpath.Home + out io.Writer +} + +func newRepoUpdateCmd(out io.Writer) *cobra.Command { + u := &repoUpdateCmd{ + out: out, + update: updateCharts, + } + cmd := &cobra.Command{ + Use: "update", + Aliases: []string{"up"}, + Short: "update information of available charts locally from chart repositories", + Long: updateDesc, + RunE: func(cmd *cobra.Command, args []string) error { + u.home = settings.Home + return u.run() + }, + } + return cmd +} + +func (u *repoUpdateCmd) run() error { + f, err := repo.LoadRepositoriesFile(u.home.RepositoryFile()) + if err != nil { + return err + } + + if len(f.Repositories) == 0 { + return errNoRepositories + } + var repos []*repo.ChartRepository + for _, cfg := range f.Repositories { + r, err := repo.NewChartRepository(cfg, getter.All(settings)) + if err != nil { + return err + } + repos = append(repos, r) + } + + u.update(repos, u.out, u.home) + return nil +} + +func updateCharts(repos []*repo.ChartRepository, out io.Writer, home helmpath.Home) { + fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") + var wg sync.WaitGroup + for _, re := range repos { + wg.Add(1) + go func(re *repo.ChartRepository) { + defer wg.Done() + if re.Config.Name == localRepository { + fmt.Fprintf(out, "...Skip %s chart repository\n", re.Config.Name) + return + } + err := re.DownloadIndexFile(home.Cache()) + if err != nil { + fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err) + } else { + fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name) + } + }(re) + } + wg.Wait() + fmt.Fprintln(out, "Update Complete. ⎈ Happy Helming!⎈ ") +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update_test.go new file mode 100644 index 000000000..68f964f32 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/repo_update_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + "testing" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +func TestUpdateCmd(t *testing.T) { + thome, err := tempHelmHome(t) + if err != nil { + t.Fatal(err) + } + + cleanup := resetEnv() + defer func() { + os.RemoveAll(thome.String()) + cleanup() + }() + + settings.Home = thome + + out := bytes.NewBuffer(nil) + // Instead of using the HTTP updater, we provide our own for this test. + // The TestUpdateCharts test verifies the HTTP behavior independently. + updater := func(repos []*repo.ChartRepository, out io.Writer, hh helmpath.Home) { + for _, re := range repos { + fmt.Fprintln(out, re.Config.Name) + } + } + uc := &repoUpdateCmd{ + update: updater, + home: helmpath.Home(thome), + out: out, + } + if err := uc.run(); err != nil { + t.Fatal(err) + } + + if got := out.String(); !strings.Contains(got, "charts") || !strings.Contains(got, "local") { + t.Errorf("Expected 'charts' and 'local' (in any order) got %q", got) + } +} + +func TestUpdateCharts(t *testing.T) { + ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*") + if err != nil { + t.Fatal(err) + } + + hh := helmpath.Home(thome) + cleanup := resetEnv() + defer func() { + ts.Stop() + os.RemoveAll(thome.String()) + cleanup() + }() + if err := ensureTestHome(hh, t); err != nil { + t.Fatal(err) + } + + settings.Home = thome + + r, err := repo.NewChartRepository(&repo.Entry{ + Name: "charts", + URL: ts.URL(), + Cache: hh.CacheIndex("charts"), + }, getter.All(settings)) + if err != nil { + t.Error(err) + } + + b := bytes.NewBuffer(nil) + updateCharts([]*repo.ChartRepository{r}, b, hh) + + got := b.String() + if strings.Contains(got, "Unable to get an update") { + t.Errorf("Failed to get a repo: %q", got) + } + if !strings.Contains(got, "Update Complete.") { + t.Error("Update was not successful") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/reset.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/reset.go new file mode 100644 index 000000000..9d3e17e03 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/reset.go @@ -0,0 +1,131 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "os" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "k8s.io/helm/cmd/helm/installer" + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/release" +) + +const resetDesc = ` +This command uninstalls Tiller (the Helm server-side component) from your +Kubernetes Cluster and optionally deletes local configuration in +$HELM_HOME (default ~/.helm/) +` + +type resetCmd struct { + force bool + removeHelmHome bool + namespace string + out io.Writer + home helmpath.Home + client helm.Interface + kubeClient internalclientset.Interface +} + +func newResetCmd(client helm.Interface, out io.Writer) *cobra.Command { + d := &resetCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "reset", + Short: "uninstalls Tiller from a cluster", + Long: resetDesc, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := setupConnection(); !d.force && err != nil { + return err + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) != 0 { + return errors.New("This command does not accept arguments") + } + + d.namespace = settings.TillerNamespace + d.home = settings.Home + d.client = ensureHelmClient(d.client) + + return d.run() + }, + } + + f := cmd.Flags() + f.BoolVarP(&d.force, "force", "f", false, "forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.)") + f.BoolVar(&d.removeHelmHome, "remove-helm-home", false, "if set deletes $HELM_HOME") + + return cmd +} + +// runReset uninstalls tiller from Kubernetes Cluster and deletes local config +func (d *resetCmd) run() error { + if d.kubeClient == nil { + c, err := getInternalKubeClient(settings.KubeContext) + if err != nil { + return fmt.Errorf("could not get kubernetes client: %s", err) + } + d.kubeClient = c + } + + res, err := d.client.ListReleases( + helm.ReleaseListStatuses([]release.Status_Code{release.Status_DEPLOYED}), + ) + if !d.force && err != nil { + return prettyError(err) + } + + if !d.force && res != nil && len(res.Releases) > 0 { + return fmt.Errorf("there are still %d deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)", len(res.Releases)) + } + + if err := installer.Uninstall(d.kubeClient, &installer.Options{Namespace: d.namespace}); err != nil { + return fmt.Errorf("error unstalling Tiller: %s", err) + } + + if d.removeHelmHome { + if err := deleteDirectories(d.home, d.out); err != nil { + return err + } + } + + fmt.Fprintln(d.out, "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster.") + return nil +} + +// deleteDirectories deletes $HELM_HOME +func deleteDirectories(home helmpath.Home, out io.Writer) error { + if _, err := os.Stat(home.String()); err == nil { + fmt.Fprintf(out, "Deleting %s \n", home.String()) + if err := os.RemoveAll(home.String()); err != nil { + return fmt.Errorf("Could not remove %s: %s", home.String(), err) + } + } + + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/reset_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/reset_test.go new file mode 100644 index 000000000..458736a63 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/reset_test.go @@ -0,0 +1,170 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "io/ioutil" + "os" + "strings" + "testing" + + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestResetCmd(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.Remove(home) + + var buf bytes.Buffer + c := &helm.FakeClient{} + fc := fake.NewSimpleClientset() + cmd := &resetCmd{ + out: &buf, + home: helmpath.Home(home), + client: c, + kubeClient: fc, + namespace: core.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := fc.Actions() + if len(actions) != 3 { + t.Errorf("Expected 3 actions, got %d", len(actions)) + } + expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster." + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } + if _, err := os.Stat(home); err != nil { + t.Errorf("Helm home directory %s does not exists", home) + } +} + +func TestResetCmd_removeHelmHome(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.Remove(home) + + var buf bytes.Buffer + c := &helm.FakeClient{} + fc := fake.NewSimpleClientset() + cmd := &resetCmd{ + removeHelmHome: true, + out: &buf, + home: helmpath.Home(home), + client: c, + kubeClient: fc, + namespace: core.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := fc.Actions() + if len(actions) != 3 { + t.Errorf("Expected 3 actions, got %d", len(actions)) + } + expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster." + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } + if _, err := os.Stat(home); err == nil { + t.Errorf("Helm home directory %s already exists", home) + } +} + +func TestReset_deployedReleases(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.Remove(home) + + var buf bytes.Buffer + resp := []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + } + c := &helm.FakeClient{ + Rels: resp, + } + fc := fake.NewSimpleClientset() + cmd := &resetCmd{ + out: &buf, + home: helmpath.Home(home), + client: c, + kubeClient: fc, + namespace: core.NamespaceDefault, + } + err = cmd.run() + expected := "there are still 1 deployed releases (Tip: use --force to remove Tiller. Releases will not be deleted.)" + if !strings.Contains(err.Error(), expected) { + t.Errorf("unexpected error: %v", err) + } + if _, err := os.Stat(home); err != nil { + t.Errorf("Helm home directory %s does not exists", home) + } +} + +func TestReset_forceFlag(t *testing.T) { + home, err := ioutil.TempDir("", "helm_home") + if err != nil { + t.Fatal(err) + } + defer os.Remove(home) + + var buf bytes.Buffer + resp := []*release.Release{ + helm.ReleaseMock(&helm.MockReleaseOptions{Name: "atlas-guide", StatusCode: release.Status_DEPLOYED}), + } + c := &helm.FakeClient{ + Rels: resp, + } + fc := fake.NewSimpleClientset() + cmd := &resetCmd{ + force: true, + out: &buf, + home: helmpath.Home(home), + client: c, + kubeClient: fc, + namespace: core.NamespaceDefault, + } + if err := cmd.run(); err != nil { + t.Errorf("unexpected error: %v", err) + } + actions := fc.Actions() + if len(actions) != 3 { + t.Errorf("Expected 3 actions, got %d", len(actions)) + } + expected := "Tiller (the Helm server-side component) has been uninstalled from your Kubernetes Cluster." + if !strings.Contains(buf.String(), expected) { + t.Errorf("expected %q, got %q", expected, buf.String()) + } + if _, err := os.Stat(home); err != nil { + t.Errorf("Helm home directory %s does not exists", home) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback.go new file mode 100644 index 000000000..889b6ae28 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback.go @@ -0,0 +1,107 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "strconv" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +const rollbackDesc = ` +This command rolls back a release to a previous revision. + +The first argument of the rollback command is the name of a release, and the +second is a revision (version) number. To see revision numbers, run +'helm history RELEASE'. +` + +type rollbackCmd struct { + name string + revision int32 + dryRun bool + recreate bool + force bool + disableHooks bool + out io.Writer + client helm.Interface + timeout int64 + wait bool +} + +func newRollbackCmd(c helm.Interface, out io.Writer) *cobra.Command { + rollback := &rollbackCmd{ + out: out, + client: c, + } + + cmd := &cobra.Command{ + Use: "rollback [flags] [RELEASE] [REVISION]", + Short: "roll back a release to a previous revision", + Long: rollbackDesc, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "release name", "revision number"); err != nil { + return err + } + + rollback.name = args[0] + + v64, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return fmt.Errorf("invalid revision number '%q': %s", args[1], err) + } + + rollback.revision = int32(v64) + rollback.client = ensureHelmClient(rollback.client) + return rollback.run() + }, + } + + f := cmd.Flags() + f.BoolVar(&rollback.dryRun, "dry-run", false, "simulate a rollback") + f.BoolVar(&rollback.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") + f.BoolVar(&rollback.force, "force", false, "force resource update through delete/recreate if needed") + f.BoolVar(&rollback.disableHooks, "no-hooks", false, "prevent hooks from running during rollback") + f.Int64Var(&rollback.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&rollback.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") + + return cmd +} + +func (r *rollbackCmd) run() error { + _, err := r.client.RollbackRelease( + r.name, + helm.RollbackDryRun(r.dryRun), + helm.RollbackRecreate(r.recreate), + helm.RollbackForce(r.force), + helm.RollbackDisableHooks(r.disableHooks), + helm.RollbackVersion(r.revision), + helm.RollbackTimeout(r.timeout), + helm.RollbackWait(r.wait)) + if err != nil { + return prettyError(err) + } + + fmt.Fprintf(r.out, "Rollback was a success! Happy Helming!\n") + + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback_test.go new file mode 100644 index 000000000..f1479b2eb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/rollback_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +func TestRollbackCmd(t *testing.T) { + + tests := []releaseCase{ + { + name: "rollback a release", + args: []string{"funny-honey", "1"}, + expected: "Rollback was a success! Happy Helming!", + }, + { + name: "rollback a release with timeout", + args: []string{"funny-honey", "1"}, + flags: []string{"--timeout", "120"}, + expected: "Rollback was a success! Happy Helming!", + }, + { + name: "rollback a release with wait", + args: []string{"funny-honey", "1"}, + flags: []string{"--wait"}, + expected: "Rollback was a success! Happy Helming!", + }, + { + name: "rollback a release without revision", + args: []string{"funny-honey"}, + err: true, + }, + } + + cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newRollbackCmd(c, out) + } + + runReleaseCases(t, tests, cmd) + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/search.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/search.go new file mode 100644 index 000000000..845bfd0be --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/search.go @@ -0,0 +1,162 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "strings" + + "github.com/Masterminds/semver" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + + "k8s.io/helm/cmd/helm/search" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +const searchDesc = ` +Search reads through all of the repositories configured on the system, and +looks for matches. + +Repositories are managed with 'helm repo' commands. +` + +// searchMaxScore suggests that any score higher than this is not considered a match. +const searchMaxScore = 25 + +type searchCmd struct { + out io.Writer + helmhome helmpath.Home + + versions bool + regexp bool + version string +} + +func newSearchCmd(out io.Writer) *cobra.Command { + sc := &searchCmd{out: out} + + cmd := &cobra.Command{ + Use: "search [keyword]", + Short: "search for a keyword in charts", + Long: searchDesc, + RunE: func(cmd *cobra.Command, args []string) error { + sc.helmhome = settings.Home + return sc.run(args) + }, + } + + f := cmd.Flags() + f.BoolVarP(&sc.regexp, "regexp", "r", false, "use regular expressions for searching") + f.BoolVarP(&sc.versions, "versions", "l", false, "show the long listing, with each version of each chart on its own line") + f.StringVarP(&sc.version, "version", "v", "", "search using semantic versioning constraints") + + return cmd +} + +func (s *searchCmd) run(args []string) error { + index, err := s.buildIndex() + if err != nil { + return err + } + + var res []*search.Result + if len(args) == 0 { + res = index.All() + } else { + q := strings.Join(args, " ") + res, err = index.Search(q, searchMaxScore, s.regexp) + if err != nil { + return err + } + } + + search.SortScore(res) + data, err := s.applyConstraint(res) + if err != nil { + return err + } + + fmt.Fprintln(s.out, s.formatSearchResults(data)) + + return nil +} + +func (s *searchCmd) applyConstraint(res []*search.Result) ([]*search.Result, error) { + if len(s.version) == 0 { + return res, nil + } + + constraint, err := semver.NewConstraint(s.version) + if err != nil { + return res, fmt.Errorf("an invalid version/constraint format: %s", err) + } + + data := res[:0] + foundNames := map[string]bool{} + for _, r := range res { + if _, found := foundNames[r.Name]; found { + continue + } + v, err := semver.NewVersion(r.Chart.Version) + if err != nil || constraint.Check(v) { + data = append(data, r) + if !s.versions { + foundNames[r.Name] = true // If user hasn't requested all versions, only show the latest that matches + } + } + } + + return data, nil +} + +func (s *searchCmd) formatSearchResults(res []*search.Result) string { + if len(res) == 0 { + return "No results found" + } + table := uitable.New() + table.MaxColWidth = 50 + table.AddRow("NAME", "CHART VERSION", "APP VERSION", "DESCRIPTION") + for _, r := range res { + table.AddRow(r.Name, r.Chart.Version, r.Chart.AppVersion, r.Chart.Description) + } + return table.String() +} + +func (s *searchCmd) buildIndex() (*search.Index, error) { + // Load the repositories.yaml + rf, err := repo.LoadRepositoriesFile(s.helmhome.RepositoryFile()) + if err != nil { + return nil, err + } + + i := search.NewIndex() + for _, re := range rf.Repositories { + n := re.Name + f := s.helmhome.CacheIndex(n) + ind, err := repo.LoadIndexFile(f) + if err != nil { + fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n) + continue + } + + i.AddRepo(n, ind, s.versions || len(s.version) > 0) + } + return i, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search.go new file mode 100644 index 000000000..6c4cb4aa4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 search provides client-side repository searching. + +This supports building an in-memory search index based on the contents of +multiple repositories, and then using string matching or regular expressions +to find matches. +*/ +package search + +import ( + "errors" + "path" + "regexp" + "sort" + "strings" + + "github.com/Masterminds/semver" + "k8s.io/helm/pkg/repo" +) + +// Result is a search result. +// +// Score indicates how close it is to match. The higher the score, the longer +// the distance. +type Result struct { + Name string + Score int + Chart *repo.ChartVersion +} + +// Index is a searchable index of chart information. +type Index struct { + lines map[string]string + charts map[string]*repo.ChartVersion +} + +const sep = "\v" + +// NewIndex creats a new Index. +func NewIndex() *Index { + return &Index{lines: map[string]string{}, charts: map[string]*repo.ChartVersion{}} +} + +// verSep is a separator for version fields in map keys. +const verSep = "$$" + +// AddRepo adds a repository index to the search index. +func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) { + ind.SortEntries() + for name, ref := range ind.Entries { + if len(ref) == 0 { + // Skip chart names that have zero releases. + continue + } + // By convention, an index file is supposed to have the newest at the + // 0 slot, so our best bet is to grab the 0 entry and build the index + // entry off of that. + // Note: Do not use filePath.Join since on Windows it will return \ + // which results in a repo name that cannot be understood. + fname := path.Join(rname, name) + if !all { + i.lines[fname] = indstr(rname, ref[0]) + i.charts[fname] = ref[0] + continue + } + + // If 'all' is set, then we go through all of the refs, and add them all + // to the index. This will generate a lot of near-duplicate entries. + for _, rr := range ref { + versionedName := fname + verSep + rr.Version + i.lines[versionedName] = indstr(rname, rr) + i.charts[versionedName] = rr + } + } +} + +// All returns all charts in the index as if they were search results. +// +// Each will be given a score of 0. +func (i *Index) All() []*Result { + res := make([]*Result, len(i.charts)) + j := 0 + for name, ch := range i.charts { + parts := strings.Split(name, verSep) + res[j] = &Result{ + Name: parts[0], + Chart: ch, + } + j++ + } + return res +} + +// Search searches an index for the given term. +// +// Threshold indicates the maximum score a term may have before being marked +// irrelevant. (Low score means higher relevance. Golf, not bowling.) +// +// If regexp is true, the term is treated as a regular expression. Otherwise, +// term is treated as a literal string. +func (i *Index) Search(term string, threshold int, regexp bool) ([]*Result, error) { + if regexp { + return i.SearchRegexp(term, threshold) + } + return i.SearchLiteral(term, threshold), nil +} + +// calcScore calculates a score for a match. +func (i *Index) calcScore(index int, matchline string) int { + + // This is currently tied to the fact that sep is a single char. + splits := []int{} + s := rune(sep[0]) + for i, ch := range matchline { + if ch == s { + splits = append(splits, i) + } + } + + for i, pos := range splits { + if index > pos { + continue + } + return i + } + return len(splits) +} + +// SearchLiteral does a literal string search (no regexp). +func (i *Index) SearchLiteral(term string, threshold int) []*Result { + term = strings.ToLower(term) + buf := []*Result{} + for k, v := range i.lines { + lk := strings.ToLower(k) + lv := strings.ToLower(v) + res := strings.Index(lv, term) + if score := i.calcScore(res, lv); res != -1 && score < threshold { + parts := strings.Split(lk, verSep) // Remove version, if it is there. + buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]}) + } + } + return buf +} + +// SearchRegexp searches using a regular expression. +func (i *Index) SearchRegexp(re string, threshold int) ([]*Result, error) { + matcher, err := regexp.Compile(re) + if err != nil { + return []*Result{}, err + } + buf := []*Result{} + for k, v := range i.lines { + ind := matcher.FindStringIndex(v) + if len(ind) == 0 { + continue + } + if score := i.calcScore(ind[0], v); ind[0] >= 0 && score < threshold { + parts := strings.Split(k, verSep) // Remove version, if it is there. + buf = append(buf, &Result{Name: parts[0], Score: score, Chart: i.charts[k]}) + } + } + return buf, nil +} + +// Chart returns the ChartVersion for a particular name. +func (i *Index) Chart(name string) (*repo.ChartVersion, error) { + c, ok := i.charts[name] + if !ok { + return nil, errors.New("no such chart") + } + return c, nil +} + +// SortScore does an in-place sort of the results. +// +// Lowest scores are highest on the list. Matching scores are subsorted alphabetically. +func SortScore(r []*Result) { + sort.Sort(scoreSorter(r)) +} + +// scoreSorter sorts results by score, and subsorts by alpha Name. +type scoreSorter []*Result + +// Len returns the length of this scoreSorter. +func (s scoreSorter) Len() int { return len(s) } + +// Swap performs an in-place swap. +func (s scoreSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// Less compares a to b, and returns true if a is less than b. +func (s scoreSorter) Less(a, b int) bool { + first := s[a] + second := s[b] + + if first.Score > second.Score { + return false + } + if first.Score < second.Score { + return true + } + if first.Name == second.Name { + v1, err := semver.NewVersion(first.Chart.Version) + if err != nil { + return true + } + v2, err := semver.NewVersion(second.Chart.Version) + if err != nil { + return true + } + // Sort so that the newest chart is higher than the oldest chart. This is + // the opposite of what you'd expect in a function called Less. + return v1.GreaterThan(v2) + } + return first.Name < second.Name +} + +func indstr(name string, ref *repo.ChartVersion) string { + i := ref.Name + sep + name + "/" + ref.Name + sep + + ref.Description + sep + strings.Join(ref.Keywords, " ") + return i +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search_test.go new file mode 100644 index 000000000..574f55448 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/search/search_test.go @@ -0,0 +1,301 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 search + +import ( + "strings" + "testing" + + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/repo" +) + +func TestSortScore(t *testing.T) { + in := []*Result{ + {Name: "bbb", Score: 0, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}}, + {Name: "aaa", Score: 5}, + {Name: "abb", Score: 5}, + {Name: "aab", Score: 0}, + {Name: "bab", Score: 5}, + {Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.4"}}}, + {Name: "ver", Score: 5, Chart: &repo.ChartVersion{Metadata: &chart.Metadata{Version: "1.2.3"}}}, + } + expect := []string{"aab", "bbb", "aaa", "abb", "bab", "ver", "ver"} + expectScore := []int{0, 0, 5, 5, 5, 5, 5} + SortScore(in) + + // Test Score + for i := 0; i < len(expectScore); i++ { + if expectScore[i] != in[i].Score { + t.Errorf("Sort error on index %d: expected %d, got %d", i, expectScore[i], in[i].Score) + } + } + // Test Name + for i := 0; i < len(expect); i++ { + if expect[i] != in[i].Name { + t.Errorf("Sort error: expected %s, got %s", expect[i], in[i].Name) + } + } + + // Test version of last two items + if in[5].Chart.Version != "1.2.4" { + t.Errorf("Expected 1.2.4, got %s", in[5].Chart.Version) + } + if in[6].Chart.Version != "1.2.3" { + t.Error("Expected 1.2.3 to be last") + } +} + +var indexfileEntries = map[string]repo.ChartVersions{ + "niña": { + { + URLs: []string{"http://example.com/charts/nina-0.1.0.tgz"}, + Metadata: &chart.Metadata{ + Name: "niña", + Version: "0.1.0", + Description: "One boat", + }, + }, + }, + "pinta": { + { + URLs: []string{"http://example.com/charts/pinta-0.1.0.tgz"}, + Metadata: &chart.Metadata{ + Name: "pinta", + Version: "0.1.0", + Description: "Two ship", + }, + }, + }, + "santa-maria": { + { + URLs: []string{"http://example.com/charts/santa-maria-1.2.3.tgz"}, + Metadata: &chart.Metadata{ + Name: "santa-maria", + Version: "1.2.3", + Description: "Three boat", + }, + }, + { + URLs: []string{"http://example.com/charts/santa-maria-1.2.2-rc-1.tgz"}, + Metadata: &chart.Metadata{ + Name: "santa-maria", + Version: "1.2.2-RC-1", + Description: "Three boat", + }, + }, + }, +} + +func loadTestIndex(t *testing.T, all bool) *Index { + i := NewIndex() + i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all) + i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{ + "pinta": { + { + URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"}, + Metadata: &chart.Metadata{ + Name: "pinta", + Version: "2.0.0", + Description: "Two ship, version two", + }, + }, + }, + }}, all) + return i +} + +func TestAll(t *testing.T) { + i := loadTestIndex(t, false) + all := i.All() + if len(all) != 4 { + t.Errorf("Expected 4 entries, got %d", len(all)) + } + + i = loadTestIndex(t, true) + all = i.All() + if len(all) != 5 { + t.Errorf("Expected 5 entries, got %d", len(all)) + } +} + +func TestAddRepo_Sort(t *testing.T) { + i := loadTestIndex(t, true) + sr, err := i.Search("TESTING/SANTA-MARIA", 100, false) + if err != nil { + t.Fatal(err) + } + SortScore(sr) + + ch := sr[0] + expect := "1.2.3" + if ch.Chart.Version != expect { + t.Errorf("Expected %q, got %q", expect, ch.Chart.Version) + } +} + +func TestSearchByName(t *testing.T) { + + tests := []struct { + name string + query string + expect []*Result + regexp bool + fail bool + failMsg string + }{ + { + name: "basic search for one result", + query: "santa-maria", + expect: []*Result{ + {Name: "testing/santa-maria"}, + }, + }, + { + name: "basic search for two results", + query: "pinta", + expect: []*Result{ + {Name: "testing/pinta"}, + {Name: "ztesting/pinta"}, + }, + }, + { + name: "repo-specific search for one result", + query: "ztesting/pinta", + expect: []*Result{ + {Name: "ztesting/pinta"}, + }, + }, + { + name: "partial name search", + query: "santa", + expect: []*Result{ + {Name: "testing/santa-maria"}, + }, + }, + { + name: "description search, one result", + query: "Three", + expect: []*Result{ + {Name: "testing/santa-maria"}, + }, + }, + { + name: "description search, two results", + query: "two", + expect: []*Result{ + {Name: "testing/pinta"}, + {Name: "ztesting/pinta"}, + }, + }, + { + name: "description upper search, two results", + query: "TWO", + expect: []*Result{ + {Name: "testing/pinta"}, + {Name: "ztesting/pinta"}, + }, + }, + { + name: "nothing found", + query: "mayflower", + expect: []*Result{}, + }, + { + name: "regexp, one result", + query: "Th[ref]*", + expect: []*Result{ + {Name: "testing/santa-maria"}, + }, + regexp: true, + }, + { + name: "regexp, fail compile", + query: "th[", + expect: []*Result{}, + regexp: true, + fail: true, + failMsg: "error parsing regexp:", + }, + } + + i := loadTestIndex(t, false) + + for _, tt := range tests { + + charts, err := i.Search(tt.query, 100, tt.regexp) + if err != nil { + if tt.fail { + if !strings.Contains(err.Error(), tt.failMsg) { + t.Fatalf("%s: Unexpected error message: %s", tt.name, err) + } + continue + } + t.Fatalf("%s: %s", tt.name, err) + } + // Give us predictably ordered results. + SortScore(charts) + + l := len(charts) + if l != len(tt.expect) { + t.Fatalf("%s: Expected %d result, got %d", tt.name, len(tt.expect), l) + } + // For empty result sets, just keep going. + if l == 0 { + continue + } + + for i, got := range charts { + ex := tt.expect[i] + if got.Name != ex.Name { + t.Errorf("%s[%d]: Expected name %q, got %q", tt.name, i, ex.Name, got.Name) + } + } + + } +} + +func TestSearchByNameAll(t *testing.T) { + // Test with the All bit turned on. + i := loadTestIndex(t, true) + cs, err := i.Search("santa-maria", 100, false) + if err != nil { + t.Fatal(err) + } + if len(cs) != 2 { + t.Errorf("expected 2 charts, got %d", len(cs)) + } +} + +func TestCalcScore(t *testing.T) { + i := NewIndex() + + fields := []string{"aaa", "bbb", "ccc", "ddd"} + matchline := strings.Join(fields, sep) + if r := i.calcScore(2, matchline); r != 0 { + t.Errorf("Expected 0, got %d", r) + } + if r := i.calcScore(5, matchline); r != 1 { + t.Errorf("Expected 1, got %d", r) + } + if r := i.calcScore(10, matchline); r != 2 { + t.Errorf("Expected 2, got %d", r) + } + if r := i.calcScore(14, matchline); r != 3 { + t.Errorf("Expected 3, got %d", r) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/search_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/search_test.go new file mode 100644 index 000000000..734f752f5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/search_test.go @@ -0,0 +1,97 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" +) + +func TestSearchCmd(t *testing.T) { + tests := []releaseCase{ + { + name: "search for 'maria', expect one match", + args: []string{"maria"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/mariadb\t0.3.0 \t \tChart for MariaDB", + }, + { + name: "search for 'alpine', expect two matches", + args: []string{"alpine"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with versions, expect three matches", + args: []string{"alpine"}, + flags: []string{"--versions"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", + args: []string{"alpine"}, + flags: []string{"--version", ">= 0.1, < 0.2"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.1.0", + args: []string{"alpine"}, + flags: []string{"--versions", "--version", ">= 0.1, < 0.2"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint, expect one match with version 0.2.0", + args: []string{"alpine"}, + flags: []string{"--version", ">= 0.1"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alpine' with version constraint and --versions, expect two matches", + args: []string{"alpine"}, + flags: []string{"--versions", "--version", ">= 0.1"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod\ntesting/alpine\t0.1.0 \t1.2.3 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'syzygy', expect no matches", + args: []string{"syzygy"}, + expected: "No results found", + }, + { + name: "search for 'alp[a-z]+', expect two matches", + args: []string{"alp[a-z]+"}, + flags: []string{"--regexp"}, + expected: "NAME \tCHART VERSION\tAPP VERSION\tDESCRIPTION \ntesting/alpine\t0.2.0 \t2.3.4 \tDeploy a basic Alpine Linux pod", + }, + { + name: "search for 'alp[', expect failure to compile regexp", + args: []string{"alp["}, + flags: []string{"--regexp"}, + err: true, + }, + } + + cleanup := resetEnv() + defer cleanup() + + settings.Home = "testdata/helmhome" + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newSearchCmd(out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/serve.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/serve.go new file mode 100644 index 000000000..21ae36da1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/serve.go @@ -0,0 +1,102 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/repo" +) + +const serveDesc = ` +This command starts a local chart repository server that serves charts from a local directory. + +The new server will provide HTTP access to a repository. By default, it will +scan all of the charts in '$HELM_HOME/repository/local' and serve those over +the local IPv4 TCP port (default '127.0.0.1:8879'). + +This command is intended to be used for educational and testing purposes only. +It is best to rely on a dedicated web server or a cloud-hosted solution like +Google Cloud Storage for production use. + +See https://github.com/kubernetes/helm/blob/master/docs/chart_repository.md#hosting-chart-repositories +for more information on hosting chart repositories in a production setting. +` + +type serveCmd struct { + out io.Writer + url string + address string + repoPath string +} + +func newServeCmd(out io.Writer) *cobra.Command { + srv := &serveCmd{out: out} + cmd := &cobra.Command{ + Use: "serve", + Short: "start a local http web server", + Long: serveDesc, + PreRunE: func(cmd *cobra.Command, args []string) error { + return srv.complete() + }, + RunE: func(cmd *cobra.Command, args []string) error { + return srv.run() + }, + } + + f := cmd.Flags() + f.StringVar(&srv.repoPath, "repo-path", "", "local directory path from which to serve charts") + f.StringVar(&srv.address, "address", "127.0.0.1:8879", "address to listen on") + f.StringVar(&srv.url, "url", "", "external URL of chart repository") + + return cmd +} + +func (s *serveCmd) complete() error { + if s.repoPath == "" { + s.repoPath = settings.Home.LocalRepository() + } + return nil +} + +func (s *serveCmd) run() error { + repoPath, err := filepath.Abs(s.repoPath) + if err != nil { + return err + } + if _, err := os.Stat(repoPath); os.IsNotExist(err) { + return err + } + + fmt.Fprintln(s.out, "Regenerating index. This may take a moment.") + if len(s.url) > 0 { + err = index(repoPath, s.url, "") + } else { + err = index(repoPath, "http://"+s.address, "") + } + if err != nil { + return err + } + + fmt.Fprintf(s.out, "Now serving you on %s\n", s.address) + return repo.StartLocalRepo(repoPath, s.address) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/status.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/status.go new file mode 100644 index 000000000..b73b6f56e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/status.go @@ -0,0 +1,157 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "text/tabwriter" + + "github.com/ghodss/yaml" + "github.com/gosuri/uitable" + "github.com/gosuri/uitable/util/strutil" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +var statusHelp = ` +This command shows the status of a named release. +The status consists of: +- last deployment time +- k8s namespace in which the release lives +- state of the release (can be: UNKNOWN, DEPLOYED, DELETED, SUPERSEDED, FAILED or DELETING) +- list of resources that this release consists of, sorted by kind +- details on last test suite run, if applicable +- additional notes provided by the chart +` + +type statusCmd struct { + release string + out io.Writer + client helm.Interface + version int32 + outfmt string +} + +func newStatusCmd(client helm.Interface, out io.Writer) *cobra.Command { + status := &statusCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "status [flags] RELEASE_NAME", + Short: "displays the status of the named release", + Long: statusHelp, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errReleaseRequired + } + status.release = args[0] + if status.client == nil { + status.client = newClient() + } + return status.run() + }, + } + + cmd.PersistentFlags().Int32Var(&status.version, "revision", 0, "if set, display the status of the named release with revision") + cmd.PersistentFlags().StringVarP(&status.outfmt, "output", "o", "", "output the status in the specified format (json or yaml)") + + return cmd +} + +func (s *statusCmd) run() error { + res, err := s.client.ReleaseStatus(s.release, helm.StatusReleaseVersion(s.version)) + if err != nil { + return prettyError(err) + } + + switch s.outfmt { + case "": + PrintStatus(s.out, res) + return nil + case "json": + data, err := json.Marshal(res) + if err != nil { + return fmt.Errorf("Failed to Marshal JSON output: %s", err) + } + s.out.Write(data) + return nil + case "yaml": + data, err := yaml.Marshal(res) + if err != nil { + return fmt.Errorf("Failed to Marshal YAML output: %s", err) + } + s.out.Write(data) + return nil + } + + return fmt.Errorf("Unknown output format %q", s.outfmt) +} + +// PrintStatus prints out the status of a release. Shared because also used by +// install / upgrade +func PrintStatus(out io.Writer, res *services.GetReleaseStatusResponse) { + if res.Info.LastDeployed != nil { + fmt.Fprintf(out, "LAST DEPLOYED: %s\n", timeconv.String(res.Info.LastDeployed)) + } + fmt.Fprintf(out, "NAMESPACE: %s\n", res.Namespace) + fmt.Fprintf(out, "STATUS: %s\n", res.Info.Status.Code) + fmt.Fprintf(out, "\n") + if len(res.Info.Status.Resources) > 0 { + re := regexp.MustCompile(" +") + + w := tabwriter.NewWriter(out, 0, 0, 2, ' ', tabwriter.TabIndent) + fmt.Fprintf(w, "RESOURCES:\n%s\n", re.ReplaceAllString(res.Info.Status.Resources, "\t")) + w.Flush() + } + if res.Info.Status.LastTestSuiteRun != nil { + lastRun := res.Info.Status.LastTestSuiteRun + fmt.Fprintf(out, "TEST SUITE:\n%s\n%s\n\n%s\n", + fmt.Sprintf("Last Started: %s", timeconv.String(lastRun.StartedAt)), + fmt.Sprintf("Last Completed: %s", timeconv.String(lastRun.CompletedAt)), + formatTestResults(lastRun.Results)) + } + + if len(res.Info.Status.Notes) > 0 { + fmt.Fprintf(out, "NOTES:\n%s\n", res.Info.Status.Notes) + } +} + +func formatTestResults(results []*release.TestRun) string { + tbl := uitable.New() + tbl.MaxColWidth = 50 + tbl.AddRow("TEST", "STATUS", "INFO", "STARTED", "COMPLETED") + for i := 0; i < len(results); i++ { + r := results[i] + n := r.Name + s := strutil.PadRight(r.Status.String(), 10, ' ') + i := r.Info + ts := timeconv.String(r.StartedAt) + tc := timeconv.String(r.CompletedAt) + tbl.AddRow(n, s, i, ts, tc) + } + return tbl.String() +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/status_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/status_test.go new file mode 100644 index 000000000..616b027f3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/status_test.go @@ -0,0 +1,151 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "testing" + + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/timeconv" +) + +var ( + date = timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + dateString = timeconv.String(&date) +) + +func TestStatusCmd(t *testing.T) { + tests := []releaseCase{ + { + name: "get status of a deployed release", + args: []string{"flummoxed-chickadee"}, + expected: outputWithStatus("DEPLOYED\n\n"), + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + }), + }, + }, + { + name: "get status of a deployed release with notes", + args: []string{"flummoxed-chickadee"}, + expected: outputWithStatus("DEPLOYED\n\nNOTES:\nrelease notes\n"), + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + Notes: "release notes", + }), + }, + }, + { + name: "get status of a deployed release with notes in json", + args: []string{"flummoxed-chickadee"}, + flags: []string{"-o", "json"}, + expected: `{"name":"flummoxed-chickadee","info":{"status":{"code":1,"notes":"release notes"},"first_deployed":{"seconds":242085845},"last_deployed":{"seconds":242085845}}}`, + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + Notes: "release notes", + }), + }, + }, + { + name: "get status of a deployed release with resources", + args: []string{"flummoxed-chickadee"}, + expected: outputWithStatus("DEPLOYED\n\nRESOURCES:\nresource A\nresource B\n\n"), + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + Resources: "resource A\nresource B\n", + }), + }, + }, + { + name: "get status of a deployed release with resources in YAML", + args: []string{"flummoxed-chickadee"}, + flags: []string{"-o", "yaml"}, + expected: "info:\n (.*)first_deployed:\n (.*)seconds: 242085845\n (.*)last_deployed:\n (.*)seconds: 242085845\n (.*)status:\n code: 1\n (.*)resources: |\n (.*)resource A\n (.*)resource B\nname: flummoxed-chickadee\n", + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + Resources: "resource A\nresource B\n", + }), + }, + }, + { + name: "get status of a deployed release with test suite", + args: []string{"flummoxed-chickadee"}, + expected: outputWithStatus( + fmt.Sprintf("DEPLOYED\n\nTEST SUITE:\nLast Started: %s\nLast Completed: %s\n\n", dateString, dateString) + + "TEST \tSTATUS (.*)\tINFO (.*)\tSTARTED (.*)\tCOMPLETED (.*)\n" + + fmt.Sprintf("test run 1\tSUCCESS (.*)\textra info\t%s\t%s\n", dateString, dateString) + + fmt.Sprintf("test run 2\tFAILURE (.*)\t (.*)\t%s\t%s\n", dateString, dateString)), + rels: []*release.Release{ + releaseMockWithStatus(&release.Status{ + Code: release.Status_DEPLOYED, + LastTestSuiteRun: &release.TestSuite{ + StartedAt: &date, + CompletedAt: &date, + Results: []*release.TestRun{ + { + Name: "test run 1", + Status: release.TestRun_SUCCESS, + Info: "extra info", + StartedAt: &date, + CompletedAt: &date, + }, + { + Name: "test run 2", + Status: release.TestRun_FAILURE, + StartedAt: &date, + CompletedAt: &date, + }, + }, + }, + }), + }, + }, + } + + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newStatusCmd(c, out) + }) + +} + +func outputWithStatus(status string) string { + return fmt.Sprintf("LAST DEPLOYED: %s\nNAMESPACE: \nSTATUS: %s", + dateString, + status) +} + +func releaseMockWithStatus(status *release.Status) *release.Release { + return &release.Release{ + Name: "flummoxed-chickadee", + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: status, + }, + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/template.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/template.go new file mode 100644 index 000000000..c04bc2dc8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/template.go @@ -0,0 +1,325 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "time" + + "github.com/Masterminds/semver" + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/tiller" + "k8s.io/helm/pkg/timeconv" + tversion "k8s.io/helm/pkg/version" +) + +const defaultDirectoryPermission = 0755 + +var ( + whitespaceRegex = regexp.MustCompile(`^\s*$`) + + // defaultKubeVersion is the default value of --kube-version flag + defaultKubeVersion = fmt.Sprintf("%s.%s", chartutil.DefaultKubeVersion.Major, chartutil.DefaultKubeVersion.Minor) +) + +const templateDesc = ` +Render chart templates locally and display the output. + +This does not require Tiller. However, any values that would normally be +looked up or retrieved in-cluster will be faked locally. Additionally, none +of the server-side testing of chart validity (e.g. whether an API is supported) +is done. + +To render just one template in a chart, use '-x': + + $ helm template mychart -x templates/deployment.yaml +` + +type templateCmd struct { + namespace string + valueFiles valueFiles + chartPath string + out io.Writer + values []string + stringValues []string + nameTemplate string + showNotes bool + releaseName string + renderFiles []string + kubeVersion string + outputDir string +} + +func newTemplateCmd(out io.Writer) *cobra.Command { + + t := &templateCmd{ + out: out, + } + + cmd := &cobra.Command{ + Use: "template [flags] CHART", + Short: fmt.Sprintf("locally render templates"), + Long: templateDesc, + RunE: t.run, + } + + f := cmd.Flags() + f.BoolVar(&t.showNotes, "notes", false, "show the computed NOTES.txt file as well") + f.StringVarP(&t.releaseName, "name", "n", "RELEASE-NAME", "release name") + f.StringArrayVarP(&t.renderFiles, "execute", "x", []string{}, "only execute the given templates") + f.VarP(&t.valueFiles, "values", "f", "specify values in a YAML file (can specify multiple)") + f.StringVar(&t.namespace, "namespace", "", "namespace to install the release into") + f.StringArrayVar(&t.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&t.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringVar(&t.nameTemplate, "name-template", "", "specify template used to name the release") + f.StringVar(&t.kubeVersion, "kube-version", defaultKubeVersion, "kubernetes version used as Capabilities.KubeVersion.Major/Minor") + f.StringVar(&t.outputDir, "output-dir", "", "writes the executed templates to files in output-dir instead of stdout") + + return cmd +} + +func (t *templateCmd) run(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return errors.New("chart is required") + } + // verify chart path exists + if _, err := os.Stat(args[0]); err == nil { + if t.chartPath, err = filepath.Abs(args[0]); err != nil { + return err + } + } else { + return err + } + // verify specified templates exist relative to chart + rf := []string{} + var af string + var err error + if len(t.renderFiles) > 0 { + for _, f := range t.renderFiles { + if !filepath.IsAbs(f) { + af, err = filepath.Abs(filepath.Join(t.chartPath, f)) + if err != nil { + return fmt.Errorf("could not resolve template path: %s", err) + } + } else { + af = f + } + rf = append(rf, af) + + if _, err := os.Stat(af); err != nil { + return fmt.Errorf("could not resolve template path: %s", err) + } + } + } + + // verify that output-dir exists if provided + if t.outputDir != "" { + _, err = os.Stat(t.outputDir) + if os.IsNotExist(err) { + return fmt.Errorf("output-dir '%s' does not exist", t.outputDir) + } + } + + if t.namespace == "" { + t.namespace = defaultNamespace() + } + // get combined values and create config + rawVals, err := vals(t.valueFiles, t.values, t.stringValues) + if err != nil { + return err + } + config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}} + + // If template is specified, try to run the template. + if t.nameTemplate != "" { + t.releaseName, err = generateName(t.nameTemplate) + if err != nil { + return err + } + } + + // Check chart requirements to make sure all dependencies are present in /charts + c, err := chartutil.Load(t.chartPath) + if err != nil { + return prettyError(err) + } + + if req, err := chartutil.LoadRequirements(c); err == nil { + if err := checkDependencies(c, req); err != nil { + return prettyError(err) + } + } else if err != chartutil.ErrRequirementsNotFound { + return fmt.Errorf("cannot load requirements: %v", err) + } + options := chartutil.ReleaseOptions{ + Name: t.releaseName, + Time: timeconv.Now(), + Namespace: t.namespace, + } + + err = chartutil.ProcessRequirementsEnabled(c, config) + if err != nil { + return err + } + err = chartutil.ProcessRequirementsImportValues(c) + if err != nil { + return err + } + + // Set up engine. + renderer := engine.New() + + caps := &chartutil.Capabilities{ + APIVersions: chartutil.DefaultVersionSet, + KubeVersion: chartutil.DefaultKubeVersion, + TillerVersion: tversion.GetVersionProto(), + } + + // kubernetes version + kv, err := semver.NewVersion(t.kubeVersion) + if err != nil { + return fmt.Errorf("could not parse a kubernetes version: %v", err) + } + caps.KubeVersion.Major = fmt.Sprint(kv.Major()) + caps.KubeVersion.Minor = fmt.Sprint(kv.Minor()) + caps.KubeVersion.GitVersion = fmt.Sprintf("v%d.%d.0", kv.Major(), kv.Minor()) + + vals, err := chartutil.ToRenderValuesCaps(c, config, options, caps) + if err != nil { + return err + } + + out, err := renderer.Render(c, vals) + listManifests := []tiller.Manifest{} + if err != nil { + return err + } + // extract kind and name + re := regexp.MustCompile("kind:(.*)\n") + for k, v := range out { + match := re.FindStringSubmatch(v) + h := "Unknown" + if len(match) == 2 { + h = strings.TrimSpace(match[1]) + } + m := tiller.Manifest{Name: k, Content: v, Head: &util.SimpleHead{Kind: h}} + listManifests = append(listManifests, m) + } + in := func(needle string, haystack []string) bool { + // make needle path absolute + d := strings.Split(needle, string(os.PathSeparator)) + dd := d[1:] + an := filepath.Join(t.chartPath, strings.Join(dd, string(os.PathSeparator))) + + for _, h := range haystack { + if h == an { + return true + } + } + return false + } + if settings.Debug { + rel := &release.Release{ + Name: t.releaseName, + Chart: c, + Config: config, + Version: 1, + Namespace: t.namespace, + Info: &release.Info{LastDeployed: timeconv.Timestamp(time.Now())}, + } + printRelease(os.Stdout, rel) + } + + for _, m := range tiller.SortByKind(listManifests) { + if len(t.renderFiles) > 0 && !in(m.Name, rf) { + continue + } + data := m.Content + b := filepath.Base(m.Name) + if !t.showNotes && b == "NOTES.txt" { + continue + } + if strings.HasPrefix(b, "_") { + continue + } + + if t.outputDir != "" { + // blank template after execution + if whitespaceRegex.MatchString(data) { + continue + } + err = writeToFile(t.outputDir, m.Name, data) + if err != nil { + return err + } + continue + } + fmt.Printf("---\n# Source: %s\n", m.Name) + fmt.Println(data) + } + return nil +} + +// write the to / +func writeToFile(outputDir string, name string, data string) error { + outfileName := strings.Join([]string{outputDir, name}, string(filepath.Separator)) + + err := ensureDirectoryForFile(outfileName) + if err != nil { + return err + } + + f, err := os.Create(outfileName) + if err != nil { + return err + } + + defer f.Close() + + _, err = f.WriteString(fmt.Sprintf("##---\n# Source: %s\n%s", name, data)) + + if err != nil { + return err + } + + fmt.Printf("wrote %s\n", outfileName) + return nil +} + +// 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) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/template_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/template_test.go new file mode 100644 index 000000000..eefa46774 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/template_test.go @@ -0,0 +1,167 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" +) + +var chartPath = "./../../pkg/chartutil/testdata/subpop/charts/subchart1" + +func TestTemplateCmd(t *testing.T) { + absChartPath, err := filepath.Abs(chartPath) + if err != nil { + t.Fatal(err) + } + tests := []struct { + name string + desc string + args []string + expectKey string + expectValue string + }{ + { + name: "check_name", + desc: "check for a known name in chart", + args: []string{chartPath}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "protocol: TCP\n name: nginx", + }, + { + name: "check_set_name", + desc: "verify --set values exist", + args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "protocol: TCP\n name: apache", + }, + { + name: "check_execute", + desc: "verify --execute single template", + args: []string{chartPath, "-x", "templates/service.yaml", "--set", "service.name=apache"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "protocol: TCP\n name: apache", + }, + { + name: "check_execute_absolute", + desc: "verify --execute single template", + args: []string{chartPath, "-x", absChartPath + "/" + "templates/service.yaml", "--set", "service.name=apache"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "protocol: TCP\n name: apache", + }, + { + name: "check_namespace", + desc: "verify --namespace", + args: []string{chartPath, "--namespace", "test"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "namespace: \"test\"", + }, + { + name: "check_release_name", + desc: "verify --release exists", + args: []string{chartPath, "--name", "test"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "release-name: \"test\"", + }, + { + name: "check_notes", + desc: "verify --notes shows notes", + args: []string{chartPath, "--notes", "true"}, + expectKey: "subchart1/templates/NOTES.txt", + expectValue: "Sample notes for subchart1", + }, + { + name: "check_values_files", + desc: "verify --values files values exist", + args: []string{chartPath, "--values", chartPath + "/charts/subchartA/values.yaml"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "name: apache", + }, + { + name: "check_name_template", + desc: "verify --name-template result exists", + args: []string{chartPath, "--name-template", "foobar-{{ b64enc \"abc\" }}-baz"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "release-name: \"foobar-YWJj-baz\"", + }, + { + name: "check_kube_version", + desc: "verify --kube-version overrides the kubernetes version", + args: []string{chartPath, "--kube-version", "1.6"}, + expectKey: "subchart1/templates/service.yaml", + expectValue: "kube-version/major: \"1\"\n kube-version/minor: \"6\"\n kube-version/gitversion: \"v1.6.0\"", + }, + } + + var buf bytes.Buffer + for _, tt := range tests { + t.Run(tt.name, func(T *testing.T) { + // capture stdout + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + // execute template command + out := bytes.NewBuffer(nil) + cmd := newTemplateCmd(out) + cmd.SetArgs(tt.args) + err := cmd.Execute() + if err != nil { + t.Errorf("expected: %v, got %v", tt.expectValue, err) + } + // restore stdout + w.Close() + os.Stdout = old + var b bytes.Buffer + io.Copy(&b, r) + r.Close() + // scan yaml into map[]yaml + scanner := bufio.NewScanner(&b) + next := false + lastKey := "" + m := map[string]string{} + for scanner.Scan() { + if scanner.Text() == "---" { + next = true + } else if next { + // remove '# Source: ' + head := "# Source: " + lastKey = scanner.Text()[len(head):] + next = false + } else { + m[lastKey] = m[lastKey] + scanner.Text() + "\n" + } + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "reading standard input:", err) + } + if v, ok := m[tt.expectKey]; ok { + if !strings.Contains(v, tt.expectValue) { + t.Errorf("failed to match expected value %s in %s", tt.expectValue, v) + } + } else { + t.Errorf("could not find key %s", tt.expectKey) + } + buf.Reset() + }) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.pub b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.pub new file mode 100644 index 0000000000000000000000000000000000000000..38714f25adaf701b08e11fd559a587074bbde0e4 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.secret b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/helm-test-key.secret new file mode 100644 index 0000000000000000000000000000000000000000..a966aef93ed97d01d764f29940738df6df2d9d24 GIT binary patch literal 2545 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.2.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.2.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ba96a80c9c54db1f77a16b45623a9dd6ce816d76 GIT binary patch literal 540 zcmV+%0^|K3iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL4di`zUDg|qgrIPYZh78+H_I2oAT&@2Y}3DZqiMX_&eQCSj_ zoHR`5zZcm`E-QSt>6!|J_tAB{Md1ZoBKy7$rn!AAdSAp4Q@#_=pq4Yj^E`Wo`rjYRzQ%$*(h3ggpXalI{uhfp zPx?Oxx1^N8{XKgx8p3y!k8W_WHBOZm4-ZL#&zhnBSxu^ul{St}HpjvIzxeI`pU;#2 z&%ziVV;*<9OVs_rV_TEPs+Fpgx`GwOS5d`8{df)Vt+mDl4_}i~sFYcu5uvE1u3o~C z5U*Hb+ne>sKi!=8vngu_@LDeMt42ql3>X}Nlh$c%eyak e!8poTCDXMakw_$t=SKhl0RR87^wW$06aWBmqzKpm literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.3.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-0.3.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..89776bfa80fd8e37cb819073ce30792fbcb28fe3 GIT binary patch literal 538 zcmV+#0_FW5iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL4fYuhjw#&hXs31R)S-sgGc!Zg;A_f!_OaN*6{_+(qrsKLqLob$`stSdR^ z{gRKz^L@Q*ALo;NJizHz@DExagd1>v>^S|+JrPRsg_AZ&qpxs{8&VCrNWjWCyla=_ zGzIdR77bmY7cSYFobS4FHf20}?A;{(m%jejLbc>iYXFDzKbc)XlU>WOi zI7l4~wx)Fm)L10~d6r@4EzNfHd*ac~UmpAOJbU{3-yh4q#)3W43J&R?=aZiP=kq*I z`acIZq?Ezk9s4L6!cUYBZg88ag)XxpG*u%0Y~ZDJw30ul#72f1FJ3Wx(rOE%yPk1Lu;iemCYG_q?YG-t c9A&JM=~|FTBofE-D*yoh|LpYS&Hxku0CM*Yp8x;= literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/compressedchart-with-hyphens-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..379210a92c1795429d2c4baae389f1e971f26824 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/.helmignore b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/.helmignore new file mode 100644 index 000000000..435b756d8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/.helmignore @@ -0,0 +1,5 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/Chart.yaml new file mode 100644 index 000000000..3e65afdfa --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: decompressedchart +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/values.yaml new file mode 100644 index 000000000..a940d1fd9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/decompressedchart/values.yaml @@ -0,0 +1,4 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/Chart.yaml new file mode 100644 index 000000000..ce1a81da6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/Chart.yaml @@ -0,0 +1,6 @@ +description: Deploy a basic Alpine Linux pod +home: https://k8s.io/helm +name: novals +sources: +- https://github.com/kubernetes/helm +version: 0.2.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/README.md b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/README.md new file mode 100644 index 000000000..3c32de5db --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/README.md @@ -0,0 +1,13 @@ +#Alpine: A simple Helm chart + +Run a single pod of Alpine Linux. + +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 docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/templates/alpine-pod.yaml new file mode 100644 index 000000000..c15ab8efc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/novals/templates/alpine-pod.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{.Release.Name}}-{{.Values.Name}}" + labels: + # The "heritage" label is used to track which tool deployed a given chart. + # It is useful for admins who want to see what releases a particular tool + # is responsible for. + heritage: {{.Release.Service | quote }} + # The "release" convention makes it easy to tie a release to all of the + # Kubernetes resources that were created as part of that release. + release: {{.Release.Name | quote }} + # This makes it easy to audit chart usage. + chart: "{{.Chart.Name}}-{{.Chart.Version}}" + annotations: + "helm.sh/created": {{.Release.Time.Seconds | quote }} +spec: + # This shows how to use a simple value. This will look for a passed-in value + # called restartPolicy. If it is not found, it will use the default value. + # {{default "Never" .restartPolicy}} is a slightly optimized version of the + # more conventional syntax: {{.restartPolicy | default "Never"}} + restartPolicy: {{default "Never" .Values.restartPolicy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..356bc93030395fa1c045c8aaefb60c9d80a079a5 GIT binary patch literal 911 zcmV;A191EwiwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PI=cZ=^O5zGwc5>E)%Zy8PMzIo?i5rB?kZ<#bY2Rh5AWxB)|L zlU(}Hzr6$8=1yd@=(ZfIWc?{JtYZ)M?tGtT28n-bRN6T&nAG+itI8L%!zDyP&|eAT ztLwSp{e9o>`N6680_I=I7PLw;Nss@(cE+1~BFIpsk~f;yB8J!S9hMcOoiD&uE#ZeY zK`D?t#1gE+7~Z>!b%Rp%Q(W7#UF*=hFxVFx{@<{&MfG_EV2b~~=a2axMS5P4G z`RApkwULSQx~j;)+w)7vNN6lO=i2GpVfmJw{3D&d-E0rq>P9#p3?;O`w&?{; zSzp`gwxKp**VO8Y?*FBsZ<*wEtKj>KZ|Q-JtpCDPTQ<*-Im0;WdWsUZ;XhqlF0n$P zm0i~9^^DJ$jQ`i}i2tWPr35hJ5+28q^FPA|MTR2fsABm24=dwDbsfXcwFXW{b?*|G zSvd-nbZ%!c_^ubO+*d1a{l<%8KZw1^4qmOJv$N{fxe{Wp>4@1wK|BK+U`rpP2ObzgPV+a3dD+x~V|6%FjW9B008H7+U)=U literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/.helmignore b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/.helmignore @@ -0,0 +1,21 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/Chart.yaml new file mode 100644 index 000000000..e2fbe4b01 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqtest +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/.helmignore @@ -0,0 +1,21 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml new file mode 100644 index 000000000..c3813bc8c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqsubchart +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml new file mode 100644 index 000000000..0f0b63f2a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqsubchart. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore new file mode 100644 index 000000000..f0c131944 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/.helmignore @@ -0,0 +1,21 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml new file mode 100644 index 000000000..9f7c22a71 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: reqsubchart2 +version: 0.2.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml new file mode 100644 index 000000000..0f0b63f2a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart2/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqsubchart. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/charts/reqsubchart3-0.2.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..84b0fb65e6b05967ef186f30248384033a7d0a5f GIT binary patch literal 593 zcmV-X0Ch>%v#7WE? zC$pWF@vy(0OR{yXP?-eX(8PUI*^YCzFFk*K*8IZsSza@3BX4#;_`d%tNnotgbgca_ zp6Y8Li2NjtVm}T-@Pjx?;u$3O^+fqzUV4)LGIe-59RwOlI$wuLG7u&KF% ztQWEns)CN?=d9w!b>{H776we;b*;A8!2Kejl5GYJvw4lyFFIsZDCf54vp7enP<^K1`_~`y8+5p!@E919de7pP^{r6u)AHHHP>bw=ewcnKgQip z?CF2aWRK_ku@8W|^dAQ4FZn8IB%Q%XT5P~2G ff*=TjAP9mW2!bF8f*=Tj_$z(_p3(*m04M+eV_7RC literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.lock b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.lock new file mode 100755 index 000000000..ab1ae8cc0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.lock @@ -0,0 +1,3 @@ +dependencies: [] +digest: Not implemented +generated: 2016-09-13T17:25:17.593788787-06:00 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.yaml new file mode 100644 index 000000000..1ddedc742 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/requirements.yaml @@ -0,0 +1,10 @@ +dependencies: + - name: reqsubchart + version: 0.1.0 + repository: "https://example.com/charts" + - name: reqsubchart2 + version: 0.2.0 + repository: "https://example.com/charts" + - name: reqsubchart3 + version: ">=0.1.0" + repository: "https://example.com/charts" diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/values.yaml new file mode 100644 index 000000000..d57f76b07 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/reqtest/values.yaml @@ -0,0 +1,4 @@ +# Default values for reqtest. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name: value diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..6de9d988d47cfea649eaa5b23ba09d8a5fee04ed GIT binary patch literal 471 zcmV;|0Vw_-iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL1s>(ek4#&_LMab!0N8r#jSu)EfR!Yhji_ntJ+cZn_Q$NgS zvpkzm;N}PUn+EH+q3y3-=lpU{L>1c7h~5dUR^fjXXQ78U)Tn=deive8Xe@3wX&i_1nlSlsVp($*z=7V%F7C^xM zSQIRo!k1Q9pohcP^~Vpd=yk`P!wPC4(Fbg>l-wYAgBYs_dM=Cwr=jqDYbjbN8Xoju zz+u-*PRsk`(N#iL^pHpB#6N4v`e~pI-g=LV{4bY(@IPBd{_mkFeD*jS6?h%LKkQpn zPz*v=LN!Ei`JFc-ufYxM(D&Ln>QK!{XrwNHOrdNk`Xv}7y2Z|u@7iDHxvD(y*l_=| z0ndAbwfI5Suoo2f>;;2QN*+L~km-*EJsOZgkHDk|z~{R{vA N|NqlRq0#^l007yS;m800 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov new file mode 100644 index 000000000..94235399a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest-0.1.0.tgz.prov @@ -0,0 +1,20 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +description: A Helm chart for Kubernetes +name: signtest +version: 0.1.0 + +... +files: + signtest-0.1.0.tgz: sha256:dee72947753628425b82814516bdaa37aef49f25e8820dd2a6e15a33a007823b +-----BEGIN PGP SIGNATURE----- + +wsBcBAEBCgAQBQJXomNHCRCEO7+YH8GHYgAALywIAG1Me852Fpn1GYu8Q1GCcw4g +l2k7vOFchdDwDhdSVbkh4YyvTaIO3iE2Jtk1rxw+RIJiUr0eLO/rnIJuxZS8WKki +DR1LI9J1VD4dxN3uDETtWDWq7ScoPsRY5mJvYZXC8whrWEt/H2kfqmoA9LloRPWp +flOE0iktA4UciZOblTj6nAk3iDyjh/4HYL4a6tT0LjjKI7OTw4YyHfjHad1ywVCz +9dMUc1rPgTnl+fnRiSPSrlZIWKOt1mcQ4fVrU3nwtRUwTId2k8FtygL0G6M+Y6t0 +S6yaU7qfk9uTxkdkUF7Bf1X3ukxfe+cNBC32vf4m8LY4NkcYfSqK2fGtQsnVr6s= +=NyOM +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/.helmignore b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/.helmignore new file mode 100644 index 000000000..435b756d8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/.helmignore @@ -0,0 +1,5 @@ +# 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/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/Chart.yaml new file mode 100644 index 000000000..90964b44a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: signtest +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml new file mode 100644 index 000000000..6fbb27f18 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/Chart.yaml @@ -0,0 +1,6 @@ +description: Deploy a basic Alpine Linux pod +home: https://k8s.io/helm +name: alpine +sources: +- https://github.com/kubernetes/helm +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/README.md b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/README.md new file mode 100644 index 000000000..5bd595747 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.yaml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/values.yaml new file mode 100644 index 000000000..bb6c06ae4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: my-alpine diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/templates/pod.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/templates/pod.yaml new file mode 100644 index 000000000..9b00ccaf7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/templates/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: signtest +spec: + restartPolicy: Never + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/values.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testcharts/signtest/values.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/index.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/index.yaml new file mode 100644 index 000000000..9cde8e8dd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/index.yaml @@ -0,0 +1 @@ +apiVersion: v1 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/repository/repositories.yaml b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/repository/repositories.yaml new file mode 100644 index 000000000..271301c95 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/testdata/testserver/repository/repositories.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +generated: 2016-10-04T13:50:02.87649685-06:00 +repositories: +- cache: "" + name: test + url: http://127.0.0.1:49216 diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade.go new file mode 100644 index 000000000..125796762 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade.go @@ -0,0 +1,246 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "fmt" + "io" + "strings" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/storage/driver" +) + +const upgradeDesc = ` +This command upgrades a release to a new version of a chart. + +The upgrade arguments must be a release and chart. The chart +argument can be either: a chart reference('stable/mariadb'), a path to a chart directory, +a packaged chart, or a fully qualified URL. For chart references, the latest +version will be specified unless the '--version' flag is set. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force string +values, use '--set-string'. + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm upgrade --set foo=bar --set foo=newbar redis ./redis +` + +type upgradeCmd struct { + release string + chart string + out io.Writer + client helm.Interface + dryRun bool + recreate bool + force bool + disableHooks bool + valueFiles valueFiles + values []string + stringValues []string + verify bool + keyring string + install bool + namespace string + version string + timeout int64 + resetValues bool + reuseValues bool + wait bool + repoURL string + username string + password string + devel bool + + certFile string + keyFile string + caFile string +} + +func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command { + + upgrade := &upgradeCmd{ + out: out, + client: client, + } + + cmd := &cobra.Command{ + Use: "upgrade [RELEASE] [CHART]", + Short: "upgrade a release", + Long: upgradeDesc, + PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkArgsLength(len(args), "release name", "chart path"); err != nil { + return err + } + + if upgrade.version == "" && upgrade.devel { + debug("setting version to >0.0.0-0") + upgrade.version = ">0.0.0-0" + } + + upgrade.release = args[0] + upgrade.chart = args[1] + upgrade.client = ensureHelmClient(upgrade.client) + + return upgrade.run() + }, + } + + f := cmd.Flags() + f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)") + f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade") + f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") + f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed") + f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks") + f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks") + f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading") + f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys") + f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install") + f.StringVar(&upgrade.namespace, "namespace", "", "namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace") + f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") + f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") + f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored.") + f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.") + + f.MarkDeprecated("disable-hooks", "use --no-hooks instead") + + return cmd +} + +func (u *upgradeCmd) run() error { + chartPath, err := locateChartPath(u.repoURL, u.username, u.password, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile) + if err != nil { + return err + } + + if u.install { + // If a release does not exist, install it. If another error occurs during + // the check, ignore the error and continue with the upgrade. + // + // The returned error is a grpc.rpcError that wraps the message from the original error. + // So we're stuck doing string matching against the wrapped error, which is nested somewhere + // inside of the grpc.rpcError message. + releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1)) + + if err == nil { + if u.namespace == "" { + u.namespace = defaultNamespace() + } + previousReleaseNamespace := releaseHistory.Releases[0].Namespace + if previousReleaseNamespace != u.namespace { + fmt.Fprintf(u.out, + "WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n", + u.namespace, previousReleaseNamespace, + ) + } + } + + if err != nil && strings.Contains(err.Error(), driver.ErrReleaseNotFound(u.release).Error()) { + fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release) + ic := &installCmd{ + chartPath: chartPath, + client: u.client, + out: u.out, + name: u.release, + valueFiles: u.valueFiles, + dryRun: u.dryRun, + verify: u.verify, + disableHooks: u.disableHooks, + keyring: u.keyring, + values: u.values, + stringValues: u.stringValues, + namespace: u.namespace, + timeout: u.timeout, + wait: u.wait, + } + return ic.run() + } + } + + rawVals, err := vals(u.valueFiles, u.values, u.stringValues) + if err != nil { + return err + } + + // Check chart requirements to make sure all dependencies are present in /charts + if ch, err := chartutil.Load(chartPath); err == nil { + if req, err := chartutil.LoadRequirements(ch); err == nil { + if err := checkDependencies(ch, req); err != nil { + return err + } + } else if err != chartutil.ErrRequirementsNotFound { + return fmt.Errorf("cannot load requirements: %v", err) + } + } else { + return prettyError(err) + } + + resp, err := u.client.UpdateRelease( + u.release, + chartPath, + helm.UpdateValueOverrides(rawVals), + helm.UpgradeDryRun(u.dryRun), + helm.UpgradeRecreate(u.recreate), + helm.UpgradeForce(u.force), + helm.UpgradeDisableHooks(u.disableHooks), + helm.UpgradeTimeout(u.timeout), + helm.ResetValues(u.resetValues), + helm.ReuseValues(u.reuseValues), + helm.UpgradeWait(u.wait)) + if err != nil { + return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err)) + } + + if settings.Debug { + printRelease(u.out, resp.Release) + } + + fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release) + + // Print the status like status command does + status, err := u.client.ReleaseStatus(u.release) + if err != nil { + return prettyError(err) + } + PrintStatus(u.out, status) + + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade_test.go new file mode 100644 index 000000000..187d3593e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/upgrade_test.go @@ -0,0 +1,170 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestUpgradeCmd(t *testing.T) { + tmpChart, _ := ioutil.TempDir("testdata", "tmp") + defer os.RemoveAll(tmpChart) + cfile := &chart.Metadata{ + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + } + chartPath, err := chartutil.Create(cfile, tmpChart) + if err != nil { + t.Errorf("Error creating chart for upgrade: %v", err) + } + ch, _ := chartutil.Load(chartPath) + _ = helm.ReleaseMock(&helm.MockReleaseOptions{ + Name: "funny-bunny", + Chart: ch, + }) + + // update chart version + cfile = &chart.Metadata{ + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.2", + } + + chartPath, err = chartutil.Create(cfile, tmpChart) + if err != nil { + t.Errorf("Error creating chart: %v", err) + } + ch, err = chartutil.Load(chartPath) + if err != nil { + t.Errorf("Error loading updated chart: %v", err) + } + + // update chart version again + cfile = &chart.Metadata{ + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.3", + } + + chartPath, err = chartutil.Create(cfile, tmpChart) + if err != nil { + t.Errorf("Error creating chart: %v", err) + } + var ch2 *chart.Chart + ch2, err = chartutil.Load(chartPath) + if err != nil { + t.Errorf("Error loading updated chart: %v", err) + } + + originalDepsPath := filepath.Join("testdata/testcharts/reqtest") + missingDepsPath := filepath.Join("testdata/testcharts/chart-missing-deps") + badDepsPath := filepath.Join("testdata/testcharts/chart-bad-requirements") + var ch3 *chart.Chart + ch3, err = chartutil.Load(originalDepsPath) + if err != nil { + t.Errorf("Error loading chart with missing dependencies: %v", err) + } + + tests := []releaseCase{ + { + name: "upgrade a release", + args: []string{"funny-bunny", chartPath}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch}), + expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 2, Chart: ch})}, + }, + { + name: "upgrade a release with timeout", + args: []string{"funny-bunny", chartPath}, + flags: []string{"--timeout", "120"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2}), + expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 3, Chart: ch2})}, + }, + { + name: "upgrade a release with --reset-values", + args: []string{"funny-bunny", chartPath}, + flags: []string{"--reset-values", "true"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2}), + expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 4, Chart: ch2})}, + }, + { + name: "upgrade a release with --reuse-values", + args: []string{"funny-bunny", chartPath}, + flags: []string{"--reuse-values", "true"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2}), + expected: "Release \"funny-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "funny-bunny", Version: 5, Chart: ch2})}, + }, + { + name: "install a release with 'upgrade --install'", + args: []string{"zany-bunny", chartPath}, + flags: []string{"-i"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch}), + expected: "Release \"zany-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "zany-bunny", Version: 1, Chart: ch})}, + }, + { + name: "install a release with 'upgrade --install' and timeout", + args: []string{"crazy-bunny", chartPath}, + flags: []string{"-i", "--timeout", "120"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch}), + expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 1, Chart: ch})}, + }, + { + name: "upgrade a release with wait", + args: []string{"crazy-bunny", chartPath}, + flags: []string{"--wait"}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2}), + expected: "Release \"crazy-bunny\" has been upgraded. Happy Helming!\n", + rels: []*release.Release{helm.ReleaseMock(&helm.MockReleaseOptions{Name: "crazy-bunny", Version: 2, Chart: ch2})}, + }, + { + name: "upgrade a release with missing dependencies", + args: []string{"bonkers-bunny", missingDepsPath}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}), + err: true, + }, + { + name: "upgrade a release with bad dependencies", + args: []string{"bonkers-bunny", badDepsPath}, + resp: helm.ReleaseMock(&helm.MockReleaseOptions{Name: "bonkers-bunny", Version: 1, Chart: ch3}), + err: true, + }, + } + + cmd := func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newUpgradeCmd(c, out) + } + + runReleaseCases(t, tests, cmd) + +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/verify.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/verify.go new file mode 100644 index 000000000..e82eb4e33 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/verify.go @@ -0,0 +1,70 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "errors" + "io" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/downloader" +) + +const verifyDesc = ` +Verify that the given chart has a valid provenance file. + +Provenance files provide crytographic verification that a chart has not been +tampered with, and was packaged by a trusted provider. + +This command can be used to verify a local chart. Several other commands provide +'--verify' flags that run the same validation. To generate a signed package, use +the 'helm package --sign' command. +` + +type verifyCmd struct { + keyring string + chartfile string + + out io.Writer +} + +func newVerifyCmd(out io.Writer) *cobra.Command { + vc := &verifyCmd{out: out} + + cmd := &cobra.Command{ + Use: "verify [flags] PATH", + Short: "verify that a chart at the given path has been signed and is valid", + Long: verifyDesc, + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("a path to a package file is required") + } + vc.chartfile = args[0] + return vc.run() + }, + } + + f := cmd.Flags() + f.StringVar(&vc.keyring, "keyring", defaultKeyring(), "keyring containing public keys") + + return cmd +} + +func (v *verifyCmd) run() error { + _, err := downloader.VerifyChart(v.chartfile, v.keyring) + return err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/verify_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/verify_test.go new file mode 100644 index 000000000..6e8b906fc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/verify_test.go @@ -0,0 +1,95 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "bytes" + "fmt" + "runtime" + "testing" +) + +func TestVerifyCmd(t *testing.T) { + + statExe := "stat" + statPathMsg := "no such file or directory" + statFileMsg := statPathMsg + if runtime.GOOS == "windows" { + statExe = "GetFileAttributesEx" + statPathMsg = "The system cannot find the path specified." + statFileMsg = "The system cannot find the file specified." + } + + tests := []struct { + name string + args []string + flags []string + expect string + err bool + }{ + { + name: "verify requires a chart", + expect: "a path to a package file is required", + err: true, + }, + { + name: "verify requires that chart exists", + args: []string{"no/such/file"}, + expect: fmt.Sprintf("%s no/such/file: %s", statExe, statPathMsg), + err: true, + }, + { + name: "verify requires that chart is not a directory", + args: []string{"testdata/testcharts/signtest"}, + expect: "unpacked charts cannot be verified", + err: true, + }, + { + name: "verify requires that chart has prov file", + args: []string{"testdata/testcharts/compressedchart-0.1.0.tgz"}, + expect: fmt.Sprintf("could not load provenance file testdata/testcharts/compressedchart-0.1.0.tgz.prov: %s testdata/testcharts/compressedchart-0.1.0.tgz.prov: %s", statExe, statFileMsg), + err: true, + }, + { + name: "verify validates a properly signed chart", + args: []string{"testdata/testcharts/signtest-0.1.0.tgz"}, + flags: []string{"--keyring", "testdata/helm-test-key.pub"}, + expect: "", + err: false, + }, + } + + for _, tt := range tests { + b := bytes.NewBuffer(nil) + vc := newVerifyCmd(b) + vc.ParseFlags(tt.flags) + err := vc.RunE(vc, tt.args) + if tt.err { + if err == nil { + t.Errorf("Expected error, but got none: %q", b.String()) + } + if err.Error() != tt.expect { + t.Errorf("Expected error %q, got %q", tt.expect, err) + } + continue + } else if err != nil { + t.Errorf("Unexpected error: %s", err) + } + if b.String() != tt.expect { + t.Errorf("Expected %q, got %q", tt.expect, b.String()) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/version.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/version.go new file mode 100644 index 000000000..d541067a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/version.go @@ -0,0 +1,151 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "errors" + "fmt" + "io" + + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + apiVersion "k8s.io/apimachinery/pkg/version" + "k8s.io/helm/pkg/helm" + pb "k8s.io/helm/pkg/proto/hapi/version" + "k8s.io/helm/pkg/version" +) + +const versionDesc = ` +Show the client and server versions for Helm and tiller. + +This will print a representation of the client and server versions of Helm and +Tiller. The output will look something like this: + +Client: &version.Version{SemVer:"v2.0.0", GitCommit:"ff52399e51bb880526e9cd0ed8386f6433b74da1", GitTreeState:"clean"} +Server: &version.Version{SemVer:"v2.0.0", GitCommit:"b0c113dfb9f612a9add796549da66c0d294508a3", GitTreeState:"clean"} + +- SemVer is the semantic version of the release. +- GitCommit is the SHA for the commit that this version was built from. +- GitTreeState is "clean" if there are no local code changes when this binary was + built, and "dirty" if the binary was built from locally modified code. + +To print just the client version, use '--client'. To print just the server version, +use '--server'. +` + +type versionCmd struct { + out io.Writer + client helm.Interface + showClient bool + showServer bool + short bool + template string +} + +func newVersionCmd(c helm.Interface, out io.Writer) *cobra.Command { + version := &versionCmd{ + client: c, + out: out, + } + + cmd := &cobra.Command{ + Use: "version", + Short: "print the client/server version information", + Long: versionDesc, + RunE: func(cmd *cobra.Command, args []string) error { + // If neither is explicitly set, show both. + if !version.showClient && !version.showServer { + version.showClient, version.showServer = true, true + } + if version.showServer { + // We do this manually instead of in PreRun because we only + // need a tunnel if server version is requested. + setupConnection() + } + version.client = ensureHelmClient(version.client) + return version.run() + }, + } + f := cmd.Flags() + f.BoolVarP(&version.showClient, "client", "c", false, "client version only") + f.BoolVarP(&version.showServer, "server", "s", false, "server version only") + f.BoolVar(&version.short, "short", false, "print the version number") + f.StringVar(&version.template, "template", "", "template for version string format") + + return cmd +} + +func (v *versionCmd) run() error { + // Store map data for template rendering + data := map[string]interface{}{} + + if v.showClient { + cv := version.GetVersionProto() + if v.template != "" { + data["Client"] = cv + } else { + fmt.Fprintf(v.out, "Client: %s\n", formatVersion(cv, v.short)) + } + } + + if !v.showServer { + return tpl(v.template, data, v.out) + } + + if settings.Debug { + k8sVersion, err := getK8sVersion() + if err != nil { + return err + } + fmt.Fprintf(v.out, "Kubernetes: %#v\n", k8sVersion) + } + + resp, err := v.client.GetVersion() + if err != nil { + if grpc.Code(err) == codes.Unimplemented { + return errors.New("server is too old to know its version") + } + debug("%s", err) + return errors.New("cannot connect to Tiller") + } + + if v.template != "" { + data["Server"] = resp.Version + } else { + fmt.Fprintf(v.out, "Server: %s\n", formatVersion(resp.Version, v.short)) + } + return tpl(v.template, data, v.out) +} + +func getK8sVersion() (*apiVersion.Info, error) { + var v *apiVersion.Info + _, client, err := getKubeClient(settings.KubeContext) + if err != nil { + return v, err + } + v, err = client.Discovery().ServerVersion() + return v, err +} + +func formatVersion(v *pb.Version, short bool) string { + if short { + return fmt.Sprintf("%s+g%s", v.SemVer, v.GitCommit[:7]) + } + return fmt.Sprintf("%#v", v) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/helm/version_test.go b/src/vendor/github.com/kubernetes/helm/cmd/helm/version_test.go new file mode 100644 index 000000000..e25724f4c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/helm/version_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 main + +import ( + "fmt" + "io" + "regexp" + "testing" + + "github.com/spf13/cobra" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/version" +) + +func TestVersion(t *testing.T) { + lver := regexp.QuoteMeta(version.GetVersionProto().SemVer) + sver := regexp.QuoteMeta("1.2.3-fakeclient+testonly") + clientVersion := fmt.Sprintf("Client: &version\\.Version{SemVer:\"%s\", GitCommit:\"\", GitTreeState:\"\"}\n", lver) + serverVersion := fmt.Sprintf("Server: &version\\.Version{SemVer:\"%s\", GitCommit:\"\", GitTreeState:\"\"}\n", sver) + + tests := []releaseCase{ + { + name: "default", + args: []string{}, + expected: clientVersion + serverVersion, + }, + { + name: "client", + args: []string{}, + flags: []string{"-c"}, + expected: clientVersion, + }, + { + name: "server", + args: []string{}, + flags: []string{"-s"}, + expected: serverVersion, + }, + { + name: "template", + args: []string{}, + flags: []string{"--template", "{{ .Client.SemVer }} {{ .Server.SemVer }}"}, + expected: lver + " " + sver, + }, + } + settings.TillerHost = "fake-localhost" + runReleaseCases(t, tests, func(c *helm.FakeClient, out io.Writer) *cobra.Command { + return newVersionCmd(c, out) + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/rudder/rudder.go b/src/vendor/github.com/kubernetes/helm/cmd/rudder/rudder.go new file mode 100644 index 000000000..30ece3998 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/rudder/rudder.go @@ -0,0 +1,158 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "bytes" + "fmt" + "net" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "github.com/spf13/pflag" + "k8s.io/helm/pkg/kube" + rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder" + "k8s.io/helm/pkg/tiller" + "k8s.io/helm/pkg/version" +) + +var kubeClient *kube.Client +var clientset internalclientset.Interface + +type options struct { + listen string +} + +func (opts *options) registerFlags() { + pflag.StringVarP(&opts.listen, "listen", "l", "127.0.0.1:10001", + "Socket for rudder grpc server (default: 127.0.0.1:10001).") +} + +func (opts *options) parseFlags() { + pflag.Parse() +} + +func (opts *options) regAndParseFlags() { + opts.registerFlags() + opts.parseFlags() +} + +func main() { + opts := new(options) + opts.regAndParseFlags() + var err error + kubeClient = kube.New(nil) + clientset, err = kubeClient.ClientSet() + if err != nil { + grpclog.Fatalf("Cannot initialize Kubernetes connection: %s", err) + } + grpclog.Printf("Creating tcp socket on %s\n", opts.listen) + lis, err := net.Listen("tcp", opts.listen) + if err != nil { + grpclog.Fatalf("failed to listen: %v", err) + } + grpcServer := grpc.NewServer() + rudderAPI.RegisterReleaseModuleServiceServer(grpcServer, &ReleaseModuleServiceServer{}) + + grpclog.Printf("Starting server on %s\n", opts.listen) + grpcServer.Serve(lis) +} + +// ReleaseModuleServiceServer provides implementation for rudderAPI.ReleaseModuleServiceServer +type ReleaseModuleServiceServer struct{} + +// Version returns Rudder version based on helm version +func (r *ReleaseModuleServiceServer) Version(ctx context.Context, in *rudderAPI.VersionReleaseRequest) (*rudderAPI.VersionReleaseResponse, error) { + grpclog.Print("version") + return &rudderAPI.VersionReleaseResponse{ + Name: "helm-rudder-native", + Version: version.Version, + }, nil +} + +// InstallRelease creates a release using kubeClient.Create +func (r *ReleaseModuleServiceServer) InstallRelease(ctx context.Context, in *rudderAPI.InstallReleaseRequest) (*rudderAPI.InstallReleaseResponse, error) { + grpclog.Print("install") + b := bytes.NewBufferString(in.Release.Manifest) + err := kubeClient.Create(in.Release.Namespace, b, 500, false) + if err != nil { + grpclog.Printf("error when creating release: %v", err) + } + return &rudderAPI.InstallReleaseResponse{}, err +} + +// DeleteRelease deletes a provided release +func (r *ReleaseModuleServiceServer) DeleteRelease(ctx context.Context, in *rudderAPI.DeleteReleaseRequest) (*rudderAPI.DeleteReleaseResponse, error) { + grpclog.Print("delete") + + resp := &rudderAPI.DeleteReleaseResponse{} + rel := in.Release + vs, err := tiller.GetVersionSet(clientset.Discovery()) + if err != nil { + return resp, fmt.Errorf("Could not get apiVersions from Kubernetes: %v", err) + } + + kept, errs := tiller.DeleteRelease(rel, vs, kubeClient) + rel.Manifest = kept + + allErrors := "" + for _, e := range errs { + allErrors = allErrors + "\n" + e.Error() + } + + if len(allErrors) > 0 { + err = fmt.Errorf(allErrors) + } + + return &rudderAPI.DeleteReleaseResponse{ + Release: rel, + }, err +} + +// RollbackRelease rolls back the release +func (r *ReleaseModuleServiceServer) RollbackRelease(ctx context.Context, in *rudderAPI.RollbackReleaseRequest) (*rudderAPI.RollbackReleaseResponse, error) { + grpclog.Print("rollback") + c := bytes.NewBufferString(in.Current.Manifest) + t := bytes.NewBufferString(in.Target.Manifest) + err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait) + return &rudderAPI.RollbackReleaseResponse{}, err +} + +// UpgradeRelease upgrades manifests using kubernetes client +func (r *ReleaseModuleServiceServer) UpgradeRelease(ctx context.Context, in *rudderAPI.UpgradeReleaseRequest) (*rudderAPI.UpgradeReleaseResponse, error) { + grpclog.Print("upgrade") + c := bytes.NewBufferString(in.Current.Manifest) + t := bytes.NewBufferString(in.Target.Manifest) + err := kubeClient.Update(in.Target.Namespace, c, t, in.Force, in.Recreate, in.Timeout, in.Wait) + // upgrade response object should be changed to include status + return &rudderAPI.UpgradeReleaseResponse{}, err +} + +// ReleaseStatus retrieves release status +func (r *ReleaseModuleServiceServer) ReleaseStatus(ctx context.Context, in *rudderAPI.ReleaseStatusRequest) (*rudderAPI.ReleaseStatusResponse, error) { + grpclog.Print("status") + + resp, err := kubeClient.Get(in.Release.Namespace, bytes.NewBufferString(in.Release.Manifest)) + in.Release.Info.Status.Resources = resp + return &rudderAPI.ReleaseStatusResponse{ + Release: in.Release, + Info: in.Release.Info, + }, err +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes.go b/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes.go new file mode 100644 index 000000000..144ad8a1b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes.go @@ -0,0 +1,43 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "net/http" + + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +func readinessProbe(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func livenessProbe(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) +} + +func newProbesMux() *http.ServeMux { + mux := http.NewServeMux() + mux.HandleFunc("/readiness", readinessProbe) + mux.HandleFunc("/liveness", livenessProbe) + return mux +} + +func addPrometheusHandler(mux *http.ServeMux) { + // Register HTTP handler for the global Prometheus registry. + mux.Handle("/metrics", promhttp.Handler()) +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes_test.go b/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes_test.go new file mode 100644 index 000000000..0b13460e0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/tiller/probes_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestProbesServer(t *testing.T) { + mux := newProbesMux() + srv := httptest.NewServer(mux) + defer srv.Close() + resp, err := http.Get(srv.URL + "/readiness") + if err != nil { + t.Fatalf("GET /readiness returned an error (%s)", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("GET /readiness returned status code %d, expected %d", resp.StatusCode, http.StatusOK) + } + + resp, err = http.Get(srv.URL + "/liveness") + if err != nil { + t.Fatalf("GET /liveness returned an error (%s)", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("GET /liveness returned status code %d, expected %d", resp.StatusCode, http.StatusOK) + } +} + +func TestPrometheus(t *testing.T) { + mux := http.NewServeMux() + addPrometheusHandler(mux) + srv := httptest.NewServer(mux) + defer srv.Close() + resp, err := http.Get(srv.URL + "/metrics") + if err != nil { + t.Fatalf("GET /metrics returned an error (%s)", err) + } + if resp.StatusCode != http.StatusOK { + t.Fatalf("GET /metrics returned status code %d, expected %d", resp.StatusCode, http.StatusOK) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller.go b/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller.go new file mode 100644 index 000000000..5d2db3816 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller.go @@ -0,0 +1,288 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main // import "k8s.io/helm/cmd/tiller" + +import ( + "crypto/tls" + "flag" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + goprom "github.com/grpc-ecosystem/go-grpc-prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/keepalive" + + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" + "k8s.io/helm/pkg/tiller" + "k8s.io/helm/pkg/tiller/environment" + "k8s.io/helm/pkg/tlsutil" + "k8s.io/helm/pkg/version" +) + +const ( + // tlsEnableEnvVar names the environment variable that enables TLS. + tlsEnableEnvVar = "TILLER_TLS_ENABLE" + // tlsVerifyEnvVar names the environment variable that enables + // TLS, as well as certificate verification of the remote. + tlsVerifyEnvVar = "TILLER_TLS_VERIFY" + // tlsCertsEnvVar names the environment variable that points to + // the directory where Tiller's TLS certificates are located. + tlsCertsEnvVar = "TILLER_TLS_CERTS" + // historyMaxEnvVar is the name of the env var for setting max history. + historyMaxEnvVar = "TILLER_HISTORY_MAX" + + storageMemory = "memory" + storageConfigMap = "configmap" + storageSecret = "secret" + + probeAddr = ":44135" + traceAddr = ":44136" + + // defaultMaxHistory sets the maximum number of releases to 0: unlimited + defaultMaxHistory = 0 +) + +var ( + grpcAddr = flag.String("listen", ":44134", "address:port to listen on") + enableTracing = flag.Bool("trace", false, "enable rpc tracing") + store = flag.String("storage", storageConfigMap, "storage driver to use. One of 'configmap', 'memory', or 'secret'") + remoteReleaseModules = flag.Bool("experimental-release", false, "enable experimental release modules") + tlsEnable = flag.Bool("tls", tlsEnableEnvVarDefault(), "enable TLS") + tlsVerify = flag.Bool("tls-verify", tlsVerifyEnvVarDefault(), "enable TLS and verify remote certificate") + keyFile = flag.String("tls-key", tlsDefaultsFromEnv("tls-key"), "path to TLS private key file") + certFile = flag.String("tls-cert", tlsDefaultsFromEnv("tls-cert"), "path to TLS certificate file") + caCertFile = flag.String("tls-ca-cert", tlsDefaultsFromEnv("tls-ca-cert"), "trust certificates signed by this CA") + maxHistory = flag.Int("history-max", historyMaxFromEnv(), "maximum number of releases kept in release history, with 0 meaning no limit") + printVersion = flag.Bool("version", false, "print the version number") + + // rootServer is the root gRPC server. + // + // Each gRPC service registers itself to this server during init(). + rootServer *grpc.Server + + // env is the default environment. + // + // Any changes to env should be done before rootServer.Serve() is called. + env = environment.New() + + logger *log.Logger +) + +func main() { + // TODO: use spf13/cobra for tiller instead of flags + flag.Parse() + + if *printVersion { + fmt.Println(version.GetVersion()) + os.Exit(0) + } + + if *enableTracing { + log.SetFlags(log.Lshortfile) + } + logger = newLogger("main") + + start() +} + +func start() { + + healthSrv := health.NewServer() + healthSrv.SetServingStatus("Tiller", healthpb.HealthCheckResponse_NOT_SERVING) + + clientset, err := kube.New(nil).ClientSet() + if err != nil { + logger.Fatalf("Cannot initialize Kubernetes connection: %s", err) + } + + switch *store { + case storageMemory: + env.Releases = storage.Init(driver.NewMemory()) + case storageConfigMap: + cfgmaps := driver.NewConfigMaps(clientset.Core().ConfigMaps(namespace())) + cfgmaps.Log = newLogger("storage/driver").Printf + + env.Releases = storage.Init(cfgmaps) + env.Releases.Log = newLogger("storage").Printf + case storageSecret: + secrets := driver.NewSecrets(clientset.Core().Secrets(namespace())) + secrets.Log = newLogger("storage/driver").Printf + + env.Releases = storage.Init(secrets) + env.Releases.Log = newLogger("storage").Printf + } + + if *maxHistory > 0 { + env.Releases.MaxHistory = *maxHistory + } + + kubeClient := kube.New(nil) + kubeClient.Log = newLogger("kube").Printf + env.KubeClient = kubeClient + + if *tlsEnable || *tlsVerify { + opts := tlsutil.Options{CertFile: *certFile, KeyFile: *keyFile} + if *tlsVerify { + opts.CaCertFile = *caCertFile + } + } + + var opts []grpc.ServerOption + if *tlsEnable || *tlsVerify { + cfg, err := tlsutil.ServerConfig(tlsOptions()) + if err != nil { + logger.Fatalf("Could not create server TLS configuration: %v", err) + } + opts = append(opts, grpc.Creds(credentials.NewTLS(cfg))) + } + + opts = append(opts, grpc.KeepaliveParams(keepalive.ServerParameters{ + MaxConnectionIdle: 10 * time.Minute, + // If needed, we can configure the max connection age + })) + opts = append(opts, grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + MinTime: time.Duration(20) * time.Second, // For compatibility with the client keepalive.ClientParameters + })) + + rootServer = tiller.NewServer(opts...) + healthpb.RegisterHealthServer(rootServer, healthSrv) + + lstn, err := net.Listen("tcp", *grpcAddr) + if err != nil { + logger.Fatalf("Server died: %s", err) + } + + logger.Printf("Starting Tiller %s (tls=%t)", version.GetVersion(), *tlsEnable || *tlsVerify) + logger.Printf("GRPC listening on %s", *grpcAddr) + logger.Printf("Probes listening on %s", probeAddr) + logger.Printf("Storage driver is %s", env.Releases.Name()) + logger.Printf("Max history per release is %d", *maxHistory) + + if *enableTracing { + startTracing(traceAddr) + } + + srvErrCh := make(chan error) + probeErrCh := make(chan error) + go func() { + svc := tiller.NewReleaseServer(env, clientset, *remoteReleaseModules) + svc.Log = newLogger("tiller").Printf + services.RegisterReleaseServiceServer(rootServer, svc) + if err := rootServer.Serve(lstn); err != nil { + srvErrCh <- err + } + }() + + go func() { + mux := newProbesMux() + + // Register gRPC server to prometheus to initialized matrix + goprom.Register(rootServer) + addPrometheusHandler(mux) + + if err := http.ListenAndServe(probeAddr, mux); err != nil { + probeErrCh <- err + } + }() + + healthSrv.SetServingStatus("Tiller", healthpb.HealthCheckResponse_SERVING) + + select { + case err := <-srvErrCh: + logger.Fatalf("Server died: %s", err) + case err := <-probeErrCh: + logger.Printf("Probes server died: %s", err) + } +} + +func newLogger(prefix string) *log.Logger { + if len(prefix) > 0 { + prefix = fmt.Sprintf("[%s] ", prefix) + } + return log.New(os.Stderr, prefix, log.Flags()) +} + +// namespace returns the namespace of tiller +func namespace() string { + if ns := os.Getenv("TILLER_NAMESPACE"); ns != "" { + return ns + } + + // Fall back to the namespace associated with the service account token, if available + if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { + if ns := strings.TrimSpace(string(data)); len(ns) > 0 { + return ns + } + } + + return environment.DefaultTillerNamespace +} + +func tlsOptions() tlsutil.Options { + opts := tlsutil.Options{CertFile: *certFile, KeyFile: *keyFile} + if *tlsVerify { + opts.CaCertFile = *caCertFile + + // We want to force the client to not only provide a cert, but to + // provide a cert that we can validate. + // http://www.bite-code.com/2015/06/25/tls-mutual-auth-in-golang/ + opts.ClientAuth = tls.RequireAndVerifyClientCert + } + return opts +} + +func tlsDefaultsFromEnv(name string) (value string) { + switch certsDir := os.Getenv(tlsCertsEnvVar); name { + case "tls-key": + return filepath.Join(certsDir, "tls.key") + case "tls-cert": + return filepath.Join(certsDir, "tls.crt") + case "tls-ca-cert": + return filepath.Join(certsDir, "ca.crt") + } + return "" +} + +func historyMaxFromEnv() int { + val := os.Getenv(historyMaxEnvVar) + if val == "" { + return defaultMaxHistory + } + ret, err := strconv.Atoi(val) + if err != nil { + log.Printf("Invalid max history %q. Defaulting to 0.", val) + return defaultMaxHistory + } + return ret +} + +func tlsEnableEnvVarDefault() bool { return os.Getenv(tlsEnableEnvVar) != "" } +func tlsVerifyEnvVarDefault() bool { return os.Getenv(tlsVerifyEnvVar) != "" } diff --git a/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller_test.go b/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller_test.go new file mode 100644 index 000000000..0698e9d94 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/tiller/tiller_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main + +import ( + "testing" + + "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/tiller/environment" +) + +// These are canary tests to make sure that the default server actually +// fulfills its requirements. +var _ environment.Engine = &engine.Engine{} + +func TestInit(t *testing.T) { + defer func() { + if recover() != nil { + t.Fatalf("Panic trapped. Check EngineYard.Default()") + } + }() + + // This will panic if it is not correct. + env.EngineYard.Default() + + e, ok := env.EngineYard.Get(environment.GoTplEngine) + if !ok { + t.Fatalf("Could not find GoTplEngine") + } + if e == nil { + t.Fatalf("Template engine GoTplEngine returned nil.") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/cmd/tiller/trace.go b/src/vendor/github.com/kubernetes/helm/cmd/tiller/trace.go new file mode 100644 index 000000000..71d7e8f72 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/cmd/tiller/trace.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 main // import "k8s.io/helm/cmd/tiller" + +import ( + "net/http" + + _ "net/http/pprof" + + "google.golang.org/grpc" +) + +func startTracing(addr string) { + logger.Printf("Tracing server is listening on %s\n", addr) + grpc.EnableTracing = true + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/" { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write([]byte(traceIndexHTML)) + }) + + go func() { + if err := http.ListenAndServe(addr, nil); err != nil { + logger.Printf("tracing error: %s", err) + } + }() +} + +const traceIndexHTML = ` + + + + + +` diff --git a/src/vendor/github.com/kubernetes/helm/code-of-conduct.md b/src/vendor/github.com/kubernetes/helm/code-of-conduct.md new file mode 100644 index 000000000..0d15c00cf --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/code-of-conduct.md @@ -0,0 +1,3 @@ +# Kubernetes Community Code of Conduct + +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/src/vendor/github.com/kubernetes/helm/docs/architecture.md b/src/vendor/github.com/kubernetes/helm/docs/architecture.md new file mode 100644 index 000000000..752a7e12c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/architecture.md @@ -0,0 +1,64 @@ +# The Kubernetes Helm Architecture + +This document describes the Helm architecture at a high level. + +## The Purpose of Helm + +Helm is a tool for managing Kubernetes packages called _charts_. Helm +can do the following: + +- Create new charts from scratch +- Package charts into chart archive (tgz) files +- Interact with chart repositories where charts are stored +- Install and uninstall charts into an existing Kubernetes cluster +- Manage the release cycle of charts that have been installed with Helm + +For Helm, there are three important concepts: + +1. The _chart_ is a bundle of information necessary to create an + instance of a Kubernetes application. +2. The _config_ contains configuration information that can be merged + into a packaged chart to create a releasable object. +3. A _release_ is a running instance of a _chart_, combined with a + specific _config_. + +## Components + +Helm has two major components: + +**The Helm Client** is a command-line client for end users. The client +is responsible for the following domains: + +- Local chart development +- Managing repositories +- Interacting with the Tiller server + - Sending charts to be installed + - Asking for information about releases + - Requesting upgrading or uninstalling of existing releases + +**The Tiller Server** is an in-cluster server that interacts with the +Helm client, and interfaces with the Kubernetes API server. The server +is responsible for the following: + +- Listening for incoming requests from the Helm client +- Combining a chart and configuration to build a release +- Installing charts into Kubernetes, and then tracking the subsequent + release +- Upgrading and uninstalling charts by interacting with Kubernetes + +In a nutshell, the client is responsible for managing charts, and the +server is responsible for managing releases. + +## Implementation + +The Helm client is written in the Go programming language, and uses the +gRPC protocol suite to interact with the Tiller server. + +The Tiller server is also written in Go. It provides a gRPC server to +connect with the client, and it uses the Kubernetes client library to +communicate with Kubernetes. Currently, that library uses REST+JSON. + +The Tiller server stores information in ConfigMaps located inside of +Kubernetes. It does not need its own database. + +Configuration files are, when possible, written in YAML. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/README.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/README.md new file mode 100644 index 000000000..58cc65407 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/README.md @@ -0,0 +1,21 @@ +# The Chart Best Practices Guide + +This guide covers the Helm Team's considered best practices for creating charts. +It focuses on how charts should be structured. + +We focus primarily on best practices for charts that may be publicly deployed. +We know that many charts are for internal-use only, and authors of such charts +may find that their internal interests override our suggestions here. + +## Table of Contents + +- [General Conventions](conventions.md): Learn about general chart conventions. +- [Values Files](values.md): See the best practices for structuring `values.yaml`. +- [Templates](templates.md): Learn some of the best techniques for writing templates. +- [Requirements](requirements.md): Follow best practices for `requirements.yaml` files. +- [Labels and Annotations](labels.md): Helm has a _heritage_ of labeling and annotating. +- Kubernetes Resources: + - [Pods and Pod Specs](pods.md): See the best practices for working with pod specifications. + - [Role-Based Access Control](rbac.md): Guidance on creating and using service accounts, roles, and role bindings. + - [Third Party Resources](third_party_resources.md): Third Party Resources (TPRs) have their own associated best practices. + diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/conventions.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/conventions.md new file mode 100644 index 000000000..324ef88f9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/conventions.md @@ -0,0 +1,59 @@ +# General Conventions + +This part of the Best Practices Guide explains general conventions. + +## Chart Names + +Chart names should be lower case letters and numbers. Words _may_ be separated with dashes (-): + +Examples: + +``` +drupal +nginx-lego +aws-cluster-autoscaler +``` + +Neither uppercase letters nor underscores should be used in chart names. Dots should not be used in chart names. + +The directory that contains a chart MUST have the same name as the chart. Thus, the chart `nginx-lego` MUST be created in a directory called `nginx-lego/`. This is not merely a stylistic detail, but a requirement of the Helm Chart format. + +## Version Numbers + +Wherever possible, Helm uses [SemVer 2](http://semver.org) to represent version numbers. (Note that Docker image tags do not necessarily follow SemVer, and are thus considered an unfortunate exception to the rule.) + +When SemVer versions are stored in Kubernetes labels, we conventionally alter the `+` character to an `_` character, as labels do not allow the `+` sign as a value. + +## Formatting YAML + +YAML files should be indented using _two spaces_ (and never tabs). + +## Usage of the Words Helm, Tiller, and Chart + +There are a few small conventions followed for using the words Helm, helm, Tiller, and tiller. + +- Helm refers to the project, and is often used as an umbrella term +- `helm` refers to the client-side command +- Tiller is the proper name of the backend +- `tiller` is the name of the binary run on the backend +- The term 'chart' does not need to be capitalized, as it is not a proper noun. + +When in doubt, use _Helm_ (with an uppercase 'H'). + +## Restricting Tiller by Version + +A `Chart.yaml` file can specify a `tillerVersion` SemVer constraint: + +```yaml +name: mychart +version: 0.2.0 +tillerVersion: ">=2.4.0" +``` + +This constraint should be set when templates use a new feature that was not +supported in older versions of Helm. While this parameter will accept sophisticated +SemVer rules, the best practice is to default to the form `>=2.4.0`, where `2.4.0` +is the version that introduced the new feature used in the chart. + +This feature was introduced in Helm 2.4.0, so any version of Tiller older than +2.4.0 will simply ignore this field. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/labels.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/labels.md new file mode 100644 index 000000000..7c3ac51db --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/labels.md @@ -0,0 +1,32 @@ +# Labels and Annotations + +This part of the Best Practices Guide discusses the best practices for using +labels and annotations in your chart. + +## Is it a Label or an Annotation? + +An item of metadata should be a label under the following conditions: + +- It is used by Kubernetes to identify this resource +- It is useful to expose to operators for the purpose of querying the system. + +For example, we suggest using `chart: NAME-VERSION` as a label so that operators +can conveniently find all of the instances of a particular chart to use. + +If an item of metadata is not used for querying, it should be set as an annotation +instead. + +Helm hooks are always annotations. + +## Standard Labels + +The following table defines common labels that Helm charts use. Helm itself never requires that a particular label be present. Labels that are marked REC +are recommended, and _should_ be placed onto a chart for global consistency. Those marked OPT are optional. These are idiomatic or commonly in use, but are not relied upon frequently for operational purposes. + +Name|Status|Description +-----|------|---------- +heritage | REC | This should always be set to `{{ .Release.Service }}`. It is for finding all things managed by Tiller. +release | REC | This should be the `{{ .Release.Name }}`. +chart | REC | This should be the chart name and version: `{{ .Chart.Name }}-{{ .Chart.Version \| replace "+" "_" }}`. +app | REC | This should be the app name, reflecting the entire app. Usually `{{ template "name" . }}` is used for this. This is used by many Kubernetes manifests, and is not Helm-specific. +component | OPT | This is a common label for marking the different roles that pieces may play in an application. For example, `component: frontend`. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/pods.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/pods.md new file mode 100644 index 000000000..3f26b0253 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/pods.md @@ -0,0 +1,69 @@ +# Pods and PodTemplates + +This part of the Best Practices Guide discusses formatting the Pod and PodTemplate +portions in chart manifests. + +The following (non-exhaustive) list of resources use PodTemplates: + +- Deployment +- ReplicationController +- ReplicaSet +- DaemonSet +- StatefulSet + +## Images + +A container image should use a fixed tag or the SHA of the image. It should not use the tags `latest`, `head`, `canary`, or other tags that are designed to be "floating". + + +Images _may_ be defined in the `values.yaml` file to make it easy to swap out images. + +``` +image: {{ .Values.redisImage | quote }} +``` + +An image and a tag _may_ be defined in `values.yaml` as two separate fields: + +``` +image: "{{ .Values.redisImage }}:{{ .Values.redisTag }}" +``` + +## ImagePullPolicy + +`helm create` sets the `imagePullPolicy` to `IfNotPresent` by default by doing the following in your `deployment.yaml`: + +```yaml +imagePullPolicy: {{ .Values.image.pullPolicy }} +``` + +And `values.yaml`: + +```yaml +pullPolicy: IfNotPresent +``` + +Similarly, Kubernetes defaults the `imagePullPolicy` to `IfNotPresent` if it is not defined at all. If you want a value other than `IfNotPresent`, simply update the value in `values.yaml` to your desired value. + + +## PodTemplates Should Declare Selectors + +All PodTemplate sections should specify a selector. For example: + +```yaml +selector: + matchLabels: + app: MyName +template: + metadata: + labels: + app: MyName +``` + +This is a good practice because it makes the relationship between the set and +the pod. + +But this is even more important for sets like Deployment. +Without this, the _entire_ set of labels is used to select matching pods, and +this will break if you use labels that change, like version or release date. + + diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/rbac.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/rbac.md new file mode 100644 index 000000000..d699e4fdd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/rbac.md @@ -0,0 +1,63 @@ +# Role-Based Access Control + +This part of the Best Practices Guide discusses the creation and formatting of RBAC resources in chart manifests. + +RBAC resources are: + +- ServiceAccount (namespaced) +- Role (namespaced) +- ClusterRole +- RoleBinding (namespaced) +- ClusterRoleBinding + +## YAML Configuration + +RBAC and ServiceAccount configuration should happen under separate keys. They are separate things. Splitting these two concepts out in the YAML disambiguates them and make this clearer. + +```yaml +rbac: + # Specifies whether RBAC resources should be created + create: true + +serviceAccount: + # Specifies whether a ServiceAccount should be created + create: true + # The name of the ServiceAccount to use. + # If not set and create is true, a name is generated using the fullname template + name: +``` + +This structure can be extended for more complex charts that require multiple ServiceAccounts. + +```yaml +serviceAccounts: + client: + create: true + name: + server: + create: true + name: +``` + +## RBAC Resources Should be Created by Default + +`rbac.create` should be a boolean value controlling whether RBAC resources are created. The default should be `true`. Users who wish to manage RBAC access controls themselves can set this value to `false` (in which case see below). + +## Using RBAC Resources + +`serviceAccount.name` should set to the name of the ServiceAccount to be used by access-controlled resources created by the chart. If `serviceAccount.create` is true, then a ServiceAccount with this name should be created. If the name is not set, then a name is generated using the `fullname` template, If `serviceAccount.create` is false, then it should not be created, but it should still be associated with the same resources so that manually-created RBAC resources created later that reference it will function correctly. If `serviceAccount.create` is false and the name is not specified, then the default ServiceAccount is used. + +The following helper template should be used for the ServiceAccount. + +```yaml +{{/* +Create the name of the service account to use +*/}} +{{- define "mychart.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "mychart.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} +``` diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/requirements.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/requirements.md new file mode 100644 index 000000000..ca63f3425 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/requirements.md @@ -0,0 +1,47 @@ +# Requirements Files + +This section of the guide covers best practices for `requirements.yaml` files. + +## Versions + +Where possible, use version ranges instead of pinning to an exact version. The suggested default is to use a patch-level version match: + +```yaml +version: ~1.2.3 +``` + +This will match version `1.2.3` and any patches to that release. In other words, `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` + +For the complete version matching syntax, please see the [semver documentation](https://github.com/Masterminds/semver#checking-version-constraints) + +### Repository URLs + +Where possible, use `https://` repository URLs, followed by `http://` URLs. + +If the repository has been added to the repository index file, the repository name can be used as an alias of URL. Use `alias:` or `@` followed by repository names. + +File URLs (`file://...`) are considered a "special case" for charts that are assembled by a fixed deployment pipeline. Charts that use `file://` in a `requirements.yaml` file are not allowed in the official Helm repository. + +## Conditions and Tags + +Conditions or tags should be added to any dependencies that _are optional_. + +The preferred form of a condition is: + +```yaml +condition: somechart.enabled +``` + +Where `somechart` is the chart name of the dependency. + +When multiple subcharts (dependencies) together provide an optional or swappable feature, those charts should share the same tags. + +For example, if both `nginx` and `memcached` together provided performance optimizations for the main app in the chart, and were required to both be present when that feature is enabled, then they might both have a +tags section like this: + +``` +tags: + - webaccelerator +``` + +This allows a user to turn that feature on and off with one tag. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/templates.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/templates.md new file mode 100644 index 000000000..c9995ea0a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/templates.md @@ -0,0 +1,198 @@ +# Templates + +This part of the Best Practices Guide focuses on templates. + +## Structure of templates/ + +The templates directory should be structured as follows: + +- Template files should have the extension `.yaml` if they produce YAML output. The + extension `.tpl` may be used for template files that produce no formatted content. +- Template file names should use dashed notation (`my-example-configmap.yaml`), not camelcase. +- Each resource definition should be in its own template file. +- Template file names should reflect the resource kind in the name. e.g. `foo-pod.yaml`, + `bar-svc.yaml` + +## Names of Defined Templates + +Defined templates (templates created inside a `{{ define }} ` directive) are +globally accessible. That means that a chart and all of its subcharts will have +access to all of the templates created with `{{ define }}`. + +For that reason, _all defined template names should be namespaced._ + +Correct: + +```yaml +{{- define "nginx.fullname" }} +{{/* ... */}} +{{ end -}} +``` + +Incorrect: + +```yaml +{{- define "fullname" -}} +{{/* ... */}} +{{ end -}} +``` +It is highly recommended that new charts are created via `helm create` command as the template names are automatically defined as per this best practice. + +## Formatting Templates + +Templates should be indented using _two spaces_ (never tabs). + +Template directives should have whitespace after the opening braces and before the +closing braces: + +Correct: +``` +{{ .foo }} +{{ print "foo" }} +{{- print "bar" -}} +``` + +Incorrect: +``` +{{.foo}} +{{print "foo"}} +{{-print "bar"-}} +``` + +Templates should chomp whitespace where possible: + +``` +foo: + {{- range .Values.items }} + {{ . }} + {{ end -}} +``` + +Blocks (such as control structures) may be indented to indicate flow of the template code. + +``` +{{ if $foo -}} + {{- with .Bar }}Hello{{ end -}} +{{- end -}} +``` + +However, since YAML is a whitespace-oriented language, it is often not possible for code indentation to follow that convention. + +## Whitespace in Generated Templates + +It is preferable to keep the amount of whitespace in generated templates to +a minimum. In particular, numerous blank lines should not appear adjacent to each +other. But occasional empty lines (particularly between logical sections) is +fine. + +This is best: + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: example + labels: + first: first + second: second +``` + +This is okay: + +```yaml +apiVersion: batch/v1 +kind: Job + +metadata: + name: example + + labels: + first: first + second: second + +``` + +But this should be avoided: + +```yaml +apiVersion: batch/v1 +kind: Job + +metadata: + name: example + + + + + + labels: + first: first + + second: second + +``` + +## Comments (YAML Comments vs. Template Comments) + +Both YAML and Helm Templates have comment markers. + +YAML comments: +```yaml +# This is a comment +type: sprocket +``` + +Template Comments: +```yaml +{{- /* +This is a comment. +*/ -}} +type: frobnitz +``` + +Template comments should be used when documenting features of a template, such as explaining a defined template: + +```yaml +{{- /* +mychart.shortname provides a 6 char truncated version of the release name. +*/ }} +{{ define "mychart.shortname" -}} +{{ .Release.Name | trunc 6 }} +{{- end -}} + +``` + +Inside of templates, YAML comments may be used when it is useful for Helm users to (possibly) see the comments during debugging. + +``` +# This may cause problems if the value is more than 100Gi +memory: {{ .Values.maxMem | quote }} +``` + +The comment above is visible when the user runs `helm install --debug`, while +comments specified in `{{- /* */ -}}` sections are not. + +## Use of JSON in Templates and Template Output + +YAML is a superset of JSON. In some cases, using a JSON syntax can be more +readable than other YAML representations. + +For example, this YAML is closer to the normal YAML method of expressing lists: + +```yaml +arguments: + - "--dirname" + - "/foo" +``` + +But it is easier to read when collapsed into a JSON list style: + +```yaml +arguments: ["--dirname", "/foo"] +``` + +Using JSON for increased legibility is good. However, JSON syntax should not +be used for representing more complex constructs. + +When dealing with pure JSON embedded inside of YAML (such as init container +configuration), it is of course appropriate to use the JSON format. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/third_party_resources.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/third_party_resources.md new file mode 100644 index 000000000..cb0fcc754 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/third_party_resources.md @@ -0,0 +1,38 @@ +# Third Party Resources + +This section of the Best Practices Guide deals with creating and using Third Party Resource +objects. + +When working with Third Party Resources (TPRs), it is important to distinguish +two different pieces: + +- There is a declaration of a TPR. This is the YAML file that has the kind `ThirdPartyResource` +- Then there are resources that _use_ the TPR. Say a TPR defines `foo.example.com/v1`. Any resource + that has `apiVersion: example.com/v1` and kind `Foo` is a resource that uses the + TPR. + +## Install a TPR Declaration Before Using the Resource + +Helm is optimized to load as many resources into Kubernetes as fast as possible. +By design, Kubernetes can take an entire set of manifests and bring them all +online (this is called the reconciliation loop). + +But there's a difference with TPRs. + +For a TPR, the declaration must be registered before any resources of that TPRs +kind(s) can be used. And the registration process sometimes takes a few seconds. + +### Method 1: Separate Charts + +One way to do this is to put the TPR definition in one chart, and then put any +resources that use that TPR in _another_ chart. + +In this method, each chart must be installed separately. + +### Method 2: Pre-install Hooks + +To package the two together, add a `pre-install` hook to the TPR definition so +that it is fully installed before the rest of the chart is executed. + +Note that if you create the TPR with a `pre-install` hook, that TPR definition +will not be deleted when `helm delete` is run. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/values.md b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/values.md new file mode 100644 index 000000000..2962e7d45 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_best_practices/values.md @@ -0,0 +1,155 @@ +# Values + +This part of the best practices guide covers using values. In this part of the +guide, we provide recommendations on how you should structure and use your +values, with focus on designing a chart's `values.yaml` file. + +## Naming Conventions + +Variables names should begin with a lowercase letter, and words should be +separated with camelcase: + +Correct: + +```yaml +chicken: true +chickenNoodleSoup: true +``` + +Incorrect: + +```yaml +Chicken: true # initial caps may conflict with built-ins +chicken-noodle-soup: true # do not use hyphens in the name +``` + +Note that all of Helm's built-in variables begin with an uppercase letter to +easily distinguish them from user-defined values: `.Release.Name`, +`.Capabilities.KubeVersion`. + +## Flat or Nested Values + +YAML is a flexible format, and values may be nested deeply or flattened. + +Nested: + +```yaml +server: + name: nginx + port: 80 +``` + +Flat: + +```yaml +serverName: nginx +serverPort: 80 +``` + +In most cases, flat should be favored over nested. The reason for this is that +it is simpler for template developers and users. + + +For optimal safety, a nested value must be checked at every level: + +``` +{{ if .Values.server }} + {{ default "none" .Values.server.name }} +{{ end }} +``` + +For every layer of nesting, an existence check must be done. But for flat +configuration, such checks can be skipped, making the template easier to read +and use. + +``` +{{ default "none" .Values.serverName }} +``` + +When there are a large number of related variables, and at least one of them +is non-optional, nested values may be used to improve readability. + +## Make Types Clear + +YAML's type coercion rules are sometimes counterintuitive. For example, +`foo: false` is not the same as `foo: "false"`. Large integers like `foo: 12345678` +will get converted to scientific notation in some cases. + +The easiest way to avoid type conversion errors is to be explicit about strings, +and implicit about everything else. Or, in short, _quote all strings_. + +Often, to avoid the integer casting issues, it is advantageous to store your +integers as strings as well, and use `{{ int $value }}` in the template to convert +from a string back to an integer. + +In most cases, explicit type tags are respected, so `foo: !!string 1234` should +treat `1234` as a string. _However_, the YAML parser consumes tags, so the type +data is lost after one parse. + +## Consider How Users Will Use Your Values + +There are three potential sources of values: + +- A chart's `values.yaml` file +- A values file supplied by `helm install -f` or `helm upgrade -f` +- The values passed to a `--set` or `--set-string` flag on `helm install` or `helm upgrade` + +When designing the structure of your values, keep in mind that users of your +chart may want to override them via either the `-f` flag or with the `--set` +option. + +Since `--set` is more limited in expressiveness, the first guidelines for writing +your `values.yaml` file is _make it easy to override from `--set`_. + +For this reason, it's often better to structure your values file using maps. + +Difficult to use with `--set`: + +```yaml +servers: + - name: foo + port: 80 + - name: bar + port: 81 +``` + +The above cannot be expressed with `--set` in Helm `<=2.4`. In Helm 2.5, the +accessing the port on foo is `--set servers[0].port=80`. Not only is it harder +for the user to figure out, but it is prone to errors if at some later time the +order of the `servers` is changed. + +Easy to use: + +```yaml +servers: + foo: + port: 80 + bar: + port: 81 +``` + +Accessing foo's port is much more obvious: `--set servers.foo.port=80`. + +## Document 'values.yaml' + +Every defined property in 'values.yaml' should be documented. The documentation string should begin with the name of the property that it describes, and then give at least a one-sentence description. + +Incorrect: + +``` +# the host name for the webserver +serverHost = example +serverPort = 9191 +``` + +Correct: + +``` +# serverHost is the host name for the webserver +serverHost = example +# serverPort is the HTTP listener port for the webserver +serverPort = 9191 + +``` + +Beginning each comment with the name of the parameter it documents makes it easy to grep out documentation, and will enable documentation tools to reliably correlate doc strings with the parameters they describe. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_repository.md b/src/vendor/github.com/kubernetes/helm/docs/chart_repository.md new file mode 100644 index 000000000..01d457e63 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_repository.md @@ -0,0 +1,294 @@ +# The Chart Repository Guide + +This section explains how to create and work with Helm chart repositories. At a +high level, a chart repository is a location where packaged charts can be +stored and shared. + +The official chart repository is maintained by the +[Kubernetes Charts](https://github.com/kubernetes/charts), and we welcome +participation. But Helm also makes it easy to create and run your own chart +repository. This guide explains how to do so. + +## Prerequisites + +* Go through the [Quickstart](quickstart.md) Guide +* Read through the [Charts](charts.md) document + +## Create a chart repository + +A _chart repository_ is an HTTP server that houses an `index.yaml` file and +optionally some packaged charts. When you're ready to share your charts, the +preferred way to do so is by uploading them to a chart repository. + +**Note:** For Helm 2.0.0, chart repositories do not have any intrinsic +authentication. There is an [issue tracking progress](https://github.com/kubernetes/helm/issues/1038) +in GitHub. + +Because a chart repository can be any HTTP server that can serve YAML and tar +files and can answer GET requests, you have a plethora of options when it comes +down to hosting your own chart repository. For example, you can use a Google +Cloud Storage (GCS) bucket, Amazon S3 bucket, Github Pages, or even create your +own web server. + +### The chart repository structure + +A chart repository consists of packaged charts and a special file called +`index.yaml` which contains an index of all of the charts in the repository. +Frequently, the charts that `index.yaml` describes are also hosted on the same +server, as are the [provenance files](provenance.md). + +For example, the layout of the repository `https://example.com/charts` might +look like this: + +``` +charts/ + | + |- index.yaml + | + |- alpine-0.1.2.tgz + | + |- alpine-0.1.2.tgz.prov +``` + +In this case, the index file would contain information about one chart, the Alpine +chart, and provide the download URL `https://example.com/charts/alpine-0.1.2.tgz` +for that chart. + +It is not required that a chart package be located on the same server as the +`index.yaml` file. However, doing so is often the easiest. + +### The index file + +The index file is a yaml file called `index.yaml`. It +contains some metadata about the package, including the contents of a +chart's `Chart.yaml` file. A valid chart repository must have an index file. The +index file contains information about each chart in the chart repository. The +`helm repo index` command will generate an index file based on a given local +directory that contains packaged charts. + +This is an example of an index file: + +``` +apiVersion: v1 +entries: + alpine: + - created: 2016-10-06T16:23:20.499814565-06:00 + description: Deploy a basic Alpine Linux pod + digest: 99c76e403d752c84ead610644d4b1c2f2b453a74b921f422b9dcb8a7c8b559cd + home: https://k8s.io/helm + name: alpine + sources: + - https://github.com/kubernetes/helm + urls: + - https://technosophos.github.io/tscharts/alpine-0.2.0.tgz + version: 0.2.0 + - created: 2016-10-06T16:23:20.499543808-06:00 + description: Deploy a basic Alpine Linux pod + digest: 515c58e5f79d8b2913a10cb400ebb6fa9c77fe813287afbacf1a0b897cd78727 + home: https://k8s.io/helm + name: alpine + sources: + - https://github.com/kubernetes/helm + urls: + - https://technosophos.github.io/tscharts/alpine-0.1.0.tgz + version: 0.1.0 + nginx: + - created: 2016-10-06T16:23:20.499543808-06:00 + description: Create a basic nginx HTTP server + digest: aaff4545f79d8b2913a10cb400ebb6fa9c77fe813287afbacf1a0b897cdffffff + home: https://k8s.io/helm + name: nginx + sources: + - https://github.com/kubernetes/charts + urls: + - https://technosophos.github.io/tscharts/nginx-1.1.0.tgz + version: 1.1.0 +generated: 2016-10-06T16:23:20.499029981-06:00 +``` + +A generated index and packages can be served from a basic webserver. You can test +things out locally with the `helm serve` command, which starts a local server. + +```console +$ helm serve --repo-path ./charts +Regenerating index. This may take a moment. +Now serving you on 127.0.0.1:8879 +``` + +The above starts a local webserver, serving the charts it finds in `./charts`. The +serve command will automatically generate an `index.yaml` file for you during +startup. + +## Hosting Chart Repositories + +This part shows several ways to serve a chart repository. + +### Google Cloud Storage + +The first step is to **create your GCS bucket**. We'll call ours +`fantastic-charts`. + +![Create a GCS Bucket](images/create-a-bucket.png) + +Next, make your bucket public by **editing the bucket permissions**. + +![Edit Permissions](images/edit-permissions.png) + +Insert this line item to **make your bucket public**: + +![Make Bucket Public](images/make-bucket-public.png) + +Congratulations, now you have an empty GCS bucket ready to serve charts! + +You may upload your chart repository using the Google Cloud Storage command line +tool, or using the GCS web UI. This is the technique the official Kubernetes +Charts repository hosts its charts, so you may want to take a +[peek at that project](https://github.com/kubernetes/charts) if you get stuck. + +**Note:** A public GCS bucket can be accessed via simple HTTPS at this address +`https://bucket-name.storage.googleapis.com/`. + +### JFrog Artifactory + +You can also set up chart repositories using JFrog Artifactory. +Read more about chart repositories with JFrog Artifactory [here](https://www.jfrog.com/confluence/display/RTF/Helm+Chart+Repositories) + +### Github Pages example + +In a similar way you can create charts repository using GitHub Pages. + +GitHub allows you to serve static web pages in two different ways: + +- By configuring a project to serve the contents of its `docs/` directory +- By configuring a project to serve a particular branch + +We'll take the second approach, though the first is just as easy. + +The first step will be to **create your gh-pages branch**. You can do that +locally as. + +```console +$ git checkout -b gh-pages +``` + +Or via web browser using **Branch** button on your Github repository: + +![Create Github Pages branch](images/create-a-gh-page-button.png) + +Next, you'll want to make sure your **gh-pages branch** is set as Github Pages, +click on your repo **Settings** and scroll down to **Github pages** section and +set as per below: + +![Create Github Pages branch](images/set-a-gh-page.png) + +By default **Source** usually gets set to **gh-pages branch**. If this is not set by default, then select it. + +You can use a **custom domain** there if you wish so. + +And check that **Enforce HTTPS** is ticked, so the **HTTPS** will be used when +charts are served. + +In such setup you can use **master branch** to store your charts code, and +**gh-pages branch** as charts repository, e.g.: +`https://USERNAME.github.io/REPONAME`. The demonstration [TS Charts](https://github.com/technosophos/tscharts) +repository is accessible at `https://technosophos.github.io/tscharts/`. + +### Ordinary web servers + +To configure an ordinary web server to serve Helm charts, you merely need to do +the following: + +- Put your index and charts in a directory that the server can serve +- Make sure the `index.yaml` file can be accessed with no authentication requirement +- Make sure `yaml` files are served with the correct content type (`text/yaml` or + `text/x-yaml`) + +For example, if you want to serve your charts out of `$WEBROOT/charts`, make sure +there is a `charts/` directory in your web root, and put the index file and +charts inside of that folder. + + +## Managing Chart Repositories + +Now that you have a chart repository, the last part of this guide explains how +to maintain charts in that repository. + + +### Store charts in your chart repository + +Now that you have a chart repository, let's upload a chart and an index file to +the repository. Charts in a chart repository must be packaged +(`helm package chart-name/`) and versioned correctly (following +[SemVer 2](https://semver.org/) guidelines). + +These next steps compose an example workflow, but you are welcome to use +whatever workflow you fancy for storing and updating charts in your chart +repository. + +Once you have a packaged chart ready, create a new directory, and move your +packaged chart to that directory. + +```console +$ helm package docs/examples/alpine/ +$ mkdir fantastic-charts +$ mv alpine-0.1.0.tgz fantastic-charts/ +$ helm repo index fantastic-charts --url https://fantastic-charts.storage.googleapis.com +``` + +The last command takes the path of the local directory that you just created and +the URL of your remote chart repository and composes an `index.yaml` file inside the +given directory path. + +Now you can upload the chart and the index file to your chart repository using +a sync tool or manually. If you're using Google Cloud Storage, check out this +[example workflow](chart_repository_sync_example.md) using the gsutil client. For +GitHub, you can simply put the charts in the appropriate destination branch. + +### Add new charts to an existing repository + +Each time you want to add a new chart to your repository, you must regenerate +the index. The `helm repo index` command will completely rebuild the `index.yaml` +file from scratch, including only the charts that it finds locally. + +However, you can use the `--merge` flag to incrementally add new charts to an +existing `index.yaml` file (a great option when working with a remote repository +like GCS). Run `helm repo index --help` to learn more, + +Make sure that you upload both the revised `index.yaml` file and the chart. And +if you generated a provenance file, upload that too. + +### Share your charts with others + +When you're ready to share your charts, simply let someone know what the URL of +your repository is. + +From there, they will add the repository to their helm client via the `helm +repo add [NAME] [URL]` command with any name they would like to use to +reference the repository. + +```console +$ helm repo add fantastic-charts https://fantastic-charts.storage.googleapis.com +$ helm repo list +fantastic-charts https://fantastic-charts.storage.googleapis.com +``` + +If the charts are backed by HTTP basic authentication, you can also supply the +username and password here: + +```console +$ helm repo add fantastic-charts https://fantastic-charts.storage.googleapis.com --username my-username --password my-password +$ helm repo list +fantastic-charts https://fantastic-charts.storage.googleapis.com +``` + +**Note:** A repository will not be added if it does not contain a valid +`index.yaml`. + +After that, your users will be able to search through your charts. After you've updated +the repository, they can use the `helm repo update` command to get the latest +chart information. + +*Under the hood, the `helm repo add` and `helm repo update` commands are +fetching the index.yaml file and storing them in the +`$HELM_HOME/repository/cache/` directory. This is where the `helm search` +function finds information about charts.* diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_repository_faq.md b/src/vendor/github.com/kubernetes/helm/docs/chart_repository_faq.md new file mode 100644 index 000000000..a3e6392ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_repository_faq.md @@ -0,0 +1,17 @@ +# Chart Repositories: Frequently Asked Questions + +This section tracks some of the more frequently encountered issues with using chart repositories. + +**We'd love your help** making this document better. To add, correct, or remove +information, [file an issue](https://github.com/kubernetes/helm/issues) or +send us a pull request. + +## Fetching + +**Q: Why do I get a `unsupported protocol scheme ""` error when trying to fetch a chart from my custom repo?** + +A: (Helm < 2.5.0) This is likely caused by you creating your chart repo index without specifying the `--url` flag. +Try recreating your `index.yaml` file with a command like `helm repo index --url http://my-repo/charts .`, +and then re-uploading it to your custom charts repo. + +This behavior was changed in Helm 2.5.0. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_repository_sync_example.md b/src/vendor/github.com/kubernetes/helm/docs/chart_repository_sync_example.md new file mode 100644 index 000000000..2fff70de9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_repository_sync_example.md @@ -0,0 +1,74 @@ +# Syncing Your Chart Repository +*Note: This example is specifically for a Google Cloud Storage (GCS) bucket which serves a chart repository.* + +## Prerequisites +* Install the [gsutil](https://cloud.google.com/storage/docs/gsutil) tool. *We rely heavily on the gsutil rsync functionality* +* Be sure to have access to the helm binary +* _Optional: We recommend you set [object versioning](https://cloud.google.com/storage/docs/gsutil/addlhelp/ObjectVersioningandConcurrencyControl#top_of_page) on your GCS bucket in case you accidentally delete something._ + +## Set up a local chart repository directory +Create a local directory like we did in [the chart repository guide](chart_repository.md), and place your packaged charts in that directory. + +For example: +```console +$ mkdir fantastic-charts +$ mv alpine-0.1.0.tgz fantastic-charts/ +``` + +## Generate an updated index.yaml +Use helm to generate an updated index.yaml file by passing in the directory path and the url of the remote repository to the `helm repo index` command like this: + +```console +$ helm repo index fantastic-charts/ --url https://fantastic-charts.storage.googleapis.com +``` +This will generate an updated index.yaml file and place in the `fantastic-charts/` directory. + +## Sync your local and remote chart repositories +Upload the contents of the directory to your GCS bucket by running `scripts/sync-repo.sh` and pass in the local directory name and the GCS bucket name. + +For example: +```console +$ pwd +/Users/funuser/go/src/github.com/kubernetes/helm +$ scripts/sync-repo.sh fantastic-charts/ fantastic-charts +Getting ready to sync your local directory (fantastic-charts/) to a remote repository at gs://fantastic-charts +Verifying Prerequisites.... +Thumbs up! Looks like you have gsutil. Let's continue. +Building synchronization state... +Starting synchronization +Would copy file://fantastic-charts/alpine-0.1.0.tgz to gs://fantastic-charts/alpine-0.1.0.tgz +Would copy file://fantastic-charts/index.yaml to gs://fantastic-charts/index.yaml +Are you sure you would like to continue with these changes?? [y/N]} y +Building synchronization state... +Starting synchronization +Copying file://fantastic-charts/alpine-0.1.0.tgz [Content-Type=application/x-tar]... +Uploading gs://fantastic-charts/alpine-0.1.0.tgz: 740 B/740 B +Copying file://fantastic-charts/index.yaml [Content-Type=application/octet-stream]... +Uploading gs://fantastic-charts/index.yaml: 347 B/347 B +Congratulations your remote chart repository now matches the contents of fantastic-charts/ +``` +## Updating your chart repository +You'll want to keep a local copy of the contents of your chart repository or use `gsutil rsync` to copy the contents of your remote chart repository to a local directory. + +For example: +```console +$ gsutil rsync -d -n gs://bucket-name local-dir/ # the -n flag does a dry run +Building synchronization state... +Starting synchronization +Would copy gs://bucket-name/alpine-0.1.0.tgz to file://local-dir/alpine-0.1.0.tgz +Would copy gs://bucket-name/index.yaml to file://local-dir/index.yaml + +$ gsutil rsync -d gs://bucket-name local-dir/ # performs the copy actions +Building synchronization state... +Starting synchronization +Copying gs://bucket-name/alpine-0.1.0.tgz... +Downloading file://local-dir/alpine-0.1.0.tgz: 740 B/740 B +Copying gs://bucket-name/index.yaml... +Downloading file://local-dir/index.yaml: 346 B/346 B +``` + + +Helpful Links: +* Documentation on [gsutil rsync](https://cloud.google.com/storage/docs/gsutil/commands/rsync#description) +* [The Chart Repository Guide](chart_repository.md) +* Documentation on [object versioning and concurrency control](https://cloud.google.com/storage/docs/gsutil/addlhelp/ObjectVersioningandConcurrencyControl#overview) in Google Cloud Storage diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/accessing_files.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/accessing_files.md new file mode 100644 index 000000000..250fd9520 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/accessing_files.md @@ -0,0 +1,209 @@ +# Accessing Files Inside Templates + +In the previous section we looked at several ways to create and access named templates. This makes it easy to import one template from within another template. But sometimes it is desirable to import a _file that is not a template_ and inject its contents without sending the contents through the template renderer. + +Helm provides access to files through the `.Files` object. Before we get going with the template examples, though, there are a few things to note about how this works: + +- It is okay to add extra files to your Helm chart. These files will be bundled and sent to Tiller. Be careful, though. Charts must be smaller than 1M because of the storage limitations of Kubernetes objects. +- Some files cannot be accessed through the `.Files` object, usually for security reasons. + - Files in `templates/` cannot be accessed. + - Files excluded using `.helmignore` cannot be accessed. +- Charts do not preserve UNIX mode information, so file-level permissions will have no impact on the availability of a file when it comes to the `.Files` object. + + + + + +- [Basic example](#basic-example) +- [Path helpers](#path-helpers) +- [Glob patterns](#glob-patterns) +- [ConfigMap and Secrets utility functions](#configmap-and-secrets-utility-functions) +- [Encoding](#encoding) +- [Lines](#lines) + + + +## Basic example + +With those caveats behind, let's write a template that reads three files into our ConfigMap. To get started, we will add three files to the chart, putting all three directly inside of the `mychart/` directory. + +`config1.toml`: + +```toml +message = Hello from config 1 +``` + +`config2.toml`: + +```toml +message = This is config 2 +``` + +`config3.toml`: + +```toml +message = Goodbye from config 3 +``` + +Each of these is a simple TOML file (think old-school Windows INI files). We know the names of these files, so we can use a `range` function to loop through them and inject their contents into our ConfigMap. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + {{- $files := .Files }} + {{- range tuple "config1.toml" "config2.toml" "config3.toml" }} + {{ . }}: |- + {{ $files.Get . }} + {{- end }} +``` + +This config map uses several of the techniques discussed in previous sections. For example, we create a `$files` variable to hold a reference to the `.Files` object. We also use the `tuple` function to create a list of files that we loop through. Then we print each file name (`{{.}}: |-`) followed by the contents of the file `{{ $files.Get . }}`. + +Running this template will produce a single ConfigMap with the contents of all three files: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: quieting-giraf-configmap +data: + config1.toml: |- + message = Hello from config 1 + + config2.toml: |- + message = This is config 2 + + config3.toml: |- + message = Goodbye from config 3 +``` + +## Path helpers + +When working with files, it can be very useful to perform some standard +operations on the file paths themselves. To help with this, Helm imports many of +the functions from Go's [path](https://golang.org/pkg/path/) package for your +use. They are all accessible with the same names as in the Go package, but +with a lowercase first letter. For example, `Base` becomes `base`, etc. + +The imported functions are: +- Base +- Dir +- Ext +- IsAbs +- Clean + +## Glob patterns + +As your chart grows, you may find you have a greater need to organize your +files more, and so we provide a `Files.Glob(pattern string)` method to assist +in extracting certain files with all the flexibility of [glob patterns](https://godoc.org/github.com/gobwas/glob). + +`.Glob` returns a `Files` type, so you may call any of the `Files` methods on +the returned object. + +For example, imagine the directory structure: + +``` +foo/: + foo.txt foo.yaml + +bar/: + bar.go bar.conf baz.yaml +``` + +You have multiple options with Globs: + + +```yaml +{{ range $path := .Files.Glob "**.yaml" }} +{{ $path }}: | +{{ .Files.Get $path }} +{{ end }} +``` + +Or + +```yaml +{{ range $path, $bytes := .Files.Glob "foo/*" }} +{{ $path }}: '{{ b64enc $bytes }}' +{{ end }} +``` + +## ConfigMap and Secrets utility functions + +(Not present in version 2.0.2 or prior) + +It is very common to want to place file content into both configmaps and +secrets, for mounting into your pods at run time. To help with this, we provide a +couple utility methods on the `Files` type. + +For further organization, it is especially useful to use these methods in +conjunction with the `Glob` method. + +Given the directory structure from the [Glob](#glob-patterns) example above: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: conf +data: +{{ (.Files.Glob "foo/*").AsConfig | indent 2 }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: very-secret +type: Opaque +data: +{{ (.Files.Glob "bar/*").AsSecrets | indent 2 }} +``` + +## Encoding + +You can import a file and have the template base-64 encode it to ensure successful transmission: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-secret +type: Opaque +data: + token: |- + {{ .Files.Get "config1.toml" | b64enc }} +``` + +The above will take the same `config1.toml` file we used before and encode it: + +```yaml +# Source: mychart/templates/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: lucky-turkey-secret +type: Opaque +data: + token: |- + bWVzc2FnZSA9IEhlbGxvIGZyb20gY29uZmlnIDEK +``` + +## Lines + +Sometimes it is desirable to access each line of a file in your template. We +provide a convenient `Lines` method for this. + +```yaml +data: + some-file.txt: {{ range .Files.Lines "foo/bar.txt" }} + {{ . }}{{ end }} +``` + +Currently, there is no way to pass files external to the chart during `helm install`. So if you are asking users to supply data, it must be loaded using `helm install -f` or `helm install --set`. + +This discussion wraps up our dive into the tools and techniques for writing Helm templates. In the next section we will see how you can use one special file, `templates/NOTES.txt`, to send post-installation instructions to the users of your chart. + diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/builtin_objects.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/builtin_objects.md new file mode 100644 index 000000000..11982229b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/builtin_objects.md @@ -0,0 +1,35 @@ +# Built-in Objects + +Objects are passed into a template from the template engine. And your code can pass objects around (we'll see examples when we look at the `with` and `range` statements). There are even a few ways to create new objects within your templates, like with the `tuple` function we'll see later. + +Objects can be simple, and have just one value. Or they can contain other objects or functions. For example. the `Release` object contains several objects (like `Release.Name`) and the `Files` object has a few functions. + +In the previous section, we use `{{.Release.Name}}` to insert the name of a release into a template. `Release` is one of the top-level objects that you can access in your templates. + +- `Release`: This object describes the release itself. It has several objects inside of it: + - `Release.Name`: The release name + - `Release.Time`: The time of the release + - `Release.Namespace`: The namespace to be released into (if the manifest doesn't override) + - `Release.Service`: The name of the releasing service (always `Tiller`). + - `Release.Revision`: The revision number of this release. It begins at 1 and is incremented for each `helm upgrade`. + - `Release.IsUpgrade`: This is set to `true` if the current operation is an upgrade or rollback. + - `Release.IsInstall`: This is set to `true` if the current operation is an install. +- `Values`: Values passed into the template from the `values.yaml` file and from user-supplied files. By default, `Values` is empty. +- `Chart`: The contents of the `Chart.yaml` file. Any data in `Chart.yaml` will be accessible here. For example `{{.Chart.Name}}-{{.Chart.Version}}` will print out the `mychart-0.1.0`. + - The available fields are listed in the [Charts Guide](https://github.com/kubernetes/helm/blob/master/docs/charts.md#the-chartyaml-file) +- `Files`: This provides access to all non-special files in a chart. While you cannot use it to access templates, you can use it to access other files in the chart. See the section _Accessing Files_ for more. + - `Files.Get` is a function for getting a file by name (`.Files.Get config.ini`) + - `Files.GetBytes` is a function for getting the contents of a file as an array of bytes instead of as a string. This is useful for things like images. +- `Capabilities`: This provides information about what capabilities the Kubernetes cluster supports. + - `Capabilities.APIVersions` is a set of versions. + - `Capabilities.APIVersions.Has $version` indicates whether a version (`batch/v1`) is enabled on the cluster. + - `Capabilities.KubeVersion` provides a way to look up the Kubernetes version. It has the following values: `Major`, `Minor`, `GitVersion`, `GitCommit`, `GitTreeState`, `BuildDate`, `GoVersion`, `Compiler`, and `Platform`. + - `Capabilities.TillerVersion` provides a way to look up the Tiller version. It has the following values: `SemVer`, `GitCommit`, and `GitTreeState`. +- `Template`: Contains information about the current template that is being executed + - `Name`: A namespaced filepath to the current template (e.g. `mychart/templates/mytemplate.yaml`) + - `BasePath`: The namespaced path to the templates directory of the current chart (e.g. `mychart/templates`). + +The values are available to any top-level template. As we will see later, this does not necessarily mean that they will be available _everywhere_. + +The built-in values always begin with a capital letter. This is in keeping with Go's naming convention. When you create your own names, you are free to use a convention that suits your team. Some teams, like the [Kubernetes Charts](https://github.com/kubernetes/charts) team, choose to use only initial lower case letters in order to distinguish local names from those built-in. In this guide, we follow that convention. + diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/control_structures.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/control_structures.md new file mode 100644 index 000000000..7575ebc35 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/control_structures.md @@ -0,0 +1,354 @@ +# Flow Control + +Control structures (called "actions" in template parlance) provide you, the template author, with the ability to control the flow of a template's generation. Helm's template language provides the following control structures: + +- `if`/`else` for creating conditional blocks +- `with` to specify a scope +- `range`, which provides a "for each"-style loop + +In addition to these, it provides a few actions for declaring and using named template segments: + +- `define` declares a new named template inside of your template +- `template` imports a named template +- `block` declares a special kind of fillable template area + +In this section, we'll talk about `if`, `with`, and `range`. The others are covered in the "Named Templates" section later in this guide. + +## If/Else + +The first control structure we'll look at is for conditionally including blocks of text in a template. This is the `if`/`else` block. + +The basic structure for a conditional looks like this: + +``` +{{ if PIPELINE }} + # Do something +{{ else if OTHER PIPELINE }} + # Do something else +{{ else }} + # Default case +{{ end }} +``` + +Notice that we're now talking about _pipelines_ instead of values. The reason for this is to make it clear that control structures can execute an entire pipeline, not just evaluate a value. + +A pipeline is evaluated as _false_ if the value is: + +- a boolean false +- a numeric zero +- an empty string +- a `nil` (empty or null) +- an empty collection (`map`, `slice`, `tuple`, `dict`, `array`) + +Under all other conditions, the condition is true. + +Let's add a simple conditional to our ConfigMap. We'll add another setting if the drink is set to coffee: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | default "tea" | quote }} + food: {{ .Values.favorite.food | upper | quote }} + {{ if eq .Values.favorite.drink "coffee" }}mug: true{{ end }} +``` + +Since we commented out `drink: coffee` in our last example, the output should not include a `mug: true` flag. But if we add that line back into our `values.yaml` file, the output should look like this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: eyewitness-elk-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + mug: true +``` + +## Controlling Whitespace + +While we're looking at conditionals, we should take a quick look at the way whitespace is controlled in templates. Let's take the previous example and format it to be a little easier to read: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | default "tea" | quote }} + food: {{ .Values.favorite.food | upper | quote }} + {{if eq .Values.favorite.drink "coffee"}} + mug: true + {{end}} +``` + +Initially, this looks good. But if we run it through the template engine, we'll get an unfortunate result: + +```console +$ helm install --dry-run --debug ./mychart +SERVER: "localhost:44134" +CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart +Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: did not find expected key +``` + +What happened? We generated incorrect YAML because of the whitespacing above. + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: eyewitness-elk-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + mug: true +``` + +`mug` is incorrectly indented. Let's simply out-dent that one line, and re-run: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | default "tea" | quote }} + food: {{ .Values.favorite.food | upper | quote }} + {{if eq .Values.favorite.drink "coffee"}} + mug: true + {{end}} +``` + +When we sent that, we'll get YAML that is valid, but still looks a little funny: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: telling-chimp-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + + mug: true + +``` + +Notice that we received a few empty lines in our YAML. Why? When the template engine runs, it _removes_ the contents inside of `{{` and `}}`, but it leaves the remaining whitespace exactly as is. + +YAML ascribes meaning to whitespace, so managing the whitespace becomes pretty important. Fortunately, Helm templates have a few tools to help. + +First, the curly brace syntax of template declarations can be modified with special characters to tell the template engine to chomp whitespace. `{{- ` (with the dash and space added) indicates that whitespace should be chomped left, while ` -}}` means whitespace to the right should be consumed. _Be careful! Newlines are whitespace!_ + +> Make sure there is a space between the `-` and the rest of your directive. `{{- 3 }}` means "trim left whitespace and print 3" while `{{-3}}` means "print -3". + +Using this syntax, we can modify our template to get rid of those new lines: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | default "tea" | quote }} + food: {{ .Values.favorite.food | upper | quote }} + {{- if eq .Values.favorite.drink "coffee"}} + mug: true + {{- end}} +``` + +Just for the sake of making this point clear, let's adjust the above, and substitute an `*` for each whitespace that will be deleted following this rule. an `*` at the end of the line indicates a newline character that would be removed + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | default "tea" | quote }} + food: {{ .Values.favorite.food | upper | quote }}* +**{{- if eq .Values.favorite.drink "coffee"}} + mug: true* +**{{- end}} + +``` + +Keeping that in mind, we can run our template through Helm and see the result: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: clunky-cat-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + mug: true +``` + +Be careful with the chomping modifiers. It is easy to accidentally do things like this: + +```yaml + food: {{ .Values.favorite.food | upper | quote }} + {{- if eq .Values.favorite.drink "coffee" -}} + mug: true + {{- end -}} + +``` + +That will produce `food: "PIZZA"mug:true` because it consumed newlines on both sides. + +> For the details on whitespace control in templates, see the [Official Go template documentation](https://godoc.org/text/template) + +Finally, sometimes it's easier to tell the template system how to indent for you instead of trying to master the spacing of template directives. For that reason, you may sometimes find it useful to use the `indent` function (`{{indent 2 "mug:true"}}`). + +## Modifying scope using `with` + +The next control structure to look at is the `with` action. This controls variable scoping. Recall that `.` is a reference to _the current scope_. So `.Values` tells the template to find the `Values` object in the current scope. + +The syntax for `with` is similar to a simple `if` statement: + +``` +{{ with PIPELINE }} + # restricted scope +{{ end }} +``` + +Scopes can be changed. `with` can allow you to set the current scope (`.`) to a particular object. For example, we've been working with `.Values.favorites`. Let's rewrite our ConfigMap to alter the `.` scope to point to `.Values.favorites`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + {{- end }} +``` + +(Note that we removed the `if` conditional from the previous exercise) + +Notice that now we can reference `.drink` and `.food` without qualifying them. That is because the `with` statement sets `.` to point to `.Values.favorite`. The `.` is reset to its previous scope after `{{ end }}`. + +But here's a note of caution! Inside of the restricted scope, you will not be able to access the other objects from the parent scope. This, for example, will fail: + +```yaml + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + release: {{ .Release.Name }} + {{- end }} +``` + +It will produce an error because `Release.Name` is not inside of the restricted scope for `.`. However, if we swap the last two lines, all will work as expected because the scope is reset after `{{end}}`. + +```yaml + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + {{- end }} + release: {{ .Release.Name }} +``` + +After looking a `range`, we will take a look at template variables, which offer one solution to the scoping issue above. + +## Looping with the `range` action + +Many programming languages have support for looping using `for` loops, `foreach` loops, or similar functional mechanisms. In Helm's template language, the way to iterate through a collection is to use the `range` operator. + +To start, let's add a list of pizza toppings to our `values.yaml` file: + +```yaml +favorite: + drink: coffee + food: pizza +pizzaToppings: + - mushrooms + - cheese + - peppers + - onions +``` + +Now we have a list (called a `slice` in templates) of `pizzaToppings`. We can modify our template to print this list into our ConfigMap: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + {{- end }} + toppings: |- + {{- range .Values.pizzaToppings }} + - {{ . | title | quote }} + {{- end }} + +``` + +Let's take a closer look at the `toppings:` list. The `range` function will "range over" (iterate through) the `pizzaToppings` list. But now something interesting happens. Just like `with` sets the scope of `.`, so does a `range` operator. Each time through the loop, `.` is set to the current pizza topping. That is, the first time, `.` is set to `mushrooms`. The second iteration it is set to `cheese`, and so on. + +We can send the value of `.` directly down a pipeline, so when we do `{{ . | title | quote }}`, it sends `.` to `title` (title case function) and then to `quote`. If we run this template, the output will be: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: edgy-dragonfly-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + toppings: |- + - "Mushrooms" + - "Cheese" + - "Peppers" + - "Onions" +``` + +Now, in this example we've done something tricky. The `toppings: |-` line is declaring a multi-line string. So our list of toppings is actually not a YAML list. It's a big string. Why would we do this? Because the data in ConfigMaps `data` is composed of key/value pairs, where both the key and the value are simple strings. To understand why this is the case, take a look at the [Kubernetes ConfigMap docs](http://kubernetes.io/docs/user-guide/configmap/). For us, though, this detail doesn't matter much. + +> The `|-` marker in YAML takes a multi-line string. This can be a useful technique for embedding big blocks of data inside of your manifests, as exemplified here. + +Sometimes it's useful to be able to quickly make a list inside of your template, and then iterate over that list. Helm templates have a function to make this easy: `tuple`. In computer science, a tuple is a list-like collection of fixed size, but with arbitrary data types. This roughly conveys the way a `tuple` is used. + +```yaml + sizes: |- + {{- range tuple "small" "medium" "large" }} + - {{ . }} + {{- end }} +``` + +The above will produce this: + +```yaml + sizes: |- + - small + - medium + - large +``` + +In addition to lists and tuples, `range` can be used to iterate over collections that have a key and a value (like a `map` or `dict`). We'll see how to do that in the next section when we introduce template variables. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/data_types.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/data_types.md new file mode 100644 index 000000000..2e6a9f15b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/data_types.md @@ -0,0 +1,14 @@ +# Appendix: Go Data Types and Templates + +The Helm template language is implemented in the strongly typed Go programming language. For that reason, variables in templates are _typed_. For the most part, variables will be exposed as one of the following types: + +- string: A string of text +- bool: a `true` or `false` +- int: An integer value (there are also 8, 16, 32, and 64 bit signed and unsigned variants of this) +- float64: a 64-bit floating point value (there are also 8, 16, and 32 bit varieties of this) +- a byte slice (`[]byte`), often used to hold (potentially) binary data +- struct: an object with properties and methods +- a slice (indexed list) of one of the previous types +- a string-keyed map (`map[string]interface{}`) where the value is one of the previous types + +There are many other types in Go, and sometimes you will have to convert between them in your templates. The easiest way to debug an object's type is to pass it through `printf "%t"` in a template, which will print the type. Also see the `typeOf` and `kindOf` functions. \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/debugging.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/debugging.md new file mode 100644 index 000000000..fac788cc4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/debugging.md @@ -0,0 +1,30 @@ +# Debugging Templates + +Debugging templates can be tricky simply because the templates are rendered on the Tiller server, not the Helm client. And then the rendered templates are sent to the Kubernetes API server, which may reject the YAML files for reasons other than formatting. + +There are a few commands that can help you debug. + +- `helm lint` is your go-to tool for verifying that your chart follows best practices +- `helm install --dry-run --debug`: We've seen this trick already. It's a great way to have the server render your templates, then return the resulting manifest file. +- `helm get manifest`: This is a good way to see what templates are installed on the server. + +When your YAML is failing to parse, but you want to see what is generated, one +easy way to retrieve the YAML is to comment out the problem section in the template, +and then re-run `helm install --dry-run --debug`: + +```YAML +apiVersion: v1 +# some: problem section +# {{ .Values.foo | quote }} +``` + +The above will be rendered and returned with the comments intact: + +```YAML +apiVersion: v1 +# some: problem section +# "bar" +``` + +This provides a quick way of viewing the generated content without YAML parse +errors blocking. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/functions_and_pipelines.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/functions_and_pipelines.md new file mode 100644 index 000000000..54eb8e24f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/functions_and_pipelines.md @@ -0,0 +1,155 @@ +# Template Functions and Pipelines + +So far, we've seen how to place information into a template. But that information is placed into the template unmodified. Sometimes we want to transform the supplied data in a way that makes it more useable to us. + +Let's start with a best practice: When injecting strings from the `.Values` object into the template, we ought to quote these strings. We can do that by calling the `quote` function in the template directive: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ quote .Values.favorite.drink }} + food: {{ quote .Values.favorite.food }} +``` + +Template functions follow the syntax `functionName arg1 arg2...`. In the snippet above, `quote .Values.favorite.drink` calls the `quote` function and passes it a single argument. + +Helm has over 60 available functions. Some of them are defined by the [Go template language](https://godoc.org/text/template) itself. Most of the others are part of the [Sprig template library](https://godoc.org/github.com/Masterminds/sprig). We'll see many of them as we progress through the examples. + +> While we talk about the "Helm template language" as if it is Helm-specific, it is actually a combination of the Go template language, some extra functions, and a variety of wrappers to expose certain objects to the templates. Many resources on Go templates may be helpful as you learn about templating. + +## Pipelines + +One of the powerful features of the template language is its concept of _pipelines_. Drawing on a concept from UNIX, pipelines are a tool for chaining together a series of template commands to compactly express a series of transformations. In other words, pipelines are an efficient way of getting several things done in sequence. Let's rewrite the above example using a pipeline. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | quote }} + food: {{ .Values.favorite.food | quote }} +``` + +In this example, instead of calling `quote ARGUMENT`, we inverted the order. We "sent" the argument to the function using a pipeline (`|`): `.Values.favorite.drink | quote`. Using pipelines, we can chain several functions together: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | quote }} + food: {{ .Values.favorite.food | upper | quote }} +``` + +> Inverting the order is a common practice in templates. You will see `.val | quote` more often than `quote .val`. Either practice is fine. + +When evaluated, that template will produce this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: trendsetting-p-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" +``` + +Note that our original `pizza` has now been transformed to `"PIZZA"`. + +When pipelining arguments like this, the result of the first evaluation (`.Values.favorite.drink`) is sent as the _last argument to the function_. We can modify the drink example above to illustrate with a function that takes two arguments: `repeat COUNT STRING`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink | repeat 5 | quote }} + food: {{ .Values.favorite.food | upper | quote }} +``` + +The `repeat` function will echo the given string the given number of times, so we will get this for output: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: melting-porcup-configmap +data: + myvalue: "Hello World" + drink: "coffeecoffeecoffeecoffeecoffee" + food: "PIZZA" +``` + +## Using the `default` function + +One function frequently used in templates is the `default` function: `default DEFAULT_VALUE GIVEN_VALUE`. This function allows you to specify a default value inside of the template, in case the value is omitted. Let's use it to modify the drink example above: + +```yaml +drink: {{ .Values.favorite.drink | default "tea" | quote }} +``` + +If we run this as normal, we'll get our `coffee`: + +``` +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: virtuous-mink-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" +``` + +Now, we will remove the favorite drink setting from `values.yaml`: + +```yaml +favorite: + #drink: coffee + food: pizza +``` + +Now re-running `helm install --dry-run --debug ./mychart` will produce this YAML: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: fair-worm-configmap +data: + myvalue: "Hello World" + drink: "tea" + food: "PIZZA" +``` + +In an actual chart, all static default values should live in the values.yaml, and should not be repeated using the `default` command (otherwise they would be redundant). However, the `default` command is perfect for computed values, which can not be declared inside values.yaml. For example: + +```yaml +drink: {{ .Values.favorite.drink | default (printf "%s-tea" (include "fullname" .)) }} +``` + +In some places, an `if` conditional guard may be better suited than `default`. We'll see those in the next section. + +Template functions and pipelines are a powerful way to transform information and then insert it into your YAML. But sometimes it's necessary to add some template logic that is a little more sophisticated than just inserting a string. In the next section we will look at the control structures provided by the template language. + +## Operators are functions + +For templates, the operators (`eq`, `ne`, `lt`, `gt`, `and`, `or` and so on) are all implemented as functions. In pipelines, operations can be grouped with parentheses (`(`, and `)`). + +Now we can turn from functions and pipelines to flow control with conditions, loops, and scope modifiers. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/getting_started.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/getting_started.md new file mode 100644 index 000000000..87ae5fa3c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/getting_started.md @@ -0,0 +1,213 @@ +# Getting Started with a Chart Template + +In this section of the guide, we'll create a chart and then add a first template. The chart we created here will be used throughout the rest of the guide. + +To get going, let's take a brief look at a Helm chart. + +## Charts + +As described in the [Charts Guide](../charts.md), Helm charts are structured like +this: + +``` +mychart/ + Chart.yaml + values.yaml + charts/ + templates/ + ... +``` + +The `templates/` directory is for template files. When Tiller evaluates a chart, +it will send all of the files in the `templates/` directory through the +template rendering engine. Tiller then collects the results of those templates +and sends them on to Kubernetes. + +The `values.yaml` file is also important to templates. This file contains the +_default values_ for a chart. These values may be overridden by users during +`helm install` or `helm upgrade`. + +The `Chart.yaml` file contains a description of the chart. You can access it +from within a template. The `charts/` directory _may_ contain other charts (which +we call _subcharts_). Later in this guide we will see how those work when it +comes to template rendering. + +## A Starter Chart + +For this guide, we'll create a simple chart called `mychart`, and then we'll +create some templates inside of the chart. + +```console +$ helm create mychart +Creating mychart +``` + +From here on, we'll be working in the `mychart` directory. + +### A Quick Glimpse of `mychart/templates/` + +If you take a look at the `mychart/templates/` directory, you'll notice a few files +already there. + +- `NOTES.txt`: The "help text" for your chart. This will be displayed to your users + when they run `helm install`. +- `deployment.yaml`: A basic manifest for creating a Kubernetes [deployment](http://kubernetes.io/docs/user-guide/deployments/) +- `service.yaml`: A basic manifest for creating a [service endpoint](http://kubernetes.io/docs/user-guide/services/) for your deployment +- `_helpers.tpl`: A place to put template helpers that you can re-use throughout the chart + +And what we're going to do is... _remove them all!_ That way we can work through our tutorial from scratch. We'll actually create our own `NOTES.txt` and `_helpers.tpl` as we go. + +```console +$ rm -rf mychart/templates/*.* +``` + +When you're writing production grade charts, having basic versions of these charts can be really useful. So in your day-to-day chart authoring, you probably won't want to remove them. + +## A First Template + +The first template we are going to create will be a `ConfigMap`. In Kubernetes, +a ConfigMap is simply a container for storing configuration data. Other things, +like pods, can access the data in a ConfigMap. + +Because ConfigMaps are basic resources, they make a great starting point for us. + +Let's begin by creating a file called `mychart/templates/configmap.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: mychart-configmap +data: + myvalue: "Hello World" +``` + +**TIP:** Template names do not follow a rigid naming pattern. However, we recommend +using the suffix `.yaml` for YAML files and `.tpl` for helpers. + +The YAML file above is a bare-bones ConfigMap, having the minimal necessary fields. +In virtue of the fact that this file is in the `templates/` directory, it will +be sent through the template engine. + +It is just fine to put a plain YAML file like this in the `templates/` directory. +When Tiller reads this template, it will simply send it to Kubernetes as-is. + +With this simple template, we now have an installable chart. And we can install +it like this: + +```console +$ helm install ./mychart +NAME: full-coral +LAST DEPLOYED: Tue Nov 1 17:36:01 2016 +NAMESPACE: default +STATUS: DEPLOYED + +RESOURCES: +==> v1/ConfigMap +NAME DATA AGE +mychart-configmap 1 1m +``` + +In the output above, we can see that our ConfigMap was created. Using Helm, we +can retrieve the release and see the actual template that was loaded. + +```console +$ helm get manifest full-coral + +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: mychart-configmap +data: + myvalue: "Hello World" +``` + +The `helm get manifest` command takes a release name (`full-coral`) and prints +out all of the Kubernetes resources that were uploaded to the server. Each file +begins with `---` to indicate the start of a YAML document, and then is followed +by an automatically generated comment line that tells us what template file +generated this YAML document. + +From there on, we can see that the YAML data is exactly what we put in our +`configmap.yaml` file. + +Now we can delete our release: `helm delete full-coral`. + +### Adding a Simple Template Call + +Hard-coding the `name:` into a resource is usually considered to be bad practice. +Names should be unique to a release. So we might want to generate a name field +by inserting the release name. + +**TIP:** The `name:` field is limited to 63 characters because of limitations to +the DNS system. For that reason, release names are limited to 53 characters. +Kubernetes 1.3 and earlier limited to only 24 characters (thus 14 character names). + +Let's alter `configmap.yaml` accordingly. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" +``` + +The big change comes in the value of the `name:` field, which is now +`{{ .Release.Name }}-configmap`. + +> A template directive is enclosed in `{{` and `}}` blocks. + +The template directive `{{ .Release.Name }}` injects the release name into the template. The values that are passed into a template can be thought of as _namespaced objects_, where a dot (`.`) separates each namespaced element. + +The leading dot before `Release` indicates that we start with the top-most namespace for this scope (we'll talk about scope in a bit). So we could read `.Release.Name` as "start at the top namespace, find the `Release` object, then look inside of it for an object called `Name`". + +The `Release` object is one of the built-in objects for Helm, and we'll cover it in more depth later. But for now, it is sufficient to say that this will display the release name that Tiller assigns to our release. + +Now when we install our resource, we'll immediately see the result of using this template directive: + +```console +$ helm install ./mychart +NAME: clunky-serval +LAST DEPLOYED: Tue Nov 1 17:45:37 2016 +NAMESPACE: default +STATUS: DEPLOYED + +RESOURCES: +==> v1/ConfigMap +NAME DATA AGE +clunky-serval-configmap 1 1m +``` + +Note that in the `RESOURCES` section, the name we see there is `clunky-serval-configmap` +instead of `mychart-configmap`. + +You can run `helm get manifest clunky-serval` to see the entire generated YAML. + +At this point, we've seen templates at their most basic: YAML files that have template directives embedded in `{{` and `}}`. In the next part, we'll take a deeper look into templates. But before moving on, there's one quick trick that can make building templates faster: When you want to test the template rendering, but not actually install anything, you can use `helm install --debug --dry-run ./mychart`. This will send the chart to the Tiller server, which will render the templates. But instead of installing the chart, it will return the rendered template to you so you can see the output: + +```console +$ helm install --debug --dry-run ./mychart +SERVER: "localhost:44134" +CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart +NAME: goodly-guppy +TARGET NAMESPACE: default +CHART: mychart 0.1.0 +MANIFEST: +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: goodly-guppy-configmap +data: + myvalue: "Hello World" + +``` + +Using `--dry-run` will make it easier to test your code, but it won't ensure that Kubernetes itself will accept the templates you generate. It's best not to assume that your chart will install just because `--dry-run` works. + +In the next few sections, we'll take the basic chart we defined here and explore the Helm template language in detail. And we'll get started with built-in objects. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/index.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/index.md new file mode 100644 index 000000000..c2bcc8f42 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/index.md @@ -0,0 +1,16 @@ +# The Chart Template Developer's Guide + +This guide provides an introduction to Helm's chart templates, with emphasis on +the template language. + +Templates generate manifest files, which are YAML-formatted resource descriptions +that Kubernetes can understand. We'll look at how templates are structured, +how they can be used, how to write Go templates, and how to debug your work. + +This guide focuses on the following concepts: + +- The Helm template language +- Using values +- Techniques for working with templates + +This guide is oriented toward learning the ins and outs of the Helm template language. Other guides provide introductory material, examples, and best practices. \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/named_templates.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/named_templates.md new file mode 100644 index 000000000..042842427 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/named_templates.md @@ -0,0 +1,264 @@ +# Named Templates + +It is time to move beyond one template, and begin to create others. In this section, we will see how to define _named templates_ in one file, and then use them elsewhere. A _named template_ (sometimes called a _partial_ or a _subtemplate_) is simply a template defined inside of a file, and given a name. We'll see two ways to create them, and a few different ways to use them. + +In the "Flow Control" section we introduced three actions for declaring and managing templates: `define`, `template`, and `block`. In this section, we'll cover those three actions, and also introduce a special-purpose `include` function that works similarly to the `template` action. + +An important detail to keep in mind when naming templates: **template names are global**. If you declare two templates with the same name, whichever one is loaded last will be the one used. Because templates in subcharts are compiled together with top-level templates, you should be careful to name your templates with _chart-specific names_. + +One popular naming convention is to prefix each defined template with the name of the chart: `{{ define "mychart.labels" }}`. By using the specific chart name as a prefix we can avoid any conflicts that may arise due to two different charts that implement templates of the same name. + +## Partials and `_` files + +So far, we've used one file, and that one file has contained a single template. But Helm's template language allows you to create named embedded templates, that can be accessed by name elsewhere. + +Before we get to the nuts-and-bolts of writing those templates, there is file naming convention that deserves mention: + +* Most files in `templates/` are treated as if they contain Kubernetes manifests +* The `NOTES.txt` is one exception +* But files whose name begins with an underscore (`_`) are assumed to _not_ have a manifest inside. These files are not rendered to Kubernetes object definitions, but are available everywhere within other chart templates for use. + +These files are used to store partials and helpers. In fact, when we first created `mychart`, we saw a file called `_helpers.tpl`. That file is the default location for template partials. + +## Declaring and using templates with `define` and `template` + +The `define` action allows us to create a named template inside of a template file. Its syntax goes like this: + +```yaml +{{ define "MY.NAME" }} + # body of template here +{{ end }} +``` + +For example, we can define a template to encapsulate a Kubernetes block of labels: + +```yaml +{{- define "mychart.labels" }} + labels: + generator: helm + date: {{ now | htmlDate }} +{{- end }} +``` + +Now we can embed this template inside of our existing ConfigMap, and then include it with the `template` action: + +```yaml +{{- define "mychart.labels" }} + labels: + generator: helm + date: {{ now | htmlDate }} +{{- end }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap + {{- template "mychart.labels" }} +data: + myvalue: "Hello World" + {{- range $key, $val := .Values.favorite }} + {{ $key }}: {{ $val | quote }} + {{- end }} +``` + +When the template engine reads this file, it will store away the reference to `mychart.labels` until `template "mychart.labels"` is called. Then it will render that template inline. So the result will look like this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: running-panda-configmap + labels: + generator: helm + date: 2016-11-02 +data: + myvalue: "Hello World" + drink: "coffee" + food: "pizza" +``` + +Conventionally, Helm charts put these templates inside of a partials file, usually `_helpers.tpl`. Let's move this function there: + +```yaml +{{/* Generate basic labels */}} +{{- define "mychart.labels" }} + labels: + generator: helm + date: {{ now | htmlDate }} +{{- end }} +``` + +By convention, `define` functions should have a simple documentation block (`{{/* ... */}}`) describing what they do. + +Even though this definition is in `_helpers.tpl`, it can still be accessed in `configmap.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap + {{- template "mychart.labels" }} +data: + myvalue: "Hello World" + {{- range $key, $val := .Values.favorite }} + {{ $key }}: {{ $val | quote }} + {{- end }} +``` + +As mentioned above, **template names are global**. As a result of this, if two templates are declared with the same name the last occurance will be the one that is used. Since templates in subcharts are compiled together with top-level templates, it is best to name your templates with _chart specific names_. A popular naming convention is to prefix each defined template with the name of the chart: `{{ define "mychart.labels" }}`. + +## Setting the scope of a template + +In the template we defined above, we did not use any objects. We just used functions. Let's modify our defined template to include the chart name and chart version: + +```yaml +{{/* Generate basic labels */}} +{{- define "mychart.labels" }} + labels: + generator: helm + date: {{ now | htmlDate }} + chart: {{ .Chart.Name }} + version: {{ .Chart.Version }} +{{- end }} +``` + +If we render this, the result will not be what we expect: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: moldy-jaguar-configmap + labels: + generator: helm + date: 2016-11-02 + chart: + version: +``` + +What happened to the name and version? They weren't in the scope for our defined template. When a named template (created with `define`) is rendered, it will receive the scope passed in by the `template` call. In our example, we included the template like this: + +```yaml +{{- template "mychart.labels" }} +``` + +No scope was passed in, so within the template we cannot access anything in `.`. This is easy enough to fix, though. We simply pass a scope to the template: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap + {{- template "mychart.labels" . }} +``` + +Note that we pass `.` at the end of the `template` call. We could just as easily pass `.Values` or `.Values.favorite` or whatever scope we want. But what we want is the top-level scope. + +Now when we execute this template with `helm install --dry-run --debug ./mychart`, we get this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: plinking-anaco-configmap + labels: + generator: helm + date: 2016-11-02 + chart: mychart + version: 0.1.0 +``` + +Now `{{ .Chart.Name }}` resolves to `mychart`, and `{{ .Chart.Version }}` resolves to `0.1.0`. + +## The `include` function + +Say we've defined a simple template that looks like this: + +```yaml +{{- define "mychart.app" -}} +app_name: {{ .Chart.Name }} +app_version: "{{ .Chart.Version }}+{{ .Release.Time.Seconds }}" +{{- end -}} +``` + +Now say I want to insert this both into the `labels:` section of my template, and also the `data:` section: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap + labels: + {{ template "mychart.app" .}} +data: + myvalue: "Hello World" + {{- range $key, $val := .Values.favorite }} + {{ $key }}: {{ $val | quote }} + {{- end }} +{{ template "mychart.app" . }} +``` + +The output will not be what we expect: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: measly-whippet-configmap + labels: + app_name: mychart +app_version: "0.1.0+1478129847" +data: + myvalue: "Hello World" + drink: "coffee" + food: "pizza" + app_name: mychart +app_version: "0.1.0+1478129847" +``` + +Note that the indentation on `app_version` is wrong in both places. Why? Because the template that is substituted in has the text aligned to the right. Because `template` is an action, and not a function, there is no way to pass the output of a `template` call to other functions; the data is simply inserted inline. + +To work around this case, Helm provides an alternative to `template` that will import the contents of a template into the present pipeline where it can be passed along to other functions in the pipeline. + +Here's the example above, corrected to use `indent` to indent the `mychart_app` template correctly: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap + labels: +{{ include "mychart.app" . | indent 4 }} +data: + myvalue: "Hello World" + {{- range $key, $val := .Values.favorite }} + {{ $key }}: {{ $val | quote }} + {{- end }} +{{ include "mychart.app" . | indent 2 }} +``` + +Now the produced YAML is correctly indented for each section: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: edgy-mole-configmap + labels: + app_name: mychart + app_version: "0.1.0+1478129987" +data: + myvalue: "Hello World" + drink: "coffee" + food: "pizza" + app_name: mychart + app_version: "0.1.0+1478129987" +``` + +> It is considered preferable to use `include` over `template` in Helm templates simply so that the output formatting can be handled better for YAML documents. + +Sometimes we want to import content, but not as templates. That is, we want to import files verbatim. We can achieve this by accessing files through the `.Files` object described in the next section. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/notes_files.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/notes_files.md new file mode 100644 index 000000000..5a8b78ca4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/notes_files.md @@ -0,0 +1,45 @@ +# Creating a NOTES.txt File + +In this section we are going to look at Helm's tool for providing instructions to your chart users. At the end of a `chart install` or `chart upgrade`, Helm can print out a block of helpful information for users. This information is highly customizable using templates. + +To add installation notes to your chart, simply create a `templates/NOTES.txt` file. This file is plain text, but it is processed like as a template, and has all the normal template functions and objects available. + +Let's create a simple `NOTES.txt` file: + +``` +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get {{ .Release.Name }} + +``` + +Now if we run `helm install ./mychart` we will see this message at the bottom: + +``` +RESOURCES: +==> v1/Secret +NAME TYPE DATA AGE +rude-cardinal-secret Opaque 1 0s + +==> v1/ConfigMap +NAME DATA AGE +rude-cardinal-configmap 3 0s + + +NOTES: +Thank you for installing mychart. + +Your release is named rude-cardinal. + +To learn more about the release, try: + + $ helm status rude-cardinal + $ helm get rude-cardinal +``` + +Using `NOTES.txt` this way is a great way to give your users detailed information about how to use their newly installed chart. Creating a `NOTES.txt` file is strongly recommended, though it is not required. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/subcharts_and_globals.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/subcharts_and_globals.md new file mode 100644 index 000000000..33274effe --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/subcharts_and_globals.md @@ -0,0 +1,207 @@ +# Subcharts and Global Values + +To this point we have been working only with one chart. But charts can have dependencies, called _subcharts_, that also have their own values and templates. In this section we will create a subchart and see the different ways we can access values from within templates. + +Before we dive into the code, there are a few important details to learn about subcharts. + +1. A subchart is considered "stand-alone", which means a subchart can never explicitly depend on its parent chart. +2. For that reason, a subchart cannot access the values of its parent. +3. A parent chart can override values for subcharts. +4. Helm has a concept of _global values_ that can be accessed by all charts. + +As we walk through the examples in this section, many of these concepts will become clearer. + +## Creating a Subchart + +For these exercises, we'll start with the `mychart/` chart we created at the beginning of this guide, and we'll add a new chart inside of it. + +```console +$ cd mychart/charts +$ helm create mysubchart +Creating mysubchart +$ rm -rf mysubchart/templates/*.* +``` + +Notice that just as before, we deleted all of the base templates so that we can start from scratch. In this guide, we are focused on how templates work, not on managing dependencies. But the [Charts Guide](../charts.md) has more information on how subcharts work. + +## Adding Values and a Template to the Subchart + +Next, let's create a simple template and values file for our `mysubchart` chart. There should already be a `values.yaml` in `mychart/charts/mysubchart`. We'll set it up like this: + +```yaml +dessert: cake +``` + +Next, we'll create a new ConfigMap template in `mychart/charts/mysubchart/templates/configmap.yaml`: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-cfgmap2 +data: + dessert: {{ .Values.dessert }} +``` + +Because every subchart is a _stand-alone chart_, we can test `mysubchart` on its own: + +```console +$ helm install --dry-run --debug mychart/charts/mysubchart +SERVER: "localhost:44134" +CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart/charts/mysubchart +NAME: newbie-elk +TARGET NAMESPACE: default +CHART: mysubchart 0.1.0 +MANIFEST: +--- +# Source: mysubchart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: newbie-elk-cfgmap2 +data: + dessert: cake +``` + +## Overriding Values from a Parent Chart + +Our original chart, `mychart` is now the _parent_ chart of `mysubchart`. This relationship is based entirely on the fact that `mysubchart` is within `mychart/charts`. + +Because `mychart` is a parent, we can specify configuration in `mychart` and have that configuration pushed into `mysubchart`. For example, we can modify `mychart/values.yaml` like this: + +```yaml +favorite: + drink: coffee + food: pizza +pizzaToppings: + - mushrooms + - cheese + - peppers + - onions + +mysubchart: + dessert: ice cream +``` + +Note the last two lines. Any directives inside of the `mysubchart` section will be sent to the `mysubchart` chart. So if we run `helm install --dry-run --debug mychart`, one of the things we will see is the `mysubchart` ConfigMap: + +```yaml +# Source: mychart/charts/mysubchart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: unhinged-bee-cfgmap2 +data: + dessert: ice cream +``` + +The value at the top level has now overridden the value of the subchart. + +There's an important detail to notice here. We didn't change the template of `mychart/charts/mysubchart/templates/configmap.yaml` to point to `.Values.mysubchart.dessert`. From that template's perspective, the value is still located at `.Values.dessert`. As the template engine passes values along, it sets the scope. So for the `mysubchart` templates, only values specifically for `mysubchart` will be available in `.Values`. + +Sometimes, though, you do want certain values to be available to all of the templates. This is accomplished using global chart values. + +## Global Chart Values + +Global values are values that can be accessed from any chart or subchart by exactly the same name. Globals require explicit declaration. You can't use an existing non-global as if it were a global. + +The Values data type has a reserved section called `Values.global` where global values can be set. Let's set one in our `mychart/values.yaml` file. + +```yaml +favorite: + drink: coffee + food: pizza +pizzaToppings: + - mushrooms + - cheese + - peppers + - onions + +mysubchart: + dessert: ice cream + +global: + salad: caesar +``` + +Because of the way globals work, both `mychart/templates/configmap.yaml` and `mysubchart/templates/configmap.yaml` should be able to access that value as `{{ .Values.global.salad}}`. + +`mychart/templates/configmap.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + salad: {{ .Values.global.salad }} +``` + +`mysubchart/templates/configmap.yaml`: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-cfgmap2 +data: + dessert: {{ .Values.dessert }} + salad: {{ .Values.global.salad }} +``` + +Now if we run a dry run install, we'll see the same value in both outputs: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: silly-snake-configmap +data: + salad: caesar + +--- +# Source: mychart/charts/mysubchart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: silly-snake-cfgmap2 +data: + dessert: ice cream + salad: caesar +``` + +Globals are useful for passing information like this, though it does take some planning to make sure the right templates are configured to use globals. + +## Sharing Templates with Subcharts + +Parent charts and subcharts can share templates. Any defined block in any chart is +available to other charts. + +For example, we can define a simple template like this: + +```yaml +{{- define "labels" }}from: mychart{{ end }} +``` + +Recall how the labels on templates are _globally shared_. Thus, the `labels` chart +can be included from any other chart. + +While chart developers have a choice between `include` and `template`, one advantage +of using `include` is that `include` can dynamically reference templates: + +```yaml +{{ include $mytemplate }} +``` + +The above will dereference `$mytemplate`. The `template` function, in contrast, +will only accept a string literal. + +## Avoid Using Blocks + +The Go template language provides a `block` keyword that allows developers to provide +a default implementation which is overridden later. In Helm charts, blocks are not +the best tool for overriding because it if multiple implementations of the same block +are provided, the one selected is unpredictable. + +The suggestion is to instead use `include`. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/values_files.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/values_files.md new file mode 100644 index 000000000..32a178735 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/values_files.md @@ -0,0 +1,132 @@ +# Values Files + +In the previous section we looked at the built-in objects that Helm templates offer. One of the four built-in objects is `Values`. This object provides access to values passed into the chart. Its contents come from four sources: + +- The `values.yaml` file in the chart +- If this is a subchart, the `values.yaml` file of a parent chart +- A values file if passed into `helm install` or `helm upgrade` with the `-f` flag (`helm install -f myvals.yaml ./mychart`) +- Individual parameters passed with `--set` (such as `helm install --set foo=bar ./mychart`) + +The list above is in order of specificity: `values.yaml` is the default, which can be overridden by a parent chart's `values.yaml`, which can in turn be overridden by a user-supplied values file, which can in turn be overridden by `--set` parameters. + +Values files are plain YAML files. Let's edit `mychart/values.yaml` and then edit our ConfigMap template. + +Removing the defaults in `values.yaml`, we'll set just one parameter: + +```yaml +favoriteDrink: coffee +``` + +Now we can use this inside of a template: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favoriteDrink }} +``` + +Notice on the last line we access `favoriteDrink` as an attribute of `Values`: `{{ .Values.favoriteDrink}}`. + +Let's see how this renders. + +```console +$ helm install --dry-run --debug ./mychart +SERVER: "localhost:44134" +CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart +NAME: geared-marsupi +TARGET NAMESPACE: default +CHART: mychart 0.1.0 +MANIFEST: +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: geared-marsupi-configmap +data: + myvalue: "Hello World" + drink: coffee +``` + +Because `favoriteDrink` is set in the default `values.yaml` file to `coffee`, that's the value displayed in the template. We can easily override that by adding a `--set` flag in our call to `helm install`: + +``` +helm install --dry-run --debug --set favoriteDrink=slurm ./mychart +SERVER: "localhost:44134" +CHART PATH: /Users/mattbutcher/Code/Go/src/k8s.io/helm/_scratch/mychart +NAME: solid-vulture +TARGET NAMESPACE: default +CHART: mychart 0.1.0 +MANIFEST: +--- +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: solid-vulture-configmap +data: + myvalue: "Hello World" + drink: slurm +``` + +Since `--set` has a higher precedence than the default `values.yaml` file, our template generates `drink: slurm`. + +Values files can contain more structured content, too. For example, we could create a `favorite` section in our `values.yaml` file, and then add several keys there: + +```yaml +favorite: + drink: coffee + food: pizza +``` + +Now we would have to modify the template slightly: + +``` +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + drink: {{ .Values.favorite.drink }} + food: {{ .Values.favorite.food }} +``` + +While structuring data this way is possible, the recommendation is that you keep your values trees shallow, favoring flatness. When we look at assigning values to subcharts, we'll see how values are named using a tree structure. + +## Deleting a default key + +If you need to delete a key from the default values, you may override the value of the key to be `null`, in which case Helm will remove the key from the overridden values merge. + +For example, the stable Drupal chart allows configuring the liveness probe, in case you configure a custom image. Here are the default values: +```yaml +livenessProbe: + httpGet: + path: /user/login + port: http + initialDelaySeconds: 120 +``` + +If you try to override the livenessProbe handler to `exec` instead of `httpGet` using `--set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt]`, Helm will coalesce the default and overridden keys together, resulting in the following YAML: +```yaml +livenessProbe: + httpGet: + path: /user/login + port: http + exec: + command: + - cat + - docroot/CHANGELOG.txt + initialDelaySeconds: 120 +``` + +However, Kubernetes would then fail because you can not declare more than one livenessProbe handler. To overcome this, you may instruct Helm to delete the `livenessProbe.httpGet` by setting it to null: +```sh +helm install stable/drupal --set image=my-registry/drupal:0.1.0 --set livenessProbe.exec.command=[cat,docroot/CHANGELOG.txt] --set livenessProbe.httpGet=null +``` + +At this point, we've seen several built-in objects, and used them to inject information into a template. Now we will take a look at another aspect of the template engine: functions and pipelines. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/variables.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/variables.md new file mode 100644 index 000000000..b55e6e422 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/variables.md @@ -0,0 +1,129 @@ +# Variables + +With functions, pipelines, objects, and control structures under our belts, we can turn to one of the more basic ideas in many programming languages: variables. In templates, they are less frequently used. But we will see how to use them to simplify code, and to make better use of `with` and `range`. + +In an earlier example, we saw that this code will fail: + +```yaml + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + release: {{ .Release.Name }} + {{- end }} +``` + +`Release.Name` is not inside of the scope that's restricted in the `with` block. One way to work around scoping issues is to assign objects to variables that can be accessed without respect to the present scope. + +In Helm templates, a variable is a named reference to another object. It follows the form `$name`. Variables are assigned with a special assignment operator: `:=`. We can rewrite the above to use a variable for `Release.Name`. + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + {{- $relname := .Release.Name -}} + {{- with .Values.favorite }} + drink: {{ .drink | default "tea" | quote }} + food: {{ .food | upper | quote }} + release: {{ $relname }} + {{- end }} +``` + +Notice that before we start the `with` block, we assign `$relname := .Release.Name`. Now inside of the `with` block, the `$relname` variable still points to the release name. + +Running that will produce this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: viable-badger-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "PIZZA" + release: viable-badger +``` + +Variables are particularly useful in `range` loops. They can be used on list-like objects to capture both the index and the value: + +```yaml + toppings: |- + {{- range $index, $topping := .Values.pizzaToppings }} + {{ $index }}: {{ $topping }} + {{- end }} + +``` + +Note that `range` comes first, then the variables, then the assignment operator, then the list. This will assign the integer index (starting from zero) to `$index` and the value to `$topping`. Running it will produce: + +```yaml + toppings: |- + 0: mushrooms + 1: cheese + 2: peppers + 3: onions +``` + +For data structures that have both a key and a value, we can use `range` to get both. For example, we can loop through `.Values.favorite` like this: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-configmap +data: + myvalue: "Hello World" + {{- range $key, $val := .Values.favorite }} + {{ $key }}: {{ $val | quote }} + {{- end}} +``` + +Now on the first iteration, `$key` will be `drink` and `$val` will be `coffee`, and on the second, `$key` will be `food` and `$val` will be `pizza`. Running the above will generate this: + +```yaml +# Source: mychart/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: eager-rabbit-configmap +data: + myvalue: "Hello World" + drink: "coffee" + food: "pizza" +``` + +Variables are normally not "global". They are scoped to the block in which they are declared. Earlier, we assigned `$relname` in the top level of the template. That variable will be in scope for the entire template. But in our last example, `$key` and `$val` will only be in scope inside of the `{{range...}}{{end}}` block. + +However, there is one variable that is always global - `$` - this +variable will always point to the root context. This can be very +useful when you are looping in a range need to know the chart's release +name. + +An example illustrating this: +```yaml +{{- range .Values.tlsSecrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .name }} + labels: + # Many helm templates would use `.` below, but that will not work, + # however `$` will work here + app: {{ template "fullname" $ }} + # I cannot reference .Chart.Name, but I can do $.Chart.Name + chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" + release: "{{ $.Release.Name }}" + heritage: "{{ $.Release.Service }}" +type: kubernetes.io/tls +data: + tls.crt: {{ .certificate }} + tls.key: {{ .key }} +--- +{{- end }} +``` + +So far we have looked at just one template declared in just one file. But one of the powerful features of the Helm template language is its ability to declare multiple templates and use them together. We'll turn to that in the next section. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/wrapping_up.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/wrapping_up.md new file mode 100755 index 000000000..1ed7c602a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/wrapping_up.md @@ -0,0 +1,20 @@ +# Wrapping Up + +This guide is intended to give you, the chart developer, a strong understanding of how to use Helm's template language. The guide focuses on the technical aspects of template development. + +But there are many things this guide has not covered when it comes to the practical day-to-day development of charts. Here are some useful pointers to other documentation that will help you as you create new charts: + +- The [Kubernetes Charts project](https://github.com/kubernetes/charts) is an indispensable source of charts. That project is also sets the standard for best practices in chart development. +- The Kubernetes [User's Guide](http://kubernetes.io/docs/user-guide/) provides detailed examples of the various resource kinds that you can use, from ConfigMaps and Secrets to DaemonSets and Deployments. +- The Helm [Charts Guide](../charts.md) explains the workflow of using charts. +- The Helm [Chart Hooks Guide](../charts_hooks.md) explains how to create lifecycle hooks. +- The Helm [Charts Tips and Tricks](../charts_tips_and_tricks.md) article provides some useful tips for writing charts. +- The [Sprig documentation](https://github.com/Masterminds/sprig) documents more than sixty of the template functions. +- The [Go template docs](https://godoc.org/text/template) explain the template syntax in detail. +- The [Schelm tool](https://github.com/databus23/schelm) is a nice helper utility for debugging charts. + +Sometimes it's easier to ask a few questions and get answers from experienced developers. The best place to do that is in the Kubernetes `#Helm` Slack channel: + +- [Kubernetes Slack](https://slack.k8s.io/): `#helm` + +Finally, if you find errors or omissions in this document, want to suggest some new content, or would like to contribute, visit [The Helm Project](https://github.com/kubernetes/helm). diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/yaml_techniques.md b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/yaml_techniques.md new file mode 100644 index 000000000..44c41f903 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_template_guide/yaml_techniques.md @@ -0,0 +1,349 @@ +# YAML Techniques + +Most of this guide has been focused on writing the template language. Here, +we'll look at the YAML format. YAML has some useful features that we, as +template authors, can use to make our templates less error prone and easier +to read. + +## Scalars and Collections + +According to the [YAML spec](http://yaml.org/spec/1.2/spec.html), there are two +types of collections, and many scalar types. + +The two types of collections are maps and sequences: + +```yaml +map: + one: 1 + two: 2 + three: 3 + +sequence: + - one + - two + - three +``` + +Scalar values are individual values (as opposed to collections) + +### Scalar Types in YAML + +In Helm's dialect of YAML, the scalar data type of a value is determined by a +complex set of rules, including the Kubernetes schema for resource definitions. +But when inferring types, the following rules tend to hold true. + +If an integer or float is an unquoted bare word, it is typically treated as +a numeric type: + +```yaml +count: 1 +size: 2.34 +``` + +But if they are quoted, they are treated as strings: + +```yaml +count: "1" # <-- string, not int +size: '2.34' # <-- string, not float +``` + +The same is true of booleans: + +```yaml +isGood: true # bool +answer: "true" # string +``` + +The word for an empty value is `null` (not `nil`). + +Note that `port: "80"` is valid YAML, and will pass through both the +template engine and the YAML parser, but will fail if Kubernetes expects +`port` to be an integer. + +In some cases, you can force a particular type inference using YAML node tags: + +```yaml +coffee: "yes, please" +age: !!str 21 +port: !!int "80" +``` + +In the above, `!!str` tells the parser that `age` is a string, even if it looks +like an int. And `port` is treated as an int, even though it is quoted. + + +## Strings in YAML + +Much of the data that we place in YAML documents are strings. YAML has more than +one way to represent a string. This section explains the ways and demonstrates +how to use some of them. + +There are three "inline" ways of declaring a string: + +```yaml +way1: bare words +way2: "double-quoted strings" +way3: 'single-quoted strings' +``` + +All inline styles must be on one line. + +- Bare words are unquoted, and are not escaped. For this reason, you have to + be careful what characters you use. +- Double-quoted strings can have specific characters escaped with `\`. For + example `"\"Hello\", she said"`. You can escape line breaks with `\n`. +- Single-quoted strings are "literal" strings, and do not use the `\` to + escape characters. The only escape sequence is `''`, which is decoded as + a single `'`. + +In addition to the one-line strings, you can declare multi-line strings: + +```yaml +coffee: | + Latte + Cappuccino + Espresso +``` + +The above will treat the value of `coffee` as a single string equivalent to +`Latte\nCappuccino\nEspresso\n`. + +Note that the first line after the `|` must be correctly indented. So we could +break the example above by doing this: + +```yaml +coffee: | + Latte + Cappuccino + Espresso + +``` + +Because `Latte` is incorrectly indented, we'd get an error like this: + +``` +Error parsing file: error converting YAML to JSON: yaml: line 7: did not find expected key +``` + +In templates, it is sometimes safer to put a fake "first line" of content in a +multi-line document just for protection from the above error: + +```yaml +coffee: | + # Commented first line + Latte + Cappuccino + Espresso + +``` + +Note that whatever that first line is, it will be preserved in the output of the +string. So if you are, for example, using this technique to inject a file's contents +into a ConfigMap, the comment should be of the type expected by whatever is +reading that entry. + +### Controlling Spaces in Multi-line Strings + +In the example above, we used `|` to indicate a multi-line string. But notice +that the content of our string was followed with a trailing `\n`. If we want +the YAML processor to strip off the trailing newline, we can add a `-` after the +`|`: + +```yaml +coffee: |- + Latte + Cappuccino + Espresso +``` + +Now the `coffee` value will be: `Latte\nCappuccino\nEspresso` (with no trailing +`\n`). + +Other times, we might want all trailing whitespace to be preserved. We can do +this with the `|+` notation: + +```yaml +coffee: |+ + Latte + Cappuccino + Espresso + + +another: value +``` + +Now the value of `coffee` will be `Latte\nCappuccino\nEspresso\n\n\n`. + +Indentation inside of a text block is preserved, and results in the preservation +of line breaks, too: + +``` +coffee: |- + Latte + 12 oz + 16 oz + Cappuccino + Espresso +``` + +In the above case, `coffee` will be `Latte\n 12 oz\n 16 oz\nCappuccino\nEspresso`. + +### Indenting and Templates + +When writing templates, you may find yourself wanting to inject the contents of +a file into the template. As we saw in previous chapters, there are two ways +of doing this: + +- Use `{{ .Files.Get "FILENAME" }}` to get the contents of a file in the chart. +- Use `{{ include "TEMPLATE" . }}` to render a template and then place its + contents into the chart. + +When inserting files into YAML, it's good to understand the multi-line rules above. +Often times, the easiest way to insert a static file is to do something like +this: + +```yaml +myfile: | +{{ .Files.Get "myfile.txt" | indent 2 }} +``` + +Note how we do the indentation above: `indent 2` tells the template engine to +indent every line in "myfile.txt" with two spaces. Note that we do not indent +that template line. That's because if we did, the file content of the first line +would be indented twice. + +### Folded Multi-line Strings + +Sometimes you want to represent a string in your YAML with multiple lines, but +want it to be treated as one long line when it is interpreted. This is called +"folding". To declare a folded block, use `>` instead of `|`: + +```yaml +coffee: > + Latte + Cappuccino + Espresso + + +``` + +The value of `coffee` above will be `Latte Cappuccino Espresso\n`. Note that all +but the last line feed will be converted to spaces. You can combine the whitespace +controls with the folded text marker, so `>-` will replace or trim all newlines. + +Note that in the folded syntax, indenting text will cause lines to be preserved. + +```yaml +coffee: >- + Latte + 12 oz + 16 oz + Cappuccino + Espresso +``` + +The above will produce `Latte\n 12 oz\n 16 oz\nCappuccino Espresso`. Note that +both the spacing and the newlines are still there. + +## Embedding Multiple Documents in One File + +It is possible to place more than one YAML documents into a single file. This +is done by prefixing a new document with `---` and ending the document with +`...` + +```yaml + +--- +document:1 +... +--- +document: 2 +... +``` + +In many cases, either the `---` or the `...` may be omitted. + +Some files in Helm cannot contain more than one doc. If, for example, more +than one document is provided inside of a `values.yaml` file, only the first +will be used. + +Template files, however, may have more than one document. When this happens, +the file (and all of its documents) is treated as one object during +template rendering. But then the resulting YAML is split into multiple +documents before it is fed to Kubernetes. + +We recommend only using multiple documents per file when it is absolutely +necessary. Having multiple documents in a file can be difficult to debug. + +## YAML is a Superset of JSON + +Because YAML is a superset of JSON, any valid JSON document _should_ be valid +YAML. + +```json +{ + "coffee": "yes, please", + "coffees": [ + "Latte", "Cappuccino", "Espresso" + ] +} +``` + +The above is another way of representing this: + +```yaml +coffee: yes, please +coffees: +- Latte +- Cappuccino +- Espresso +``` + +And the two can be mixed (with care): + +```yaml +coffee: "yes, please" +coffees: [ "Latte", "Cappuccino", "Espresso"] +``` + +All three of these should parse into the same internal representation. + +While this means that files such as `values.yaml` may contain JSON data, Helm +does not treat the file extension `.json` as a valid suffix. + +## YAML Anchors + +The YAML spec provides a way to store a reference to a value, and later +refer to that value by reference. YAML refers to this as "anchoring": + +```yaml +coffee: "yes, please" +favorite: &favoriteCoffee "Cappucino" +coffees: + - Latte + - *favoriteCoffee + - Espresso +``` + +In the above, `&favoriteCoffee` sets a reference to `Cappuccino`. Later, that +reference is used as `*favoriteCoffee`. So `coffees` becomes +`Latte, Cappuccino, Espresso`. + +While there are a few cases where anchors are useful, there is one aspect of +them that can cause subtle bugs: The first time the YAML is consumed, the +reference is expanded and then discarded. + +So if we were to decode and then re-encode the example above, the resulting +YAML would be: + +```YAML +coffee: yes, please +favorite: Cappucino +coffees: +- Latte +- Cappucino +- Espresso +``` + +Because Helm and Kubernetes often read, modify, and then rewrite YAML files, +the anchors will be lost. diff --git a/src/vendor/github.com/kubernetes/helm/docs/chart_tests.md b/src/vendor/github.com/kubernetes/helm/docs/chart_tests.md new file mode 100644 index 000000000..d1cfe5017 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/chart_tests.md @@ -0,0 +1,83 @@ +# Chart Tests + +A chart contains a number of Kubernetes resources and components that work together. As a chart author, you may want to write some tests that validate that your chart works as expected when it is installed. These tests also help the chart consumer understand what your chart is supposed to do. + +A **test** in a helm chart lives under the `templates/` directory and is a pod definition that specifies a container with a given command to run. The container should exit successfully (exit 0) for a test to be considered a success. The pod definition must contain one of the helm test hook annotations: `helm.sh/hook: test-success` or `helm.sh/hook: test-failure`. + +Example tests: +- Validate that your configuration from the values.yaml file was properly injected. + - Make sure your username and password work correctly + - Make sure an incorrect username and password does not work +- Assert that your services are up and correctly load balancing +- etc. + +You can run the pre-defined tests in Helm on a release using the command `helm test `. For a chart consumer, this is a great way to sanity check that their release of a chart (or application) works as expected. + +## A Breakdown of the Helm Test Hooks + +In Helm, there are two test hooks: `test-success` and `test-failure` + +`test-success` indicates that test pod should complete successfully. In other words, the containers in the pod should exit 0. +`test-failure` is a way to assert that a test pod should not complete successfully. If the containers in the pod do not exit 0, that indicates success. + +## Example Test + +Here is an example of a helm test pod definition in an example mariadb chart: + +``` +mariadb/ + Chart.yaml + README.md + values.yaml + charts/ + templates/ + templates/tests/test-mariadb-connection.yaml +``` +In `wordpress/templates/tests/test-mariadb-connection.yaml`: +``` +apiVersion: v1 +kind: Pod +metadata: + name: "{{ .Release.Name }}-credentials-test" + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: {{ .Release.Name }}-credentials-test + image: {{ .Values.image }} + env: + - name: MARIADB_HOST + value: {{ template "mariadb.fullname" . }} + - name: MARIADB_PORT + value: "3306" + - name: WORDPRESS_DATABASE_NAME + value: {{ default "" .Values.mariadb.mariadbDatabase | quote }} + - name: WORDPRESS_DATABASE_USER + value: {{ default "" .Values.mariadb.mariadbUser | quote }} + - name: WORDPRESS_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ template "mariadb.fullname" . }} + key: mariadb-password + command: ["sh", "-c", "mysql --host=$MARIADB_HOST --port=$MARIADB_PORT --user=$WORDPRESS_DATABASE_USER --password=$WORDPRESS_DATABASE_PASSWORD"] + restartPolicy: Never +``` + +## Steps to Run a Test Suite on a Release +1. `$ helm install mariadb` +``` +NAME: quirky-walrus +LAST DEPLOYED: Mon Feb 13 13:50:43 2017 +NAMESPACE: default +STATUS: DEPLOYED +``` + +2. `$ helm test quirky-walrus` +``` +RUNNING: quirky-walrus-credentials-test +SUCCESS: quirky-walrus-credentials-test +``` + +## Notes +- You can define as many tests as you would like in a single yaml file or spread across several yaml files in the `templates/` directory +- You are welcome to nest your test suite under a `tests/` directory like `/templates/tests/` for more isolation diff --git a/src/vendor/github.com/kubernetes/helm/docs/charts.md b/src/vendor/github.com/kubernetes/helm/docs/charts.md new file mode 100644 index 000000000..8722e6862 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/charts.md @@ -0,0 +1,858 @@ +# Charts + +Helm uses a packaging format called _charts_. A chart is a collection of files +that describe a related set of Kubernetes resources. A single chart +might be used to deploy something simple, like a memcached pod, or +something complex, like a full web app stack with HTTP servers, +databases, caches, and so on. + +Charts are created as files laid out in a particular directory tree, +then they can be packaged into versioned archives to be deployed. + +This document explains the chart format, and provides basic guidance for +building charts with Helm. + +## The Chart File Structure + +A chart is organized as a collection of files inside of a directory. The +directory name is the name of the chart (without versioning information). Thus, +a chart describing WordPress would be stored in the `wordpress/` directory. + +Inside of this directory, Helm will expect a structure that matches this: + +``` +wordpress/ + Chart.yaml # A YAML file containing information about the chart + LICENSE # OPTIONAL: A plain text file containing the license for the chart + README.md # OPTIONAL: A human-readable README file + requirements.yaml # OPTIONAL: A YAML file listing dependencies for the chart + values.yaml # The default configuration values for this chart + charts/ # OPTIONAL: A directory containing any charts upon which this chart depends. + templates/ # OPTIONAL: A directory of templates that, when combined with values, + # will generate valid Kubernetes manifest files. + templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes +``` + +Helm reserves use of the `charts/` and `templates/` directories, and of +the listed file names. Other files will be left as they are. + +While the `charts` and `templates` directories are optional there must be at least one chart dependency or template file for the chart to be valid. + +## The Chart.yaml File + +The `Chart.yaml` file is required for a chart. It contains the following fields: + +```yaml +name: The name of the chart (required) +version: A SemVer 2 version (required) +kubeVersion: A SemVer range of compatible Kubernetes versions (optional) +description: A single-sentence description of this project (optional) +keywords: + - A list of keywords about this project (optional) +home: The URL of this project's home page (optional) +sources: + - A list of URLs to source code for this project (optional) +maintainers: # (optional) + - name: The maintainer's name (required for each maintainer) + email: The maintainer's email (optional for each maintainer) + url: A URL for the maintainer (optional for each maintainer) +engine: gotpl # The name of the template engine (optional, defaults to gotpl) +icon: A URL to an SVG or PNG image to be used as an icon (optional). +appVersion: The version of the app that this contains (optional). This needn't be SemVer. +deprecated: Whether this chart is deprecated (optional, boolean) +tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional) +``` + +If you are familiar with the `Chart.yaml` file format for Helm Classic, you will +notice that fields specifying dependencies have been removed. That is because +the new Chart format expresses dependencies using the `charts/` directory. + +Other fields will be silently ignored. + +### Charts and Versioning + +Every chart must have a version number. A version must follow the +[SemVer 2](http://semver.org/) standard. Unlike Helm Classic, Kubernetes +Helm uses version numbers as release markers. Packages in repositories +are identified by name plus version. + +For example, an `nginx` chart whose version field is set to `version: +1.2.3` will be named: + +``` +nginx-1.2.3.tgz +``` + +More complex SemVer 2 names are also supported, such as +`version: 1.2.3-alpha.1+ef365`. But non-SemVer names are explicitly +disallowed by the system. + +**NOTE:** Whereas Helm Classic and Deployment Manager were both +very GitHub oriented when it came to charts, Kubernetes Helm does not +rely upon or require GitHub or even Git. Consequently, it does not use +Git SHAs for versioning at all. + +The `version` field inside of the `Chart.yaml` is used by many of the +Helm tools, including the CLI and the Tiller server. When generating a +package, the `helm package` command will use the version that it finds +in the `Chart.yaml` as a token in the package name. The system assumes +that the version number in the chart package name matches the version number in +the `Chart.yaml`. Failure to meet this assumption will cause an error. + +### The appVersion field + +Note that the `appVersion` field is not related to the `version` field. It is +a way of specifying the version of the application. For example, the `drupal` +chart may have an `appVersion: 8.2.1`, indicating that the version of Drupal +included in the chart (by default) is `8.2.1`. This field is informational, and +has no impact on chart version calculations. + +### Deprecating Charts + +When managing charts in a Chart Repository, it is sometimes necessary to +deprecate a chart. The optional `deprecated` field in `Chart.yaml` can be used +to mark a chart as deprecated. If the **latest** version of a chart in the +repository is marked as deprecated, then the chart as a whole is considered to +be deprecated. The chart name can later be reused by publishing a newer version +that is not marked as deprecated. The workflow for deprecating charts, as +followed by the [kubernetes/charts](https://github.com/kubernetes/charts) +project is: + - Update chart's `Chart.yaml` to mark the chart as deprecated, bumping the + version + - Release the new chart version in the Chart Repository + - Remove the chart from the source repository (e.g. git) + +## Chart LICENSE, README and NOTES + +Charts can also contain files that describe the installation, configuration, usage and license of a +chart. A README for a chart should be formatted in Markdown (README.md), and should generally +contain: + +- A description of the application or service the chart provides +- Any prerequisites or requirements to run the chart +- Descriptions of options in `values.yaml` and default values +- Any other information that may be relevant to the installation or configuration of the chart + +The chart can also contain a short plain text `templates/NOTES.txt` file that will be printed out +after installation, and when viewing the status of a release. This file is evaluated as a +[template](#templates-and-values), and can be used to display usage notes, next steps, or any other +information relevant to a release of the chart. For example, instructions could be provided for +connecting to a database, or accessing a web UI. Since this file is printed to STDOUT when running +`helm install` or `helm status`, it is recommended to keep the content brief and point to the README +for greater detail. + +## Chart Dependencies + +In Helm, one chart may depend on any number of other charts. +These dependencies can be dynamically linked through the `requirements.yaml` +file or brought in to the `charts/` directory and managed manually. + +Although manually managing your dependencies has a few advantages some teams need, +the preferred method of declaring dependencies is by using a +`requirements.yaml` file inside of your chart. + +**Note:** The `dependencies:` section of the `Chart.yaml` from Helm +Classic has been completely removed. + + +### Managing Dependencies with `requirements.yaml` + +A `requirements.yaml` file is a simple file for listing your +dependencies. + +```yaml +dependencies: + - name: apache + version: 1.2.3 + repository: http://example.com/charts + - name: mysql + version: 3.2.1 + repository: http://another.example.com/charts +``` + +- The `name` field is the name of the chart you want. +- The `version` field is the version of the chart you want. +- The `repository` field is the full URL to the chart repository. Note + that you must also use `helm repo add` to add that repo locally. + +Once you have a dependencies file, you can run `helm dependency update` +and it will use your dependency file to download all the specified +charts into your `charts/` directory for you. + +```console +$ helm dep up foochart +Hang tight while we grab the latest from your chart repositories... +...Successfully got an update from the "local" chart repository +...Successfully got an update from the "stable" chart repository +...Successfully got an update from the "example" chart repository +...Successfully got an update from the "another" chart repository +Update Complete. Happy Helming! +Saving 2 charts +Downloading apache from repo http://example.com/charts +Downloading mysql from repo http://another.example.com/charts +``` + +When `helm dependency update` retrieves charts, it will store them as +chart archives in the `charts/` directory. So for the example above, one +would expect to see the following files in the charts directory: + +``` +charts/ + apache-1.2.3.tgz + mysql-3.2.1.tgz +``` + +Managing charts with `requirements.yaml` is a good way to easily keep +charts updated, and also share requirements information throughout a +team. + +#### Alias field in requirements.yaml + +In addition to the other fields above, each requirements entry may contain +the optional field `alias`. + +Adding an alias for a dependency chart would put +a chart in dependencies using alias as name of new dependency. + +One can use `alias` in cases where they need to access a chart +with other name(s). + +```yaml +# parentchart/requirements.yaml +dependencies: + - name: subchart + repository: http://localhost:10191 + version: 0.1.0 + alias: new-subchart-1 + - name: subchart + repository: http://localhost:10191 + version: 0.1.0 + alias: new-subchart-2 + - name: subchart + repository: http://localhost:10191 + version: 0.1.0 +``` + +In the above example we will get 3 dependencies in all for `parentchart` +``` +subchart +new-subchart-1 +new-subchart-2 +``` + +The manual way of achieving this is by copy/pasting the same chart in the +`charts/` directory multiple times with different names. + +#### Tags and Condition fields in requirements.yaml + +In addition to the other fields above, each requirements entry may contain +the optional fields `tags` and `condition`. + +All charts are loaded by default. If `tags` or `condition` fields are present, +they will be evaluated and used to control loading for the chart(s) they are applied to. + +Condition - The condition field holds one or more YAML paths (delimited by commas). +If this path exists in the top parent's values and resolves to a boolean value, +the chart will be enabled or disabled based on that boolean value. Only the first +valid path found in the list is evaluated and if no paths exist then the condition has no effect. + +Tags - The tags field is a YAML list of labels to associate with this chart. +In the top parent's values, all charts with tags can be enabled or disabled by +specifying the tag and a boolean value. + +```` +# parentchart/requirements.yaml +dependencies: + - name: subchart1 + repository: http://localhost:10191 + version: 0.1.0 + condition: subchart1.enabled, global.subchart1.enabled + tags: + - front-end + - subchart1 + + - name: subchart2 + repository: http://localhost:10191 + version: 0.1.0 + condition: subchart2.enabled,global.subchart2.enabled + tags: + - back-end + - subchart2 + +```` +```` +# parentchart/values.yaml + +subchart1: + enabled: true +tags: + front-end: false + back-end: true +```` + +In the above example all charts with the tag `front-end` would be disabled but since the +`subchart1.enabled` path evaluates to 'true' in the parent's values, the condition will override the +`front-end` tag and `subchart1` will be enabled. + +Since `subchart2` is tagged with `back-end` and that tag evaluates to `true`, `subchart2` will be +enabled. Also notes that although `subchart2` has a condition specified in `requirements.yaml`, there +is no corresponding path and value in the parent's values so that condition has no effect. + +##### Using the CLI with Tags and Conditions + +The `--set` parameter can be used as usual to alter tag and condition values. + +```` +helm install --set tags.front-end=true --set subchart2.enabled=false + +```` + +##### Tags and Condition Resolution + + + * **Conditions (when set in values) always override tags.** The first condition + path that exists wins and subsequent ones for that chart are ignored. + * Tags are evaluated as 'if any of the chart's tags are true then enable the chart'. + * Tags and conditions values must be set in the top parent's values. + * The `tags:` key in values must be a top level key. Globals and nested `tags:` tables + are not currently supported. + +#### Importing Child Values via requirements.yaml + +In some cases it is desirable to allow a child chart's values to propagate to the parent chart and be +shared as common defaults. An additional benefit of using the `exports` format is that it will enable future +tooling to introspect user-settable values. + +The keys containing the values to be imported can be specified in the parent chart's `requirements.yaml` file +using a YAML list. Each item in the list is a key which is imported from the child chart's `exports` field. + +To import values not contained in the `exports` key, use the [child-parent](#using-the-child-parent-format) format. +Examples of both formats are described below. + +##### Using the exports format + +If a child chart's `values.yaml` file contains an `exports` field at the root, its contents may be imported +directly into the parent's values by specifying the keys to import as in the example below: + +```yaml +# parent's requirements.yaml file + ... + import-values: + - data +``` +```yaml +# child's values.yaml file +... +exports: + data: + myint: 99 +``` + +Since we are specifying the key `data` in our import list, Helm looks in the `exports` field of the child +chart for `data` key and imports its contents. + +The final parent values would contain our exported field: + +```yaml +# parent's values file +... +myint: 99 + +``` + +Please note the parent key `data` is not contained in the parent's final values. If you need to specify the +parent key, use the 'child-parent' format. + +##### Using the child-parent format + +To access values that are not contained in the `exports` key of the child chart's values, you will need to +specify the source key of the values to be imported (`child`) and the destination path in the parent chart's +values (`parent`). + +The `import-values` in the example below instructs Helm to take any values found at `child:` path and copy them +to the parent's values at the path specified in `parent:` + +```yaml +# parent's requirements.yaml file +dependencies: + - name: subchart1 + repository: http://localhost:10191 + version: 0.1.0 + ... + import-values: + - child: default.data + parent: myimports +``` +In the above example, values found at `default.data` in the subchart1's values will be imported +to the `myimports` key in the parent chart's values as detailed below: + +```yaml +# parent's values.yaml file + +myimports: + myint: 0 + mybool: false + mystring: "helm rocks!" + +``` +```yaml +# subchart1's values.yaml file + +default: + data: + myint: 999 + mybool: true + +``` +The parent chart's resulting values would be: + +```yaml +# parent's final values + +myimports: + myint: 999 + mybool: true + mystring: "helm rocks!" + +``` + +The parent's final values now contains the `myint` and `mybool` fields imported from subchart1. + +### Managing Dependencies manually via the `charts/` directory + +If more control over dependencies is desired, these dependencies can +be expressed explicitly by copying the dependency charts into the +`charts/` directory. + +A dependency can be either a chart archive (`foo-1.2.3.tgz`) or an +unpacked chart directory. But its name cannot start with `_` or `.`. +Such files are ignored by the chart loader. + +For example, if the WordPress chart depends on the Apache chart, the +Apache chart (of the correct version) is supplied in the WordPress +chart's `charts/` directory: + +``` +wordpress: + Chart.yaml + requirements.yaml + # ... + charts/ + apache/ + Chart.yaml + # ... + mysql/ + Chart.yaml + # ... +``` + +The example above shows how the WordPress chart expresses its dependency +on Apache and MySQL by including those charts inside of its `charts/` +directory. + +**TIP:** _To drop a dependency into your `charts/` directory, use the +`helm fetch` command_ + +### Operational aspects of using dependencies + +The above sections explain how to specify chart dependencies, but how does this affect +chart installation using `helm install` and `helm upgrade`? + +Suppose that a chart named "A" creates the following Kubernetes objects + +- namespace "A-Namespace" +- statefulset "A-StatefulSet" +- service "A-Service" + +Furthermore, A is dependent on chart B that creates objects + +- namespace "B-Namespace" +- replicaset "B-ReplicaSet" +- service "B-Service" + +After installation/upgrade of chart A a single Helm release is created/modified. The release will +create/update all of the above Kubernetes objects in the following order: + +- A-Namespace +- B-Namespace +- A-StatefulSet +- B-ReplicaSet +- A-Service +- B-Service + +This is because when Helm installs/upgrades charts, +the Kubernetes objects from the charts and all its dependencies are + +- aggregrated into a single set; then +- sorted by type followed by name; and then +- created/updated in that order. + +Hence a single release is created with all the objects for the chart and its dependencies. + +The install order of Kubernetes types is given by the enumeration InstallOrder in kind_sorter.go +(see [the Helm source file](https://github.com/kubernetes/helm/blob/master/pkg/tiller/kind_sorter.go#L26)). + +## Templates and Values + +Helm Chart templates are written in the +[Go template language](https://golang.org/pkg/text/template/), with the +addition of 50 or so add-on template +functions [from the Sprig library](https://github.com/Masterminds/sprig) and a +few other [specialized functions](charts_tips_and_tricks.md). + +All template files are stored in a chart's `templates/` folder. When +Helm renders the charts, it will pass every file in that directory +through the template engine. + +Values for the templates are supplied two ways: + + - Chart developers may supply a file called `values.yaml` inside of a + chart. This file can contain default values. + - Chart users may supply a YAML file that contains values. This can be + provided on the command line with `helm install`. + +When a user supplies custom values, these values will override the +values in the chart's `values.yaml` file. + +### Template Files + +Template files follow the standard conventions for writing Go templates +(see [the text/template Go package documentation](https://golang.org/pkg/text/template/) +for details). +An example template file might look something like this: + +```yaml +apiVersion: v1 +kind: ReplicationController +metadata: + name: deis-database + namespace: deis + labels: + heritage: deis +spec: + replicas: 1 + selector: + app: deis-database + template: + metadata: + labels: + app: deis-database + spec: + serviceAccount: deis-database + containers: + - name: deis-database + image: {{.Values.imageRegistry}}/postgres:{{.Values.dockerTag}} + imagePullPolicy: {{.Values.pullPolicy}} + ports: + - containerPort: 5432 + env: + - name: DATABASE_STORAGE + value: {{default "minio" .Values.storage}} +``` + +The above example, based loosely on [https://github.com/deis/charts](https://github.com/deis/charts), is a template for a Kubernetes replication controller. +It can use the following four template values (usually defined in a +`values.yaml` file): + +- `imageRegistry`: The source registry for the Docker image. +- `dockerTag`: The tag for the docker image. +- `pullPolicy`: The Kubernetes pull policy. +- `storage`: The storage backend, whose default is set to `"minio"` + +All of these values are defined by the template author. Helm does not +require or dictate parameters. + +To see many working charts, check out the [Kubernetes Charts +project](https://github.com/kubernetes/charts) + +### Predefined Values + +Values that are supplied via a `values.yaml` file (or via the `--set` +flag) are accessible from the `.Values` object in a template. But there +are other pre-defined pieces of data you can access in your templates. + +The following values are pre-defined, are available to every template, and +cannot be overridden. As with all values, the names are _case +sensitive_. + +- `Release.Name`: The name of the release (not the chart) +- `Release.Time`: The time the chart release was last updated. This will + match the `Last Released` time on a Release object. +- `Release.Namespace`: The namespace the chart was released to. +- `Release.Service`: The service that conducted the release. Usually + this is `Tiller`. +- `Release.IsUpgrade`: This is set to true if the current operation is an upgrade or rollback. +- `Release.IsInstall`: This is set to true if the current operation is an + install. +- `Release.Revision`: The revision number. It begins at 1, and increments with + each `helm upgrade`. +- `Chart`: The contents of the `Chart.yaml`. Thus, the chart version is + obtainable as `Chart.Version` and the maintainers are in + `Chart.Maintainers`. +- `Files`: A map-like object containing all non-special files in the chart. This + will not give you access to templates, but will give you access to additional + files that are present (unless they are excluded using `.helmignore`). Files can be + accessed using `{{index .Files "file.name"}}` or using the `{{.Files.Get name}}` or + `{{.Files.GetString name}}` functions. You can also access the contents of the file + as `[]byte` using `{{.Files.GetBytes}}` +- `Capabilities`: A map-like object that contains information about the versions + of Kubernetes (`{{.Capabilities.KubeVersion}}`, Tiller + (`{{.Capabilities.TillerVersion}}`, and the supported Kubernetes API versions + (`{{.Capabilities.APIVersions.Has "batch/v1"`) + +**NOTE:** Any unknown Chart.yaml fields will be dropped. They will not +be accessible inside of the `Chart` object. Thus, Chart.yaml cannot be +used to pass arbitrarily structured data into the template. The values +file can be used for that, though. + +### Values files + +Considering the template in the previous section, a `values.yaml` file +that supplies the necessary values would look like this: + +```yaml +imageRegistry: "quay.io/deis" +dockerTag: "latest" +pullPolicy: "Always" +storage: "s3" +``` + +A values file is formatted in YAML. A chart may include a default +`values.yaml` file. The Helm install command allows a user to override +values by supplying additional YAML values: + +```console +$ helm install --values=myvals.yaml wordpress +``` + +When values are passed in this way, they will be merged into the default +values file. For example, consider a `myvals.yaml` file that looks like +this: + +```yaml +storage: "gcs" +``` + +When this is merged with the `values.yaml` in the chart, the resulting +generated content will be: + +```yaml +imageRegistry: "quay.io/deis" +dockerTag: "latest" +pullPolicy: "Always" +storage: "gcs" +``` + +Note that only the last field was overridden. + +**NOTE:** The default values file included inside of a chart _must_ be named +`values.yaml`. But files specified on the command line can be named +anything. + +**NOTE:** If the `--set` flag is used on `helm install` or `helm upgrade`, those +values are simply converted to YAML on the client side. + +**NOTE:** If any required entries in the values file exist, they can be declared +as required in the chart template by using the ['required' function](charts_tips_and_tricks.md) + +Any of these values are then accessible inside of templates using the +`.Values` object: + +```yaml +apiVersion: v1 +kind: ReplicationController +metadata: + name: deis-database + namespace: deis + labels: + heritage: deis +spec: + replicas: 1 + selector: + app: deis-database + template: + metadata: + labels: + app: deis-database + spec: + serviceAccount: deis-database + containers: + - name: deis-database + image: {{.Values.imageRegistry}}/postgres:{{.Values.dockerTag}} + imagePullPolicy: {{.Values.pullPolicy}} + ports: + - containerPort: 5432 + env: + - name: DATABASE_STORAGE + value: {{default "minio" .Values.storage}} + +``` + +### Scope, Dependencies, and Values + +Values files can declare values for the top-level chart, as well as for +any of the charts that are included in that chart's `charts/` directory. +Or, to phrase it differently, a values file can supply values to the +chart as well as to any of its dependencies. For example, the +demonstration WordPress chart above has both `mysql` and `apache` as +dependencies. The values file could supply values to all of these +components: + +```yaml +title: "My WordPress Site" # Sent to the WordPress template + +mysql: + max_connections: 100 # Sent to MySQL + password: "secret" + +apache: + port: 8080 # Passed to Apache +``` + +Charts at a higher level have access to all of the variables defined +beneath. So the WordPress chart can access the MySQL password as +`.Values.mysql.password`. But lower level charts cannot access things in +parent charts, so MySQL will not be able to access the `title` property. Nor, +for that matter, can it access `apache.port`. + +Values are namespaced, but namespaces are pruned. So for the WordPress +chart, it can access the MySQL password field as `.Values.mysql.password`. But +for the MySQL chart, the scope of the values has been reduced and the +namespace prefix removed, so it will see the password field simply as +`.Values.password`. + +#### Global Values + +As of 2.0.0-Alpha.2, Helm supports special "global" value. Consider +this modified version of the previous example: + +```yaml +title: "My WordPress Site" # Sent to the WordPress template + +global: + app: MyWordPress + +mysql: + max_connections: 100 # Sent to MySQL + password: "secret" + +apache: + port: 8080 # Passed to Apache +``` + +The above adds a `global` section with the value `app: MyWordPress`. +This value is available to _all_ charts as `.Values.global.app`. + +For example, the `mysql` templates may access `app` as `{{.Values.global.app}}`, and +so can the `apache` chart. Effectively, the values file above is +regenerated like this: + +```yaml +title: "My WordPress Site" # Sent to the WordPress template + +global: + app: MyWordPress + +mysql: + global: + app: MyWordPress + max_connections: 100 # Sent to MySQL + password: "secret" + +apache: + global: + app: MyWordPress + port: 8080 # Passed to Apache +``` + +This provides a way of sharing one top-level variable with all +subcharts, which is useful for things like setting `metadata` properties +like labels. + +If a subchart declares a global variable, that global will be passed +_downward_ (to the subchart's subcharts), but not _upward_ to the parent +chart. There is no way for a subchart to influence the values of the +parent chart. + +Also, global variables of parent charts take precedence over the global variables from subcharts. + +### References + +When it comes to writing templates and values files, there are several +standard references that will help you out. + +- [Go templates](https://godoc.org/text/template) +- [Extra template functions](https://godoc.org/github.com/Masterminds/sprig) +- [The YAML format](http://yaml.org/spec/) + +## Using Helm to Manage Charts + +The `helm` tool has several commands for working with charts. + +It can create a new chart for you: + +```console +$ helm create mychart +Created mychart/ +``` + +Once you have edited a chart, `helm` can package it into a chart archive +for you: + +```console +$ helm package mychart +Archived mychart-0.1.-.tgz +``` + +You can also use `helm` to help you find issues with your chart's +formatting or information: + +```console +$ helm lint mychart +No issues found +``` + +## Chart Repositories + +A _chart repository_ is an HTTP server that houses one or more packaged +charts. While `helm` can be used to manage local chart directories, when +it comes to sharing charts, the preferred mechanism is a chart +repository. + +Any HTTP server that can serve YAML files and tar files and can answer +GET requests can be used as a repository server. + +Helm comes with built-in package server for developer testing (`helm +serve`). The Helm team has tested other servers, including Google Cloud +Storage with website mode enabled, and S3 with website mode enabled. + +A repository is characterized primarily by the presence of a special +file called `index.yaml` that has a list of all of the packages supplied +by the repository, together with metadata that allows retrieving and +verifying those packages. + +On the client side, repositories are managed with the `helm repo` +commands. However, Helm does not provide tools for uploading charts to +remote repository servers. This is because doing so would add +substantial requirements to an implementing server, and thus raise the +barrier for setting up a repository. + +## Chart Starter Packs + +The `helm create` command takes an optional `--starter` option that lets you +specify a "starter chart". + +Starters are just regular charts, but are located in `$HELM_HOME/starters`. +As a chart developer, you may author charts that are specifically designed +to be used as starters. Such charts should be designed with the following +considerations in mind: + +- The `Chart.yaml` will be overwritten by the generator. +- Users will expect to modify such a chart's contents, so documentation + should indicate how users can do so. +- All occurences of `` will be replaced with the specified chart + name so that starter charts can be used as templates. + +Currently the only way to add a chart to `$HELM_HOME/starters` is to manually +copy it there. In your chart's documentation, you may want to explain that +process. diff --git a/src/vendor/github.com/kubernetes/helm/docs/charts_hooks.md b/src/vendor/github.com/kubernetes/helm/docs/charts_hooks.md new file mode 100644 index 000000000..af6d0f4f9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/charts_hooks.md @@ -0,0 +1,198 @@ +# Hooks + +Helm provides a _hook_ mechanism to allow chart developers to intervene +at certain points in a release's life cycle. For example, you can use +hooks to: + +- Load a ConfigMap or Secret during install before any other charts are + loaded. +- Execute a Job to back up a database before installing a new chart, + and then execute a second job after the upgrade in order to restore + data. +- Run a Job before deleting a release to gracefully take a service out + of rotation before removing it. + +Hooks work like regular templates, but they have special annotations +that cause Helm to utilize them differently. In this section, we cover +the basic usage pattern for hooks. + +## The Available Hooks + +The following hooks are defined: + +- pre-install: Executes after templates are rendered, but before any + resources are created in Kubernetes. +- post-install: Executes after all resources are loaded into Kubernetes +- pre-delete: Executes on a deletion request before any resources are + deleted from Kubernetes. +- post-delete: Executes on a deletion request after all of the release's + resources have been deleted. +- pre-upgrade: Executes on an upgrade request after templates are + rendered, but before any resources are loaded into Kubernetes (e.g. + before a Kubernetes apply operation). +- post-upgrade: Executes on an upgrade after all resources have been + upgraded. +- pre-rollback: Executes on a rollback request after templates are + rendered, but before any resources have been rolled back. +- post-rollback: Executes on a rollback request after all resources + have been modified. + +## Hooks and the Release Lifecycle + +Hooks allow you, the chart developer, an opportunity to perform +operations at strategic points in a release lifecycle. For example, +consider the lifecycle for a `helm install`. By default, the lifecycle +looks like this: + +1. User runs `helm install foo` +2. Chart is loaded into Tiller +3. After some verification, Tiller renders the `foo` templates +4. Tiller loads the resulting resources into Kubernetes +5. Tiller returns the release name (and other data) to the client +6. The client exits + +Helm defines two hooks for the `install` lifecycle: `pre-install` and +`post-install`. If the developer of the `foo` chart implements both +hooks, the lifecycle is altered like this: + +1. User runs `helm install foo` +2. Chart is loaded into Tiller +3. After some verification, Tiller renders the `foo` templates +4. Tiller prepares to execute the `pre-install` hooks (loading hook resources into + Kubernetes) +5. Tiller sorts hooks by weight (assigning a weight of 0 by default) and by name for those hooks with the same weight in ascending order. +6. Tiller then loads the hook with the lowest weight first (negative to positive) +7. Tiller waits until the hook is "Ready" +8. Tiller loads the resulting resources into Kubernetes. Note that if the `--wait` +flag is set, Tiller will wait until all resources are in a ready state +and will not run the `post-install` hook until they are ready. +9. Tiller executes the `post-install` hook (loading hook resources) +10. Tiller waits until the hook is "Ready" +11. Tiller returns the release name (and other data) to the client +12. The client exits + +What does it mean to wait until a hook is ready? This depends on the +resource declared in the hook. If the resources is a `Job` kind, Tiller +will wait until the job successfully runs to completion. And if the job +fails, the release will fail. This is a _blocking operation_, so the +Helm client will pause while the Job is run. + +For all other kinds, as soon as Kubernetes marks the resource as loaded +(added or updated), the resource is considered "Ready". When many +resources are declared in a hook, the resources are executed serially. If they +have hook weights (see below), they are executed in weighted order. Otherwise, +ordering is not guaranteed. (In Helm 2.3.0 and after, they are sorted +alphabetically. That behavior, though, is not considered binding and could change +in the future.) It is considered good practice to add a hook weight, and set it +to `0` if weight is not important. + + +### Hook resources are not managed with corresponding releases + +The resources that a hook creates are not tracked or managed as part of the +release. Once Tiller verifies that the hook has reached its ready state, it +will leave the hook resource alone. + +Practically speaking, this means that if you create resources in a hook, you +cannot rely upon `helm delete` to remove the resources. To destroy such +resources, you need to either write code to perform this operation in a `pre-delete` +or `post-delete` hook or add `"helm.sh/hook-delete-policy"` annotation to the hook template file. + +## Writing a Hook + +Hooks are just Kubernetes manifest files with special annotations in the +`metadata` section. Because they are template files, you can use all of +the normal template features, including reading `.Values`, `.Release`, +and `.Template`. + +For example, this template, stored in `templates/post-install-job.yaml`, +declares a job to be run on `post-install`: + +```yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: "{{.Release.Name}}" + labels: + heritage: {{.Release.Service | quote }} + release: {{.Release.Name | quote }} + chart: "{{.Chart.Name}}-{{.Chart.Version}}" + annotations: + # This is what defines this resource as a hook. Without this line, the + # job is considered part of the release. + "helm.sh/hook": post-install + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": hook-succeeded +spec: + template: + metadata: + name: "{{.Release.Name}}" + labels: + heritage: {{.Release.Service | quote }} + release: {{.Release.Name | quote }} + chart: "{{.Chart.Name}}-{{.Chart.Version}}" + spec: + restartPolicy: Never + containers: + - name: post-install-job + image: "alpine:3.3" + command: ["/bin/sleep","{{default "10" .Values.sleepyTime}}"] + +``` + +What makes this template a hook is the annotation: + +``` + annotations: + "helm.sh/hook": post-install +``` + +One resource can implement multiple hooks: + +``` + annotations: + "helm.sh/hook": post-install,post-upgrade +``` + +Similarly, there is no limit to the number of different resources that +may implement a given hook. For example, one could declare both a secret +and a config map as a pre-install hook. + +When subcharts declare hooks, those are also evaluated. There is no way +for a top-level chart to disable the hooks declared by subcharts. + +It is possible to define a weight for a hook which will help build a +deterministic executing order. Weights are defined using the following annotation: + +``` + annotations: + "helm.sh/hook-weight": "5" +``` + +Hook weights can be positive or negative numbers but must be represented as +strings. When Tiller starts the execution cycle of hooks of a particular Kind it +will sort those hooks in ascending order. + +It is also possible to define policies that determine when to delete corresponding hook resources. Hook deletion policies are defined using the following annotation: + +``` + annotations: + "helm.sh/hook-delete-policy": hook-succeeded +``` + +You can choose one or more defined annotation values: +* `"hook-succeeded"` specifies Tiller should delete the hook after the hook is successfully executed. +* `"hook-failed"` specifies Tiller should delete the hook if the hook failed during execution. +* `"before-hook-creation"` specifies Tiller should delete the previous hook before the new hook is launched. + +### Automatically delete hook from previous release + +When helm release being updated it is possible, that hook resource already exists in cluster. By default helm will try to create resource and fail with `"... already exists"` error. + +One might choose `"helm.sh/hook-delete-policy": "before-hook-creation"` over `"helm.sh/hook-delete-policy": "hook-succeeded,hook-failed"` because: + +* It is convinient to keep failed hook job resource in kubernetes for example for manual debug. +* It may be necessary to keep succeeded hook resource in kubernetes for some reason. +* At the same time it is not desireable to do manual resource deletion before helm release upgrade. + +`"helm.sh/hook-delete-policy": "before-hook-creation"` annotation on hook causes tiller to remove the hook from previous release if there is one before the new hook is launched and can be used with another policies. diff --git a/src/vendor/github.com/kubernetes/helm/docs/charts_tips_and_tricks.md b/src/vendor/github.com/kubernetes/helm/docs/charts_tips_and_tricks.md new file mode 100644 index 000000000..484d8b936 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/charts_tips_and_tricks.md @@ -0,0 +1,250 @@ +# Chart Development Tips and Tricks + +This guide covers some of the tips and tricks Helm chart developers have +learned while building production-quality charts. + +## Know Your Template Functions + +Helm uses [Go templates](https://godoc.org/text/template) for templating +your resource files. While Go ships several built-in functions, we have +added many others. + +First, we added almost all of the functions in the +[Sprig library](https://godoc.org/github.com/Masterminds/sprig). We removed two +for security reasons: `env` and `expandenv` (which would have given chart authors +access to Tiller's environment). + +We also added two special template functions: `include` and `required`. The `include` +function allows you to bring in another template, and then pass the results to other +template functions. + +For example, this template snippet includes a template called `mytpl`, then +lowercases the result, then wraps that in double quotes. + +```yaml +value: {{include "mytpl" . | lower | quote}} +``` + +The `required` function allows you to declare a particular +values entry as required for template rendering. If the value is empty, the template +rendering will fail with a user submitted error message. + +The following example of the `required` function declares an entry for .Values.who +is required, and will print an error message when that entry is missing: + +```yaml +value: {{required "A valid .Values.who entry required!" .Values.who }} +``` + +## Quote Strings, Don't Quote Integers + +When you are working with string data, you are always safer quoting the +strings than leaving them as bare words: + +``` +name: {{.Values.MyName | quote }} +``` + +But when working with integers _do not quote the values._ That can, in +many cases, cause parsing errors inside of Kubernetes. + +``` +port: {{ .Values.Port }} +``` + +This remark does not apply to env variables values which are expected to be string, even if they represent integers: + +``` +env: + -name: HOST + value: "http://host" + -name: PORT + value: "1234" +``` + +## Using the 'include' Function + +Go provides a way of including one template in another using a built-in +`template` directive. However, the built-in function cannot be used in +Go template pipelines. + +To make it possible to include a template, and then perform an operation +on that template's output, Helm has a special `include` function: + +``` +{{ include "toYaml" $value | indent 2 }} +``` + +The above includes a template called `toYaml`, passes it `$value`, and +then passes the output of that template to the `indent` function. + +Because YAML ascribes significance to indentation levels and whitespace, +this is one great way to include snippets of code, but handle +indentation in a relevant context. + +## Using the 'required' function + +Go provides a way for setting template options to control behavior +when a map is indexed with a key that's not present in the map. This +is typically set with template.Options("missingkey=option"), where option +can be default, zero, or error. While setting this option to error will +stop execution with an error, this would apply to every missing key in the +map. There may be situations where a chart developer wants to enforce this +behavior for select values in the values.yml file. + +The `required` function gives developers the ability to declare a value entry +as required for template rendering. If the entry is empty in values.yml, the +template will not render and will return an error message supplied by the +developer. + +For example: + +``` +{{ required "A valid foo is required!" .Values.foo }} +``` + +The above will render the template when .Values.foo is defined, but will fail +to render and exit when .Values.foo is undefined. + +## Creating Image Pull Secrets +Image pull secrets are essentially a combination of _registry_, _username_, and _password_. You may need them in an application you are deploying, but to create them requires running _base64_ a couple of times. We can write a helper template to compose the Docker configuration file for use as the Secret's payload. Here is an example: + +First, assume that the credentials are defined in the `values.yaml` file like so: +``` +imageCredentials: + registry: quay.io + username: someone + password: sillyness +``` + +We then define our helper template as follows: +``` +{{- define "imagePullSecret" }} +{{- printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" .Values.imageCredentials.registry (printf "%s:%s" .Values.imageCredentials.username .Values.imageCredentials.password | b64enc) | b64enc }} +{{- end }} +``` + +Finally, we use the helper template in a larger template to create the Secret manifest: +``` +apiVersion: v1 +kind: Secret +metadata: + name: myregistrykey +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: {{ template "imagePullSecret" . }} +``` + +## Automatically Roll Deployments When ConfigMaps or Secrets change + +Often times configmaps or secrets are injected as configuration +files in containers. +Depending on the application a restart may be required should those +be updated with a subsequent `helm upgrade`, but if the +deployment spec itself didn't change the application keeps running +with the old configuration resulting in an inconsistent deployment. + +The `sha256sum` function can be used to ensure a deployment's +annotation section is updated if another file changes: + +```yaml +kind: Deployment +spec: + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} +[...] +``` + +See also the `helm upgrade --recreate-pods` flag for a slightly +different way of addressing this issue. + +## Tell Tiller Not To Delete a Resource + +Sometimes there are resources that should not be deleted when Helm runs a +`helm delete`. Chart developers can add an annotation to a resource to prevent +it from being deleted. + +```yaml +kind: Secret +metadata: + annotations: + "helm.sh/resource-policy": keep +[...] +``` + +(Quotation marks are required) + +The annotation `"helm.sh/resource-policy": keep` instructs Tiller to skip this +resource during a `helm delete` operation. _However_, this resource becomes +orphaned. Helm will no longer manage it in any way. This can lead to problems +if using `helm install --replace` on a release that has already been deleted, but +has kept resources. + +## Using "Partials" and Template Includes + +Sometimes you want to create some reusable parts in your chart, whether +they're blocks or template partials. And often, it's cleaner to keep +these in their own files. + +In the `templates/` directory, any file that begins with an +underscore(`_`) is not expected to output a Kubernetes manifest file. So +by convention, helper templates and partials are placed in a +`_helpers.tpl` file. + +## Complex Charts with Many Dependencies + +Many of the charts in the [official charts repository](https://github.com/kubernetes/charts) +are "building blocks" for creating more advanced applications. But charts may be +used to create instances of large-scale applications. In such cases, a single +umbrella chart may have multiple subcharts, each of which functions as a piece +of the whole. + +The current best practice for composing a complex application from discrete parts +is to create a top-level umbrella chart that +exposes the global configurations, and then use the `charts/` subdirectory to +embed each of the components. + +Two strong design patterns are illustrated by these projects: + +**SAP's [OpenStack chart](https://github.com/sapcc/openstack-helm):** This chart +installs a full OpenStack IaaS on Kubernetes. All of the charts are collected +together in one GitHub repository. + +**Deis's [Workflow](https://github.com/deis/workflow/tree/master/charts/workflow):** +This chart exposes the entire Deis PaaS system with one chart. But it's different +from the SAP chart in that this umbrella chart is built from each component, and +each component is tracked in a different Git repository. Check out the +`requirements.yaml` file to see how this chart is composed by their CI/CD +pipeline. + +Both of these charts illustrate proven techniques for standing up complex environments +using Helm. + +## YAML is a Superset of JSON + +According to the YAML specification, YAML is a superset of JSON. That +means that any valid JSON structure ought to be valid in YAML. + +This has an advantage: Sometimes template developers may find it easier +to express a datastructure with a JSON-like syntax rather than deal with +YAML's whitespace sensitivity. + +As a best practice, templates should follow a YAML-like syntax _unless_ +the JSON syntax substantially reduces the risk of a formatting issue. + +## Be Careful with Generating Random Values + +There are functions in Helm that allow you to generate random data, +cryptographic keys, and so on. These are fine to use. But be aware that +during upgrades, templates are re-executed. When a template run +generates data that differs from the last run, that will trigger an +update of that resource. + +## Upgrade a release idempotently + +In order to use the same command when installing and upgrading a release, use the following command: +```shell +helm upgrade --install --values +``` diff --git a/src/vendor/github.com/kubernetes/helm/docs/developers.md b/src/vendor/github.com/kubernetes/helm/docs/developers.md new file mode 100644 index 000000000..5095bf1a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/developers.md @@ -0,0 +1,238 @@ +# Developers Guide + +This guide explains how to set up your environment for developing on +Helm and Tiller. + +## Prerequisites + +- The latest version of Go +- The latest version of Glide +- A Kubernetes cluster w/ kubectl (optional) +- The gRPC toolchain +- Git +- Mercurial + +## Building Helm/Tiller + +We use Make to build our programs. The simplest way to get started is: + +```console +$ make bootstrap build +``` + +NOTE: This will fail if not running from the path `$GOPATH/src/k8s.io/helm`. The +directory `k8s.io` should not be a symlink or `build` will not find the relevant +packages. + +This will build both Helm and Tiller. `make bootstrap` will attempt to +install certain tools if they are missing. + +To run all the tests (without running the tests for `vendor/`), run +`make test`. + +To run Helm and Tiller locally, you can run `bin/helm` or `bin/tiller`. + +- Helm and Tiller are known to run on macOS and most Linuxes, including + Alpine. +- Tiller must have access to a Kubernetes cluster. It learns about the + cluster by examining the Kube config files that `kubectl` uses. + +### Man pages + +Man pages and Markdown documentation are already pre-built in `docs/`. You may +regenerate documentation using `make docs`. + +To expose the Helm man pages to your `man` client, you can put the files in your +`$MANPATH`: + +``` +$ export MANPATH=$GOPATH/src/k8s.io/helm/docs/man:$MANPATH +$ man helm +``` + +## gRPC and Protobuf + +Helm and Tiller communicate using gRPC. To get started with gRPC, you will need to... + +- Install `protoc` for compiling protobuf files. Releases are + [here](https://github.com/google/protobuf/releases) +- Run Helm's `make bootstrap` to generate the `protoc-gen-go` plugin and + place it in `bin/`. + +Note that you need to be on protobuf 3.2.0 (`protoc --version`). The +version of `protoc-gen-go` is tied to the version of gRPC used in +Kubernetes. So the plugin is maintained locally. + +While the gRPC and ProtoBuf specs remain silent on indentation, we +require that the indentation style matches the Go format specification. +Namely, protocol buffers should use tab-based indentation and rpc +declarations should follow the style of Go function declarations. + +### The Helm API (HAPI) + +We use gRPC as an API layer. See `pkg/proto/hapi` for the generated Go code, +and `_proto` for the protocol buffer definitions. + +To regenerate the Go files from the protobuf source, `make protoc`. + +## Docker Images + +To build Docker images, use `make docker-build`. + +Pre-build images are already available in the official Kubernetes Helm +GCR registry. + +## Running a Local Cluster + +For development, we highly recommend using the +[Kubernetes Minikube](https://github.com/kubernetes/minikube) +developer-oriented distribution. Once this is installed, you can use +`helm init` to install into the cluster. Note that version of tiller you're using for +development may not be available in Google Cloud Container Registry. If you're getting +image pull errors, you can override the version of Tiller. Example: + +```console +helm init --tiller-image=gcr.io/kubernetes-helm/tiller:2.7.2 +``` + +Or use the latest version: + +```console +helm init --canary-image +``` + +For developing on Tiller, it is sometimes more expedient to run Tiller locally +instead of packaging it into an image and running it in-cluster. You can do +this by telling the Helm client to us a local instance. + +```console +$ make build +$ bin/tiller +``` + +And to configure the Helm client, use the `--host` flag or export the `HELM_HOST` +environment variable: + +```console +$ export HELM_HOST=localhost:44134 +$ helm install foo +``` + +(Note that you do not need to use `helm init` when you are running Tiller directly) + +Tiller should run on any >= 1.3 Kubernetes cluster. + +## Contribution Guidelines + +We welcome contributions. This project has set up some guidelines in +order to ensure that (a) code quality remains high, (b) the project +remains consistent, and (c) contributions follow the open source legal +requirements. Our intent is not to burden contributors, but to build +elegant and high-quality open source code so that our users will benefit. + +Make sure you have read and understood the main CONTRIBUTING guide: + +https://github.com/kubernetes/helm/blob/master/CONTRIBUTING.md + +### Structure of the Code + +The code for the Helm project is organized as follows: + +- The individual programs are located in `cmd/`. Code inside of `cmd/` + is not designed for library re-use. +- Shared libraries are stored in `pkg/`. +- The raw ProtoBuf files are stored in `_proto/hapi` (where `hapi` stands for + the Helm Application Programming Interface). +- The Go files generated from the `proto` definitions are stored in `pkg/proto`. +- The `scripts/` directory contains a number of utility scripts. Most of these + are used by the CI/CD pipeline. +- The `rootfs/` folder is used for Docker-specific files. +- The `docs/` folder is used for documentation and examples. + +Go dependencies are managed with +[Glide](https://github.com/Masterminds/glide) and stored in the +`vendor/` directory. + +### Git Conventions + +We use Git for our version control system. The `master` branch is the +home of the current development candidate. Releases are tagged. + +We accept changes to the code via GitHub Pull Requests (PRs). One +workflow for doing this is as follows: + +1. Go to your `$GOPATH/src/k8s.io` directory and `git clone` the + `github.com/kubernetes/helm` repository. +2. Fork that repository into your GitHub account +3. Add your repository as a remote for `$GOPATH/src/k8s.io/helm` +4. Create a new working branch (`git checkout -b feat/my-feature`) and + do your work on that branch. +5. When you are ready for us to review, push your branch to GitHub, and + then open a new pull request with us. + +For Git commit messages, we follow the [Semantic Commit Messages](http://karma-runner.github.io/0.13/dev/git-commit-msg.html): + +``` +fix(helm): add --foo flag to 'helm install' + +When 'helm install --foo bar' is run, this will print "foo" in the +output regardless of the outcome of the installation. + +Closes #1234 +``` + +Common commit types: + +- fix: Fix a bug or error +- feat: Add a new feature +- docs: Change documentation +- test: Improve testing +- ref: refactor existing code + +Common scopes: + +- helm: The Helm CLI +- tiller: The Tiller server +- proto: Protobuf definitions +- pkg/lint: The lint package. Follow a similar convention for any + package +- `*`: two or more scopes + +Read more: +- The [Deis Guidelines](https://github.com/deis/workflow/blob/master/src/contributing/submitting-a-pull-request.md) + were the inspiration for this section. +- Karma Runner [defines](http://karma-runner.github.io/0.13/dev/git-commit-msg.html) the semantic commit message idea. + +### Go Conventions + +We follow the Go coding style standards very closely. Typically, running +`go fmt` will make your code beautiful for you. + +We also typically follow the conventions recommended by `go lint` and +`gometalinter`. Run `make test-style` to test the style conformance. + +Read more: + +- Effective Go [introduces formatting](https://golang.org/doc/effective_go.html#formatting). +- The Go Wiki has a great article on [formatting](https://github.com/golang/go/wiki/CodeReviewComments). + +### Protobuf Conventions + +Because this project is largely Go code, we format our Protobuf files as +closely to Go as possible. There are currently no real formatting rules +or guidelines for Protobuf, but as they emerge, we may opt to follow +those instead. + +Standards: +- Tabs for indentation, not spaces. +- Spacing rules follow Go conventions (curly braces at line end, spaces + around operators). + +Conventions: +- Files should specify their package with `option go_package = "...";` +- Comments should translate into good Go code comments (since `protoc` + copies comments into the destination source code file). +- RPC functions are defined in the same file as their request/response + messages. +- Deprecated RPCs, messages, and fields are marked deprecated in the comments (`// UpdateFoo + DEPRECATED updates a foo.`). diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/README.md b/src/vendor/github.com/kubernetes/helm/docs/examples/README.md new file mode 100644 index 000000000..723040ca8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/README.md @@ -0,0 +1,19 @@ +# Helm Examples + +This directory contains example charts to help you get started with +chart development. + +## Alpine + +The `alpine` chart is very simple, and is a good starting point. + +It simply deploys a single pod running Alpine Linux. + +## Nginx + +The `nginx` chart shows how to compose several resources into one chart, +and it illustrates more complex template usage. + +It deploys a `deployment` (which creates a `replica set`), a `config +map`, and a `service`. The replica set starts an nginx pod. The config +map stores the files that the nginx server can serve. diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/Chart.yaml new file mode 100644 index 000000000..f4b660d4f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/Chart.yaml @@ -0,0 +1,7 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://github.com/kubernetes/helm +sources: + - https://github.com/kubernetes/helm +appVersion: 3.3 diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/README.md b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/README.md new file mode 100644 index 000000000..3e354724c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/README.md @@ -0,0 +1,11 @@ +# Alpine: A simple Helm chart + +Run a single pod of Alpine Linux. + +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 docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/_helpers.tpl b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/_helpers.tpl new file mode 100644 index 000000000..3e9c25bed --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/_helpers.tpl @@ -0,0 +1,16 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "alpine.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 "alpine.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..da9caef78 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/templates/alpine-pod.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{ template "alpine.fullname" . }} + labels: + # The "heritage" label is used to track which tool deployed a given chart. + # It is useful for admins who want to see what releases a particular tool + # is responsible for. + heritage: {{ .Release.Service }} + # The "release" convention makes it easy to tie a release to all of the + # Kubernetes resources that were created as part of that release. + release: {{ .Release.Name }} + # This makes it easy to audit chart usage. + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "alpine.name" . }} +spec: + # This shows how to use a simple value. This will look for a passed-in value called restartPolicy. + restartPolicy: {{ .Values.restartPolicy }} + containers: + - name: waiter + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: ["/bin/sleep", "9000"] diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/values.yaml new file mode 100644 index 000000000..afe8cc6c0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/alpine/values.yaml @@ -0,0 +1,6 @@ +image: + repository: alpine + tag: 3.3 + pullPolicy: IfNotPresent + +restartPolicy: Never diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/.helmignore b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/.helmignore new file mode 100644 index 000000000..435b756d8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/.helmignore @@ -0,0 +1,5 @@ +# 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/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/Chart.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/Chart.yaml new file mode 100644 index 000000000..807455210 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/Chart.yaml @@ -0,0 +1,15 @@ +name: nginx +description: A basic NGINX HTTP server +version: 0.1.0 +kubeVersion: ">=1.2.0" +keywords: + - http + - nginx + - www + - web +home: https://github.com/kubernetes/helm +sources: + - https://hub.docker.com/_/nginx/ +maintainers: + - name: technosophos + email: mbutcher@deis.com diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/README.md b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/README.md new file mode 100644 index 000000000..e7a02e578 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/README.md @@ -0,0 +1,33 @@ +# nginx: An advanced example chart + +This Helm chart provides examples of some of Helm's more powerful +features. + +**This is not a production-grade chart. It is an example.** + +The chart installs a simple nginx server according to the following +pattern: + +- A `ConfigMap` is used to store the files the server will serve. + ([templates/configmap.yaml](templates/configmap.yaml)) +- A `Deployment` is used to create a Replica Set of nginx pods. + ([templates/deployment.yaml](templates/deployment.yaml)) +- A `Service` is used to create a gateway to the pods running in the + replica set ([templates/service.yaml](templates/service.yaml)) + +The [values.yaml](values.yaml) exposes a few of the configuration options in the +charts, though there are some that are not exposed there (like +`.image`). + +The [templates/_helpers.tpl](templates/_helpers.tpl) file contains helper templates. The leading +underscore (`_`) on the filename is semantic. It tells the template renderer +that this file does not contain a manifest. That file declares some +templates that are used elsewhere in the chart. + +Helpers (usually called "partials" in template languages) are an +advanced way for developers to structure their templates for optimal +reuse. + +You can deploy this chart with `helm install docs/examples/nginx`. Or +you can see how this chart would render with `helm install --dry-run +--debug docs/examples/nginx`. diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/_helpers.tpl b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/_helpers.tpl new file mode 100644 index 000000000..2ec6ba757 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/_helpers.tpl @@ -0,0 +1,16 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "nginx.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 "nginx.fullname" -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/configmap.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/configmap.yaml new file mode 100644 index 000000000..b90d6c0c7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/configmap.yaml @@ -0,0 +1,14 @@ +# This is a simple example of using a config map to create a single page static site. +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "nginx.fullname" . }} + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "nginx.name" . }} +data: + # When the config map is mounted as a volume, these will be created as files. + index.html: {{ .Values.index | quote }} + test.txt: test diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/deployment.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/deployment.yaml new file mode 100644 index 000000000..5fa2633ea --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/deployment.yaml @@ -0,0 +1,57 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + # This uses a "fullname" template (see _helpers) + # Basing names on .Release.Name means that the same chart can be installed + # multiple times into the same namespace. + name: {{ template "nginx.fullname" . }} + labels: + # The "heritage" label is used to track which tool deployed a given chart. + # It is useful for admins who want to see what releases a particular tool + # is responsible for. + heritage: {{ .Release.Service }} + # The "release" convention makes it easy to tie a release to all of the + # Kubernetes resources that were created as part of that release. + release: {{ .Release.Name }} + # This makes it easy to audit chart usage. + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "nginx.name" . }} +spec: + replicas: {{ .Values.replicaCount }} + template: + metadata: +{{- if .Values.podAnnotations }} + # Allows custom annotations to be specified + annotations: +{{ toYaml .Values.podAnnotations | indent 8 }} +{{- end }} + labels: + app: {{ template "nginx.name" . }} + release: {{ .Release.Name }} + spec: + containers: + - name: {{ template "nginx.name" . }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 80 + protocol: TCP + # This (and the volumes section below) mount the config map as a volume. + volumeMounts: + - mountPath: /usr/share/nginx/html + name: wwwdata-volume + resources: +# Allow chart users to specify resources. Usually, no default should be set, so this is left to be a conscious +# choice to the chart users and avoids that charts don't run out of the box on, e. g., Minikube when high resource +# requests are specified by default. +{{ toYaml .Values.resources | indent 12 }} + {{- if .Values.nodeSelector }} + nodeSelector: + # Node selectors can be important on mixed Windows/Linux clusters. +{{ toYaml .Values.nodeSelector | indent 8 }} + {{- end }} + volumes: + - name: wwwdata-volume + configMap: + name: {{ template "nginx.fullname" . }} diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/post-install-job.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/post-install-job.yaml new file mode 100644 index 000000000..9ec90cd0a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/post-install-job.yaml @@ -0,0 +1,37 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "nginx.fullname" . }} + labels: + # The "heritage" label is used to track which tool deployed a given chart. + # It is useful for admins who want to see what releases a particular tool + # is responsible for. + heritage: {{ .Release.Service }} + # The "release" convention makes it easy to tie a release to all of the + # Kubernetes resources that were created as part of that release. + release: {{ .Release.Name }} + # This makes it easy to audit chart usage. + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "nginx.name" . }} + annotations: + # This is what defines this resource as a hook. Without this line, the + # job is considered part of the release. + "helm.sh/hook": post-install +spec: + template: + metadata: + name: {{ template "nginx.fullname" . }} + labels: + release: {{ .Release.Name }} + app: {{ template "nginx.name" . }} + spec: + # This shows how to use a simple value. This will look for a passed-in value + # called restartPolicy. If it is not found, it will use the default value. + # {{ default "Never" .restartPolicy }} is a slightly optimized version of the + # more conventional syntax: {{ .restartPolicy | default "Never" }} + restartPolicy: {{ .Values.restartPolicy }} + containers: + - name: post-install-job + image: "alpine:3.3" + # All we're going to do is sleep for a while, then exit. + command: ["/bin/sleep", "{{ .Values.sleepyTime }}"] diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/pre-install-secret.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/pre-install-secret.yaml new file mode 100644 index 000000000..6392f9684 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/pre-install-secret.yaml @@ -0,0 +1,19 @@ +# This shows a secret as a pre-install hook. +# A pre-install hook is run before the rest of the chart is loaded. +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "nginx.fullname" . }} + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "nginx.name" . }} + # This declares the resource to be a hook. By convention, we also name the + # file "pre-install-XXX.yaml", but Helm itself doesn't care about file names. + annotations: + "helm.sh/hook": pre-install +type: Opaque +data: + password: {{ b64enc "secret" }} + username: {{ b64enc "user1" }} diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service-test.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service-test.yaml new file mode 100644 index 000000000..3913ead9c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service-test.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ template "nginx.fullname" . }}-service-test" + labels: + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + app: {{ template "nginx.name" . }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: curl + image: radial/busyboxplus:curl + command: ['curl'] + args: ['{{ template "nginx.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service.yaml new file mode 100644 index 000000000..1481e34f0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/templates/service.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +kind: Service +metadata: +{{- if .Values.service.annotations }} + annotations: +{{ toYaml .Values.service.annotations | indent 4 }} +{{- end }} + labels: + app: {{ template "nginx.name" . }} + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + name: {{ template "nginx.fullname" . }} +spec: +# Provides options for the service so chart users have the full choice + type: "{{ .Values.service.type }}" + clusterIP: "{{ .Values.service.clusterIP }}" +{{- if .Values.service.externalIPs }} + externalIPs: +{{ toYaml .Values.service.externalIPs | indent 4 }} +{{- end }} +{{- if .Values.service.loadBalancerIP }} + loadBalancerIP: "{{ .Values.service.loadBalancerIP }}" +{{- end }} +{{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml .Values.service.loadBalancerSourceRanges | indent 4 }} +{{- end }} + ports: + - name: http + port: {{ .Values.service.port }} + protocol: TCP + targetPort: http + {{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} + nodePort: {{ .Values.service.nodePort }} + {{- end }} + selector: + app: {{ template "nginx.name" . }} + release: {{ .Release.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/values.yaml b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/values.yaml new file mode 100644 index 000000000..b40208cce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/examples/nginx/values.yaml @@ -0,0 +1,34 @@ +# Default values for nginx. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. + +replicaCount: 1 +restartPolicy: Never + +# Evaluated by the post-install hook +sleepyTime: "10" + +index: >- +

Hello

+

This is a test

+ +image: + repository: nginx + tag: 1.11.0 + pullPolicy: IfNotPresent + +service: + annotations: {} + clusterIP: "" + externalIPs: [] + loadBalancerIP: "" + loadBalancerSourceRanges: [] + type: ClusterIP + port: 8888 + nodePort: "" + +podAnnotations: {} + +resources: {} + +nodeSelector: {} diff --git a/src/vendor/github.com/kubernetes/helm/docs/glossary.md b/src/vendor/github.com/kubernetes/helm/docs/glossary.md new file mode 100644 index 000000000..875807268 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/glossary.md @@ -0,0 +1,170 @@ +# Helm Glossary + +Helm uses a few special terms to describe components of the +architecture. + +## Chart + +A Helm package that contains information sufficient for installing a set +of Kubernetes resources into a Kubernetes cluster. + +Charts contain a `Chart.yaml` file as well as templates, default values +(`values.yaml`), and dependencies. + +Charts are developed in a well-defined directory structure, and then +packaged into an archive format called a _chart archive_. + +## Chart Archive + +A _chart archive_ is a tarred and gzipped (and optionally signed) chart. + +## Chart Dependency (Subcharts) + +Charts may depend upon other charts. There are two ways a dependency may +occur: + +- Soft dependency: A chart may simply not function without another chart + being installed in a cluster. Helm does not provide tooling for this + case. In this case, dependencies may be managed separately. +- Hard dependency: A chart may contain (inside of its `charts/` + directory) another chart upon which it depends. In this case, + installing the chart will install all of its dependencies. In this + case, a chart and its dependencies are managed as a collection. + +When a chart is packaged (via `helm package`) all of its hard dependencies +are bundled with it. + +## Chart Version + +Charts are versioned according to the [SemVer 2 +spec](http://semver.org). A version number is required on every chart. + +## Chart.yaml + +Information about a chart is stored in a special file called +`Chart.yaml`. Every chart must have this file. + +## Helm (and helm) + +Helm is the package manager for Kubernetes. As an operating system +package manager makes it easy to install tools on an OS, Helm makes it +easy to install applications and resources into Kubernetes clusters. + +While _Helm_ is the name of the project, the command line client is also +named `helm`. By convention, when speaking of the project, _Helm_ is +capitalized. When speaking of the client, _helm_ is in lowercase. + +## Helm Home (HELM_HOME) + +The Helm client stores information in a local directory referred to as +_helm home_. By default, this is in the `$HOME/.helm` directory. + +This directory contains configuration and cache data, and is created by +`helm init`. + +## Kube Config (KUBECONFIG) + +The Helm client learns about Kubernetes clusters by using files in the _Kube +config_ file format. By default, Helm attempts to find this file in the +place where `kubectl` creates it (`$HOME/.kube/config`). + +## Lint (Linting) + +To _lint_ a chart is to validate that it follows the conventions and +requirements of the Helm chart standard. Helm provides tools to do this, +notably the `helm lint` command. + +## Provenance (Provenance file) + +Helm charts may be accompanied by a _provenance file_ which provides +information about where the chart came from and what it contains. + +Provenance files are one part of the Helm security story. A provenance contains +a cryptographic hash of the chart archive file, the Chart.yaml data, and +a signature block (an OpenPGP "clearsign" block). When coupled with a +keychain, this provides chart users with the ability to: + +- Validate that a chart was signed by a trusted party +- Validate that the chart file has not been tampered with +- Validate the contents of a chart metadata (`Chart.yaml`) +- Quickly match a chart to its provenance data + +Provenance files have the `.prov` extension, and can be served from a +chart repository server or any other HTTP server. + +## Release + +When a chart is installed, Tiller (the Helm server) creates a _release_ +to track that installation. + +A single chart may be installed many times into the same cluster, and +create many different releases. For example, one can install three +PostgreSQL databases by running `helm install` three times with a +different release name. + +(Prior to 2.0.0-Alpha.1, releases were called _deployments_. But this +caused confusion with the Kubernetes _Deployment_ kind.) + +## Release Number (Release Version) + +A single release can be updated multiple times. A sequential counter is +used to track releases as they change. After a first `helm install`, a +release will have _release number_ 1. Each time a release is upgraded or +rolled back, the release number will be incremented. + +## Rollback + +A release can be upgraded to a newer chart or configuration. But since +release history is stored, a release can also be _rolled back_ to a +previous release number. This is done with the `helm rollback` command. + +Importantly, a rolled back release will receive a new release number. + +Operation | Release Number +----------|--------------- +install | release 1 +upgrade | release 2 +upgrade | release 3 +rollback 1| release 4 (but running the same config as release 1) + +The above table illustrates how release numbers increment across +install, upgrade, and rollback. + +## Tiller + +Tiller is the in-cluster component of Helm. It interacts directly with +the Kubernetes API server to install, upgrade, query, and remove +Kubernetes resources. It also stores the objects that represent +releases. + +## Repository (Repo, Chart Repository) + +Helm charts may be stored on dedicated HTTP servers called _chart +repositories_ (_repositories_, or just _repos_). + +A chart repository server is a simple HTTP server that can serve an +`index.yaml` file that describes a batch of charts, and provides +information on where each chart can be downloaded from. (Many chart +repositories serve the charts as well as the `index.yaml` file.) + +A Helm client can point to zero or more chart repositories. By default, +Helm clients point to the `stable` official Kubernetes chart +repository. + +## Values (Values Files, values.yaml) + +Values provide a way to override template defaults with your own +information. + +Helm Charts are "parameterized", which means the chart developer may +expose configuration that can be overridden at installation time. For +example, a chart may expose a `username` field that allows setting a +user name for a service. + +These exposed variables are called _values_ in Helm parlance. + +Values can be set during `helm install` and `helm upgrade` operations, +either by passing them in directly, or by uploading a `values.yaml` +file. + + diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm.md new file mode 100644 index 000000000..8592cad7c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm.md @@ -0,0 +1,71 @@ +## helm + +The Helm package manager for Kubernetes. + +### Synopsis + + +The Kubernetes package manager + +To begin working with Helm, run the 'helm init' command: + + $ helm init + +This will install Tiller to your running Kubernetes cluster. +It will also set up any necessary local configuration. + +Common actions from this point include: + +- helm search: search for charts +- helm fetch: download a chart to your local directory to view +- helm install: upload the chart to Kubernetes +- helm list: list releases of charts + +Environment: + $HELM_HOME set an alternative location for Helm files. By default, these are stored in ~/.helm + $HELM_HOST set an alternative Tiller host. The format is host:port + $HELM_NO_PLUGINS disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins. + $TILLER_NAMESPACE set an alternative Tiller namespace (default "kube-system") + $KUBECONFIG set an alternative Kubernetes configuration file (default "~/.kube/config") + + +### Options + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm completion](helm_completion.md) - Generate autocompletions script for the specified shell (bash or zsh) +* [helm create](helm_create.md) - create a new chart with the given name +* [helm delete](helm_delete.md) - given a release name, delete the release from Kubernetes +* [helm dependency](helm_dependency.md) - manage a chart's dependencies +* [helm fetch](helm_fetch.md) - download a chart from a repository and (optionally) unpack it in local directory +* [helm get](helm_get.md) - download a named release +* [helm history](helm_history.md) - fetch release history +* [helm home](helm_home.md) - displays the location of HELM_HOME +* [helm init](helm_init.md) - initialize Helm on both client and server +* [helm inspect](helm_inspect.md) - inspect a chart +* [helm install](helm_install.md) - install a chart archive +* [helm lint](helm_lint.md) - examines a chart for possible issues +* [helm list](helm_list.md) - list releases +* [helm package](helm_package.md) - package a chart directory into a chart archive +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories +* [helm reset](helm_reset.md) - uninstalls Tiller from a cluster +* [helm rollback](helm_rollback.md) - roll back a release to a previous revision +* [helm search](helm_search.md) - search for a keyword in charts +* [helm serve](helm_serve.md) - start a local http web server +* [helm status](helm_status.md) - displays the status of the named release +* [helm template](helm_template.md) - locally render templates +* [helm test](helm_test.md) - test a release +* [helm upgrade](helm_upgrade.md) - upgrade a release +* [helm verify](helm_verify.md) - verify that a chart at the given path has been signed and is valid +* [helm version](helm_version.md) - print the client/server version information + +###### Auto generated by spf13/cobra on 14-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_completion.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_completion.md new file mode 100644 index 000000000..994205d88 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_completion.md @@ -0,0 +1,38 @@ +## helm completion + +Generate autocompletions script for the specified shell (bash or zsh) + +### Synopsis + + + +Generate autocompletions script for Helm for the specified shell (bash or zsh). + +This command can generate shell autocompletions. e.g. + + $ helm completion bash + +Can be sourced as such + + $ source <(helm completion bash) + + +``` +helm completion SHELL +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_create.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_create.md new file mode 100644 index 000000000..6e0f3de78 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_create.md @@ -0,0 +1,57 @@ +## helm create + +create a new chart with the given name + +### Synopsis + + + +This command creates a chart directory along with the common files and +directories used in a chart. + +For example, 'helm create foo' will create a directory structure that looks +something like this: + + foo/ + | + |- .helmignore # Contains patterns to ignore when packaging Helm charts. + | + |- Chart.yaml # Information about your chart + | + |- values.yaml # The default values for your templates + | + |- charts/ # Charts that this chart depends on + | + |- templates/ # The template files + +'helm create' takes a path for an argument. If directories in the given path +do not exist, Helm will attempt to create them as it goes. If the given +destination exists and there are files in that directory, conflicting files +will be overwritten, but other files will be left alone. + + +``` +helm create NAME +``` + +### Options + +``` + -p, --starter string the named Helm starter scaffold +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_delete.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_delete.md new file mode 100644 index 000000000..5d41cd7ea --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_delete.md @@ -0,0 +1,48 @@ +## helm delete + +given a release name, delete the release from Kubernetes + +### Synopsis + + + +This command takes a release name, and then deletes the release from Kubernetes. +It removes all of the resources associated with the last release of the chart. + +Use the '--dry-run' flag to see which releases will be deleted without actually +deleting them. + + +``` +helm delete [flags] RELEASE_NAME [...] +``` + +### Options + +``` + --dry-run simulate a delete + --no-hooks prevent hooks from running during deletion + --purge remove the release from the store and make its name free for later use + --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency.md new file mode 100644 index 000000000..34d49e20a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency.md @@ -0,0 +1,74 @@ +## helm dependency + +manage a chart's dependencies + +### Synopsis + + + +Manage the dependencies of a chart. + +Helm charts store their dependencies in 'charts/'. For chart developers, it is +often easier to manage a single dependency file ('requirements.yaml') +which declares all dependencies. + +The dependency commands operate on that file, making it easy to synchronize +between the desired dependencies and the actual dependencies stored in the +'charts/' directory. + +A 'requirements.yaml' file is a YAML file in which developers can declare chart +dependencies, along with the location of the chart and the desired version. +For example, this requirements file declares two dependencies: + + # requirements.yaml + dependencies: + - name: nginx + version: "1.2.3" + repository: "https://example.com/charts" + - name: memcached + version: "3.2.1" + repository: "https://another.example.com/charts" + +The 'name' should be the name of a chart, where that name must match the name +in that chart's 'Chart.yaml' file. + +The 'version' field should contain a semantic version or version range. + +The 'repository' URL should point to a Chart Repository. Helm expects that by +appending '/index.yaml' to the URL, it should be able to retrieve the chart +repository's index. Note: 'repository' can be an alias. The alias must start +with 'alias:' or '@'. + +Starting from 2.2.0, repository can be defined as the path to the directory of +the dependency charts stored locally. The path should start with a prefix of +"file://". For example, + + # requirements.yaml + dependencies: + - name: nginx + version: "1.2.3" + repository: "file://../dependency_chart/nginx" + +If the dependency chart is retrieved locally, it is not required to have the +repository added to helm by "helm add repo". Version matching is also supported +for this case. + + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. +* [helm dependency build](helm_dependency_build.md) - rebuild the charts/ directory based on the requirements.lock file +* [helm dependency list](helm_dependency_list.md) - list the dependencies for the given chart +* [helm dependency update](helm_dependency_update.md) - update charts/ based on the contents of requirements.yaml + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_build.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_build.md new file mode 100644 index 000000000..0413a9a85 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_build.md @@ -0,0 +1,44 @@ +## helm dependency build + +rebuild the charts/ directory based on the requirements.lock file + +### Synopsis + + + +Build out the charts/ directory from the requirements.lock file. + +Build is used to reconstruct a chart's dependencies to the state specified in +the lock file. This will not re-negotiate dependencies, as 'helm dependency update' +does. + +If no lock file is found, 'helm dependency build' will mirror the behavior +of 'helm dependency update'. + + +``` +helm dependency build [flags] CHART +``` + +### Options + +``` + --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") + --verify verify the packages against signatures +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm dependency](helm_dependency.md) - manage a chart's dependencies + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_list.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_list.md new file mode 100644 index 000000000..b4343081c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_list.md @@ -0,0 +1,36 @@ +## helm dependency list + +list the dependencies for the given chart + +### Synopsis + + + +List all of the dependencies declared in a chart. + +This can take chart archives and chart directories as input. It will not alter +the contents of a chart. + +This will produce an error if the chart cannot be loaded. It will emit a warning +if it cannot find a requirements.yaml. + + +``` +helm dependency list [flags] CHART +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm dependency](helm_dependency.md) - manage a chart's dependencies + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_update.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_update.md new file mode 100644 index 000000000..3c90ff779 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_dependency_update.md @@ -0,0 +1,49 @@ +## helm dependency update + +update charts/ based on the contents of requirements.yaml + +### Synopsis + + + +Update the on-disk dependencies to mirror the requirements.yaml file. + +This command verifies that the required charts, as expressed in 'requirements.yaml', +are present in 'charts/' and are at an acceptable version. It will pull down +the latest charts that satisfy the dependencies, and clean up old dependencies. + +On successful update, this will generate a lock file that can be used to +rebuild the requirements to an exact version. + +Dependencies are not required to be represented in 'requirements.yaml'. For that +reason, an update command will not remove charts unless they are (a) present +in the requirements.yaml file, but (b) at the wrong version. + + +``` +helm dependency update [flags] CHART +``` + +### Options + +``` + --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") + --skip-refresh do not refresh the local repository cache + --verify verify the packages against signatures +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm dependency](helm_dependency.md) - manage a chart's dependencies + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_fetch.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_fetch.md new file mode 100644 index 000000000..1ddef65fa --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_fetch.md @@ -0,0 +1,58 @@ +## helm fetch + +download a chart from a repository and (optionally) unpack it in local directory + +### Synopsis + + + +Retrieve a package from a package repository, and download it locally. + +This is useful for fetching packages to inspect, modify, or repackage. It can +also be used to perform cryptographic verification of a chart without installing +the chart. + +There are options for unpacking the chart after download. This will create a +directory for the chart and uncompress into that directory. + +If the --verify flag is specified, the requested chart MUST have a provenance +file, and MUST pass the verification process. Failure in any part of this will +result in an error, and the chart will not be saved locally. + + +``` +helm fetch [flags] [chart URL | repo/chartname] [...] +``` + +### Options + +``` + --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle + --cert-file string identify HTTPS client using this SSL certificate file + -d, --destination string location to write the chart. If this and tardir are specified, tardir is appended to this (default ".") + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. + --key-file string identify HTTPS client using this SSL key file + --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password + --prov fetch the provenance file, but don't perform verification + --repo string chart repository url where to locate the requested chart + --untar if set to true, will untar the chart after downloading it + --untardir string if untar is specified, this flag specifies the name of the directory into which the chart is expanded (default ".") + --username string chart repository username + --verify verify the package against its signature + --version string specific version of a chart. Without this, the latest version is fetched +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get.md new file mode 100644 index 000000000..9cd70e520 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get.md @@ -0,0 +1,53 @@ +## helm get + +download a named release + +### Synopsis + + + +This command shows the details of a named release. + +It can be used to get extended information about the release, including: + + - The values used to generate the release + - The chart used to generate the release + - The generated manifest file + +By default, this prints a human readable collection of information about the +chart, the supplied values, and the generated manifest file. + + +``` +helm get [flags] RELEASE_NAME +``` + +### Options + +``` + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. +* [helm get hooks](helm_get_hooks.md) - download all hooks for a named release +* [helm get manifest](helm_get_manifest.md) - download the manifest for a named release +* [helm get values](helm_get_values.md) - download the values file for a named release + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_hooks.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_hooks.md new file mode 100644 index 000000000..85fa5d04b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_hooks.md @@ -0,0 +1,43 @@ +## helm get hooks + +download all hooks for a named release + +### Synopsis + + + +This command downloads hooks for a given release. + +Hooks are formatted in YAML and separated by the YAML '---\n' separator. + + +``` +helm get hooks [flags] RELEASE_NAME +``` + +### Options + +``` + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm get](helm_get.md) - download a named release + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_manifest.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_manifest.md new file mode 100644 index 000000000..a00c1be56 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_manifest.md @@ -0,0 +1,45 @@ +## helm get manifest + +download the manifest for a named release + +### Synopsis + + + +This command fetches the generated manifest for a given release. + +A manifest is a YAML-encoded representation of the Kubernetes resources that +were generated from this release's chart(s). If a chart is dependent on other +charts, those resources will also be included in the manifest. + + +``` +helm get manifest [flags] RELEASE_NAME +``` + +### Options + +``` + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm get](helm_get.md) - download a named release + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_values.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_values.md new file mode 100644 index 000000000..d8944b475 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_get_values.md @@ -0,0 +1,42 @@ +## helm get values + +download the values file for a named release + +### Synopsis + + + +This command downloads a values file for a given release. + + +``` +helm get values [flags] RELEASE_NAME +``` + +### Options + +``` + -a, --all dump all (computed) values + --revision int32 get the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm get](helm_get.md) - download a named release + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_history.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_history.md new file mode 100755 index 000000000..ac51a8994 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_history.md @@ -0,0 +1,55 @@ +## helm history + +fetch release history + +### Synopsis + + + +History prints historical revisions for a given release. + +A default maximum of 256 revisions will be returned. Setting '--max' +configures the maximum length of the revision list returned. + +The historical release set is printed as a formatted table, e.g: + + $ helm history angry-bird --max=4 + REVISION UPDATED STATUS CHART DESCRIPTION + 1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Initial install + 2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Upgraded successfully + 3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine-0.1.0 Rolled back to 2 + 4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine-0.1.0 Upgraded successfully + + +``` +helm history [flags] RELEASE_NAME +``` + +### Options + +``` + --col-width uint specifies the max column width of output (default 60) + --max int32 maximum number of revision to include in history (default 256) + -o, --output string prints the output in the specified format (json|table|yaml) (default "table") + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 14-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_home.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_home.md new file mode 100644 index 000000000..bdccd756f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_home.md @@ -0,0 +1,31 @@ +## helm home + +displays the location of HELM_HOME + +### Synopsis + + + +This command displays the location of HELM_HOME. This is where +any helm configuration files live. + + +``` +helm home +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_init.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_init.md new file mode 100644 index 000000000..5374488af --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_init.md @@ -0,0 +1,74 @@ +## helm init + +initialize Helm on both client and server + +### Synopsis + + + +This command installs Tiller (the Helm server-side component) onto your +Kubernetes Cluster and sets up local configuration in $HELM_HOME (default ~/.helm/). + +As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters +by reading $KUBECONFIG (default '~/.kube/config') and using the default context. + +To set up just a local environment, use '--client-only'. That will configure +$HELM_HOME, but not attempt to connect to a Kubernetes cluster and install the Tiller +deployment. + +When installing Tiller, 'helm init' will attempt to install the latest released +version. You can specify an alternative image with '--tiller-image'. For those +frequently working on the latest code, the flag '--canary-image' will install +the latest pre-release version of Tiller (e.g. the HEAD commit in the GitHub +repository on the master branch). + +To dump a manifest containing the Tiller deployment YAML, combine the +'--dry-run' and '--debug' flags. + + +``` +helm init +``` + +### Options + +``` + --canary-image use the canary Tiller image + -c, --client-only if set does not install Tiller + --dry-run do not install local or remote + --force-upgrade force upgrade of Tiller to the current helm version + --history-max int limit the maximum number of revisions saved per release. Use 0 for no limit. + --local-repo-url string URL for local repository (default "http://127.0.0.1:8879/charts") + --net-host install Tiller with net=host + --node-selectors string labels to specify the node on which Tiller is installed (app=tiller,helm=rocks) + -o, --output OutputFormat skip installation and output Tiller's manifest in specified format (json or yaml) + --override stringArray override values for the Tiller Deployment manifest (can specify multiple or separate values with commas: key1=val1,key2=val2) + --replicas int amount of tiller instances to run on the cluster (default 1) + --service-account string name of service account + --skip-refresh do not refresh (download) the local repository cache + --stable-repo-url string URL for stable repository (default "https://kubernetes-charts.storage.googleapis.com") + -i, --tiller-image string override Tiller image + --tiller-tls install Tiller with TLS enabled + --tiller-tls-cert string path to TLS certificate file to install with Tiller + --tiller-tls-key string path to TLS key file to install with Tiller + --tiller-tls-verify install Tiller with TLS enabled and to verify remote certificates + --tls-ca-cert string path to CA root certificate + --upgrade upgrade if Tiller is already installed + --wait block until Tiller is running and ready to receive requests +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect.md new file mode 100644 index 000000000..e46b3dbf4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect.md @@ -0,0 +1,50 @@ +## helm inspect + +inspect a chart + +### Synopsis + + + +This command inspects a chart and displays information. It takes a chart reference +('stable/drupal'), a full path to a directory or packaged chart, or a URL. + +Inspect prints the contents of the Chart.yaml file and the values.yaml file. + + +``` +helm inspect [CHART] +``` + +### Options + +``` + --ca-file string chart repository url where to locate the requested chart + --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle + --key-file string identify HTTPS client using this SSL key file + --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart + --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart + --verify verify the provenance data for this chart + --version string version of the chart. By default, the newest chart is shown +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. +* [helm inspect chart](helm_inspect_chart.md) - shows inspect chart +* [helm inspect readme](helm_inspect_readme.md) - shows inspect readme +* [helm inspect values](helm_inspect_values.md) - shows inspect values + +###### Auto generated by spf13/cobra on 14-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_chart.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_chart.md new file mode 100644 index 000000000..cd1328b59 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_chart.md @@ -0,0 +1,45 @@ +## helm inspect chart + +shows inspect chart + +### Synopsis + + + +This command inspects a chart (directory, file, or URL) and displays the contents +of the Charts.yaml file + + +``` +helm inspect chart [CHART] +``` + +### Options + +``` + --ca-file string chart repository url where to locate the requested chart + --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle + --key-file string identify HTTPS client using this SSL key file + --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart + --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart + --verify verify the provenance data for this chart + --version string version of the chart. By default, the newest chart is shown +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm inspect](helm_inspect.md) - inspect a chart + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_readme.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_readme.md new file mode 100644 index 000000000..9dd9ebd43 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_readme.md @@ -0,0 +1,43 @@ +## helm inspect readme + +shows inspect readme + +### Synopsis + + + +This command inspects a chart (directory, file, or URL) and displays the contents +of the README file + + +``` +helm inspect readme [CHART] +``` + +### Options + +``` + --ca-file string chart repository url where to locate the requested chart + --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle + --key-file string identify HTTPS client using this SSL key file + --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --repo string chart repository url where to locate the requested chart + --verify verify the provenance data for this chart + --version string version of the chart. By default, the newest chart is shown +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm inspect](helm_inspect.md) - inspect a chart + +###### Auto generated by spf13/cobra on 14-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_values.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_values.md new file mode 100644 index 000000000..6a907cc7d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_inspect_values.md @@ -0,0 +1,45 @@ +## helm inspect values + +shows inspect values + +### Synopsis + + + +This command inspects a chart (directory, file, or URL) and displays the contents +of the values.yaml file + + +``` +helm inspect values [CHART] +``` + +### Options + +``` + --ca-file string chart repository url where to locate the requested chart + --cert-file string verify certificates of HTTPS-enabled servers using this CA bundle + --key-file string identify HTTPS client using this SSL key file + --keyring string path to the keyring containing public verification keys (default "~/.gnupg/pubring.gpg") + --password string chart repository password where to locate the requested chart + --repo string chart repository url where to locate the requested chart + --username string chart repository username where to locate the requested chart + --verify verify the provenance data for this chart + --version string version of the chart. By default, the newest chart is shown +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm inspect](helm_inspect.md) - inspect a chart + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_install.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_install.md new file mode 100644 index 000000000..25ccea1bd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_install.md @@ -0,0 +1,120 @@ +## helm install + +install a chart archive + +### Synopsis + + + +This command installs a chart archive. + +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force +a string value use '--set-string'. + + $ helm install -f myvalues.yaml ./redis + +or + + $ helm install --set name=prod ./redis + +or + + $ helm install --set-string long_int=1234567890 ./redis + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm install -f myvalues.yaml -f override.yaml ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm install --set foo=bar --set foo=newbar ./redis + + +To check the generated manifests of a release without installing the chart, +the '--debug' and '--dry-run' flags can be combined. This will still require a +round-trip to the Tiller server. + +If --verify is set, the chart MUST have a provenance file, and the provenance +file MUST pass all verification steps. + +There are five different ways you can express the chart you want to install: + +1. By chart reference: helm install stable/mariadb +2. By path to a packaged chart: helm install ./nginx-1.2.3.tgz +3. By path to an unpacked chart directory: helm install ./nginx +4. By absolute URL: helm install https://example.com/charts/nginx-1.2.3.tgz +5. By chart reference and repo url: helm install --repo https://example.com/charts/ nginx + +CHART REFERENCES + +A chart reference is a convenient way of reference a chart in a chart repository. + +When you use a chart reference with a repo prefix ('stable/mariadb'), Helm will look in the local +configuration for a chart repository named 'stable', and will then look for a +chart in that repository whose name is 'mariadb'. It will install the latest +version of that chart unless you also supply a version number with the +'--version' flag. + +To see the list of chart repositories, use 'helm repo list'. To search for +charts in a repository, use 'helm search'. + + +``` +helm install [CHART] +``` + +### Options + +``` + --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle + --cert-file string identify HTTPS client using this SSL certificate file + --dep-up run helm dependency update before installing the chart + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. + --dry-run simulate an install + --key-file string identify HTTPS client using this SSL key file + --keyring string location of public keys used for verification (default "~/.gnupg/pubring.gpg") + -n, --name string release name. If unspecified, it will autogenerate one for you + --name-template string specify template used to name the release + --namespace string namespace to install the release into. Defaults to the current kube config namespace. + --no-hooks prevent hooks from running during install + --password string chart repository password where to locate the requested chart + --replace re-use the given name, even if that name is already used. This is unsafe in production + --repo string chart repository url where to locate the requested chart + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote + --username string chart repository username where to locate the requested chart + -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) + --verify verify the package before installing it + --version string specify the exact chart version to install. If this is not specified, the latest version is installed + --wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 20-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_lint.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_lint.md new file mode 100644 index 000000000..596edf2bb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_lint.md @@ -0,0 +1,45 @@ +## helm lint + +examines a chart for possible issues + +### Synopsis + + + +This command takes a path to a chart and runs a series of tests to verify that +the chart is well-formed. + +If the linter encounters things that will cause the chart to fail installation, +it will emit [ERROR] messages. If it encounters issues that break with convention +or recommendation, it will emit [WARNING] messages. + + +``` +helm lint [flags] PATH +``` + +### Options + +``` + --namespace string namespace to install the release into (only used if --install is set) (default "default") + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --strict fail on lint warnings + -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 9-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_list.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_list.md new file mode 100755 index 000000000..1d5bf7ea2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_list.md @@ -0,0 +1,76 @@ +## helm list + +list releases + +### Synopsis + + + +This command lists all of the releases. + +By default, it lists only releases that are deployed or failed. Flags like +'--deleted' and '--all' will alter this behavior. Such flags can be combined: +'--deleted --failed'. + +By default, items are sorted alphabetically. Use the '-d' flag to sort by +release date. + +If an argument is provided, it will be treated as a filter. Filters are +regular expressions (Perl compatible) that are applied to the list of releases. +Only items that match the filter will be returned. + + $ helm list 'ara[a-z]+' + NAME UPDATED CHART + maudlin-arachnid Mon May 9 16:07:08 2016 alpine-0.1.0 + +If no results are found, 'helm list' will exit 0, but with no output (or in +the case of no '-q' flag, only headers). + +By default, up to 256 items may be returned. To limit this, use the '--max' flag. +Setting '--max' to 0 will not return all results. Rather, it will return the +server's default, which may be much higher than 256. Pairing the '--max' +flag with the '--offset' flag allows you to page through results. + + +``` +helm list [flags] [FILTER] +``` + +### Options + +``` + -a, --all show all releases, not just the ones marked DEPLOYED + --col-width uint specifies the max column width of output (default 60) + -d, --date sort by release date + --deleted show deleted releases + --deleting show releases that are currently being deleted + --deployed show deployed releases. If no other is specified, this will be automatically enabled + --failed show failed releases + -m, --max int maximum number of releases to fetch (default 256) + --namespace string show releases within a specific namespace + -o, --offset string next release name in the list, used to offset from start value + --pending show pending releases + -r, --reverse reverse the sort order + -q, --short output short (quiet) listing format + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_package.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_package.md new file mode 100644 index 000000000..c51aa7032 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_package.md @@ -0,0 +1,50 @@ +## helm package + +package a chart directory into a chart archive + +### Synopsis + + + +This command packages a chart into a versioned chart archive file. If a path +is given, this will look at that path for a chart (which must contain a +Chart.yaml file) and then package that directory. + +If no path is given, this will look in the present working directory for a +Chart.yaml file, and (if found) build the current directory into a chart. + +Versioned chart archives are used by Helm package repositories. + + +``` +helm package [flags] [CHART_PATH] [...] +``` + +### Options + +``` + --app-version string set the appVersion on the chart to this version + -u, --dependency-update update dependencies from "requirements.yaml" to dir "charts/" before packaging + -d, --destination string location to write the chart. (default ".") + --key string name of the key to use when signing. Used if --sign is true + --keyring string location of a public keyring (default "~/.gnupg/pubring.gpg") + --save save packaged chart to local chart repository (default true) + --sign use a PGP private key to sign this package + --version string set the version on the chart to this semver version +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin.md new file mode 100644 index 000000000..cc42aa4dc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin.md @@ -0,0 +1,30 @@ +## helm plugin + +add, list, or remove Helm plugins + +### Synopsis + + + +Manage client-side Helm plugins. + + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. +* [helm plugin install](helm_plugin_install.md) - install one or more Helm plugins +* [helm plugin list](helm_plugin_list.md) - list installed Helm plugins +* [helm plugin remove](helm_plugin_remove.md) - remove one or more Helm plugins +* [helm plugin update](helm_plugin_update.md) - update one or more Helm plugins + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_install.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_install.md new file mode 100644 index 000000000..196ca97dd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_install.md @@ -0,0 +1,39 @@ +## helm plugin install + +install one or more Helm plugins + +### Synopsis + + + +This command allows you to install a plugin from a url to a VCS repo or a local path. + +Example usage: + $ helm plugin install https://github.com/technosophos/helm-template + + +``` +helm plugin install [options] ... +``` + +### Options + +``` + --version string specify a version constraint. If this is not specified, the latest version is installed +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_list.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_list.md new file mode 100644 index 000000000..ddfd04ee6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_list.md @@ -0,0 +1,28 @@ +## helm plugin list + +list installed Helm plugins + +### Synopsis + + +list installed Helm plugins + +``` +helm plugin list +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_remove.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_remove.md new file mode 100644 index 000000000..8543a367a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_remove.md @@ -0,0 +1,28 @@ +## helm plugin remove + +remove one or more Helm plugins + +### Synopsis + + +remove one or more Helm plugins + +``` +helm plugin remove ... +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_update.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_update.md new file mode 100644 index 000000000..9e5e205f0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_plugin_update.md @@ -0,0 +1,28 @@ +## helm plugin update + +update one or more Helm plugins + +### Synopsis + + +update one or more Helm plugins + +``` +helm plugin update ... +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm plugin](helm_plugin.md) - add, list, or remove Helm plugins + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo.md new file mode 100644 index 000000000..4109ceca4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo.md @@ -0,0 +1,35 @@ +## helm repo + +add, list, remove, update, and index chart repositories + +### Synopsis + + + +This command consists of multiple subcommands to interact with chart repositories. + +It can be used to add, remove, list, and index chart repositories. +Example usage: + $ helm repo add [NAME] [REPO_URL] + + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. +* [helm repo add](helm_repo_add.md) - add a chart repository +* [helm repo index](helm_repo_index.md) - generate an index file given a directory containing packaged charts +* [helm repo list](helm_repo_list.md) - list chart repositories +* [helm repo remove](helm_repo_remove.md) - remove a chart repository +* [helm repo update](helm_repo_update.md) - update information of available charts locally from chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_add.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_add.md new file mode 100644 index 000000000..456ffa27e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_add.md @@ -0,0 +1,39 @@ +## helm repo add + +add a chart repository + +### Synopsis + + +add a chart repository + +``` +helm repo add [flags] [NAME] [URL] +``` + +### Options + +``` + --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle + --cert-file string identify HTTPS client using this SSL certificate file + --key-file string identify HTTPS client using this SSL key file + --no-update raise error if repo is already registered + --password string chart repository password + --username string chart repository username +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_index.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_index.md new file mode 100644 index 000000000..14b412b29 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_index.md @@ -0,0 +1,44 @@ +## helm repo index + +generate an index file given a directory containing packaged charts + +### Synopsis + + + +Read the current directory and generate an index file based on the charts found. + +This tool is used for creating an 'index.yaml' file for a chart repository. To +set an absolute URL to the charts, use '--url' flag. + +To merge the generated index with an existing index file, use the '--merge' +flag. In this case, the charts found in the current directory will be merged +into the existing index, with local charts taking priority over existing charts. + + +``` +helm repo index [flags] [DIR] +``` + +### Options + +``` + --merge string merge the generated index into the given index + --url string url of chart repository +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_list.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_list.md new file mode 100644 index 000000000..858ef957f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_list.md @@ -0,0 +1,28 @@ +## helm repo list + +list chart repositories + +### Synopsis + + +list chart repositories + +``` +helm repo list [flags] +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_remove.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_remove.md new file mode 100644 index 000000000..801bc3c3f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_remove.md @@ -0,0 +1,28 @@ +## helm repo remove + +remove a chart repository + +### Synopsis + + +remove a chart repository + +``` +helm repo remove [flags] [NAME] +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_update.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_update.md new file mode 100644 index 000000000..897ed24b7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_repo_update.md @@ -0,0 +1,34 @@ +## helm repo update + +update information of available charts locally from chart repositories + +### Synopsis + + + +Update gets the latest information about charts from the respective chart repositories. +Information is cached locally, where it is used by commands like 'helm search'. + +'helm update' is the deprecated form of 'helm repo update'. It will be removed in +future releases. + + +``` +helm repo update +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm repo](helm_repo.md) - add, list, remove, update, and index chart repositories + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_reset.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_reset.md new file mode 100644 index 000000000..ed68b1b84 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_reset.md @@ -0,0 +1,44 @@ +## helm reset + +uninstalls Tiller from a cluster + +### Synopsis + + + +This command uninstalls Tiller (the Helm server-side component) from your +Kubernetes Cluster and optionally deletes local configuration in +$HELM_HOME (default ~/.helm/) + + +``` +helm reset +``` + +### Options + +``` + -f, --force forces Tiller uninstall even if there are releases installed, or if Tiller is not in ready state. Releases are not deleted.) + --remove-helm-home if set deletes $HELM_HOME + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_rollback.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_rollback.md new file mode 100644 index 000000000..4b6dcbbb2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_rollback.md @@ -0,0 +1,50 @@ +## helm rollback + +roll back a release to a previous revision + +### Synopsis + + + +This command rolls back a release to a previous revision. + +The first argument of the rollback command is the name of a release, and the +second is a revision (version) number. To see revision numbers, run +'helm history RELEASE'. + + +``` +helm rollback [flags] [RELEASE] [REVISION] +``` + +### Options + +``` + --dry-run simulate a rollback + --force force resource update through delete/recreate if needed + --no-hooks prevent hooks from running during rollback + --recreate-pods performs pods restart for the resource if applicable + --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote + --wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_search.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_search.md new file mode 100644 index 000000000..f59814b9a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_search.md @@ -0,0 +1,41 @@ +## helm search + +search for a keyword in charts + +### Synopsis + + + +Search reads through all of the repositories configured on the system, and +looks for matches. + +Repositories are managed with 'helm repo' commands. + + +``` +helm search [keyword] +``` + +### Options + +``` + -r, --regexp use regular expressions for searching + -v, --version string search using semantic versioning constraints + -l, --versions show the long listing, with each version of each chart on its own line +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_serve.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_serve.md new file mode 100644 index 000000000..90ebb6da9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_serve.md @@ -0,0 +1,49 @@ +## helm serve + +start a local http web server + +### Synopsis + + + +This command starts a local chart repository server that serves charts from a local directory. + +The new server will provide HTTP access to a repository. By default, it will +scan all of the charts in '$HELM_HOME/repository/local' and serve those over +the local IPv4 TCP port (default '127.0.0.1:8879'). + +This command is intended to be used for educational and testing purposes only. +It is best to rely on a dedicated web server or a cloud-hosted solution like +Google Cloud Storage for production use. + +See https://github.com/kubernetes/helm/blob/master/docs/chart_repository.md#hosting-chart-repositories +for more information on hosting chart repositories in a production setting. + + +``` +helm serve +``` + +### Options + +``` + --address string address to listen on (default "127.0.0.1:8879") + --repo-path string local directory path from which to serve charts + --url string external URL of chart repository +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_status.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_status.md new file mode 100644 index 000000000..02ec0ad66 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_status.md @@ -0,0 +1,49 @@ +## helm status + +displays the status of the named release + +### Synopsis + + + +This command shows the status of a named release. +The status consists of: +- last deployment time +- k8s namespace in which the release lives +- state of the release (can be: UNKNOWN, DEPLOYED, DELETED, SUPERSEDED, FAILED or DELETING) +- list of resources that this release consists of, sorted by kind +- details on last test suite run, if applicable +- additional notes provided by the chart + + +``` +helm status [flags] RELEASE_NAME +``` + +### Options + +``` + -o, --output string output the status in the specified format (json or yaml) + --revision int32 if set, display the status of the named release with revision + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_template.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_template.md new file mode 100644 index 000000000..3a4e9ce4a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_template.md @@ -0,0 +1,54 @@ +## helm template + +locally render templates + +### Synopsis + + + +Render chart templates locally and display the output. + +This does not require Tiller. However, any values that would normally be +looked up or retrieved in-cluster will be faked locally. Additionally, none +of the server-side testing of chart validity (e.g. whether an API is supported) +is done. + +To render just one template in a chart, use '-x': + + $ helm template mychart -x templates/deployment.yaml + + +``` +helm template [flags] CHART +``` + +### Options + +``` + -x, --execute stringArray only execute the given templates + --kube-version string kubernetes version used as Capabilities.KubeVersion.Major/Minor (default "1.9") + -n, --name string release name (default "RELEASE-NAME") + --name-template string specify template used to name the release + --namespace string namespace to install the release into + --notes show the computed NOTES.txt file as well + --output-dir string writes the executed templates to files in output-dir instead of stdout + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + -f, --values valueFiles specify values in a YAML file (can specify multiple) (default []) +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 9-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_test.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_test.md new file mode 100644 index 000000000..062244e73 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_test.md @@ -0,0 +1,45 @@ +## helm test + +test a release + +### Synopsis + + + +The test command runs the tests for a release. + +The argument this command takes is the name of a deployed release. +The tests to be run are defined in the chart that was installed. + + +``` +helm test [RELEASE] +``` + +### Options + +``` + --cleanup delete test pods upon completion + --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_upgrade.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_upgrade.md new file mode 100644 index 000000000..7ada6025f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_upgrade.md @@ -0,0 +1,84 @@ +## helm upgrade + +upgrade a release + +### Synopsis + + + +This command upgrades a release to a new version of a chart. + +The upgrade arguments must be a release and chart. The chart +argument can be either: a chart reference('stable/mariadb'), a path to a chart directory, +a packaged chart, or a fully qualified URL. For chart references, the latest +version will be specified unless the '--version' flag is set. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force string +values, use '--set-string'. + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm upgrade --set foo=bar --set foo=newbar redis ./redis + + +``` +helm upgrade [RELEASE] [CHART] +``` + +### Options + +``` + --ca-file string verify certificates of HTTPS-enabled servers using this CA bundle + --cert-file string identify HTTPS client using this SSL certificate file + --devel use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored. + --dry-run simulate an upgrade + --force force resource update through delete/recreate if needed + -i, --install if a release by this name doesn't already exist, run an install + --key-file string identify HTTPS client using this SSL key file + --keyring string path to the keyring that contains public signing keys (default "~/.gnupg/pubring.gpg") + --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace + --no-hooks disable pre/post upgrade hooks + --password string chart repository password where to locate the requested chart + --recreate-pods performs pods restart for the resource if applicable + --repo string chart repository url where to locate the requested chart + --reset-values when upgrading, reset the values to the ones built into the chart + --reuse-values when upgrading, reuse the last release's values, and merge in any new values. If '--reset-values' is specified, this is ignored. + --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --set-string stringArray set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote + --username string chart repository username where to locate the requested chart + -f, --values valueFiles specify values in a YAML file or a URL(can specify multiple) (default []) + --verify verify the provenance of the chart before upgrading + --version string specify the exact chart version to use. If this is not specified, the latest version is used + --wait if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 20-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_verify.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_verify.md new file mode 100644 index 000000000..bc5343937 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_verify.md @@ -0,0 +1,43 @@ +## helm verify + +verify that a chart at the given path has been signed and is valid + +### Synopsis + + + +Verify that the given chart has a valid provenance file. + +Provenance files provide crytographic verification that a chart has not been +tampered with, and was packaged by a trusted provider. + +This command can be used to verify a local chart. Several other commands provide +'--verify' flags that run the same validation. To generate a signed package, use +the 'helm package --sign' command. + + +``` +helm verify [flags] PATH +``` + +### Options + +``` + --keyring string keyring containing public keys (default "~/.gnupg/pubring.gpg") +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/helm/helm_version.md b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_version.md new file mode 100644 index 000000000..1f48cceba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/helm/helm_version.md @@ -0,0 +1,58 @@ +## helm version + +print the client/server version information + +### Synopsis + + + +Show the client and server versions for Helm and tiller. + +This will print a representation of the client and server versions of Helm and +Tiller. The output will look something like this: + +Client: &version.Version{SemVer:"v2.0.0", GitCommit:"ff52399e51bb880526e9cd0ed8386f6433b74da1", GitTreeState:"clean"} +Server: &version.Version{SemVer:"v2.0.0", GitCommit:"b0c113dfb9f612a9add796549da66c0d294508a3", GitTreeState:"clean"} + +- SemVer is the semantic version of the release. +- GitCommit is the SHA for the commit that this version was built from. +- GitTreeState is "clean" if there are no local code changes when this binary was + built, and "dirty" if the binary was built from locally modified code. + +To print just the client version, use '--client'. To print just the server version, +use '--server'. + + +``` +helm version +``` + +### Options + +``` + -c, --client client version only + -s, --server server version only + --short print the version number + --template string template for version string format + --tls enable TLS for request + --tls-ca-cert string path to TLS CA certificate file (default "$HELM_HOME/ca.pem") + --tls-cert string path to TLS certificate file (default "$HELM_HOME/cert.pem") + --tls-key string path to TLS key file (default "$HELM_HOME/key.pem") + --tls-verify enable TLS for request and verify remote +``` + +### Options inherited from parent commands + +``` + --debug enable verbose output + --home string location of your Helm config. Overrides $HELM_HOME (default "~/.helm") + --host string address of Tiller. Overrides $HELM_HOST + --kube-context string name of the kubeconfig context to use + --tiller-connection-timeout int the duration (in seconds) Helm will wait to establish a connection to tiller (default 300) + --tiller-namespace string namespace of Tiller (default "kube-system") +``` + +### SEE ALSO +* [helm](helm.md) - The Helm package manager for Kubernetes. + +###### Auto generated by spf13/cobra on 8-Mar-2018 diff --git a/src/vendor/github.com/kubernetes/helm/docs/history.md b/src/vendor/github.com/kubernetes/helm/docs/history.md new file mode 100644 index 000000000..71e63c6b2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/history.md @@ -0,0 +1,29 @@ +## The History of the Project + +Kubernetes Helm is the merged result of [Helm +Classic](https://github.com/helm/helm) and the Kubernetes port of GCS Deployment +Manager. The project was jointly started by Google and Deis, though it +is now part of the CNCF. Many companies now contribute regularly to Helm. + +Differences from Helm Classic: + +- Helm now has both a client (`helm`) and a server (`tiller`). The + server runs inside of Kubernetes, and manages your resources. +- Helm's chart format has changed for the better: + - Dependencies are immutable and stored inside of a chart's `charts/` + directory. + - Charts are strongly versioned using [SemVer 2](http://semver.org/spec/v2.0.0.html) + - Charts can be loaded from directories or from chart archive files + - Helm supports Go templates without requiring you to run `generate` + or `template` commands. + - Helm makes it easy to configure your releases -- and share the + configuration with the rest of your team. +- Helm chart repositories now use plain HTTP(S) instead of Git/GitHub. + There is no longer any GitHub dependency. + - A chart server is a simple HTTP server + - Charts are referenced by version + - The `helm serve` command will run a local chart server, though you + can easily use object storage (S3, GCS) or a regular web server. + - And you can still load charts from a local directory. +- The Helm workspace is gone. You can now work anywhere on your + filesystem that you want to work. diff --git a/src/vendor/github.com/kubernetes/helm/docs/images/create-a-bucket.png b/src/vendor/github.com/kubernetes/helm/docs/images/create-a-bucket.png new file mode 100644 index 0000000000000000000000000000000000000000..f7f5dbfdc322ce695c2cd7868148b01acc203ebc GIT binary patch literal 51471 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/images/create-a-gh-page-button.png b/src/vendor/github.com/kubernetes/helm/docs/images/create-a-gh-page-button.png new file mode 100644 index 0000000000000000000000000000000000000000..b9d43a7059b37dbb4c5cc2df3fef55cf0d5d2833 GIT binary patch literal 30225 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

iM6KS2Zo4QhTLT@5OQK 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/images/make-bucket-public.png b/src/vendor/github.com/kubernetes/helm/docs/images/make-bucket-public.png new file mode 100644 index 0000000000000000000000000000000000000000..f8a5e17f02b62f312325975638ea9bcaa2d7cb9e GIT binary patch literal 15768 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 MvASaMBlD2|15`@6hX4Qo literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/images/nothing.png b/src/vendor/github.com/kubernetes/helm/docs/images/nothing.png new file mode 100644 index 0000000000000000000000000000000000000000..f41c11bf3e16b8792eca8695fd9180608a8406bf GIT binary patch literal 275670 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�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%0AcIt7?-X-J{v*f)w4i8Ug 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�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

Z4|G*Sz^ZXbN0Kuj``s1||7HWxRDF{-Hjf+3FH zX10_QQXCM%R_h<8*b{V9+JR{>x<#3KMw<8ZU*-O#wpUdw#x`MXwL*x8Y~9U`d0fi^ z(V>sX#|J$8=I7v_{tCD6GcK;LFx*~(wv%cHTaHA!DL+8UJEyo#W6iM`QEintTb&Z- z)VhB($IV^bdWMVl&(bq=ZuY|;CIMv;C|Dxz08qiI@a~`AU!gh-v`GIfm(_JQ1LPQX z2fz0ae3vEL8d5v>mc3gjV|w~szej`4I=HUaVK)+Bc z_K(=__jo)!;qmDK$KxZG(-CD^Br8agTAg#m((K#$iH;0hgCoxW2l@ zIBtY2*4C^wJ|Neu5~BgbxIsvRw6gnNOnUb}ln=%~(ot zqZyOfktJuH-vB}Uw0xLUR~~Nr8x}$Wc(lgWEHauw}_-!cFt}9mSv9x71JQ>_Pk^u z5T@}02qUa%O$=cGVgjS0lo=_BF_XE+q!Wh(>Z#Rg?ZLAa>fua#;(oYv<3nm+q+)rvAq`RsQ%JwhPc7s5Q=Z*xcXskV ze8uWMEn7X$fX~~>`U7YXdGM&0Kl}}3K2i04L{67iSMX|2` zw?j-I?|XH)dhPpFxPhEsvC8HxQ#Y`T9B5VOvDM7XsrTgpy9U;vg94*epcF>o1*HnR z{PA$W{_upS{R7@Syv6Icukq&l@A2^PP8hf)BZ4uF1Fp6^++N?{?&bzJmp8cFU1GD@ zVj9Hw9`+BIrY&x6o~dC7qRL0o6~b0-;AW876kjSU(_GFTDe0(?NFOhGLE(yN+F(R% zoD2Zg(_M-{|1n18eFm&~1F|YjoJq5p%6jfujaB2Xh#V-Z1SYwmjv?UXvljpbR0hhD zJtOm)G67G=Jzl^2URu#q*L+BWcE%KNDeBA@(YOc+o6QygAup2948!Dl=zwL9Qj?vR z88Jj80Q0hVhF+MZ_Nj*1HO4*24~}BE%?}a4h*A~+z`rBy5I>n<;E z1!8yTIIgl-Msaw*c5ogMyxpwS1xq=CC~DA`AVWl0kAmKR5U5;4P)zDHI8-fK?h67D zreW)al~QYy9`-D`m0?!fsVlEEwLVfZnU)tl2oO7H;79LpJ9phVcylJrZu!?~8LQTc z5F-L559WnL$e<)@qxKy`8bGu|4B8%Oya;ODJL2FAKt+`TTML{7p$W`{(|iJ>VvN!$ zus=Q_h5>2ZqA-Iv`#DO90+>|v1dOF-L=}~{7Ap!7DO8NZ25%0Jc=Pxjo?RXB?CKr~ z!i-FDzz{bQnbm^hX^((C2mz<#0pqX*(oO>JT2X!BgjhBVBU*#LX2vXJCCj>v_+wI> zM5+W;TK(R#XMwzw+((kd2NI$k;)u;43VE!CB$tYqCPX!Y!OXN17y?eq0S^yvF-#LK zE^l?fd?V^vQeiB7ldly9ZpV`EjM#Z(Cu9!2esyF-0BXVM-M5&3@-yuI?Q7iSgp21_ zNHR0>M!h|{OL;j`saT&B*p4q_6K4@poIMRgv0mW zqUIUHIEZu6OqN2Q{k9rt3y7>=RbDR=0A|pFzqUpGW+C+NNM=-d5U_e)EfAA|9M%fd z^=#D_-d!s%p^!BGVjiUxTywL8}n3KyVSH%Nb`shzb} zgk5`E3hkVF(BG3AWnKH0&ECoxyWB$n3fddq+b8*bE(_*)#yD-HY@w)&F#RhAB!6Zx zL@9xUfDi+6o;3(5U`1BQd+k}zg9Kv&+Kh^s2xER2jO!3V5TdGR_p|#$+lNVr6D*Fo ziabNowlrdevqQu<1bq7Z2N<{47>94LNs}Ia(F#r!5QAj9JZ^M)4Pic=aCmyec)gJu z2?4ceKV{U(HpEE70NWooGAsx%R4E&9mR;>mN7x9x(X_xWitL z;yiQO?$kS;K@aIur#KgcLs5@24sYI|EHkEXto>3Q6eOFa7EkvaZ;=n@}P%{TyY%UR=PY5aD z`?s%U-)ZMcPzh!sw-L%PqCh5kJv_ccWyWrIi5Sp^h{Q(Llg^h>MV$!aIKj#bY1B4Z zW}TZZgQY~hQsHY@r-1COT5CtF3|@+{4hSpdRNb3#*dPuh!Y|5Ik;KXR!7~o?0Z;q) zAPShKZC5Xr!6Jc-OeQUODGVM|1nP!a^{}WZS1^}pTw-n`S#?12;0q7OW=I6plUMIx+r6J z&^}dxo0q)TwEjY&(gD+$@H}mBd+`heV5EddLdXd2G^8DC7&aKgfFVrC^CGNpeHeDQ z0!1K-uuO}osQUH#3r?s-Oi{&(m7)GIPv z^WV-2w)<$Mgtcewp{@4WYE-(%#Ck|A017E$+H4R`3zpLnwG>3FjjM}mY`0rXye%z13Dl@J|e~h4iY@4uxXm*EcJewogh=r z7BI>z2*MZ>5~zWX91tQ<$^tG$>eS}U86sw7zpt!k2AC$*p3rAt64i#Nke-#`+yl$F zR5h#1VDo%LVMrFLPA!Fm-8A5f=U?FE?QQfx_Ov4f$0?8H(VqiS-iIcMZt zG@~0twNjPy8qY@@S*=oIPiewt+91VIY_7uPAzF)E7V=z_p&(I(U6{1rf7K!iM^bHb zu_7u}12FOlWIA&rCdB@}d~l3BW1FXLnY!6}7$3pSmfTy;3n~|+VeoTpLSbYy<%Or?P2%T#T4z*Gg!qoE=SQY;6r*5`px39Hw@=*y?;k8W1nVke=pQ#(j>Xni3n3 zffhj;JV;epl5*Tb>}TXcg)gFSoD_9>Q=P#KL}nb{y~5#VKgI1&{)qd>h^zY>On0{k z(-xp4$bt%QFq3Doe$XsC*TeCFA9NP-8V)z-ZjP}Dwiq1UJ2Jq`8duBk$h8K1IvF>& zXNQX*K}{S6Y^Di;9#NKzr5KMv+Z>_UH%qo;ZAdFV8(Is;s+d9T*^*GMF0;E@9^;MEna@|1vcAD z;Z$qJlTySmTxdX1g*)SHYMDY-K^W465C>dd-fDYTv`USL#=vG*OHh{X*a%BLCU=~yx*>*QHtAv6i~h}>gRdr@r4Akx{drmswPdhG zM_)@TvoEr$&Pfp1EKVOhpOytc6^CA-9E=9b%i9ak1rAb%71A-mN|(^&k8BvJ1V|xj zj_Ad8G*F&f9x~#_6bl9 ziI5ya2uKD+2H1dgQ6?LQEyg5uZLSpyGL{T%#+@4u>7_PEQEsy6;Fd9sKpap@#k9TF zdcXjX)By@h`;7DjN=(nHrdFyj(5>|aI(djlHKD5*<5CvHBsGm-QnDnPw$!z*N`TO2 zC~GT!8yL{l=B*}f6Pj{fL@*{s?X|2LY004yHn6-uF!^{&6C~)zDh91lala|Fz8X~C z6(JxnV@w;=no*8#@%X!c!}9ZA;_A%_7uzdL*EgWu4oCwygw}G^!ThTfyz|}-(E9M_ zL+MiY4DJwHX5JuY3jvs^bJVS_)4!@A6ReK0bwR`!kOCppiuv&o%i&23CIYHz{@b{~ zrgzXZ+WkA%zE{tl!QOg19^99+`X@+4@mM&kdj9Qc+7pt!@p_OHf{MtPXk&rGG$I~7uC2Yp2U7L=UW41O=Ln_Q* z%d;HffXz6m<1NTsv7Bab5b~0A^iZ3Wr4FltkyZXW#7W9NX~M9z*jKR~} z$`2C}J&BgVtDHF#kVuDxSSExJ5r!?E-+eB|fdeWrUcG&d$HN|9eEMg&+zj3>;&oNB zWvzNsBN$QJagxj{R!5gh!BQ6-PkTJ=-ysbncDpN4Q{pz#&NA=9P*MydB~_*?3z&nD zfYL_mbDF832}x9in7D&u*`hpZo3+(^yt>-za%|BsYi2&d=jwL#`Q8#qqS2%)q%LcR zV%?K1=Sf@6ShA@|1mc3HZ+?&C&;AA5uij!eZm@ZFjdXPhN=c8n=~XLm+7r0!ceNST zk2&(*iGNhrz29D1|MZPTmhg37@R7Q+InPx_%=UhBD#B7m0Z#89aC&?Mshn=`Yhw?f zyl+`Ar2qHxd-d9!oo{=dW*}S8tywv=b**#pXApjYkYyeZ4|w(FTfBdKkIEINWyb5* z-{GsTzr=UnevRdH#O?JB{=;AX@A%;tKf?3-7Z`^L490faVKZ$V4i8`%-NRMLp0IW| z-OmPtT9yQBh_|epu*@?K$31r1Q|=D4TMM%6B1B!gAWZv`1zHT94!W+d@7EZGQ*wAh zN+WJBE-(xu#*}m{uS#lmM)B(67Q2m<<7zb}uEzHe$91;4AZnuQ114_wXyEe$GB)QRi7x>Ax0AY=$KqIq1LLb(yChX zgyVd~)BYVc(^j>gNnstSb#)cq<_Hxw3Nc~QD37bO6$KLJVyRglDqP^A*;UyfiK4@l zvOz`ukQ5fIU>z%I8&TUxz=$YV-hPYuXFtW&Pyc}1eZb}2HKu1bh}#j>2z%THz?f$; z)WI@wB6jv>@0dJT=^VL5*dHI*;xwu=$bRkf?X%bKUBlVt>?_u6)fV0>5vF~H1xNPN1Z}EjS4&=SIPUTN<6C_5%^&gm zKl~QI|J|?g=H2(8Ko~}uLO7-YpT776KlpMg@sm^N@$pkw}4 zH`WKw(lEpXWD;usQVSw6rVv0;WKYMGFpY!sf_lx55HaZJPex{)*c3+)=`3N+*rZ7} zpAI?ELv0bPh$%BTFWMIlAKY*}oq!M#r_mu{)AU_$u|}jqC<+m&F^tI#ZSyR$kxUjA zLF zzJ2u_o*v&LFk`bFadmZx`|B%w`pM_`{L>%c#r;d%T-{r}E%1ELs@Aku zOC-W!xSTFD5DLx$=BSvT2EkIU)>0S=%o;<)X_@i#^oY$?6zV*%L>GA2*V?mkjdjN9 zvH8{Y^IGPKj0>xO-hC{@|QODp%f8WNolL=9DyqE{K7>@RWZR3 zz8Djl(wNlbbIC{;fJ7}ACoB7DM@ThcA4sbh02f-*lh^octSSrKretcbw;W^@mN7r+ zdRkVphAmV8t)#w2wG%~k`b^`$H6DXi+|D&Y#K z5DkMXX)pj>Y%Xa>l0fAy zsp_5)0ouYuhIg(U^Qxw70WKWqfz_7%+4nvkA0%3CBs^+&v_LadGIJbUZc`Fh(dG_p z>(=hkgD+#7>wj%P2OGJ`Y=%Noxn|TQqn3iDW|U)Ojk-#=9!CT;}pJ#7Ba&T1OMImdcp{;Pu>s~Y6GMf;9%Zn=ysx5Hqb@xAp4B5GCKK?0@5(aI);F&?Uf%4;Sn|H-!mg_I8Wt@ z!obXouim}J`{M&Xzk817+iM^GLQDurB(qCdq&^)45;r+tXLJ+kv(jCA4er@(a3&1tfg_efCOCC|vqjLnoV z41 z(lR~U+~&d5x%=nXV4LbN9Z0n5gVt062%!@4!yDj#{v5lXe}(HDaryEV)6J!{sOk0D zcs(~i8jQ8IToc?r=;u89{kb#m$B^1EgdTo&2kpI^AO2A14Ezw(&Iy>2QKMr+G=PFI z4xnL>-ppXwgNaAj_)U3lUS{l{-eLdnJx-7BaC&@?ayS54xERbpEgAWgk&jQP#{4c?JJVwHsX~gRgfQxH<{+EA&+h_N>Kc?J z0kD?7HchzmA{MkL(JqFFtb=YUMa;F}tGD0byZ5iL+iY;Xxx@%UA_Ay2b{dIH0{i|< zwDC1WP}slg<>};!BPAee54zeDxi`@vuemD8O-r2myh;@XwiKd26K`i_(?C~!9 zBdJ3hS|gFau+r7$mnt*SZQTWF0&L`JKh&g^87$y&v!@r;WtsX{A++^3Buhhe32@lN z$Bf7f{c}OkF}B7~7w5>>*NiXUy~3-9ceua0!6%nDm{LLokQqWkgawxbQ=T(a;C!f$ zZgM5YNzh&L0-0S96b$L?C9ZTR_z zKj$;mSN~=akQ>teDj@YU6$MG#Ey6f~O988RN+jKT%Gy1hPI&tMTYUffU*h}U{0dLs zeTA}rM63m&W+2<=S!Tj6ZEUbZB$4NoM1mzME&{dh4^O~$#ApBKU*ShT`U{k4hwb$} z?p}O`?PiA|CXwY$15z4vbgxZzs!kR(ooT}nh=9B-GC^;Uy-YR9AR>p)joha}RM45+ zibxd;vdPAh%K9zKau$p!(aT`*AVM)qmcbDzCgjR`y^R29dr1b5$gV4_;uRWv*lQi; z)_bik$J!pRm4bRkd!*K4X~SE3eRunbAu3#11s(^9^5h5|Xk$T8aKq?M6#}QmQG{x0 z(K>OA8qmuHRt+W_YOpRPSt*UcxVgN>I3!$eFI&(`?K1!qvrF!HWCd*0Wxw~^0FhoN zoJgjJ8NnB-^In;;a&A1W1K?*G>hVprO7vKTOIqw{fL;)2`KH_vb zBBcq_IC+eSpyd&VP38MW+lwx=16aEwR~zkX!Z)J_nK!Pll{&vXKw*>zfjk(v*<9dav%zlMUZPlC2-sH7nEJR3BOm6zR~Bj$tc~LdrP9Fu~^($bTea z*FUGEieAbH7RXfVq72c>Ultq=Pq@0gMNBgM#?Q8es!Gf<7}~~}df2PiXwSAXV?ORh zgvE6cqQBk(&UP*vq`D#`_RgHEjy<+e$V6mxi7!_Nc*l6uoHfP;b_!_^(55uao*Bpi&jusrz%75q@ z8zimS7{!2)`lLaAY$m#8N`~}^(qe~xa6!o@5M?aO1DFe@&2uCIaH33*yYxg({{>Juu!i`v>Y=*ZTO72zW=_J>Ct z%7Ppi2d+4B0i}pACe&aAY!l?NiI?OFrkyi`_^te6s6kNjV_+pN))}ZR*dS(bFl9;G z*g=P#Rn)2=nUS+ycGmowA>chHsX`jdZI3HSQ4Tqh>77?*lw1&~^{SfG+%nH1|2j$w zOwq|mMsjdAtYr#1HEdZjGFMFFfNh+FRbH~P!dr9-|E{Pv&we9z@|$LQaG1W&B*Y05 z*fV+fwMA+%q6r-Yg~3UOwpdGpZ$JpkGlrx@6uoZJrLGI+(*cPH6r(<7I3D$5uhO{w{SUgn#^3T<3u6Pwp|@-XTm|QPkpA5~Fta&8h0) zTSwMs(6_G_xidd^k;jz~N8LS6?}Kk1%b5T}BKQVR^#*?U#5T zB2Y8x(|hE{cPPgv)OiLU4miH~9`)TD+%RMN;vT!*4&yZH6u^q0vjC#)O;U@mS)mbp ztUb)!Lg5NJ>`~_h+ly=5-#tT}wxDT?m?GXkzDG&}#zBU`FjwUHh@3Na+bxI)^L!9! zc^DKW5%jCaPK4Cc>G}w?qN}(|j>Gc)cQF2CIijK83!XKIdiaPTi&5}0R zjMIDsQp9fDhz~6pC{3H&BuuRI2*3BLEl{lGtImfR2;f1kn(=DRSo)(w2r~VQSR6S; za8z=#^A_w2%}xmM5?YfrR7zqT>4TTguUO_iN|`Z^JGV6yTloXQY`obE?aA&C2rErO z_j0KP%aXx>j>T@`AneakZ3;JoE*ZW+$(3hRK7oi;T0AO&RGhmN9b4R^ZFFGdx}eqt zyUi8;;?p1F`Lh@J^yUsJ2>KB;*tX{ox#APTsVo9uk|8uhN+2r8c|p!cFi1z% z##YUtwqL6vT>(Na6)pRIDxKj=+Og3Qb|Di6JTSDG@UmqX!p5n(k+^uBT;Ox7Png4$ zwy-&8B<&0JdYb-MdcVa`0@8{$24Up#lAYKhtWq0DpiU5LQ3*5tAp}GYN*plOxbiSr zCOIXAxNW0I86`;u0)awHS+xBud+kyBeT&x+#W~5~g31L~m$w+wh`h+agBSp|KuN!J zQ!^n#=KLla!K{N1%A!tPQ4LtnbU@X*QzT=0>SL)HPj9}$n}7JP;J^K6Jbwi4U*2Q$ z;tsUE5E5LAZQxnD&Exfm@=rTa-z~iz`G^z$Ae;Bnjg~vy(AhIHR|tn2|Mb^)NB|n0 z!vdec0o!rJv+WM^@r3fPKf${%e*>P6pyh<23dS*#5J0X1Av}AAm=aQoNJL1?3c0tM zympKFb;<0%aU8PuVUx0I@NKf5?_J^&=!*2d(ZNSK`=3PF_2YSrn8 z!DIJYq{p?Cf=TQ82H&SNVq{={IDi6SoOS}|>vPVvU_gQk%6Lacvj>*C^n-@RQ55bd znBt?c_^ElfKt5u16^~l5>A|X=4)Pr$wc%0}j*h+`TRT&?ZP}(HMhUppCdAnFhBM3O zh0wYR&_UmTPa_jmw4m~^#)lF67OgZwrWP&%Qcza&xwNu5^~RcJ55R0{+7OeIqlf}f z10o_Qc3EVVX>P)NF3JDkGxY%}fw z8E^%pX++=wm^S#c=bzwMX5`ZWkB@J0adnHJT^)*#cZr}X#3{+JsRW+0owQK{0~XhR7q z#b#@%Vi>j<(iTIS5JU3F)Y?%@8=5WSs&ZINkxDk9)S{02*Ld}df5O{;`bXS9>~VAV z9NYUl#O+of;;N6ggMWYpSSwI7Le;FivIdQo6{;Vn zu5aJaI##L4`GE59fOtIO^7;x_ch`vHh%zKp)zlt`38d1ftoxP%VMtN>$(ga6cA6oI z@oD2eiw80`k1E8dOkhREs9mo(yokcZJ2?8m6oG=~lsZid0GEn+p0VHWn{t<{h(Xi{ zP8o*PsE2?55t3KaliRcG z3~sEwK4=@ph%mMnZOu%$L_f@&zXhJL8YDa$Q_Gg04e2}CmYS+P%XDo#=qyeLL_E=ya)R9Vp zV?Ae8ROD)QraCdB(~yp$*3Xio zfD|HX4H7Ua($w1%3vRrQ&K8^%Fx$B5K3FikU*K*>eSGc32^XE6kqH>Uh&5x`zXxi? zZXA>kRS^`8b^QduOEJ~6-=>4#rYG8EHB>aFgs37-mD>nwPhm(91_}{WD@r~gFN>-Z zLX{62YDAuKnDI1wVE<$Fk?@YNl$8lQMCiO*gI3J$o3I328+UXjZ1r{wgMO;^P7(!~ zlm;0$NYGXXs8i?+zxCZUn7F{EB6*)M3|>$LInBF5s2Yewh0ZIM<%Ad$hPd$&umOw| z145`eRcUC$KB{cA%B1pv5%J;{F-B(#kK8t7m*?PpB+Iice`grVXN~!v!Ok8M!P7bOFSnLpvH;QoA!7 zLhDc<5~lmG*~$#{5T&io(rcH)ju^%Z4@%nV1Q9L?Ry#Hg{HCBOYnP##b_igUazrV! zpiAiji~*H{W~|o2R$E!fLI_KA3`P$oi)z7x@|&veO`E7-GY5i=M;3YU$I~MY%Z%HU zz%_$%#>Ms$H@izv&dAFVQw&P1Y&Ocu$AQt%2(;15W;iT)6^=gh^mke6KVO7?~_c)oLt2va(avQ&+(<$&9t`rqPyB`kjqO z#%2}Sy{dbp)~pfc0IJcakA*<~AjzN}2sF_ql)>wQqF6=RuE9FWPg~0nT%-d4DlIBf zFVT05B02*WVTq$D827zAk$WxN^$R|uO?VD$pyhIVxr?tYK6I;v(t z=b5q@${u#_>*sw&-ah#(;XeOdgZtIscg4Xb-eRvYNlu`d4f#xB2GxuT#>k9V0t6t@ z`X3tt#Ik?%YkV+C)y%-AC$Za|_S0zq$^DtD(bv`rsu|_52S4mVAdL6V5by3WCy_C> z?8s4$!b(chthIw{O$Y*j?&GGwm!e6Y2AvsD8VtSqY1e0(&!^K7%mtfidxnxULQSm| zDJ5K8U1M2h86u>mD|2wBAZGpRB9pqv6s{N$lZ=K8dQSlXHTqz;-ttRL)15s;_-3_B zS_ekM*gK=Ox?j!xy4Mnfoo-2iqZ>V;Y?UBbYm;SyMvmilj<|<*5H7SqW{CT-YbYLGN4plu6HkT4y6*HCrdGumcU? zs$*b5NP~@`F20|NX2#JFOlB>Y*nZNi5!CTD$lKm5aHv?oJS%L#C}l6T=H|{W8 z5jNw5DJCrQ38G1op#D|{JtAsK2s+y*AVpP2i<#oDo#08>uZ^DU+Tveil1d zE4VK+d)&$Z&_tS<5K>S?tqmwD7C{HGxhbXyVyz){@MLAF1;*H5J=r(rh^oy(7?d=ivV6J;q>fTik!3Eek=cl}FH$zfM&Zs{ zD+*-)ol)(cpf1e#n_n6jh($$$1CNhU-*SNZF22cv9Rzw7ZTvwUAl^g7O0t`+! zwYh$>Sbw>89a^h%(oh@LBbUek7u3TMWnPeWTa2H)1TJooZPqvGfwq7FQuBt|QXiWanBO8TsIjyDquXDqV}^@wTEGEGOQBodgOGYA38d;)X9cC&@ez_uEl?G{ri z9EO0+X0zHW?Yhjos*?(m0guDj<}Z`g`q+DGMS1P4FSp3a_f{!A4`jn-{3#px$6(5C zzf#+MzDM-Wl^c`SvSj~m+uQm_%gUJ>0gnNdgcbs;8Dtm6(myNk-CajiU`Ng$GrXXcjg01zDlhq%{=2!<MvOV9Eez&-MusRSMdqa#S-^&zDCDj>y0wx{wW+9efs7Mn4>qhuW4+8(oji38 zvxlGC1Eua5b}bVY#dpLcgHGF;S&zLee#s{s-u@AfKmRAtKm9ANUY~Gtb&K8eTf~bE z5Unn%h%g0@lzac@tzNKyB6QxyA@$B}x@YXAHZ5E6_n_IO58S*t@D>O7T1f_O#&)d$ z=I`ylOFzKAL4gkd1)-L+e_tOjuQ98GmH!xl)K^75czJ)Bpx@#XuAkeuQerZK&?!nh7kd!J6EzoPTUS&`_(;-UMUW&Bbne=n$j3XBfqI?ZI42RkK zvjHWeDjKJ99V=&M2FM?o+ZKG_;GMrgl69^i(S8qiX@1lez~bbhuGZ*!=WXvc=PlPA zs%6u%hkd>`0GqWA4cuz2NZM=O2ghuH0h8C<8*0!L^{UQpU=Q}iAH)!q2+N89Mj2G% zyHBWu>o&(ryZPkf_Dvn~PmAR-e5ORS8gW3$qhu*o@p_2#ZGW z-8Hzkw*}HYn2$%yPkUe*F@Es{=#$T|Yt7fOX*;5 zf)JPHd6xO-De1bY)1csvOR_LA>}e9I$R;If!Iv_9S*D2v3K;D6&CwK*Fph&D4aegV z<2WG>gL|Q+NZoz2nUI2#nL0_)KA^NZuuM*4EK3GOnO^`S8Jc$^D9=WRG%x5TFRjcR=D4OS4od?5eqb!nzIy2`nG);8%W3bYp%#yG-0Y+w!wP-MI+l_6XyR~rs zf}W`m0M6hd%8Qb>B!npX=cO#DD8i=*%C)Lijtx$fHZ6DZev1eoImb#1Ct`0M6JZTl z79EC@G&$=LV}kKipwFa<2+6+6^@RQWi2Zy(E)|!%TWrTmP!*k$7);RRgi;n+64g-$ z2!;3E`PD7~bJm73vX%_0rNMIFUcQUXIV5dctDS(6?e$V*-0Eoj9H>&C4gq7%B2QlJ zIcaNYWr!-CsYfb;ruH-09%$VmBLX@Z;GTJ_>HKwkVqvhsunNhPuvQit@3`OgHWH28!RxzpB zM+|NJo*D(K%@LY%5u+osp^;Fm?IgsAXap0RL>JopW2u8hNI`tT@zo!2_@{ry_TT=9 zo3z38%jekKUIWtvHes9&1rP+JL4&ItQ;owplx@2E=dZUPcl2iTJ7;e1KP#ZG-|uUq z-{1Yd&)%#)&u7lPU4sQB9U5THu-$!Ulq~Y>9Ck~=9TM{)5nbTOXa5(SX>mYxDacPx zSPnD7-7SVMK1aE^M@}O`NGMg6+H|Za*CO+;5iktalU?w%f5Pc>z;<_u?RMv9iZ#NI zkbZK25rzTRSJyHUSOdclE&Hh`xp;f1LG_hP3&9}bmX)>Iuw~0#UwdVQs981(5|HyO ztz3iF5jA+IK^%=aEPsX{**~ys7TWPrgDWZ4vY=|)lPEq`-*7e}w06(8X-llR8&13p z#+vut54iq>{1h$?OEYM_yYgE5wl@nD;0}TieCnBP#ZrpcHw0m~N82~ns>ag0?fbH0 z&jVVfBS)_cMG1(ajLD9-;W!_Wf=rS#grus^vNa~uMvUr%T(x?xCiXF}kMNI?hYup~ibQ3+f5QUsNVi)5`q zA^j>)d~yO{Xg0V*aA5asHW`fl=@D=D-%CGsoG`=@i3SNoNW`8hkz59qW>2;IN~W%r zRmo0BX~mzTNfTQzWR!!d;fu+5do_k2j@eNqL$wGgPKareE)vcfAT8i}LP15qAo}lF zT1`mzhlrYG-=|_D3{`*u0CJWIXBMrBk76N&*@o;skC??Maz?ESmSt&?BoyYN zh#?89)C`ZP)3MeHMzz&azYigJ`+$up6vCR^yd2CZ8Glts-G+Wtq0NvjFbUhOwOFnXC7QTEUJnEUqc>n!Zrrm{oC z{YmI$9M7G(Ia4Rm5nM>*gTSQ!-c|>2gKhWHgmNJJfV=GY{HH6WSg(3L&6pqd$hG3) z<_7V_OO)*;K&@qwstANcEpNn_I{2++NVzPU<;uYgu_q*SOnbXjyzf z>!UYE*;w}dJ~JIjy|-GiD*Ji5PaVwhx~O7L@2V77zT2iKh$R_9QL*lZC@wpyQ4ia`l)GW)xh zMQkv)vtG)ACC?br1|bY!ZQZi$T}sqV??B3}ny_i3+?@9)c|n*ig;)*k?PeCo=M%^} z_)wx$3~7Vw?K6ZpU`QjTVJApIY(t*|2q8>}V+7YlM5|Z=%rtBvZ2cm3m22Z(n7y&U zTFHo8 zU({WIwRr@qEo&{R365I51D+#T7F`NN*P5c&RA^WWK=|PWM0%C1@7orwI}{M+Va{0y zdV#eTC=t~-sus4vzRI#^E&_Q(&Ei<=@LJChQb`jkusz=73HuBb4uiR|^mS8MA5rJ5 zJIkO!drE3LK75Tg|MWMgfAbHx{(g^}+dFKY-y-THJE^C&ted?C*c@gh?nrIaHnn5q zX6_rymbwC^nZLeWZDp{j&y719(A6~iy8K#ydHo);jjq>je^>i}srz!D0O!|f&!M{~ zW`A!pq2ZPuQ0(6txWCruR zEK(;Q1`q?IF`o${mICg^rc%4Nj$l@DMy>_pFc=9Jw&J9Ua>nLPF@nNInAQ}KE29=U z9Wr3TY(ksUAp1Z=G;P6>>{ii3@90n>E38bR{2%>p3#!{^S>4gD_ukO3b9IJ>@swE6n-C-@6NSaBqj*g)MKXD!q{Fkw z*IVwOLgs9!?gv+$MrefOAd=W1&2+^{**P%;#pJv#XcdGxVK-i3J6r<6B!@dWV>Y#R zDIvu$L5o6QWQF|oG=(I7h9!`bax8g?NoiNG@us~Tx~yS!+vp=uyM$>Nq^uC4wYVvY z7m~L31^t`oXp(Ehp!#IP_Wi=&3LHfGb(Iy7MV{QU?9Ltnrh&XR-C{!mfDyivsKIy^ z6{|_tn`ISEk*rzRxlyF;MBP|=SbIOM&dQ-}gQL5Ed)3#yvP*_;boKMjh*5Vhz5!Q0gBnz{y6=yH_<+^a z&mE|Lmt_oI7U-Nb;M&)#_jec6gh%W!8N3PK*d9dD*)??rd=<a${`q?<%xaebIE2Ay$0ej6+9gg-BXm3`!j?*|*Z$g+!(0Le2J}wpARxPPg+l zaO~Zk1%Xi&r3(~6F*WCcHuAP18pi3-0FJmT&7oy}9ZF*M!`3s@uGN^-e(FpFx4GPS zSRh74mOWP%ATSt`{D7+me8j*IHvo-@Az)b!*dHD+O;GaGhB~DsI+B+EkK*~chx4a=HrDS+Ffo0`dHnu%Tx~9zXxPq#Nvf4PJ z0B~&OlMn%x^O7*>^fe7VD54YwS8^Q(%jj(n8F~{yHfgt=777~O`2%II0oj}7Fe|&* zdA63dhE`Ou#n&5dpA{P6L9_ix(H%N-aKB@(i&3(PoRvvC3VX7t4oPOhAtI=OG=g2r zNUPR8Y-TxJ=){E$NV_}@s8eh6Anv?2?2kX6fY;E>um6WBIE*;sR9zE!UzVa%j3b-U&e4I&&3$mC&s3pCVEW;GkOIkgOXdtaHgUKk~43%_O7L32I(aF>OEJ zwPu%B2oQJo#av{Ddq^-gZDruy!&@wQ#@)?xOv9v%U-=%N)j;j6tRd%ha4YL1C6iWC zGZ?$IP*#-{C2n={sz$w)#{yC$H~6dPjV^m;Mqv}&YV}ppanOP+K!c;;b|O$~Gb2~N z6YtSTpKTpYvxEw9T#;4iXz_ggS$` z%^2A^>RUZDQSrYsHn16)77Pp+QWe5RwSL=1Qivgd(f|=6XvQz7WGDNyHm&EC1;yIl zI>+a1MyrQ(*~p4L?nDRn_Zie9FRlp73CC}KiTz*yXKeod7q~tJ+|kd{=D1Yf6VBeqd6b`oW0)u_Kc8YBp1T^ zdRE`V9|ltX`_4x9@H~C&dmSxkf7e>+3d-?_C(1OT+*E(ZAm(3T<- z@mxPU%Z%EBXb#OH18ol0#iP2+#@ev@E(hPE=rf?;giF@>(w14Ye7&JEU}Ms=2h)~S znxiwg6sk6s^Mb7NAsnsgoKSEjH6!D-V7Gy5XO5mVi_)NnJ7hwRG`H@GFi82%&_;hI zUETV_jScS`hlq}_j)In?P#mxw4kPtapiUChoWDWVs^9NOik&STyakK!q9Umpkx(?< z&=s*wF0*m=Wt-Y_FTPMR8$EQxysTEgX5DjLKwMGF;>i>!2v_6mGYwI+5yac+0%Mxw z{y3}PSjV&^Xb5i_D7nsHLw{`# zSi7vQRa3fail;&n1(-y|GK6l{h-vS0K`n|-V*p{$ti7q=2};zrvf4q{WVSosX|P&= zMh)^EDxWdgbQL!YHuzbI*2lcF9SyzB?E zGx)1A!I@$24Shoe)=poxXfsCr=bBOPoi&}mrimJ~47}OS^|f^GM|VBve`n6HL2Xze z2CO6(&ob=2rd}7i3@m3Xk55=mGvd`2<7b~C+}xu^nKY(2uyQ|mt4?1- z+Xt=0p#sXP_$Z2qR;wsGaLB>I!5+DI!(M zWDdRmjOL)^KBbp`8YgVWEr!@w`es*L-$U(}Nw6B<2K}|`!}NAAA|ItHZL-R5Q3!lq zPGW$g_Uu~#pf;+(;`SZgjsa>my$X6QkhB0uU)z6Azjn+) zSo&n+rAtd(x+t)c3d&SdWHXy@&md@!mw}M5yST$NY(XI8GOHSBa{DYdZ%BfYNJCtS zcvTe}V=h=qar;RuoiS;cbhd^jg~7ThDy+lM&YQvd03)gDobZ~e#&QD7i`bc}HEMB$ zYe7Zm8YjesB-My*w40xQOt5LqHIz*vwAj&J^e@BYVs!r{OF zEuOu7#Le9uE?(RrY`0(~j?~^VIuiMj%-0bBMnA@{!8jdQ_OZ;_Gu3s!FVGoh55hV} zijRHNhh)0-rFPDvw`~2jeJm?S?;JV@-o4ih)_GRHg3~{^s8nmOt-kUAqHjyoZ!Rc!G$Ad(DASKdJpz2H_-lJW+m;I zCV6`vHhRg_w@ObCWZ*Q936ZEdWwd}vVA$wovz!+^Jw2k7g3F6*5e;E!lbiF5 zGzx{ z7BqX3!a~55Bxb~Bx3ZU(kPS6p!2IYk1&9`{`r@saMpTEPiFW=$i?iyDw zo@2PW1kwnTp|A{4;|RNuk2oS-`|es>$8l$I7o&VGyw1YRdYU|3n-^^ekk#HztKQZw zbcl%|L|i3{kl6!R8Si#is%)i$B82N!VR~)m#E682Esiz91fjr;<7tl+BZfG*&bwq4 zO`VUSV?JR>2w-ia4b|&2%H|A30a6(Dc5e{@R2BAB4gfT{+ zg;h$%{^xzJ#+m0wym8x#FhLrOc?q`ZCU}{I z0w_(&Y`0-U0U;&`c}x(4!WCTRt|S65XXJbWqawx;CL9(>d*&$!=%Ax~Wm@AP{=nqG zxg)u%sR>%LF^g-cV5EqcMGBlydUnCtS(vkajV(vh?F>L@G566P6J6u7%lE=g|6ACT-IRgqR|b1!iH5 zptK31b24K1O34VW(j}igFEc1c!~~DLtSza6vClJ(%LyrxOrBF8BP62*^V2Il{OjLi z{+s`XtKYuDv&{u=p5J4O?=$i48Vzdy=mno=>TGH68T{YD`uwLp>+ECX8pyK}&npey zss(v}KwUD1+Z&8O_yPFl9yvtSLDc=F*{t^`o1yaWYA6mv5(zIzX*r0LDP23K!LD{%LzFbDHCWW$F-p38Q_Ez zlRA>>q%G)u<|?o=kqo==fJ~uW)>6fXND+5pH?o#SvMW^->Ov@<^-8Ot!LyD(VH_sh z-P|Gs(VlmZo7MU01S;ztF0M7ykeybBk!Q)Z`1%_(YL{;gRfa8zcEHu{5_+eqW8dj; zGhp2eGOpRo^_pU92&0g-B*>1T>6a4(woNIDj(3%oFAuU6a+i!$kkDF{^{*Q!7g6XO zhe^6_(8OYiM85cNnq~B7nh@duGEo<`yM|917a~&XtSKX<30vPI@^@r}UL{_(IrMGZ zDS{;m1W;7Lr78$6OAmJFOF0!H5`(B@H)(gy8_CL+^N3^`Xqo+lNgA-fg#k%0m#X`1MlYe7r(8R$~qFQR6ur8**0(vx!? zLIe|`)EPNTX(4rLav=nrWzzUL)(V-%X@_arDT`d;pg<_KF~=R=Hk0UeKDm*Q2CZ$h z_6#W#vEy^&Frp^W(~SWDFK{$K>k2u`v?muw&^7|DMYWl=HGS8#5v1jQ@wSTaj42kltXPW73=J3tpQaK3V?+R5D1&i7Gq2pgN(u?g*@}=J>LKJ zr+D}G{|y(v`wExS7FW;jG2Y&QrVW^)!|YP)`ygy;4hL@g)>-jKoLisOHK*1qxb<*j z`($SL;p{)#pzaS~_j3o|IdUfl(i-%x`iH&+(47`F@u1ND@`L;90|^kV0-TSP1^gPg zW$bPU1A(}p%qQf>12{8=n_Hw$K111F_#l>m2!^n^%{jO~*EStrIqII3K0vQ|so^qK zthHlxsoV#5VQ0@!CQ=BINDwRUtI$O=gbUPyP=~Io81WDzDqGn?l;eaHq|Yt@exg$c%UP30Q$7U>~lo z_4ykApaQjEjKVhnif25;jj2wycMJ`p4vMFWs57a>2(zC}V?&$G+ioVGHYv0U%e19W zxRi{g7G2(ikQ)4Lkh!<+Lby1=`j0g`w(}cf1WT_N`avuJbcj`uQeZZ9HxWQWk$o4t zjtj%btWO^yVY9tNiU~uE5_oumA+5vIAHn_{a@Cqd5HTDUL?GCV4^8}-88NhLmbcr! zW*skUuyH}Di?@PtC;+04B^@M)qJT)+8;*(;C*9KxwhnC0GJrT~W}aZ=ObP*k6BvUc zI7Owp_3Y?8`pOHqij;DoQA)bCz#?aH;Ue=p1k%8+*+0qBr9CY~nV47Ev=wBpNoPQ_ z-+jVGDnun`w`gWgE4$lsm(q53iD}xx^k{9SthM0;SesX8(9dhAI`z!j0sxEFrQOW> zR#Qj-n@(}+AQ*AT%X~rwkkTj_dT1KXMa2lX7GD5p)?92PD+oH=$LkknH(CDDBWNMC z6{+2+HasgV7D7Z;@(Y4adP2ayobYDD7$U*hy%{sz1M z`8&K=2Hbw~4CDPX&~^*B7QAh?^CZ*AJf6C>pPo$ik<8SMt~t73gpKC>{dH&c=Jt8U zoA|h+{FufzuV3&%@W6UItKM+57LN}Q0K3-?vWEps_8PBi|LMMXCsg*IuM19Kpk&mi zJ?5t;a0(b+J_qjak%tjQ13qXl$wf5ETPu}11(GX^hDce#%N@VMgtaPsOI>C~e$f&v z^g0dZtmQq`CQ_|BLM3CWUbut*YEZM^Iv1Vi4Gpvv$?BZH!P(tH?0Qi_QoNULFGML?25$J*Nt zM%Fs+!~Q+yl5xGeMv4sx6|xbtT0)Vto}8TK0R^#?!x#wKUdS~l zie}|yk+H3cfW$Crz!3ab3G}45J}OKu?X;R)vMHffojPZzn+0vHcCIJZZ3vOH?+(MH zq1cf@LRFW{{gf4if3xfD|)K4N5R^nCLz0M5Lqy z(Lzq47ifqMlUw9#n^XuO&EdtQ(wn7y7AYH%QL*GB0uhEZ>M)63_@z@BBB>XmW?^cQ z4VDn25Cd?^B45Z+##o-RE?gS$YX1&jzWNG3eECB>PxnY7=^%2uHqW$mlQtOtBlp#1-RIDK2Xwo?{IDPU?1%ezb&CrPdgdAV z{S)eG#&CUw;q%W?uWwN)DyGvAAjHtfE~=wkxq?eo0?*mn7#!Y3H3Bf1^ECqlcN%>O zrKcd6K!>-nR3rDa?8l=I0H`uO3N~So85E-iyowWQ2A8ui=xd*=wSqFkML(De z=92{6M*a(dT6V4_xK$v5mOa=k1bcomX{;bZP=FW{#xZ&TFH)gZgXvC0#he+NvZS$) zGblz3Nkv+8*RUdh5pZ@nHy;JlO>OI~6R(;Y74+n&N*b^%Gnl1ZVd$cxhPCSt*S+8P zD)PM{1f-NiG)Dial^=y`5u*+_X?2eI?hxVxBNmblIbroRmUxI`m&tS{K~xomwL!Q61ehL)-oWY{R0&!P!Z&^c z0Z7E4Fac4rbHEbJiY1>kAc)SjvN5@=<4=hzDrT4@uz+=R_>q|g^s*^pjR)lU#*W(E+!J` zm_SC()|*2l;RR2W}ouxPJBmn-_OTm!bqGD?zPXp*yTsJTpl6JV#!^=4bWg=0rLN zdL9sP2hpya;0kBa{ch0dhYa^~v6=Pv;3S{&XI`z z%e~FbKC4ue{So=`34wv}{vPqO&rr7)q7G+@b^70+?C)N8ps3+B)vb?RgGst_cMZ;D zUp<@k67|l!L374}hJOxGR1IusKm^GK6pGZ7>wm7CW!AMiv#{z(=4NQAk$?#ae5P<9~c^f6f&~3L{q#zC^5`jZ|eq3$xS=DRl2VWSmwaWDxQL3wJ zmr@n2Ykkn9x)ieBVyF!r>L;wNHXaE)�(HlM>xKUc0xkn-;v+f}B|;VQmgJ+}YUu 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 Eg8w$+)c^nh literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/images/set-a-gh-page.png b/src/vendor/github.com/kubernetes/helm/docs/images/set-a-gh-page.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9dccd69350a8c1b2022db2bae645d0081a35ba GIT binary patch literal 130550 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 p|C5}jg6{xk&#douM#y1_&=T?02TlM literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/index.md b/src/vendor/github.com/kubernetes/helm/docs/index.md new file mode 100644 index 000000000..4ca93bd1f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/index.md @@ -0,0 +1,37 @@ +# Helm Documentation + +- [Quick Start](quickstart.md) - Read me first! +- [Installing Helm](install.md) - Install Helm and Tiller + - [Kubernetes Distribution Notes](kubernetes_distros.md) + - [Frequently Asked Questions](install_faq.md) +- [Using Helm](using_helm.md) - Learn the Helm tools + - [Plugins](plugins.md) + - [Role-based Access Control](rbac.md) + - [TLS/SSL for Helm and Tiller](tiller_ssl.md) - Use Helm-to-Tiller encryption +- [Developing Charts](charts.md) - An introduction to chart development + - [Chart Lifecycle Hooks](charts_hooks.md) + - [Chart Tips and Tricks](charts_tips_and_tricks.md) + - [Chart Repository Guide](chart_repository.md) + - [Syncing your Chart Repository](chart_repository_sync_example.md) + - [Signing Charts](provenance.md) + - [Writing Tests for Charts](chart_tests.md) +- [Chart Template Developer's Guide](chart_template_guide/index.md) - Master Helm templates + - [Getting Started with Templates](chart_template_guide/getting_started.md) + - [Built-in Objects](chart_template_guide/builtin_objects.md) + - [Values Files](chart_template_guide/values_files.md) + - [Functions and Pipelines](chart_template_guide/functions_and_pipelines.md) + - [Flow Control (if/else, with, range, whitespace management)](chart_template_guide/control_structures.md) + - [Variables](chart_template_guide/variables.md) + - [Named Templates (Partials)](chart_template_guide/named_templates.md) + - [Accessing Files Inside Templates](chart_template_guide/accessing_files.md) + - [Creating a NOTES.txt File](chart_template_guide/notes_files.md) + - [Subcharts and Global Values](chart_template_guide/subcharts_and_globals.md) + - [Debugging Templates](chart_template_guide/debugging.md) + - [Wrapping Up](chart_template_guide/wrapping_up.md) + - [Appendix A: YAML Techniques](chart_template_guide/yaml_techniques.md) + - [Appendix B: Go Data Types](chart_template_guide/data_types.md) +- [Related Projects](related.md) - More Helm tools, articles, and plugins +- [Architecture](architecture.md) - Overview of the Helm/Tiller design +- [Developers](developers.md) - About the developers +- [History](history.md) - A brief history of the project +- [Glossary](glossary.md) - Decode the Helm vocabulary diff --git a/src/vendor/github.com/kubernetes/helm/docs/install.md b/src/vendor/github.com/kubernetes/helm/docs/install.md new file mode 100755 index 000000000..78b1a53b6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/install.md @@ -0,0 +1,347 @@ +# Installing Helm + +There are two parts to Helm: The Helm client (`helm`) and the Helm +server (Tiller). This guide shows how to install the client, and then +proceeds to show two ways to install the server. + +**IMPORTANT**: If you are responsible for ensuring your cluster is a controlled environment, especially when resources are shared, it is strongly recommended installing Tiller using a secured configuration. For guidance, see [Securing your Helm Installation](securing_installation.md). + +## Installing the Helm Client + +The Helm client can be installed either from source, or from pre-built binary +releases. + +### From the Binary Releases + +Every [release](https://github.com/kubernetes/helm/releases) of Helm +provides binary releases for a variety of OSes. These binary versions +can be manually downloaded and installed. + +1. Download your [desired version](https://github.com/kubernetes/helm/releases) +2. Unpack it (`tar -zxvf helm-v2.0.0-linux-amd64.tgz`) +3. Find the `helm` binary in the unpacked directory, and move it to its + desired destination (`mv linux-amd64/helm /usr/local/bin/helm`) + +From there, you should be able to run the client: `helm help`. + +### From Homebrew (macOS) + +Members of the Kubernetes community have contributed a Helm formula build to +Homebrew. This formula is generally up to date. + +``` +brew install kubernetes-helm +``` + +(Note: There is also a formula for emacs-helm, which is a different +project.) + +## From Script + +Helm now has an installer script that will automatically grab the latest version +of the Helm client and [install it locally](https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get). + +You can fetch that script, and then execute it locally. It's well documented so +that you can read through it and understand what it is doing before you run it. + +``` +$ curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh +$ chmod 700 get_helm.sh +$ ./get_helm.sh +``` + +Yes, you can `curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash` that if you want to live on the edge. + +### From Canary Builds + +"Canary" builds are versions of the Helm software that are built from +the latest master branch. They are not official releases, and may not be +stable. However, they offer the opportunity to test the cutting edge +features. + +Canary Helm binaries are stored in the [Kubernetes Helm GCS bucket](https://kubernetes-helm.storage.googleapis.com). +Here are links to the common builds: + +- [Linux AMD64](https://kubernetes-helm.storage.googleapis.com/helm-canary-linux-amd64.tar.gz) +- [macOS AMD64](https://kubernetes-helm.storage.googleapis.com/helm-canary-darwin-amd64.tar.gz) +- [Experimental Windows AMD64](https://kubernetes-helm.storage.googleapis.com/helm-canary-windows-amd64.zip) + +### From Source (Linux, macOS) + +Building Helm from source is slightly more work, but is the best way to +go if you want to test the latest (pre-release) Helm version. + +You must have a working Go environment with +[glide](https://github.com/Masterminds/glide) and Mercurial installed. + +```console +$ cd $GOPATH +$ mkdir -p src/k8s.io +$ cd src/k8s.io +$ git clone https://github.com/kubernetes/helm.git +$ cd helm +$ make bootstrap build +``` + +The `bootstrap` target will attempt to install dependencies, rebuild the +`vendor/` tree, and validate configuration. + +The `build` target will compile `helm` and place it in `bin/helm`. +Tiller is also compiled, and is placed in `bin/tiller`. + +## Installing Tiller + +Tiller, the server portion of Helm, typically runs inside of your +Kubernetes cluster. But for development, it can also be run locally, and +configured to talk to a remote Kubernetes cluster. + +### Easy In-Cluster Installation + +The easiest way to install `tiller` into the cluster is simply to run +`helm init`. This will validate that `helm`'s local environment is set +up correctly (and set it up if necessary). Then it will connect to +whatever cluster `kubectl` connects to by default (`kubectl config +view`). Once it connects, it will install `tiller` into the +`kube-system` namespace. + +After `helm init`, you should be able to run `kubectl get pods --namespace +kube-system` and see Tiller running. + +You can explicitly tell `helm init` to... + +- Install the canary build with the `--canary-image` flag +- Install a particular image (version) with `--tiller-image` +- Install to a particular cluster with `--kube-context` +- Install into a particular namespace with `--tiller-namespace` + +Once Tiller is installed, running `helm version` should show you both +the client and server version. (If it shows only the client version, +`helm` cannot yet connect to the server. Use `kubectl` to see if any +`tiller` pods are running.) + +Helm will look for Tiller in the `kube-system` namespace unless +`--tiller-namespace` or `TILLER_NAMESPACE` is set. + +### Installing Tiller Canary Builds + +Canary images are built from the `master` branch. They may not be +stable, but they offer you the chance to test out the latest features. + +The easiest way to install a canary image is to use `helm init` with the +`--canary-image` flag: + +```console +$ helm init --canary-image +``` + +This will use the most recently built container image. You can always +uninstall Tiller by deleting the Tiller deployment from the +`kube-system` namespace using `kubectl`. + +### Running Tiller Locally + +For development, it is sometimes easier to work on Tiller locally, and +configure it to connect to a remote Kubernetes cluster. + +The process of building Tiller is explained above. + +Once `tiller` has been built, simply start it: + +```console +$ bin/tiller +Tiller running on :44134 +``` + +When Tiller is running locally, it will attempt to connect to the +Kubernetes cluster that is configured by `kubectl`. (Run `kubectl config +view` to see which cluster that is.) + +You must tell `helm` to connect to this new local Tiller host instead of +connecting to the one in-cluster. There are two ways to do this. The +first is to specify the `--host` option on the command line. The second +is to set the `$HELM_HOST` environment variable. + +```console +$ export HELM_HOST=localhost:44134 +$ helm version # Should connect to localhost. +Client: &version.Version{SemVer:"v2.0.0-alpha.4", GitCommit:"db...", GitTreeState:"dirty"} +Server: &version.Version{SemVer:"v2.0.0-alpha.4", GitCommit:"a5...", GitTreeState:"dirty"} +``` + +Importantly, even when running locally, Tiller will store release +configuration in ConfigMaps inside of Kubernetes. + +## Upgrading Tiller + +As of Helm 2.2.0, Tiller can be upgraded using `helm init --upgrade`. + +For older versions of Helm, or for manual upgrades, you can use `kubectl` to modify +the Tiller image: + +```console +$ export TILLER_TAG=v2.0.0-beta.1 # Or whatever version you want +$ kubectl --namespace=kube-system set image deployments/tiller-deploy tiller=gcr.io/kubernetes-helm/tiller:$TILLER_TAG +deployment "tiller-deploy" image updated +``` + +Setting `TILLER_TAG=canary` will get the latest snapshot of master. + +## Deleting or Reinstalling Tiller + +Because Tiller stores its data in Kubernetes ConfigMaps, you can safely +delete and re-install Tiller without worrying about losing any data. The +recommended way of deleting Tiller is with `kubectl delete deployment +tiller-deploy --namespace kube-system`, or more concisely `helm reset`. + +Tiller can then be re-installed from the client with: + +```console +$ helm init +``` + +## Advanced Usage + +`helm init` provides additional flags for modifying Tiller's deployment +manifest before it is installed. + +### Using `--node-selectors` + +The `--node-selectors` flag allows us to specify the node labels required +for scheduling the Tiller pod. + +The example below will create the specified label under the nodeSelector +property. + +``` +helm init --node-selectors "beta.kubernetes.io/os"="linux" +``` + +The installed deployment manifest will contain our node selector label. + +``` +... +spec: + template: + spec: + nodeSelector: + beta.kubernetes.io/os: linux +... +``` + + +### Using `--override` + +`--override` allows you to specify properties of Tiller's +deployment manifest. Unlike the `--set` command used elsewhere in Helm, +`helm init --override` manipulates the specified properties of the final +manifest (there is no "values" file). Therefore you may specify any valid +value for any valid property in the deployment manifest. + +#### Override annotation + +In the example below we use `--override` to add the revision property and set +its value to 1. + +``` +helm init --override metadata.annotations."deployment\.kubernetes\.io/revision"="1" +``` +Output: + +``` +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" +... +``` + +#### Override affinity + +In the example below we set properties for node affinity. Multiple +`--override` commands may be combined to modify different properties of the +same list item. + +``` +helm init --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].weight"="1" --override "spec.template.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution[0].preference.matchExpressions[0].key"="e2e-az-name" +``` + +The specified properties are combined into the +"preferredDuringSchedulingIgnoredDuringExecution" property's first +list item. + +``` +... +spec: + strategy: {} + template: + ... + spec: + affinity: + nodeAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - preference: + matchExpressions: + - key: e2e-az-name + operator: "" + weight: 1 +... +``` + +### Using `--output` + +The `--output` flag allows us skip the installation of Tiller's deployment +manifest and simply output the deployment manifest to stdout in either +JSON or YAML format. The output may then be modified with tools like `jq` +and installed manually with `kubectl`. + +In the example below we execute `helm init` with the `--output json` flag. + +``` +helm init --output json +``` + +The Tiller installation is skipped and the manifest is output to stdout +in JSON format. + +``` +"apiVersion": "extensions/v1beta1", +"kind": "Deployment", +"metadata": { + "creationTimestamp": null, + "labels": { + "app": "helm", + "name": "tiller" + }, + "name": "tiller-deploy", + "namespace": "kube-system" +}, +... +``` + +### Storage backends +By default, `tiller` stores release information in `ConfigMaps` in the namespace +where it is running. As of Helm 2.7.0, there is now a beta storage backend that +uses `Secrets` for storing release information. This was added for additional +security in protecting charts in conjunction with the release of `Secret` +encryption in Kubernetes. + +To enable the secrets backend, you'll need to init Tiller with the following +options: + +```shell +helm init --override 'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}' +``` + +Currently, if you want to switch from the default backend to the secrets +backend, you'll have to do the migration for this on your own. When this backend +graduates from beta, there will be a more official path of migration + +## Conclusion + +In most cases, installation is as simple as getting a pre-built `helm` binary +and running `helm init`. This document covers additional cases for those +who want to do more sophisticated things with Helm. + +Once you have the Helm Client and Tiller successfully installed, you can +move on to using Helm to manage charts. diff --git a/src/vendor/github.com/kubernetes/helm/docs/install_faq.md b/src/vendor/github.com/kubernetes/helm/docs/install_faq.md new file mode 100644 index 000000000..f2eae5b48 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/install_faq.md @@ -0,0 +1,232 @@ +# Installation: Frequently Asked Questions + +This section tracks some of the more frequently encountered issues with installing +or getting started with Helm. + +**We'd love your help** making this document better. To add, correct, or remove +information, [file an issue](https://github.com/kubernetes/helm/issues) or +send us a pull request. + +## Downloading + +I want to know more about my downloading options. + +**Q: I can't get to GitHub releases of the newest Helm. Where are they?** + +A: We no longer use GitHub releases. Binaries are now stored in a +[GCS public bucket](https://kubernetes-helm.storage.googleapis.com). + +**Q: Why aren't there Debian/Fedora/... native packages of Helm?** + +We'd love to provide these or point you toward a trusted provider. If you're +interested in helping, we'd love it. This is how the Homebrew formula was +started. + +**Q: Why do you provide a `curl ...|bash` script?** + +A: There is a script in our repository (`scripts/get`) that can be executed as +a `curl ..|bash` script. The transfers are all protected by HTTPS, and the script +does some auditing of the packages it fetches. However, the script has all the +usual dangers of any shell script. + +We provide it because it is useful, but we suggest that users carefully read the +script first. What we'd really like, though, are better packaged releases of +Helm. + +## Installing + +I'm trying to install Helm/Tiller, but something is not right. + +**Q: How do I put the Helm client files somewhere other than ~/.helm?** + +Set the `$HELM_HOME` environment variable, and then run `helm init`: + +```console +export HELM_HOME=/some/path +helm init --client-only +``` + +Note that if you have existing repositories, you will need to re-add them +with `helm repo add...`. + +**Q: How do I configure Helm, but not install Tiller?** + +A: By default, `helm init` will ensure that the local `$HELM_HOME` is configured, +and then install Tiller on your cluster. To locally configure, but not install +Tiller, use `helm init --client-only`. + +**Q: How do I manually install Tiller on the cluster?** + +A: Tiller is installed as a Kubernetes `deployment`. You can get the manifest +by running `helm init --dry-run --debug`, and then manually install it with +`kubectl`. It is suggested that you do not remove or change the labels on that +deployment, as they are sometimes used by supporting scripts and tools. + +**Q: Why do I get `Error response from daemon: target is unknown` during Tiller install?** + +A: Users have reported being unable to install Tiller on Kubernetes instances that +are using Docker 1.13.0. The root cause of this was a bug in Docker that made +that one version incompatible with images pushed to the Docker registry by +earlier versions of Docker. + +This [issue](https://github.com/docker/docker/issues/30083) was fixed shortly +after the release, and is available in Docker 1.13.1-RC1 and later. + +## Getting Started + +I successfully installed Helm/Tiller but I can't use it. + +**Q: Trying to use Helm, I get the error "client transport was broken"** + +``` +E1014 02:26:32.885226 16143 portforward.go:329] an error occurred forwarding 37008 -> 44134: error forwarding port 44134 to pod tiller-deploy-2117266891-e4lev_kube-system, uid : unable to do port forwarding: socat not found. +2016/10/14 02:26:32 transport: http2Client.notifyError got notified that the client transport was broken EOF. +Error: transport is closing +``` + +A: This is usually a good indication that Kubernetes is not set up to allow port forwarding. + +Typically, the missing piece is `socat`. If you are running CoreOS, we have been +told that it may have been misconfigured on installation. The CoreOS team +recommends reading this: + +- https://coreos.com/kubernetes/docs/latest/kubelet-wrapper.html + +Here are a few resolved issues that may help you get started: + +- https://github.com/kubernetes/helm/issues/1371 +- https://github.com/kubernetes/helm/issues/966 + +**Q: Trying to use Helm, I get the error "lookup XXXXX on 8.8.8.8:53: no such host"** + +``` +Error: Error forwarding ports: error upgrading connection: dial tcp: lookup kube-4gb-lon1-02 on 8.8.8.8:53: no such host +``` + +A: We have seen this issue with Ubuntu and Kubeadm in multi-node clusters. The +issue is that the nodes expect certain DNS records to be obtainable via global +DNS. Until this is resolved upstream, you can work around the issue as +follows. On each of the control plane nodes: + +1) Add entries to `/etc/hosts`, mapping your hostnames to their public IPs +2) Install `dnsmasq` (e.g. `apt install -y dnsmasq`) +3) Remove the k8s api server container (kubelet will recreate it) +4) Then `systemctl restart docker` (or reboot the node) for it to pick up the /etc/resolv.conf changes + +See this issue for more information: https://github.com/kubernetes/helm/issues/1455 + +**Q: On GKE (Google Container Engine) I get "No SSH tunnels currently open"** + +``` +Error: Error forwarding ports: error upgrading connection: No SSH tunnels currently open. Were the targets able to accept an ssh-key for user "gke-[redacted]"? +``` + +Another variation of the error message is: + + +``` +Unable to connect to the server: x509: certificate signed by unknown authority + +``` + +A: The issue is that your local Kubernetes config file must have the correct credentials. + +When you create a cluster on GKE, it will give you credentials, including SSL +certificates and certificate authorities. These need to be stored in a Kubernetes +config file (Default: `~/.kube/config` so that `kubectl` and `helm` can access +them. + +**Q: When I run a Helm command, I get an error about the tunnel or proxy** + +A: Helm uses the Kubernetes proxy service to connect to the Tiller server. +If the command `kubectl proxy` does not work for you, neither will Helm. +Typically, the error is related to a missing `socat` service. + +**Q: Tiller crashes with a panic** + +When I run a command on Helm, Tiller crashes with an error like this: + +``` +Tiller is listening on :44134 +Probes server is listening on :44135 +Storage driver is ConfigMap +Cannot initialize Kubernetes connection: the server has asked for the client to provide credentials 2016-12-20 15:18:40.545739 I | storage.go:37: Getting release "bailing-chinchilla" (v1) from storage +panic: runtime error: invalid memory address or nil pointer dereference +[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x8053d5] + +goroutine 77 [running]: +panic(0x1abbfc0, 0xc42000a040) + /usr/local/go/src/runtime/panic.go:500 +0x1a1 +k8s.io/helm/vendor/k8s.io/kubernetes/pkg/client/unversioned.(*ConfigMaps).Get(0xc4200c6200, 0xc420536100, 0x15, 0x1ca7431, 0x6, 0xc42016b6a0) + /home/ubuntu/.go_workspace/src/k8s.io/helm/vendor/k8s.io/kubernetes/pkg/client/unversioned/configmap.go:58 +0x75 +k8s.io/helm/pkg/storage/driver.(*ConfigMaps).Get(0xc4201d6190, 0xc420536100, 0x15, 0xc420536100, 0x15, 0xc4205360c0) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/storage/driver/cfgmaps.go:69 +0x62 +k8s.io/helm/pkg/storage.(*Storage).Get(0xc4201d61a0, 0xc4205360c0, 0x12, 0xc400000001, 0x12, 0x0, 0xc420200070) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/storage/storage.go:38 +0x160 +k8s.io/helm/pkg/tiller.(*ReleaseServer).uniqName(0xc42002a000, 0x0, 0x0, 0xc42016b800, 0xd66a13, 0xc42055a040, 0xc420558050, 0xc420122001) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/tiller/release_server.go:577 +0xd7 +k8s.io/helm/pkg/tiller.(*ReleaseServer).prepareRelease(0xc42002a000, 0xc42027c1e0, 0xc42002a001, 0xc42016bad0, 0xc42016ba08) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/tiller/release_server.go:630 +0x71 +k8s.io/helm/pkg/tiller.(*ReleaseServer).InstallRelease(0xc42002a000, 0x7f284c434068, 0xc420250c00, 0xc42027c1e0, 0x0, 0x31a9, 0x31a9) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/tiller/release_server.go:604 +0x78 +k8s.io/helm/pkg/proto/hapi/services._ReleaseService_InstallRelease_Handler(0x1c51f80, 0xc42002a000, 0x7f284c434068, 0xc420250c00, 0xc42027c190, 0x0, 0x0, 0x0, 0x0, 0x0) + /home/ubuntu/.go_workspace/src/k8s.io/helm/pkg/proto/hapi/services/tiller.pb.go:747 +0x27d +k8s.io/helm/vendor/google.golang.org/grpc.(*Server).processUnaryRPC(0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690, 0xc420166150, 0x288cbe8, 0xc420250bd0, 0x0, 0x0) + /home/ubuntu/.go_workspace/src/k8s.io/helm/vendor/google.golang.org/grpc/server.go:608 +0xc50 +k8s.io/helm/vendor/google.golang.org/grpc.(*Server).handleStream(0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690, 0xc420250bd0) + /home/ubuntu/.go_workspace/src/k8s.io/helm/vendor/google.golang.org/grpc/server.go:766 +0x6b0 +k8s.io/helm/vendor/google.golang.org/grpc.(*Server).serveStreams.func1.1(0xc420124710, 0xc4202f3ea0, 0x28610a0, 0xc420078000, 0xc420264690) + /home/ubuntu/.go_workspace/src/k8s.io/helm/vendor/google.golang.org/grpc/server.go:419 +0xab +created by k8s.io/helm/vendor/google.golang.org/grpc.(*Server).serveStreams.func1 + /home/ubuntu/.go_workspace/src/k8s.io/helm/vendor/google.golang.org/grpc/server.go:420 +0xa3 +``` + +A: Check your security settings for Kubernetes. + +A panic in Tiller is almost always the result of a failure to negotiate with the +Kubernetes API server (at which point Tiller can no longer do anything useful, so +it panics and exits). + +Often, this is a result of authentication failing because the Pod in which Tiller +is running does not have the right token. + +To fix this, you will need to change your Kubernetes configuration. Make sure +that `--service-account-private-key-file` from `controller-manager` and +`--service-account-key-file` from apiserver point to the _same_ x509 RSA key. + + +## Upgrading + +My Helm used to work, then I upgrade. Now it is broken. + +**Q: After upgrade, I get the error "Client version is incompatible". What's wrong?** + +Tiller and Helm have to negotiate a common version to make sure that they can safely +communicate without breaking API assumptions. That error means that the version +difference is too great to safely continue. Typically, you need to upgrade +Tiller manually for this. + +The [Installation Guide](install.md) has definitive information about safely +upgrading Helm and Tiller. + +The rules for version numbers are as follows: + +- Pre-release versions are incompatible with everything else. `Alpha.1` is incompatible with `Alpha.2`. +- Patch revisions _are compatible_: 1.2.3 is compatible with 1.2.4 +- Minor revisions _are not compatible_: 1.2.0 is not compatible with 1.3.0, + though we may relax this constraint in the future. +- Major revisions _are not compatible_: 1.0.0 is not compatible with 2.0.0. + +## Uninstalling + +I am trying to remove stuff. + +**Q: When I delete the Tiller deployment, how come all the releases are still there?** + +Releases are stored in ConfigMaps inside of the `kube-system` namespace. You will +have to manually delete them to get rid of the record, or use ```helm delete --purge```. + +**Q: I want to delete my local Helm. Where are all its files?** + +Along with the `helm` binary, Helm stores some files in `$HELM_HOME`, which is +located by default in `~/.helm`. diff --git a/src/vendor/github.com/kubernetes/helm/docs/kubernetes_distros.md b/src/vendor/github.com/kubernetes/helm/docs/kubernetes_distros.md new file mode 100644 index 000000000..8b80519ec --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/kubernetes_distros.md @@ -0,0 +1,51 @@ +# Kubernetes Distribution Guide + +This document captures information about using Helm in specific Kubernetes +environments. + +We are trying to add more details to this document. Please contribute via Pull +Requests if you can. + +## MiniKube + +Helm is tested and known to work with [minikube](https://github.com/kubernetes/minikube). +It requires no additional configuration. + +## `scripts/local-cluster` and Hyperkube + +Hyperkube configured via `scripts/local-cluster.sh` is known to work. For raw +Hyperkube you may need to do some manual configuration. + +## GKE + +Google's GKE hosted Kubernetes platform is known to work with Helm, and requires +no additional configuration. + +## Ubuntu with 'kubeadm' + +Kubernetes bootstrapped with `kubeadm` is known to work on the following Linux +distributions: + +- Ubuntu 16.04 +- Fedora release 25 + +Some versions of Helm (v2.0.0-beta2) require you to `export KUBECONFIG=/etc/kubernetes/admin.conf` +or create a `~/.kube/config`. + +## Container Linux by CoreOS + +Helm requires that kubelet have access to a copy of the `socat` program to proxy connections to the Tiller API. On Container Linux the Kubelet runs inside of a [hyperkube](https://github.com/kubernetes/kubernetes/tree/master/cluster/images/hyperkube) container image that has socat. So, even though Container Linux doesn't ship `socat` the container filesystem running kubelet does have socat. To learn more read the [Kubelet Wrapper](https://coreos.com/kubernetes/docs/latest/kubelet-wrapper.html) docs. + +## Openshift + +Helm works straightforward on OpenShift Online, OpenShift Dedicated, OpenShift Container Platform (version >= 3.6) or OpenShift Origin (version >= 3.6). To learn more read [this blog](https://blog.openshift.com/getting-started-helm-openshift/) post. + +## Platform9 + +Helm Client and Helm Server (Tiller) are pre-installed with [Platform9 Managed Kubernetes](https://platform9.com/managed-kubernetes/?utm_source=helm_distro_notes). Platform9 provides access to all official Helm charts through the App Catalog UI and native Kubernetes CLI. Additional repositories can be manually added. Further details are available in this [Platform9 App Catalog article](https://platform9.com/support/deploying-kubernetes-apps-platform9-managed-kubernetes/?utm_source=helm_distro_notes). + +## DC/OS + +Helm (both client and server) has been tested and is working on Mesospheres DC/OS 1.11 Kubernetes platform, and requires +no additional configuration. + diff --git a/src/vendor/github.com/kubernetes/helm/docs/logos/helm_logo_transparent.png b/src/vendor/github.com/kubernetes/helm/docs/logos/helm_logo_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..cd5526281f0748d17b05086438a16cfedbc893d4 GIT binary patch literal 36682 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 n_dOQxx>-SG9tv{!C-kGgTe~DWM4ffvx5u literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm.1 new file mode 100644 index 000000000..4128830f4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm.1 @@ -0,0 +1,85 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm \- The Helm package manager for Kubernetes. + + +.SH SYNOPSIS +.PP +\fBhelm\fP + + +.SH DESCRIPTION +.PP +The Kubernetes package manager + +.PP +To begin working with Helm, run the 'helm init' command: + +.PP +.RS + +.nf +$ helm init + +.fi +.RE + +.PP +This will install Tiller to your running Kubernetes cluster. +It will also set up any necessary local configuration. + +.PP +Common actions from this point include: +.IP \(bu 2 +helm search: search for charts +.IP \(bu 2 +helm fetch: download a chart to your local directory to view +.IP \(bu 2 +helm install: upload the chart to Kubernetes +.IP \(bu 2 +helm list: list releases of charts + +.PP +Environment: + $HELM\_HOME set an alternative location for Helm files. By default, these are stored in \~/.helm + $HELM\_HOST set an alternative Tiller host. The format is host:port + $HELM\_NO\_PLUGINS disable plugins. Set HELM\_NO\_PLUGINS=1 to disable plugins. + $TILLER\_NAMESPACE set an alternative Tiller namespace (default "kube\-namespace") + $KUBECONFIG set an alternative Kubernetes configuration file (default "\~/.kube/config") + + +.SH OPTIONS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-completion(1)\fP, \fBhelm\-create(1)\fP, \fBhelm\-delete(1)\fP, \fBhelm\-dependency(1)\fP, \fBhelm\-fetch(1)\fP, \fBhelm\-get(1)\fP, \fBhelm\-history(1)\fP, \fBhelm\-home(1)\fP, \fBhelm\-init(1)\fP, \fBhelm\-inspect(1)\fP, \fBhelm\-install(1)\fP, \fBhelm\-lint(1)\fP, \fBhelm\-list(1)\fP, \fBhelm\-package(1)\fP, \fBhelm\-plugin(1)\fP, \fBhelm\-repo(1)\fP, \fBhelm\-reset(1)\fP, \fBhelm\-rollback(1)\fP, \fBhelm\-search(1)\fP, \fBhelm\-serve(1)\fP, \fBhelm\-status(1)\fP, \fBhelm\-test(1)\fP, \fBhelm\-upgrade(1)\fP, \fBhelm\-verify(1)\fP, \fBhelm\-version(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_completion.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_completion.1 new file mode 100644 index 000000000..83217073f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_completion.1 @@ -0,0 +1,74 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-completion \- Generate autocompletions script for the specified shell (bash or zsh) + + +.SH SYNOPSIS +.PP +\fBhelm completion SHELL\fP + + +.SH DESCRIPTION +.PP +Generate autocompletions script for Helm for the specified shell (bash or zsh). + +.PP +This command can generate shell autocompletions. e.g. + +.PP +.RS + +.nf +$ helm completion bash + +.fi +.RE + +.PP +Can be sourced as such + +.PP +.RS + +.nf +$ source <(helm completion bash) + +.fi +.RE + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_create.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_create.1 new file mode 100644 index 000000000..3a1f4b26f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_create.1 @@ -0,0 +1,86 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-create \- create a new chart with the given name + + +.SH SYNOPSIS +.PP +\fBhelm create NAME\fP + + +.SH DESCRIPTION +.PP +This command creates a chart directory along with the common files and +directories used in a chart. + +.PP +For example, 'helm create foo' will create a directory structure that looks +something like this: + +.PP +.RS + +.nf +foo/ + | + |\- .helmignore # Contains patterns to ignore when packaging Helm charts. + | + |\- Chart.yaml # Information about your chart + | + |\- values.yaml # The default values for your templates + | + |\- charts/ # Charts that this chart depends on + | + |\- templates/ # The template files + +.fi +.RE + +.PP +\&'helm create' takes a path for an argument. If directories in the given path +do not exist, Helm will attempt to create them as it goes. If the given +destination exists and there are files in that directory, conflicting files +will be overwritten, but other files will be left alone. + + +.SH OPTIONS +.PP +\fB\-p\fP, \fB\-\-starter\fP="" + the named Helm starter scaffold + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_delete.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_delete.1 new file mode 100644 index 000000000..ecce68398 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_delete.1 @@ -0,0 +1,93 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-delete \- given a release name, delete the release from Kubernetes + + +.SH SYNOPSIS +.PP +\fBhelm delete [flags] RELEASE\_NAME [...]\fP + + +.SH DESCRIPTION +.PP +This command takes a release name, and then deletes the release from Kubernetes. +It removes all of the resources associated with the last release of the chart. + +.PP +Use the '\-\-dry\-run' flag to see which releases will be deleted without actually +deleting them. + + +.SH OPTIONS +.PP +\fB\-\-dry\-run\fP[=false] + simulate a delete + +.PP +\fB\-\-no\-hooks\fP[=false] + prevent hooks from running during deletion + +.PP +\fB\-\-purge\fP[=false] + remove the release from the store and make its name free for later use + +.PP +\fB\-\-timeout\fP=300 + time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency.1 new file mode 100644 index 000000000..fd4fc195e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency.1 @@ -0,0 +1,117 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-dependency \- manage a chart's dependencies + + +.SH SYNOPSIS +.PP +\fBhelm dependency update|build|list\fP + + +.SH DESCRIPTION +.PP +Manage the dependencies of a chart. + +.PP +Helm charts store their dependencies in 'charts/'. For chart developers, it is +often easier to manage a single dependency file ('requirements.yaml') +which declares all dependencies. + +.PP +The dependency commands operate on that file, making it easy to synchronize +between the desired dependencies and the actual dependencies stored in the +'charts/' directory. + +.PP +A 'requirements.yaml' file is a YAML file in which developers can declare chart +dependencies, along with the location of the chart and the desired version. +For example, this requirements file declares two dependencies: + +.PP +.RS + +.nf +# requirements.yaml +dependencies: +\- name: nginx + version: "1.2.3" + repository: "https://example.com/charts" +\- name: memcached + version: "3.2.1" + repository: "https://another.example.com/charts" + +.fi +.RE + +.PP +The 'name' should be the name of a chart, where that name must match the name +in that chart's 'Chart.yaml' file. + +.PP +The 'version' field should contain a semantic version or version range. + +.PP +The 'repository' URL should point to a Chart Repository. Helm expects that by +appending '/index.yaml' to the URL, it should be able to retrieve the chart +repository's index. Note: 'repository' cannot be a repository alias. It must be +a URL. + +.PP +Starting from 2.2.0, repository can be defined as the path to the directory of +the dependency charts stored locally. The path should start with a prefix of +"file://". For example, + +.PP +.RS + +.nf +# requirements.yaml +dependencies: +\- name: nginx + version: "1.2.3" + repository: "file://../dependency\_chart/nginx" + +.fi +.RE + +.PP +If the dependency chart is retrieved locally, it is not required to have the +repository added to helm by "helm add repo". Version matching is also supported +for this case. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP, \fBhelm\-dependency\-build(1)\fP, \fBhelm\-dependency\-list(1)\fP, \fBhelm\-dependency\-update(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_build.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_build.1 new file mode 100644 index 000000000..adc225a81 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_build.1 @@ -0,0 +1,69 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-dependency\-build \- rebuild the charts/ directory based on the requirements.lock file + + +.SH SYNOPSIS +.PP +\fBhelm dependency build [flags] CHART\fP + + +.SH DESCRIPTION +.PP +Build out the charts/ directory from the requirements.lock file. + +.PP +Build is used to reconstruct a chart's dependencies to the state specified in +the lock file. This will not re\-negotiate dependencies, as 'helm dependency update' +does. + +.PP +If no lock file is found, 'helm dependency build' will mirror the behavior +of 'helm dependency update'. + + +.SH OPTIONS +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + keyring containing public keys + +.PP +\fB\-\-verify\fP[=false] + verify the packages against signatures + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-dependency(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_list.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_list.1 new file mode 100644 index 000000000..30a686bb4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_list.1 @@ -0,0 +1,58 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-dependency\-list \- list the dependencies for the given chart + + +.SH SYNOPSIS +.PP +\fBhelm dependency list [flags] CHART\fP + + +.SH DESCRIPTION +.PP +List all of the dependencies declared in a chart. + +.PP +This can take chart archives and chart directories as input. It will not alter +the contents of a chart. + +.PP +This will produce an error if the chart cannot be loaded. It will emit a warning +if it cannot find a requirements.yaml. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-dependency(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_update.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_update.1 new file mode 100644 index 000000000..02d9ec94b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_dependency_update.1 @@ -0,0 +1,78 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-dependency\-update \- update charts/ based on the contents of requirements.yaml + + +.SH SYNOPSIS +.PP +\fBhelm dependency update [flags] CHART\fP + + +.SH DESCRIPTION +.PP +Update the on\-disk dependencies to mirror the requirements.yaml file. + +.PP +This command verifies that the required charts, as expressed in 'requirements.yaml', +are present in 'charts/' and are at an acceptable version. It will pull down +the latest charts that satisfy the dependencies, and clean up old dependencies. + +.PP +On successful update, this will generate a lock file that can be used to +rebuild the requirements to an exact version. + +.PP +Dependencies are not required to be represented in 'requirements.yaml'. For that +reason, an update command will not remove charts unless they are (a) present +in the requirements.yaml file, but (b) at the wrong version. + + +.SH OPTIONS +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + keyring containing public keys + +.PP +\fB\-\-skip\-refresh\fP[=false] + do not refresh the local repository cache + +.PP +\fB\-\-verify\fP[=false] + verify the packages against signatures + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-dependency(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_fetch.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_fetch.1 new file mode 100644 index 000000000..9a8ad185c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_fetch.1 @@ -0,0 +1,114 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-fetch \- download a chart from a repository and (optionally) unpack it in local directory + + +.SH SYNOPSIS +.PP +\fBhelm fetch [flags] [chart URL | repo/chartname] [...]\fP + + +.SH DESCRIPTION +.PP +Retrieve a package from a package repository, and download it locally. + +.PP +This is useful for fetching packages to inspect, modify, or repackage. It can +also be used to perform cryptographic verification of a chart without installing +the chart. + +.PP +There are options for unpacking the chart after download. This will create a +directory for the chart and uncomparess into that directory. + +.PP +If the \-\-verify flag is specified, the requested chart MUST have a provenance +file, and MUST pass the verification process. Failure in any part of this will +result in an error, and the chart will not be saved locally. + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-cert\-file\fP="" + identify HTTPS client using this SSL certificate file + +.PP +\fB\-d\fP, \fB\-\-destination\fP="." + location to write the chart. If this and tardir are specified, tardir is appended to this + +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + keyring containing public keys + +.PP +\fB\-\-prov\fP[=false] + fetch the provenance file, but don't perform verification + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-untar\fP[=false] + if set to true, will untar the chart after downloading it + +.PP +\fB\-\-untardir\fP="." + if untar is specified, this flag specifies the name of the directory into which the chart is expanded + +.PP +\fB\-\-verify\fP[=false] + verify the package against its signature + +.PP +\fB\-\-version\fP="" + specific version of a chart. Without this, the latest version is fetched + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get.1 new file mode 100644 index 000000000..e680f49dc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get.1 @@ -0,0 +1,89 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-get \- download a named release + + +.SH SYNOPSIS +.PP +\fBhelm get [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +This command shows the details of a named release. + +.PP +It can be used to get extended information about the release, including: +.IP \(bu 2 +The values used to generate the release +.IP \(bu 2 +The chart used to generate the release +.IP \(bu 2 +The generated manifest file + +.PP +By default, this prints a human readable collection of information about the +chart, the supplied values, and the generated manifest file. + + +.SH OPTIONS +.PP +\fB\-\-revision\fP=0 + get the named release with revision + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP, \fBhelm\-get\-hooks(1)\fP, \fBhelm\-get\-manifest(1)\fP, \fBhelm\-get\-values(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_hooks.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_hooks.1 new file mode 100644 index 000000000..34e460d19 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_hooks.1 @@ -0,0 +1,59 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-get\-hooks \- download all hooks for a named release + + +.SH SYNOPSIS +.PP +\fBhelm get hooks [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +This command downloads hooks for a given release. + +.PP +Hooks are formatted in YAML and separated by the YAML '\-\-\-\\n' separator. + + +.SH OPTIONS +.PP +\fB\-\-revision\fP=0 + get the named release with revision + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-get(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_manifest.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_manifest.1 new file mode 100644 index 000000000..7132db38e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_manifest.1 @@ -0,0 +1,61 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-get\-manifest \- download the manifest for a named release + + +.SH SYNOPSIS +.PP +\fBhelm get manifest [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +This command fetches the generated manifest for a given release. + +.PP +A manifest is a YAML\-encoded representation of the Kubernetes resources that +were generated from this release's chart(s). If a chart is dependent on other +charts, those resources will also be included in the manifest. + + +.SH OPTIONS +.PP +\fB\-\-revision\fP=0 + get the named release with revision + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-get(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_values.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_values.1 new file mode 100644 index 000000000..349f97c14 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_get_values.1 @@ -0,0 +1,60 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-get\-values \- download the values file for a named release + + +.SH SYNOPSIS +.PP +\fBhelm get values [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +This command downloads a values file for a given release. + + +.SH OPTIONS +.PP +\fB\-a\fP, \fB\-\-all\fP[=false] + dump all (computed) values + +.PP +\fB\-\-revision\fP=0 + get the named release with revision + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-get(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_history.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_history.1 new file mode 100644 index 000000000..40789ef92 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_history.1 @@ -0,0 +1,97 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-history \- fetch release history + + +.SH SYNOPSIS +.PP +\fBhelm history [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +History prints historical revisions for a given release. + +.PP +A default maximum of 256 revisions will be returned. Setting '\-\-max' +configures the maximum length of the revision list returned. + +.PP +The historical release set is printed as a formatted table, e.g: + +.PP +.RS + +.nf +$ helm history angry\-bird \-\-max=4 +REVISION UPDATED STATUS CHART DESCRIPTION +1 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine\-0.1.0 Initial install +2 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine\-0.1.0 Upgraded successfully +3 Mon Oct 3 10:15:13 2016 SUPERSEDED alpine\-0.1.0 Rolled back to 2 +4 Mon Oct 3 10:15:13 2016 DEPLOYED alpine\-0.1.0 Upgraded successfully + +.fi +.RE + + +.SH OPTIONS +.PP +\fB\-\-max\fP=256 + maximum number of revision to include in history + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_home.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_home.1 new file mode 100644 index 000000000..77024d53e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_home.1 @@ -0,0 +1,51 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-home \- displays the location of HELM\_HOME + + +.SH SYNOPSIS +.PP +\fBhelm home\fP + + +.SH DESCRIPTION +.PP +This command displays the location of HELM\_HOME. This is where +any helm configuration files live. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_init.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_init.1 new file mode 100644 index 000000000..74871ebe8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_init.1 @@ -0,0 +1,135 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-init \- initialize Helm on both client and server + + +.SH SYNOPSIS +.PP +\fBhelm init\fP + + +.SH DESCRIPTION +.PP +This command installs Tiller (the helm server side component) onto your +Kubernetes Cluster and sets up local configuration in $HELM\_HOME (default \~/.helm/) + +.PP +As with the rest of the Helm commands, 'helm init' discovers Kubernetes clusters +by reading $KUBECONFIG (default '\~/.kube/config') and using the default context. + +.PP +To set up just a local environment, use '\-\-client\-only'. That will configure +$HELM\_HOME, but not attempt to connect to a remote cluster and install the Tiller +deployment. + +.PP +When installing Tiller, 'helm init' will attempt to install the latest released +version. You can specify an alternative image with '\-\-tiller\-image'. For those +frequently working on the latest code, the flag '\-\-canary\-image' will install +the latest pre\-release version of Tiller (e.g. the HEAD commit in the GitHub +repository on the master branch). + +.PP +To dump a manifest containing the Tiller deployment YAML, combine the +'\-\-dry\-run' and '\-\-debug' flags. + + +.SH OPTIONS +.PP +\fB\-\-canary\-image\fP[=false] + use the canary tiller image + +.PP +\fB\-c\fP, \fB\-\-client\-only\fP[=false] + if set does not install tiller + +.PP +\fB\-\-dry\-run\fP[=false] + do not install local or remote + +.PP +\fB\-\-local\-repo\-url\fP=" +\[la]http://127.0.0.1:8879/charts"\[ra] + URL for local repository + +.PP +\fB\-\-net\-host\fP[=false] + install tiller with net=host + +.PP +\fB\-\-service\-account\fP="" + name of service account + +.PP +\fB\-\-skip\-refresh\fP[=false] + do not refresh (download) the local repository cache + +.PP +\fB\-\-stable\-repo\-url\fP=" +\[la]https://kubernetes-charts.storage.googleapis.com"\[ra] + URL for stable repository + +.PP +\fB\-i\fP, \fB\-\-tiller\-image\fP="" + override tiller image + +.PP +\fB\-\-tiller\-tls\fP[=false] + install tiller with TLS enabled + +.PP +\fB\-\-tiller\-tls\-cert\fP="" + path to TLS certificate file to install with tiller + +.PP +\fB\-\-tiller\-tls\-key\fP="" + path to TLS key file to install with tiller + +.PP +\fB\-\-tiller\-tls\-verify\fP[=false] + install tiller with TLS enabled and to verify remote certificates + +.PP +\fB\-\-tls\-ca\-cert\fP="" + path to CA root certificate + +.PP +\fB\-\-upgrade\fP[=false] + upgrade if tiller is already installed + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect.1 new file mode 100644 index 000000000..0783476c7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect.1 @@ -0,0 +1,84 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-inspect \- inspect a chart + + +.SH SYNOPSIS +.PP +\fBhelm inspect [CHART]\fP + + +.SH DESCRIPTION +.PP +This command inspects a chart and displays information. It takes a chart reference +('stable/drupal'), a full path to a directory or packaged chart, or a URL. + +.PP +Inspect prints the contents of the Chart.yaml file and the values.yaml file. + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-cert\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + path to the keyring containing public verification keys + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-verify\fP[=false] + verify the provenance data for this chart + +.PP +\fB\-\-version\fP="" + version of the chart. By default, the newest chart is shown + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP, \fBhelm\-inspect\-chart(1)\fP, \fBhelm\-inspect\-values(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_chart.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_chart.1 new file mode 100644 index 000000000..f728df410 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_chart.1 @@ -0,0 +1,81 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-inspect\-chart \- shows inspect chart + + +.SH SYNOPSIS +.PP +\fBhelm inspect chart [CHART]\fP + + +.SH DESCRIPTION +.PP +This command inspects a chart (directory, file, or URL) and displays the contents +of the Charts.yaml file + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-cert\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + path to the keyring containing public verification keys + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-verify\fP[=false] + verify the provenance data for this chart + +.PP +\fB\-\-version\fP="" + version of the chart. By default, the newest chart is shown + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-inspect(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_values.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_values.1 new file mode 100644 index 000000000..c87dd9c60 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_inspect_values.1 @@ -0,0 +1,81 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-inspect\-values \- shows inspect values + + +.SH SYNOPSIS +.PP +\fBhelm inspect values [CHART]\fP + + +.SH DESCRIPTION +.PP +This command inspects a chart (directory, file, or URL) and displays the contents +of the values.yaml file + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-cert\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + path to the keyring containing public verification keys + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-verify\fP[=false] + verify the provenance data for this chart + +.PP +\fB\-\-version\fP="" + version of the chart. By default, the newest chart is shown + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-inspect(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_install.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_install.1 new file mode 100644 index 000000000..fe1856bed --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_install.1 @@ -0,0 +1,243 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-install \- install a chart archive + + +.SH SYNOPSIS +.PP +\fBhelm install [CHART]\fP + + +.SH DESCRIPTION +.PP +This command installs a chart archive. + +.PP +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. + +.PP +To override values in a chart, use either the '\-\-values' flag and pass in a file +or use the '\-\-set' flag and pass configuration from the command line. + +.PP +.RS + +.nf +$ helm install \-f myvalues.yaml ./redis + +.fi +.RE + +.PP +or + +.PP +.RS + +.nf +$ helm install \-\-set name=prod ./redis + +.fi +.RE + +.PP +You can specify the '\-\-values'/'\-f' flag multiple times. The priority will be given to the +last (right\-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + +.PP +.RS + +.nf +$ helm install \-f myvalues.yaml \-f override.yaml ./redis + +.fi +.RE + +.PP +You can specify the '\-\-set' flag multiple times. The priority will be given to the +last (right\-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + +.PP +.RS + +.nf +$ helm install \-\-set foo=bar \-\-set foo=newbar ./redis + +.fi +.RE + +.PP +To check the generated manifests of a release without installing the chart, +the '\-\-debug' and '\-\-dry\-run' flags can be combined. This will still require a +round\-trip to the Tiller server. + +.PP +If \-\-verify is set, the chart MUST have a provenance file, and the provenenace +fall MUST pass all verification steps. + +.PP +There are four different ways you can express the chart you want to install: +.IP " 1." 5 +By chart reference: helm install stable/mariadb +.IP " 2." 5 +By path to a packaged chart: helm install ./nginx\-1.2.3.tgz +.IP " 3." 5 +By path to an unpacked chart directory: helm install ./nginx +.IP " 4." 5 +By absolute URL: helm install +\[la]https://example.com/charts/nginx-1.2.3.tgz\[ra] + +.PP +CHART REFERENCES + +.PP +A chart reference is a convenient way of reference a chart in a chart repository. + +.PP +When you use a chart reference ('stable/mariadb'), Helm will look in the local +configuration for a chart repository named 'stable', and will then look for a +chart in that repository whose name is 'mariadb'. It will install the latest +version of that chart unless you also supply a version number with the +'\-\-version' flag. + +.PP +To see the list of chart repositories, use 'helm repo list'. To search for +charts in a repository, use 'helm search'. + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-cert\-file\fP="" + identify HTTPS client using this SSL certificate file + +.PP +\fB\-\-dep\-up\fP[=false] + run helm dependency update before installing the chart. + +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + +.PP +\fB\-\-dry\-run\fP[=false] + simulate an install + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + location of public keys used for verification + +.PP +\fB\-n\fP, \fB\-\-name\fP="" + release name. If unspecified, it will autogenerate one for you + +.PP +\fB\-\-name\-template\fP="" + specify template used to name the release + +.PP +\fB\-\-namespace\fP="" + namespace to install the release into + +.PP +\fB\-\-no\-hooks\fP[=false] + prevent hooks from running during install + +.PP +\fB\-\-replace\fP[=false] + re\-use the given name, even if that name is already used. This is unsafe in production + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-set\fP=[] + set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + +.PP +\fB\-\-timeout\fP=300 + time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + +.PP +\fB\-f\fP, \fB\-\-values\fP=[] + specify values in a YAML file or a URL(can specify multiple) + +.PP +\fB\-\-verify\fP[=false] + verify the package before installing it + +.PP +\fB\-\-version\fP="" + specify the exact chart version to install. If this is not specified, the latest version is installed + +.PP +\fB\-\-wait\fP[=false] + if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as \-\-timeout + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +31\-Oct\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_lint.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_lint.1 new file mode 100644 index 000000000..e0d522685 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_lint.1 @@ -0,0 +1,62 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-lint \- examines a chart for possible issues + + +.SH SYNOPSIS +.PP +\fBhelm lint [flags] PATH\fP + + +.SH DESCRIPTION +.PP +This command takes a path to a chart and runs a series of tests to verify that +the chart is well\-formed. + +.PP +If the linter encounters things that will cause the chart to fail installation, +it will emit [ERROR] messages. If it encounters issues that break with convention +or recommendation, it will emit [WARNING] messages. + + +.SH OPTIONS +.PP +\fB\-\-strict\fP[=false] + fail on lint warnings + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_list.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_list.1 new file mode 100644 index 000000000..d4fccf960 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_list.1 @@ -0,0 +1,151 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-list \- list releases + + +.SH SYNOPSIS +.PP +\fBhelm list [flags] [FILTER]\fP + + +.SH DESCRIPTION +.PP +This command lists all of the releases. + +.PP +By default, it lists only releases that are deployed or failed. Flags like +'\-\-deleted' and '\-\-all' will alter this behavior. Such flags can be combined: +'\-\-deleted \-\-failed'. + +.PP +By default, items are sorted alphabetically. Use the '\-d' flag to sort by +release date. + +.PP +If an argument is provided, it will be treated as a filter. Filters are +regular expressions (Perl compatible) that are applied to the list of releases. +Only items that match the filter will be returned. + +.PP +.RS + +.nf +$ helm list 'ara[a\-z]+' +NAME UPDATED CHART +maudlin\-arachnid Mon May 9 16:07:08 2016 alpine\-0.1.0 + +.fi +.RE + +.PP +If no results are found, 'helm list' will exit 0, but with no output (or in +the case of no '\-q' flag, only headers). + +.PP +By default, up to 256 items may be returned. To limit this, use the '\-\-max' flag. +Setting '\-\-max' to 0 will not return all results. Rather, it will return the +server's default, which may be much higher than 256. Pairing the '\-\-max' +flag with the '\-\-offset' flag allows you to page through results. + + +.SH OPTIONS +.PP +\fB\-\-all\fP[=false] + show all releases, not just the ones marked DEPLOYED + +.PP +\fB\-d\fP, \fB\-\-date\fP[=false] + sort by release date + +.PP +\fB\-\-deleted\fP[=false] + show deleted releases + +.PP +\fB\-\-deleting\fP[=false] + show releases that are currently being deleted + +.PP +\fB\-\-deployed\fP[=false] + show deployed releases. If no other is specified, this will be automatically enabled + +.PP +\fB\-\-failed\fP[=false] + show failed releases + +.PP +\fB\-m\fP, \fB\-\-max\fP=256 + maximum number of releases to fetch + +.PP +\fB\-\-namespace\fP="" + show releases within a specific namespace + +.PP +\fB\-o\fP, \fB\-\-offset\fP="" + next release name in the list, used to offset from start value + +.PP +\fB\-r\fP, \fB\-\-reverse\fP[=false] + reverse the sort order + +.PP +\fB\-q\fP, \fB\-\-short\fP[=false] + output short (quiet) listing format + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_package.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_package.1 new file mode 100644 index 000000000..07185a4c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_package.1 @@ -0,0 +1,85 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-package \- package a chart directory into a chart archive + + +.SH SYNOPSIS +.PP +\fBhelm package [flags] [CHART\_PATH] [...]\fP + + +.SH DESCRIPTION +.PP +This command packages a chart into a versioned chart archive file. If a path +is given, this will look at that path for a chart (which must contain a +Chart.yaml file) and then package that directory. + +.PP +If no path is given, this will look in the present working directory for a +Chart.yaml file, and (if found) build the current directory into a chart. + +.PP +Versioned chart archives are used by Helm package repositories. + + +.SH OPTIONS +.PP +\fB\-d\fP, \fB\-\-destination\fP="." + location to write the chart. + +.PP +\fB\-\-key\fP="" + name of the key to use when signing. Used if \-\-sign is true + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + location of a public keyring + +.PP +\fB\-\-save\fP[=true] + save packaged chart to local chart repository + +.PP +\fB\-\-sign\fP[=false] + use a PGP private key to sign this package + +.PP +\fB\-\-version\fP="" + set the version on the chart to this semver version + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin.1 new file mode 100644 index 000000000..7af4f39fc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin \- add, list, or remove Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin\fP + + +.SH DESCRIPTION +.PP +Manage client\-side Helm plugins. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP, \fBhelm\-plugin\-install(1)\fP, \fBhelm\-plugin\-list(1)\fP, \fBhelm\-plugin\-remove(1)\fP, \fBhelm\-plugin\-update(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_install.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_install.1 new file mode 100644 index 000000000..d2a8d1326 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_install.1 @@ -0,0 +1,56 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin\-install \- install one or more Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin install [options] \&...\fP + + +.SH DESCRIPTION +.PP +install one or more Helm plugins + + +.SH OPTIONS +.PP +\fB\-\-version\fP="" + specify a version constraint. If this is not specified, the latest version is installed + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-plugin(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_list.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_list.1 new file mode 100644 index 000000000..5fcebd748 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_list.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin\-list \- list installed Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin list\fP + + +.SH DESCRIPTION +.PP +list installed Helm plugins + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-plugin(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_remove.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_remove.1 new file mode 100644 index 000000000..de64220dd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_remove.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin\-remove \- remove one or more Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin remove \&...\fP + + +.SH DESCRIPTION +.PP +remove one or more Helm plugins + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-plugin(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_update.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_update.1 new file mode 100644 index 000000000..ad7d70a17 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_plugin_update.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-plugin\-update \- update one or more Helm plugins + + +.SH SYNOPSIS +.PP +\fBhelm plugin update \&...\fP + + +.SH DESCRIPTION +.PP +update one or more Helm plugins + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-plugin(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo.1 new file mode 100644 index 000000000..19b2da8a3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo.1 @@ -0,0 +1,55 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo \- add, list, remove, update, and index chart repositories + + +.SH SYNOPSIS +.PP +\fBhelm repo [FLAGS] add|remove|list|index|update [ARGS]\fP + + +.SH DESCRIPTION +.PP +This command consists of multiple subcommands to interact with chart repositories. + +.PP +It can be used to add, remove, list, and index chart repositories. +Example usage: + $ helm repo add [NAME] [REPO\_URL] + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP, \fBhelm\-repo\-add(1)\fP, \fBhelm\-repo\-index(1)\fP, \fBhelm\-repo\-list(1)\fP, \fBhelm\-repo\-remove(1)\fP, \fBhelm\-repo\-update(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_add.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_add.1 new file mode 100644 index 000000000..3cd9f790b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_add.1 @@ -0,0 +1,68 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo\-add \- add a chart repository + + +.SH SYNOPSIS +.PP +\fBhelm repo add [flags] [NAME] [URL]\fP + + +.SH DESCRIPTION +.PP +add a chart repository + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-cert\-file\fP="" + identify HTTPS client using this SSL certificate file + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-no\-update\fP[=false] + raise error if repo is already registered + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-repo(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_index.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_index.1 new file mode 100644 index 000000000..edfcda6f5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_index.1 @@ -0,0 +1,69 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo\-index \- generate an index file given a directory containing packaged charts + + +.SH SYNOPSIS +.PP +\fBhelm repo index [flags] [DIR]\fP + + +.SH DESCRIPTION +.PP +Read the current directory and generate an index file based on the charts found. + +.PP +This tool is used for creating an 'index.yaml' file for a chart repository. To +set an absolute URL to the charts, use '\-\-url' flag. + +.PP +To merge the generated index with an existing index file, use the '\-\-merge' +flag. In this case, the charts found in the current directory will be merged +into the existing index, with local charts taking priority over existing charts. + + +.SH OPTIONS +.PP +\fB\-\-merge\fP="" + merge the generated index into the given index + +.PP +\fB\-\-url\fP="" + url of chart repository + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-repo(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_list.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_list.1 new file mode 100644 index 000000000..7623f73fe --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_list.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo\-list \- list chart repositories + + +.SH SYNOPSIS +.PP +\fBhelm repo list [flags]\fP + + +.SH DESCRIPTION +.PP +list chart repositories + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-repo(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_remove.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_remove.1 new file mode 100644 index 000000000..cfbf217a4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_remove.1 @@ -0,0 +1,50 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo\-remove \- remove a chart repository + + +.SH SYNOPSIS +.PP +\fBhelm repo remove [flags] [NAME]\fP + + +.SH DESCRIPTION +.PP +remove a chart repository + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-repo(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_update.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_update.1 new file mode 100644 index 000000000..42bf511dd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_repo_update.1 @@ -0,0 +1,55 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-repo\-update \- update information of available charts locally from chart repositories + + +.SH SYNOPSIS +.PP +\fBhelm repo update\fP + + +.SH DESCRIPTION +.PP +Update gets the latest information about charts from the respective chart repositories. +Information is cached locally, where it is used by commands like 'helm search'. + +.PP +\&'helm update' is the deprecated form of 'helm repo update'. It will be removed in +future releases. + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm\-repo(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_reset.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_reset.1 new file mode 100644 index 000000000..bf735591d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_reset.1 @@ -0,0 +1,82 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-reset \- uninstalls Tiller from a cluster + + +.SH SYNOPSIS +.PP +\fBhelm reset\fP + + +.SH DESCRIPTION +.PP +This command uninstalls Tiller (the helm server side component) from your +Kubernetes Cluster and optionally deletes local configuration in +$HELM\_HOME (default \~/.helm/) + + +.SH OPTIONS +.PP +\fB\-f\fP, \fB\-\-force\fP[=false] + forces Tiller uninstall even if there are releases installed + +.PP +\fB\-\-remove\-helm\-home\fP[=false] + if set deletes $HELM\_HOME + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_rollback.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_rollback.1 new file mode 100644 index 000000000..d91ab881d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_rollback.1 @@ -0,0 +1,101 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-rollback \- roll back a release to a previous revision + + +.SH SYNOPSIS +.PP +\fBhelm rollback [flags] [RELEASE] [REVISION]\fP + + +.SH DESCRIPTION +.PP +This command rolls back a release to a previous revision. + +.PP +The first argument of the rollback command is the name of a release, and the +second is a revision (version) number. To see revision numbers, run +'helm history RELEASE'. + + +.SH OPTIONS +.PP +\fB\-\-dry\-run\fP[=false] + simulate a rollback + +.PP +\fB\-\-force\fP[=false] + force resource update through delete/recreate if needed + +.PP +\fB\-\-no\-hooks\fP[=false] + prevent hooks from running during rollback + +.PP +\fB\-\-recreate\-pods\fP[=false] + performs pods restart for the resource if applicable + +.PP +\fB\-\-timeout\fP=300 + time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + +.PP +\fB\-\-wait\fP[=false] + if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as \-\-timeout + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_search.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_search.1 new file mode 100644 index 000000000..ac2467bf2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_search.1 @@ -0,0 +1,68 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-search \- search for a keyword in charts + + +.SH SYNOPSIS +.PP +\fBhelm search [keyword]\fP + + +.SH DESCRIPTION +.PP +Search reads through all of the repositories configured on the system, and +looks for matches. + +.PP +Repositories are managed with 'helm repo' commands. + + +.SH OPTIONS +.PP +\fB\-r\fP, \fB\-\-regexp\fP[=false] + use regular expressions for searching + +.PP +\fB\-v\fP, \fB\-\-version\fP="" + search using semantic versioning constraints + +.PP +\fB\-l\fP, \fB\-\-versions\fP[=false] + show the long listing, with each version of each chart on its own line + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_serve.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_serve.1 new file mode 100644 index 000000000..a4a9c51da --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_serve.1 @@ -0,0 +1,79 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-serve \- start a local http web server + + +.SH SYNOPSIS +.PP +\fBhelm serve\fP + + +.SH DESCRIPTION +.PP +This command starts a local chart repository server that serves charts from a local directory. + +.PP +The new server will provide HTTP access to a repository. By default, it will +scan all of the charts in '$HELM\_HOME/repository/local' and serve those over +the local IPv4 TCP port (default '127.0.0.1:8879'). + +.PP +This command is intended to be used for educational and testing purposes only. +It is best to rely on a dedicated web server or a cloud\-hosted solution like +Google Cloud Storage for production use. + +.PP +See +\[la]https://github.com/kubernetes/helm/blob/master/docs/chart_repository.md#hosting-chart-repositories\[ra] +for more information on hosting chart repositories in a production setting. + + +.SH OPTIONS +.PP +\fB\-\-address\fP="127.0.0.1:8879" + address to listen on + +.PP +\fB\-\-repo\-path\fP="" + local directory path from which to serve charts + +.PP +\fB\-\-url\fP="" + external URL of chart repository + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_status.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_status.1 new file mode 100644 index 000000000..8f2366808 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_status.1 @@ -0,0 +1,83 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-status \- displays the status of the named release + + +.SH SYNOPSIS +.PP +\fBhelm status [flags] RELEASE\_NAME\fP + + +.SH DESCRIPTION +.PP +This command shows the status of a named release. +The status consists of: +\- last deployment time +\- k8s namespace in which the release lives +\- state of the release (can be: UNKNOWN, DEPLOYED, DELETED, SUPERSEDED, FAILED or DELETING) +\- list of resources that this release consists of, sorted by kind +\- details on last test suite run, if applicable +\- additional notes provided by the chart + + +.SH OPTIONS +.PP +\fB\-\-revision\fP=0 + if set, display the status of the named release with revision + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_test.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_test.1 new file mode 100644 index 000000000..6da36b33b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_test.1 @@ -0,0 +1,84 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-test \- test a release + + +.SH SYNOPSIS +.PP +\fBhelm test [RELEASE]\fP + + +.SH DESCRIPTION +.PP +The test command runs the tests for a release. + +.PP +The argument this command takes is the name of a deployed release. +The tests to be run are defined in the chart that was installed. + + +.SH OPTIONS +.PP +\fB\-\-cleanup\fP[=false] + delete test pods upon completion + +.PP +\fB\-\-timeout\fP=300 + time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_upgrade.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_upgrade.1 new file mode 100644 index 000000000..24bba7c85 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_upgrade.1 @@ -0,0 +1,190 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-upgrade \- upgrade a release + + +.SH SYNOPSIS +.PP +\fBhelm upgrade [RELEASE] [CHART]\fP + + +.SH DESCRIPTION +.PP +This command upgrades a release to a new version of a chart. + +.PP +The upgrade arguments must be a release and chart. The chart +argument can be either: a chart reference('stable/mariadb'), a path to a chart directory, +a packaged chart, or a fully qualified URL. For chart references, the latest +version will be specified unless the '\-\-version' flag is set. + +.PP +To override values in a chart, use either the '\-\-values' flag and pass in a file +or use the '\-\-set' flag and pass configuration from the command line. + +.PP +You can specify the '\-\-values'/'\-f' flag multiple times. The priority will be given to the +last (right\-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + +.PP +.RS + +.nf +$ helm upgrade \-f myvalues.yaml \-f override.yaml redis ./redis + +.fi +.RE + +.PP +You can specify the '\-\-set' flag multiple times. The priority will be given to the +last (right\-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + +.PP +.RS + +.nf +$ helm upgrade \-\-set foo=bar \-\-set foo=newbar redis ./redis + +.fi +.RE + + +.SH OPTIONS +.PP +\fB\-\-ca\-file\fP="" + verify certificates of HTTPS\-enabled servers using this CA bundle + +.PP +\fB\-\-cert\-file\fP="" + identify HTTPS client using this SSL certificate file + +.PP +\fB\-\-devel\fP[=false] + use development versions, too. Equivalent to version '>0.0.0\-a'. If \-\-version is set, this is ignored. + +.PP +\fB\-\-dry\-run\fP[=false] + simulate an upgrade + +.PP +\fB\-\-force\fP[=false] + force resource update through delete/recreate if needed + +.PP +\fB\-i\fP, \fB\-\-install\fP[=false] + if a release by this name doesn't already exist, run an install + +.PP +\fB\-\-key\-file\fP="" + identify HTTPS client using this SSL key file + +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + path to the keyring that contains public signing keys + +.PP +\fB\-\-namespace\fP="default" + namespace to install the release into (only used if \-\-install is set) + +.PP +\fB\-\-no\-hooks\fP[=false] + disable pre/post upgrade hooks + +.PP +\fB\-\-recreate\-pods\fP[=false] + performs pods restart for the resource if applicable + +.PP +\fB\-\-repo\fP="" + chart repository url where to locate the requested chart + +.PP +\fB\-\-reset\-values\fP[=false] + when upgrading, reset the values to the ones built into the chart + +.PP +\fB\-\-reuse\-values\fP[=false] + when upgrading, reuse the last release's values, and merge in any new values. If '\-\-reset\-values' is specified, this is ignored. + +.PP +\fB\-\-set\fP=[] + set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) + +.PP +\fB\-\-timeout\fP=300 + time in seconds to wait for any individual kubernetes operation (like Jobs for hooks) + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + +.PP +\fB\-f\fP, \fB\-\-values\fP=[] + specify values in a YAML file or a URL(can specify multiple) + +.PP +\fB\-\-verify\fP[=false] + verify the provenance of the chart before upgrading + +.PP +\fB\-\-version\fP="" + specify the exact chart version to use. If this is not specified, the latest version is used + +.PP +\fB\-\-wait\fP[=false] + if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as \-\-timeout + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_verify.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_verify.1 new file mode 100644 index 000000000..5297924ae --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_verify.1 @@ -0,0 +1,65 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-verify \- verify that a chart at the given path has been signed and is valid + + +.SH SYNOPSIS +.PP +\fBhelm verify [flags] PATH\fP + + +.SH DESCRIPTION +.PP +Verify that the given chart has a valid provenance file. + +.PP +Provenance files provide crytographic verification that a chart has not been +tampered with, and was packaged by a trusted provider. + +.PP +This command can be used to verify a local chart. Several other commands provide +'\-\-verify' flags that run the same validation. To generate a signed package, use +the 'helm package \-\-sign' command. + + +.SH OPTIONS +.PP +\fB\-\-keyring\fP="~/.gnupg/pubring.gpg" + keyring containing public keys + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_version.1 b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_version.1 new file mode 100644 index 000000000..1f1bf600d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/man/man1/helm_version.1 @@ -0,0 +1,103 @@ +.TH "HELM" "1" "May 2017" "Auto generated by spf13/cobra" "" +.nh +.ad l + + +.SH NAME +.PP +helm\-version \- print the client/server version information + + +.SH SYNOPSIS +.PP +\fBhelm version\fP + + +.SH DESCRIPTION +.PP +Show the client and server versions for Helm and tiller. + +.PP +This will print a representation of the client and server versions of Helm and +Tiller. The output will look something like this: + +.PP +Client: \&version.Version{SemVer:"v2.0.0", GitCommit:"ff52399e51bb880526e9cd0ed8386f6433b74da1", GitTreeState:"clean"} +Server: \&version.Version{SemVer:"v2.0.0", GitCommit:"b0c113dfb9f612a9add796549da66c0d294508a3", GitTreeState:"clean"} +.IP \(bu 2 +SemVer is the semantic version of the release. +.IP \(bu 2 +GitCommit is the SHA for the commit that this version was built from. +.IP \(bu 2 +GitTreeState is "clean" if there are no local code changes when this binary was +built, and "dirty" if the binary was built from locally modified code. + +.PP +To print just the client version, use '\-\-client'. To print just the server version, +use '\-\-server'. + + +.SH OPTIONS +.PP +\fB\-c\fP, \fB\-\-client\fP[=false] + client version only + +.PP +\fB\-s\fP, \fB\-\-server\fP[=false] + server version only + +.PP +\fB\-\-short\fP[=false] + print the version number + +.PP +\fB\-\-tls\fP[=false] + enable TLS for request + +.PP +\fB\-\-tls\-ca\-cert\fP="$HELM\_HOME/ca.pem" + path to TLS CA certificate file + +.PP +\fB\-\-tls\-cert\fP="$HELM\_HOME/cert.pem" + path to TLS certificate file + +.PP +\fB\-\-tls\-key\fP="$HELM\_HOME/key.pem" + path to TLS key file + +.PP +\fB\-\-tls\-verify\fP[=false] + enable TLS for request and verify remote + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +.PP +\fB\-\-debug\fP[=false] + enable verbose output + +.PP +\fB\-\-home\fP="~/.helm" + location of your Helm config. Overrides $HELM\_HOME + +.PP +\fB\-\-host\fP="localhost:44134" + address of tiller. Overrides $HELM\_HOST + +.PP +\fB\-\-kube\-context\fP="" + name of the kubeconfig context to use + +.PP +\fB\-\-tiller\-namespace\fP="kube\-system" + namespace of tiller + + +.SH SEE ALSO +.PP +\fBhelm(1)\fP + + +.SH HISTORY +.PP +19\-May\-2017 Auto generated by spf13/cobra diff --git a/src/vendor/github.com/kubernetes/helm/docs/plugins.md b/src/vendor/github.com/kubernetes/helm/docs/plugins.md new file mode 100644 index 000000000..b2cc3c812 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/plugins.md @@ -0,0 +1,201 @@ +# The Helm Plugins Guide + +Helm 2.1.0 introduced the concept of a client-side Helm _plugin_. A plugin is a +tool that can be accessed through the `helm` CLI, but which is not part of the +built-in Helm codebase. + +Existing plugins can be found on [related](related.md#helm-plugins) section or by searching [Github](https://github.com/search?q=topic%3Ahelm-plugin&type=Repositories). + +This guide explains how to use and create plugins. + +## An Overview + +Helm plugins are add-on tools that integrate seamlessly with Helm. They provide +a way to extend the core feature set of Helm, but without requiring every new +feature to be written in Go and added to the core tool. + +Helm plugins have the following features: + +- They can be added and removed from a Helm installation without impacting the + core Helm tool. +- They can be written in any programming language. +- They integrate with Helm, and will show up in `helm help` and other places. + +Helm plugins live in `$(helm home)/plugins`. + +The Helm plugin model is partially modeled on Git's plugin model. To that end, +you may sometimes hear `helm` referred to as the _porcelain_ layer, with +plugins being the _plumbing_. This is a shorthand way of suggesting that +Helm provides the user experience and top level processing logic, while the +plugins do the "detail work" of performing a desired action. + +## Installing a Plugin + +Plugins are installed using the `$ helm plugin install ` command. You can pass in a path to a plugin on your local file system or a url of a remote VCS repo. The `helm plugin install` command clones or copies the plugin at the path/url given into `$ (helm home)/plugins` + +```console +$ helm plugin install https://github.com/technosophos/helm-template +``` + +If you have a plugin tar distribution, simply untar the plugin into the +`$(helm home)/plugins` directory. + +You can also install tarball plugins directly from url by issuing `helm plugin install http://domain/path/to/plugin.tar.gz` + +## Building Plugins + +In many ways, a plugin is similar to a chart. Each plugin has a top-level +directory, and then a `plugin.yaml` file. + +``` +$(helm home)/plugins/ + |- keybase/ + | + |- plugin.yaml + |- keybase.sh + +``` + +In the example above, the `keybase` plugin is contained inside of a directory +named `keybase`. It has two files: `plugin.yaml` (required) and an executable +script, `keybase.sh` (optional). + +The core of a plugin is a simple YAML file named `plugin.yaml`. +Here is a plugin YAML for a plugin that adds support for Keybase operations: + +``` +name: "keybase" +version: "0.1.0" +usage: "Integrate Keybase.io tools with Helm" +description: |- + This plugin provides Keybase services to Helm. +ignoreFlags: false +useTunnel: false +command: "$HELM_PLUGIN_DIR/keybase.sh" +``` + +The `name` is the name of the plugin. When Helm executes it plugin, this is the +name it will use (e.g. `helm NAME` will invoke this plugin). + +_`name` should match the directory name._ In our example above, that means the +plugin with `name: keybase` should be contained in a directory named `keybase`. + +Restrictions on `name`: + +- `name` cannot duplicate one of the existing `helm` top-level commands. +- `name` must be restricted to the characters ASCII a-z, A-Z, 0-9, `_` and `-`. + +`version` is the SemVer 2 version of the plugin. +`usage` and `description` are both used to generate the help text of a command. + +The `ignoreFlags` switch tells Helm to _not_ pass flags to the plugin. So if a +plugin is called with `helm myplugin --foo` and `ignoreFlags: true`, then `--foo` +is silently discarded. + +The `useTunnel` switch indicates that the plugin needs a tunnel to Tiller. This +should be set to `true` _anytime a plugin talks to Tiller_. It will cause Helm +to open a tunnel, and then set `$TILLER_HOST` to the right local address for that +tunnel. But don't worry: if Helm detects that a tunnel is not necessary because +Tiller is running locally, it will not create the tunnel. + +Finally, and most importantly, `command` is the command that this plugin will +execute when it is called. Environment variables are interpolated before the plugin +is executed. The pattern above illustrates the preferred way to indicate where +the plugin program lives. + +There are some strategies for working with plugin commands: + +- If a plugin includes an executable, the executable for a `command:` should be + packaged in the plugin directory. +- The `command:` line will have any environment variables expanded before + execution. `$HELM_PLUGIN_DIR` will point to the plugin directory. +- The command itself is not executed in a shell. So you can't oneline a shell script. +- Helm injects lots of configuration into environment variables. Take a look at + the environment to see what information is available. +- Helm makes no assumptions about the language of the plugin. You can write it + in whatever you prefer. +- Commands are responsible for implementing specific help text for `-h` and `--help`. + Helm will use `usage` and `description` for `helm help` and `helm help myplugin`, + but will not handle `helm myplugin --help`. + +## Downloader Plugins +By default, Helm is able to fetch Charts using HTTP/S. As of Helm 2.4.0, plugins +can have a special capability to download Charts from arbitrary sources. + +Plugins shall declare this special capability in the `plugin.yaml` file (top level): + +``` +downloaders: +- command: "bin/mydownloader" + protocols: + - "myprotocol" + - "myprotocols" +``` + +If such plugin is installed, Helm can interact with the repository using the specified +protocol scheme by invoking the `command`. The special repository shall be added +similarily to the regular ones: `helm repo add favorite myprotocol://example.com/` +The rules for the special repos are the same to the regular ones: Helm must be able +to download the `index.yaml` file in order to discover and cache the list of +available Charts. + +The defined command will be invoked with the following scheme: +`command certFile keyFile caFile full-URL`. The SSL credentials are coming from the +repo definition, stored in `$HELM_HOME/repository/repositories.yaml`. Downloader +plugin is expected to dump the raw content to stdout and report errors on stderr. + +## Environment Variables + +When Helm executes a plugin, it passes the outer environment to the plugin, and +also injects some additional environment variables. + +Variables like `KUBECONFIG` are set for the plugin if they are set in the +outer environment. + +The following variables are guaranteed to be set: + +- `HELM_PLUGIN`: The path to the plugins directory +- `HELM_PLUGIN_NAME`: The name of the plugin, as invoked by `helm`. So + `helm myplug` will have the short name `myplug`. +- `HELM_PLUGIN_DIR`: The directory that contains the plugin. +- `HELM_BIN`: The path to the `helm` command (as executed by the user). +- `HELM_HOME`: The path to the Helm home. +- `HELM_PATH_*`: Paths to important Helm files and directories are stored in + environment variables prefixed by `HELM_PATH`. +- `TILLER_HOST`: The `domain:port` to Tiller. If a tunnel is created, this + will point to the local endpoint for the tunnel. Otherwise, it will point + to `$HELM_HOST`, `--host`, or the default host (according to Helm's rules of + precedence). + +While `HELM_HOST` _may_ be set, there is no guarantee that it will point to the +correct Tiller instance. This is done to allow plugin developer to access +`HELM_HOST` in its raw state when the plugin itself needs to manually configure +a connection. + +## A Note on `useTunnel` + +If a plugin specifies `useTunnel: true`, Helm will do the following (in order): + +1. Parse global flags and the environment +2. Create the tunnel +3. Set `TILLER_HOST` +4. Execute the plugin +5. Close the tunnel + +The tunnel is removed as soon as the `command` returns. So, for example, a +command cannot background a process and assume that that process will be able +to use the tunnel. + +## A Note on Flag Parsing + +When executing a plugin, Helm will parse global flags for its own use. Some of +these flags are _not_ passed on to the plugin. + +- `--debug`: If this is specified, `$HELM_DEBUG` is set to `1` +- `--home`: This is converted to `$HELM_HOME` +- `--host`: This is converted to `$HELM_HOST` +- `--kube-context`: This is simply dropped. If your plugin uses `useTunnel`, this + is used to set up the tunnel for you. + +Plugins _should_ display help text and then exit for `-h` and `--help`. In all +other cases, plugins may use flags as appropriate. diff --git a/src/vendor/github.com/kubernetes/helm/docs/provenance.md b/src/vendor/github.com/kubernetes/helm/docs/provenance.md new file mode 100644 index 000000000..331074e8c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/provenance.md @@ -0,0 +1,277 @@ +# Helm Provenance and Integrity + +Helm has provenance tools which help chart users verify the integrity and origin +of a package. Using industry-standard tools based on PKI, GnuPG, and well-respected +package managers, Helm can generate and verify signature files. + +## Overview + +Integrity is established by comparing a chart to a provenance record. Provenance +records are stored in _provenance files_, which are stored alongside a packaged +chart. For example, if a chart is named `myapp-1.2.3.tgz`, its provenance file +will be `myapp-1.2.3.tgz.prov`. + +Provenance files are generated at packaging time (`helm package --sign ...`), and +can be checked by multiple commands, notable `helm install --verify`. + +## The Workflow + +This section describes a potential workflow for using provenance data effectively. + +Prerequisites: + +- A valid PGP keypair in a binary (not ASCII-armored) format +- The `helm` command line tool +- GnuPG command line tools (optional) +- Keybase command line tools (optional) + +**NOTE:** If your PGP private key has a passphrase, you will be prompted to enter +that passphrase for any commands that support the `--sign` option. + +Creating a new chart is the same as before: + +``` +$ helm create mychart +Creating mychart +``` + +Once ready to package, add the `--sign` flag to `helm package`. Also, specify +the name under which the signing key is known and the keyring containing the corresponding private key: + +``` +$ helm package --sign --key 'helm signing key' --keyring path/to/keyring.secret mychart +``` + +**TIP:** for GnuPG users, your secret keyring is in `~/.gnupg/secring.gpg`. You can +use `gpg --list-secret-keys` to list the keys you have. + +**Warning:** the GnuPG v2 store your secret keyring using a new format 'kbx' on the default location '~/.gnupg/pubring.kbx'. Please use the following command to convert your keyring to the legacy gpg format: + +``` +$ gpg --export-secret-keys >~/.gnupg/secring.gpg +``` + +At this point, you should see both `mychart-0.1.0.tgz` and `mychart-0.1.0.tgz.prov`. +Both files should eventually be uploaded to your desired chart repository. + +You can verify a chart using `helm verify`: + +``` +$ helm verify mychart-0.1.0.tgz +``` + +A failed verification looks like this: + +``` +$ helm verify topchart-0.1.0.tgz +Error: sha256 sum does not match for topchart-0.1.0.tgz: "sha256:1939fbf7c1023d2f6b865d137bbb600e0c42061c3235528b1e8c82f4450c12a7" != "sha256:5a391a90de56778dd3274e47d789a2c84e0e106e1a37ef8cfa51fd60ac9e623a" +``` + +To verify during an install, use the `--verify` flag. + +``` +$ helm install --verify mychart-0.1.0.tgz +``` + +If the keyring (containing the public key associated with the signed chart) is not in the default location, you may need to point to the +keyring with `--keyring PATH` as in the `helm package` example. + +If verification fails, the install will be aborted before the chart is even pushed +up to Tiller. + +### Using Keybase.io credentials + +The [Keybase.io](https://keybase.io) service makes it easy to establish a chain of +trust for a cryptographic identity. Keybase credentials can be used to sign charts. + +Prerequisites: + +- A configured Keybase.io account +- GnuPG installed locally +- The `keybase` CLI installed locally + +#### Signing packages + +The first step is to import your keybase keys into your local GnuPG keyring: + +``` +$ keybase pgp export -s | gpg --import +``` + +This will convert your Keybase key into the OpenPGP format, and then import it +locally into your `~/.gnupg/secring.gpg` file. + +You can double check by running `gpg --list-secret-keys`. + +``` +$ gpg --list-secret-keys 1 ↵ +/Users/mattbutcher/.gnupg/secring.gpg +------------------------------------- +sec 2048R/1FC18762 2016-07-25 +uid technosophos (keybase.io/technosophos) +ssb 2048R/D125E546 2016-07-25 +``` + +Note that your secret key will have an identifier string: + +``` +technosophos (keybase.io/technosophos) +``` + +That is the full name of your key. + +Next, you can package and sign a chart with `helm package`. Make sure you use at +least part of that name string in `--key`. + +``` +$ helm package --sign --key technosophos --keyring ~/.gnupg/secring.gpg mychart +``` + +As a result, the `package` command should produce both a `.tgz` file and a `.tgz.prov` +file. + +#### Verifying packages + +You can also use a similar technique to verify a chart signed by someone else's +Keybase key. Say you want to verify a package signed by `keybase.io/technosophos`. +To do this, use the `keybase` tool: + +``` +$ keybase follow technosophos +$ keybase pgp pull +``` + +The first command above tracks the user `technosophos`. Next `keybase pgp pull` +downloads the OpenPGP keys of all of the accounts you follow, placing them in +your GnuPG keyring (`~/.gnupg/pubring.gpg`). + +At this point, you can now use `helm verify` or any of the commands with a `--verify` +flag: + +``` +$ helm verify somechart-1.2.3.tgz +``` + +### Reasons a chart may not verify + +These are common reasons for failure. + +- The prov file is missing or corrupt. This indicates that something is misconfigured + or that the original maintainer did not create a provenance file. +- The key used to sign the file is not in your keyring. This indicate that the + entity who signed the chart is not someone you've already signaled that you trust. +- The verification of the prov file failed. This indicates that something is wrong + with either the chart or the provenance data. +- The file hashes in the provenance file do not match the hash of the archive file. This + indicates that the archive has been tampered with. + +If a verification fails, there is reason to distrust the package. + +## The Provenance File +The provenance file contains a chart’s YAML file plus several pieces of +verification information. Provenance files are designed to be automatically +generated. + + +The following pieces of provenance data are added: + + +* The chart file (Chart.yaml) is included to give both humans and tools an easy + view into the contents of the chart. +* The signature (SHA256, just like Docker) of the chart package (the .tgz file) + is included, and may be used to verify the integrity of the chart package. +* The entire body is signed using the algorithm used by PGP (see + [http://keybase.io] for an emerging way of making crypto signing and + verification easy). + +The combination of this gives users the following assurances: + +* The package itself has not been tampered with (checksum package tgz). +* The entity who released this package is known (via the GnuPG/PGP signature). + +The format of the file looks something like this: + +``` +-----BEGIN PGP SIGNED MESSAGE----- +name: nginx +description: The nginx web server as a replication controller and service pair. +version: 0.5.1 +keywords: + - https + - http + - web server + - proxy +source: +- https://github.com/foo/bar +home: http://nginx.com + +... +files: + nginx-0.5.1.tgz: “sha256:9f5270f50fc842cfcb717f817e95178f” +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.9 (GNU/Linux) + +iEYEARECAAYFAkjilUEACgQkB01zfu119ZnHuQCdGCcg2YxF3XFscJLS4lzHlvte +WkQAmQGHuuoLEJuKhRNo+Wy7mhE7u1YG +=eifq +-----END PGP SIGNATURE----- +``` + +Note that the YAML section contains two documents (separated by `...\n`). The +first is the Chart.yaml. The second is the checksums, a map of filenames to +SHA-256 digests (value shown is fake/truncated) + +The signature block is a standard PGP signature, which provides [tamper +resistance](http://www.rossde.com/PGP/pgp_signatures.html). + +## Chart Repositories + +Chart repositories serve as a centralized collection of Helm charts. + +Chart repositories must make it possible to serve provenance files over HTTP via +a specific request, and must make them available at the same URI path as the chart. + +For example, if the base URL for a package is `https://example.com/charts/mychart-1.2.3.tgz`, +the provenance file, if it exists, MUST be accessible at `https://example.com/charts/mychart-1.2.3.tgz.prov`. + +From the end user's perspective, `helm install --verify myrepo/mychart-1.2.3` +should result in the download of both the chart and the provenance file with no +additional user configuration or action. + +## Establishing Authority and Authenticity + +When dealing with chain-of-trust systems, it is important to be able to +establish the authority of a signer. Or, to put this plainly, the system +above hinges on the fact that you trust the person who signed the chart. +That, in turn, means you need to trust the public key of the signer. + +One of the design decisions with Kubernetes Helm has been that the Helm +project would not insert itself into the chain of trust as a necessary +party. We don't want to be "the certificate authority" for all chart +signers. Instead, we strongly favor a decentralized model, which is part +of the reason we chose OpenPGP as our foundational technology. +So when it comes to establishing authority, we have left this +step more-or-less undefined in Helm 2.0.0. + +However, we have some pointers and recommendations for those interested +in using the provenance system: + +- The [Keybase](https://keybase.io) platform provides a public + centralized repository for trust information. + - You can use Keybase to store your keys or to get the public keys of others. + - Keybase also has fabulous documentation available + - While we haven't tested it, Keybase's "secure website" feature could + be used to serve Helm charts. +- The [official Kubernetes Charts project](https://github.com/kubernetes/charts) + is trying to solve this problem for the official chart repository. + - There is a long issue there [detailing the current thoughts](https://github.com/kubernetes/charts/issues/23). + - The basic idea is that an official "chart reviewer" signs charts with + her or his key, and the resulting provenance file is then uploaded + to the chart repository. + - There has been some work on the idea that a list of valid signing + keys may be included in the `index.yaml` file of a repository. + +Finally, chain-of-trust is an evolving feature of Helm, and some +community members have proposed adapting part of the OSI model for +signatures. This is an open line of inquiry in the Helm team. If you're +interested, jump on in. diff --git a/src/vendor/github.com/kubernetes/helm/docs/quickstart.md b/src/vendor/github.com/kubernetes/helm/docs/quickstart.md new file mode 100644 index 000000000..52a7c800f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/quickstart.md @@ -0,0 +1,137 @@ +# Quickstart Guide + +This guide covers how you can quickly get started using Helm. + +## Prerequisites + +The following prerequisites are required for a successful and properly secured use of Helm. + +1. A Kubernetes cluster +2. Deciding what security configurations to apply to your installation, if any +3. Installing and configuring Helm and Tiller, the cluster-side service. + + +### Install Kubernetes or have access to a cluster +- You must have Kubernetes installed. For the latest release of Helm, we recommend the latest stable release of Kubernetes, which in most cases is the second-latest minor release. +- You should also have a local configured copy of `kubectl`. + +NOTE: Kubernetes versions prior to 1.6 have limited or no support for role-based access controls (RBAC). + +Helm will figure out where to install Tiller by reading your Kubernetes +configuration file (usually `$HOME/.kube/config`). This is the same file +that `kubectl` uses. + +To find out which cluster Tiller would install to, you can run +`kubectl config current-context` or `kubectl cluster-info`. + +```console +$ kubectl config current-context +my-cluster +``` + +### Understand your Security Context + +As with all powerful tools, ensure you are installing it correctly for your scenario. + +If you're using Helm on a cluster that you completely control, like minikube or a cluster on a private network in which sharing is not a concern, the default installation -- which applies no security configuration -- is fine, and it's definitely the easiest. To install Helm without additional security steps, [install Helm](#Install-Helm) and then [initialize Helm](#initialize-helm-and-install-tiller). + +However, if your cluster is exposed to a larger network or if you share your cluster with others -- production clusters fall into this category -- you must take extra steps to secure your installation to prevent careless or malicious actors from damaging the cluster or its data. To apply configurations that secure Helm for use in production environments and other multi-tenant scenarios, see [Securing a Helm installation](securing_installation.md) + +If your cluster has Role-Based Access Control (RBAC) enabled, you may want +to [configure a service account and rules](rbac.md) before proceeding. + +## Install Helm + +Download a binary release of the Helm client. You can use tools like +`homebrew`, or look at [the official releases page](https://github.com/kubernetes/helm/releases). + +For more details, or for other options, see [the installation +guide](install.md). + +## Initialize Helm and Install Tiller + +Once you have Helm ready, you can initialize the local CLI and also +install Tiller into your Kubernetes cluster in one step: + +```console +$ helm init +``` + +This will install Tiller into the Kubernetes cluster you saw with +`kubectl config current-context`. + +**TIP:** Want to install into a different cluster? Use the +`--kube-context` flag. + +**TIP:** When you want to upgrade Tiller, just run `helm init --upgrade`. + +By default, when Tiller is installed,it does not have authentication enabled. +To learn more about configuring strong TLS authentication for Tiller, consult +[the Tiller TLS guide](tiller_ssl.md). + +## Install an Example Chart + +To install a chart, you can run the `helm install` command. Helm has +several ways to find and install a chart, but the easiest is to use one +of the official `stable` charts. + +```console +$ helm repo update # Make sure we get the latest list of charts +$ helm install stable/mysql +Released smiling-penguin +``` + +In the example above, the `stable/mysql` chart was released, and the name of +our new release is `smiling-penguin`. You get a simple idea of the +features of this MySQL chart by running `helm inspect stable/mysql`. + +Whenever you install a chart, a new release is created. So one chart can +be installed multiple times into the same cluster. And each can be +independently managed and upgraded. + +The `helm install` command is a very powerful command with many +capabilities. To learn more about it, check out the [Using Helm +Guide](using_helm.md) + +## Learn About Releases + +It's easy to see what has been released using Helm: + +```console +$ helm ls +NAME VERSION UPDATED                   STATUS   CHART +smiling-penguin 1 Wed Sep 28 12:59:46 2016 DEPLOYED mysql-0.1.0 +``` + +The `helm list` function will show you a list of all deployed releases. + +## Uninstall a Release + +To uninstall a release, use the `helm delete` command: + +```console +$ helm delete smiling-penguin +Removed smiling-penguin +``` + +This will uninstall `smiling-penguin` from Kubernetes, but you will +still be able to request information about that release: + +```console +$ helm status smiling-penguin +Status: DELETED +... +``` + +Because Helm tracks your releases even after you've deleted them, you +can audit a cluster's history, and even undelete a release (with `helm +rollback`). + +## Reading the Help Text + +To learn more about the available Helm commands, use `helm help` or type +a command followed by the `-h` flag: + +```console +$ helm get -h +``` diff --git a/src/vendor/github.com/kubernetes/helm/docs/rbac.md b/src/vendor/github.com/kubernetes/helm/docs/rbac.md new file mode 100644 index 000000000..bc138ceee --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/rbac.md @@ -0,0 +1,281 @@ +# Role-based Access Control + +In Kubernetes, granting a role to an application-specific service account is a best practice to ensure that your application is operating in the scope that you have specified. Read more about service account permissions [in the official Kubernetes docs](https://kubernetes.io/docs/admin/authorization/rbac/#service-account-permissions). + +Bitnami also has a fantastic guide for [configuring RBAC in your cluster](https://docs.bitnami.com/kubernetes/how-to/configure-rbac-in-your-kubernetes-cluster/) that takes you through RBAC basics. + +This guide is for users who want to restrict tiller's capabilities to install resources to certain namespaces, or to grant a helm client running access to a tiller instance. + +## Tiller and Role-based Access Control + +You can add a service account to Tiller using the `--service-account ` flag while you're configuring helm. As a prerequisite, you'll have to create a role binding which specifies a [role](https://kubernetes.io/docs/admin/authorization/rbac/#role-and-clusterrole) and a [service account](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) name that have been set up in advance. + +Once you have satisfied the pre-requisite and have a service account with the correct permissions, you'll run a command like this: `helm init --service-account ` + +### Example: Service account with cluster-admin role + +```console +$ kubectl create serviceaccount tiller --namespace kube-system +serviceaccount "tiller" created +``` + +In `rbac-config.yaml`: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tiller + namespace: kube-system +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: tiller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: ServiceAccount + name: tiller + namespace: kube-system +``` + +_Note: The cluster-admin role is created by default in a Kubernetes cluster, so you don't have to define it explicitly._ + +```console +$ kubectl create -f rbac-config.yaml +serviceaccount "tiller" created +clusterrolebinding "tiller" created +$ helm init --service-account tiller +``` + +### Example: Deploy tiller in a namespace, restricted to deploying resources only in that namespace + +In the example above, we gave Tiller admin access to the entire cluster. You are not at all required to give Tiller cluster-admin access for it to work. Instead of specifying a ClusterRole or a ClusterRoleBinding, you can specify a Role and RoleBinding to limit Tiller's scope to a particular namespace. + +```console +$ kubectl create namespace tiller-world +namespace "tiller-world" created +$ kubectl create serviceaccount tiller --namespace tiller-world +serviceaccount "tiller" created +``` + +Define a Role that allows tiller to manage all resources in `tiller-world` like in `role-tiller.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-manager + namespace: tiller-world +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller.yaml +role "tiller-manager" created +``` + +In `rolebinding-tiller.yaml`, + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: tiller-world +subjects: +- kind: ServiceAccount + name: tiller + namespace: tiller-world +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller.yaml +rolebinding "tiller-binding" created +``` + +Afterwards you can run `helm init` to install tiller in the `tiller-world` namespace. + +```console +$ helm init --service-account tiller --tiller-namespace tiller-world +$HELM_HOME has been configured at /Users/awesome-user/.helm. + +Tiller (the helm server side component) has been installed into your Kubernetes Cluster. +Happy Helming! + +$ helm install nginx --tiller-namespace tiller-world --namespace tiller-world +NAME: wayfaring-yak +LAST DEPLOYED: Mon Aug 7 16:00:16 2017 +NAMESPACE: tiller-world +STATUS: DEPLOYED + +RESOURCES: +==> v1/Pod +NAME READY STATUS RESTARTS AGE +wayfaring-yak-alpine 0/1 ContainerCreating 0 0s +``` + +### Example: Deploy tiller in a namespace, restricted to deploying resources in another namespace + +In the example above, we gave Tiller admin access to the namespace it was deployed inside. Now, let's limit Tiller's scope to deploy resources in a different namespace! + +For example, let's install tiller in the namespace `myorg-system` and allow tiller to deploy resources in the namespace `myorg-users`. + +```console +$ kubectl create namespace myorg-system +namespace "myorg-system" created +$ kubectl create serviceaccount tiller --namespace myorg-system +serviceaccount "tiller" created +``` + +Define a Role that allows tiller to manage all resources in `myorg-users` like in `role-tiller.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-manager + namespace: myorg-users +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["*"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller.yaml +role "tiller-manager" created +``` + +Bind the service account to that role. In `rolebinding-tiller.yaml`, + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: myorg-users +subjects: +- kind: ServiceAccount + name: tiller + namespace: myorg-system +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller.yaml +rolebinding "tiller-binding" created +``` + +We'll also need to grant tiller access to read configmaps in myorg-system so it can store release information. In `role-tiller-myorg-system.yaml`: + +```yaml +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + namespace: myorg-system + name: tiller-manager +rules: +- apiGroups: ["", "extensions", "apps"] + resources: ["configmaps"] + verbs: ["*"] +``` + +```console +$ kubectl create -f role-tiller-myorg-system.yaml +role "tiller-manager" created +``` + +And the respective role binding. In `rolebinding-tiller-myorg-system.yaml`: + +```yaml +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: tiller-binding + namespace: myorg-system +subjects: +- kind: ServiceAccount + name: tiller + namespace: myorg-system +roleRef: + kind: Role + name: tiller-manager + apiGroup: rbac.authorization.k8s.io +``` + +```console +$ kubectl create -f rolebinding-tiller-myorg-system.yaml +rolebinding "tiller-binding" created +``` + +## Helm and Role-based Access Control + +When running a helm client in a pod, in order for the helm client to talk to a tiller instance, it will need certain privileges to be granted. Specifically, the helm client will need to be able to create pods, forward ports and be able to list pods in the namespace where tiller is running (so it can find tiller). + +### Example: Deploy Helm in a namespace, talking to Tiller in another namespace + +In this example, we will assume tiller is running in a namespace called `tiller-world` and that the helm client is running in a namespace called `helm-world`. By default, tiller is running in the `kube-system` namespace. + +In `helm-user.yaml`: + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: helm + namespace: helm-world +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: tiller-user + namespace: tiller-world +rules: +- apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create +- apiGroups: + - "" + resources: + - pods + verbs: + - list +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: tiller-user-binding + namespace: tiller-world +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: tiller-user +subjects: +- kind: ServiceAccount + name: helm + namespace: helm-world +``` + +```console +$ kubectl create -f helm-user.yaml +serviceaccount "helm" created +role "tiller-user" created +rolebinding "tiller-user-binding" created +``` diff --git a/src/vendor/github.com/kubernetes/helm/docs/related.md b/src/vendor/github.com/kubernetes/helm/docs/related.md new file mode 100644 index 000000000..997e3f01d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/related.md @@ -0,0 +1,87 @@ +# Related Projects and Documentation + +The Helm community has produced many extra tools, plugins, and documentation about +Helm. We love to hear about these projects. If you have anything you'd like to +add to this list, please open an [issue](https://github.com/kubernetes/helm/issues) +or [pull request](https://github.com/kubernetes/helm/pulls). + +## Article, Blogs, How-Tos, and Extra Documentation +- [Using Helm to Deploy to Kubernetes](https://daemonza.github.io/2017/02/20/using-helm-to-deploy-to-kubernetes/) +- [Honestbee's Helm Chart Conventions](https://gist.github.com/so0k/f927a4b60003cedd101a0911757c605a) +- [Deploying Kubernetes Applications with Helm](http://cloudacademy.com/blog/deploying-kubernetes-applications-with-helm/) +- [Releasing backward-incompatible changes: Kubernetes, Jenkins, Prometheus Operator, Helm and Traefik](https://medium.com/@enxebre/releasing-backward-incompatible-changes-kubernetes-jenkins-plugin-prometheus-operator-helm-self-6263ca61a1b1#.e0c7elxhq) +- [CI/CD with Kubernetes, Helm & Wercker ](http://www.slideshare.net/Diacode/cicd-with-kubernetes-helm-wercker-madscalability) +- [The missing CI/CD Kubernetes component: Helm package manager](https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680#.691sk2zhu) +- [The Workflow "Umbrella" Helm Chart](https://deis.com/blog/2017/workflow-chart-assembly) +- [GitLab, Consumer Driven Contracts, Helm and Kubernetes](https://medium.com/@enxebre/gitlab-consumer-driven-contracts-helm-and-kubernetes-b7235a60a1cb#.xwp1y4tgi) +- [Writing a Helm Chart](https://www.influxdata.com/packaged-kubernetes-deployments-writing-helm-chart/) +- [Creating a Helm Plugin in 3 Steps](http://technosophos.com/2017/03/21/creating-a-helm-plugin.html) + +## Video, Audio, and Podcast + +- [CI/CD with Jenkins, Kubernetes, and Helm](https://www.youtube.com/watch?v=NVoln4HdZOY): AKA "The Infamous Croc Hunter Video". +- [KubeCon2016: Delivering Kubernetes-Native Applications by Michelle Noorali](https://www.youtube.com/watch?v=zBc1goRfk3k&index=49&list=PLj6h78yzYM2PqgIGU1Qmi8nY7dqn9PCr4) +- [Helm with Michelle Noorali and Matthew Butcher](https://gcppodcast.com/post/episode-50-helm-with-michelle-noorali-and-matthew-butcher/): The official Google CloudPlatform Podcast interviews Michelle and Matt about Helm. + +## Helm Plugins + +- [helm-tiller](https://github.com/adamreese/helm-tiller) - Additional commands to work with Tiller +- [Technosophos's Helm Plugins](https://github.com/technosophos/helm-plugins) - Plugins for GitHub, Keybase, and GPG +- [helm-template](https://github.com/technosophos/helm-template) - Debug/render templates client-side +- [Helm Value Store](https://github.com/skuid/helm-value-store) - Plugin for working with Helm deployment values +- [Helm Diff](https://github.com/databus23/helm-diff) - Preview `helm upgrade` as a coloured diff +- [helm-env](https://github.com/adamreese/helm-env) - Plugin to show current environment +- [helm-last](https://github.com/adamreese/helm-last) - Plugin to show the latest release +- [helm-nuke](https://github.com/adamreese/helm-nuke) - Plugin to destroy all releases +- [helm-local](https://github.com/adamreese/helm-local) - Plugin to run Tiller as a local daemon +- [App Registry](https://github.com/app-registry/helm-plugin) - Plugin to manage charts via the [App Registry specification](https://github.com/app-registry/spec) +- [helm-secrets](https://github.com/futuresimple/helm-secrets) - Plugin to manage and store secrets safely +- [helm-edit](https://github.com/mstrzele/helm-edit) - Plugin for editing release's values +- [helm-gcs](https://github.com/nouney/helm-gcs) - Plugin to manage repositories on Google Cloud Storage +- [helm-github](https://github.com/sagansystems/helm-github) - Plugin to install Helm Charts from Github repositories +- [helm-monitor](https://github.com/ContainerSolutions/helm-monitor) - Plugin to monitor a release and rollback based on Prometheus/ElasticSearch query +- [helm-k8comp](https://github.com/cststack/k8comp) - Plugin to create Helm Charts from hiera using k8comp +- [helm-hashtag](https://github.com/balboah/helm-hashtag) - Plugin for tracking docker tag hash digests as values +- [helm-unittest](https://github.com/lrills/helm-unittest) - Plugin for unit testing chart locally with YAML + +We also encourage GitHub authors to use the [helm-plugin](https://github.com/search?q=topic%3Ahelm-plugin&type=Repositories) +tag on their plugin repositories. + +## Additional Tools + +Tools layered on top of Helm or Tiller. + +- [AppsCode Swift](https://github.com/appscode/swift) - Ajax friendly Helm Tiller Proxy using [grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) +- [Quay App Registry](https://coreos.com/blog/quay-application-registry-for-kubernetes.html) - Open Kubernetes application registry, including a Helm access client +- [Chartify](https://github.com/appscode/chartify) - Generate Helm charts from existing Kubernetes resources. +- [VIM-Kubernetes](https://github.com/andrewstuart/vim-kubernetes) - VIM plugin for Kubernetes and Helm +- [Landscaper](https://github.com/Eneco/landscaper/) - "Landscaper takes a set of Helm Chart references with values (a desired state), and realizes this in a Kubernetes cluster." +- [Rudder](https://github.com/AcalephStorage/rudder) - RESTful (JSON) proxy for Tiller's API +- [Helmfile](https://github.com/roboll/helmfile) - Helmfile is a declarative spec for deploying helm charts +- [Autohelm](https://github.com/reactiveops/autohelm) - Autohelm is _another_ simple declarative spec for deploying helm charts. Written in python and supports git urls as a source for helm charts. +- [Helmsman](https://github.com/Praqma/helmsman) - Helmsman is a helm-charts-as-code tool which enables installing/upgrading/protecting/moving/deleting releases from version controlled desired state files (described in a simple TOML format). +- [Schelm](https://github.com/databus23/schelm) - Render a Helm manifest to a directory +- [Drone.io Helm Plugin](http://plugins.drone.io/ipedrazas/drone-helm/) - Run Helm inside of the Drone CI/CD system +- [Cog](https://github.com/ohaiwalt/cog-helm) - Helm chart to deploy Cog on Kubernetes +- [Monocular](https://github.com/helm/monocular) - Web UI for Helm Chart repositories +- [Helm Chart Publisher](https://github.com/luizbafilho/helm-chart-publisher) - HTTP API for publishing Helm Charts in an easy way +- [Armada](https://github.com/att-comdev/armada) - Manage prefixed releases throughout various Kubernetes namespaces, and removes completed jobs for complex deployments. Used by the [Openstack-Helm](https://github.com/openstack/openstack-helm) team. +- [ChartMuseum](https://github.com/chartmuseum/chartmuseum) - Helm Chart Repository with support for Amazon S3 and Google Cloud Storage +- [Helm.NET](https://github.com/qmfrederik/helm) - A .NET client for Tiller's API +- [Codefresh](https://codefresh.io) - Kubernetes native CI/CD and management platform with UI dashboards for managing Helm charts and releases + +## Helm Included + +Platforms, distributions, and services that include Helm support. + +- [Kubernetic](https://kubernetic.com/) - Kubernetes Desktop Client +- [Cabin](http://www.skippbox.com/cabin/) - Mobile App for Managing Kubernetes +- [Qstack](https://qstack.com) +- [Fabric8](https://fabric8.io) - Integrated development platform for Kubernetes +- [Jenkins X](http://jenkins-x.io/) - open source automated CI/CD for Kubernetes which uses Helm for [promoting](http://jenkins-x.io/about/features/#promotion) applications through [environments via GitOps](http://jenkins-x.io/about/features/#environments) + +## Misc + +Grab bag of useful things for Chart authors and Helm users + +- [Await](https://github.com/saltside/await) - Docker image to "await" different conditions--especially useful for init containers. [More Info](http://blog.slashdeploy.com/2017/02/16/introducing-await/) diff --git a/src/vendor/github.com/kubernetes/helm/docs/release_checklist.md b/src/vendor/github.com/kubernetes/helm/docs/release_checklist.md new file mode 100644 index 000000000..26506985c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/release_checklist.md @@ -0,0 +1,267 @@ +# Release Checklist + +**IMPORTANT**: If your experience deviates from this document, please document the changes to keep it up-to-date. + +## A Maintainer's Guide to Releasing Helm + +So you're in charge of a new release for helm? Cool. Here's what to do... + +![TODO: Nothing](images/nothing.png) + +Just kidding! :trollface: + +All releases will be of the form vX.Y.Z where X is the major version number, Y is the minor version number and Z is the patch release number. This project strictly follows [semantic versioning](http://semver.org/) so following this step is critical. + +It is important to note that this document assumes that the git remote in your repository that corresponds to "https://github.com/kubernetes/helm" is named "upstream". If yours is not (for example, if you've chosen to name it "origin" or something similar instead), be sure to adjust the listed snippets for your local environment accordingly. If you are not sure what your upstream remote is named, use a command like `git remote -v` to find out. + +If you don't have an upstream remote, you can add one easily using something like: + +```shell +git remote add upstream git@github.com:kubernetes/helm.git +``` + +In this doc, we are going to reference a few environment variables as well, which you may want to set for convenience. For major/minor releases, use the following: + +```shell +export RELEASE_NAME=vX.Y.0 +export RELEASE_BRANCH_NAME="release-$RELEASE_NAME" +export RELEASE_CANDIDATE_NAME="$RELEASE_NAME-rc1" +``` + +If you are creating a patch release, you may want to use the following instead: + +```shell +export PREVIOUS_PATCH_RELEASE=vX.Y.Z +export RELEASE_NAME=vX.Y.Z+1 +export RELEASE_BRANCH_NAME="release-X.Y" +export RELEASE_CANDIDATE_NAME="$RELEASE_NAME-rc1" +``` + +## 1. Create the Release Branch + +### Major/Minor Releases + +Major releases are for new feature additions and behavioral changes *that break backwards compatibility*. Minor releases are for new feature additions that do not break backwards compatibility. To create a major or minor release, start by creating a `release-vX.Y.0` branch from master. + +```shell +git fetch upstream +git checkout upstream/master +git checkout -b $RELEASE_BRANCH_NAME +``` + +This new branch is going to be the base for the release, which we are going to iterate upon later. + +### Patch releases + +Patch releases are a few critical cherry-picked fixes to existing releases. Start by creating a `release-vX.Y.Z` branch from the latest patch release. + +```shell +git fetch upstream --tags +git checkout $PREVIOUS_PATCH_RELEASE +git checkout -b $RELEASE_BRANCH_NAME +``` + +From here, we can cherry-pick the commits we want to bring into the patch release: + +```shell +# get the commits ids we want to cherry-pick +git log --oneline +# cherry-pick the commits starting from the oldest one, without including merge commits +git cherry-pick -x +git cherry-pick -x +``` + +This new branch is going to be the base for the release, which we are going to iterate upon later. + +## 2. Change the Version Number in Git + +When doing a minor release, make sure to update pkg/version/version.go with the new release version. + +```shell +$ git diff pkg/version/version.go +diff --git a/pkg/version/version.go b/pkg/version/version.go +index 2109a0a..6f5a1a4 100644 +--- a/pkg/version/version.go ++++ b/pkg/version/version.go +@@ -26,7 +26,7 @@ var ( + // Increment major number for new feature additions and behavioral changes. + // Increment minor number for bug fixes and performance enhancements. + // Increment patch number for critical fixes to existing releases. +- Version = "v2.6" ++ Version = "v2.7" + + // BuildMetadata is extra build time data + BuildMetadata = "unreleased" +``` + +The README stores links to the latest release for helm. We want to change the version to the first release candidate which we are releasing (more on that in step 5). + +```shell +$ git diff README.md +diff --git a/README.md b/README.md +index 022afd79..547839e2 100644 +--- a/README.md ++++ b/README.md +@@ -34,10 +34,10 @@ Think of it like apt/yum/homebrew for Kubernetes. + + Binary downloads of the Helm client can be found at the following links: + +-- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-darwin-amd64.tar.gz) +-- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz) +-- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-386.tar.gz) +-- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-windows-amd64.tar.gz) ++- [OSX](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-darwin-amd64.tar.gz) ++- [Linux](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-linux-amd64.tar.gz) ++- [Linux 32-bit](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-linux-386.tar.gz) ++- [Windows](https://kubernetes-helm.storage.googleapis.com/helm-v2.8.0-windows-amd64.tar.gz) + + Unpack the `helm` binary and add it to your PATH and you are good to go! + macOS/[homebrew](https://brew.sh/) users can also use `brew install kubernetes-helm`. +``` + +For patch releases, the old version number will be the latest patch release, so just bump the patch number, incrementing Z by one. + +```shell +git add . +git commit -m "bump version to $RELEASE_CANDIDATE_NAME" +``` + +## 3. Commit and Push the Release Branch + +In order for others to start testing, we can now push the release branch upstream and start the test process. + +```shell +git push upstream $RELEASE_BRANCH_NAME +``` + +Make sure to check [helm on CircleCI](https://circleci.com/gh/kubernetes/helm) and make sure the release passed CI before proceeding. + +If anyone is available, let others peer-review the branch before continuing to ensure that all the proper changes have been made and all of the commits for the release are there. + +## 4. Create a Release Candidate + +Now that the release branch is out and ready, it is time to start creating and iterating on release candidates. + +```shell +git tag --sign --annotate "${RELEASE_CANDIDATE_NAME}" --message "Helm release ${RELEASE_CANDIDATE_NAME}" +git push upstream $RELEASE_CANDIDATE_NAME +``` + +CircleCI will automatically create a tagged release image and client binary to test with. + +For testers, the process to start testing after CircleCI finishes building the artifacts involves the following steps to grab the client from Google Cloud Storage: + +linux/amd64, using /bin/bash: + +```shell +wget https://kubernetes-helm.storage.googleapis.com/helm-$RELEASE_CANDIDATE_NAME-linux-amd64.tar.gz +``` + +darwin/amd64, using Terminal.app: + +```shell +wget https://kubernetes-helm.storage.googleapis.com/helm-$RELEASE_CANDIDATE_NAME-darwin-amd64.tar.gz +``` + +windows/amd64, using PowerShell: + +```shell +PS C:\> Invoke-WebRequest -Uri "https://kubernetes-helm.storage.googleapis.com/helm-$RELEASE_CANDIDATE_NAME-windows-amd64.tar.gz" -OutFile "helm-$ReleaseCandidateName-windows-amd64.tar.gz" +``` + +Then, unpack and move the binary to somewhere on your $PATH, or move it somewhere and add it to your $PATH (e.g. /usr/local/bin/helm for linux/macOS, C:\Program Files\helm\helm.exe for Windows). + +## 5. Iterate on Successive Release Candidates + +Spend several days explicitly investing time and resources to try and break helm in every possible way, documenting any findings pertinent to the release. This time should be spent testing and finding ways in which the release might have caused various features or upgrade environments to have issues, not coding. During this time, the release is in code freeze, and any additional code changes will be pushed out to the next release. + +During this phase, the $RELEASE_BRANCH_NAME branch will keep evolving as you will produce new release candidates. The frequency of new candidates is up to the release manager: use your best judgement taking into account the severity of reported issues, testers' availability, and the release deadline date. Generally speaking, it is better to let a release roll over the deadline than to ship a broken release. + +Each time you'll want to produce a new release candidate, you will start by adding commits to the branch by cherry-picking from master: + +```shell +git cherry-pick -x +``` + +You will also want to update the release version number and the CHANGELOG as we did in steps 2 and 3 as separate commits. + +After that, tag it and notify users of the new release candidate: + +```shell +export RELEASE_CANDIDATE_NAME="$RELEASE_NAME-rc2" +git tag --sign --annotate "${RELEASE_CANDIDATE_NAME}" --message "Helm release ${RELEASE_CANDIDATE_NAME}" +git push upstream $RELEASE_CANDIDATE_NAME +``` + +From here on just repeat this process, continuously testing until you're happy with the release candidate. + +## 6. Finalize the Release + +When you're finally happy with the quality of a release candidate, you can move on and create the real thing. Double-check one last time to make sure everything is in order, then finally push the release tag. + +```shell +git checkout $RELEASE_BRANCH_NAME +git tag --sign --annotate "${RELEASE_NAME}" --message "Helm release ${RELEASE_NAME}" +git push upstream $RELEASE_NAME +``` + +## 7. Write the Release Notes + +We will auto-generate a changelog based on the commits that occurred during a release cycle, but it is usually more beneficial to the end-user if the release notes are hand-written by a human being/marketing team/dog. + +If you're releasing a major/minor release, listing notable user-facing features is usually sufficient. For patch releases, do the same, but make note of the symptoms and who is affected. + +An example release note for a minor release would look like this: + +```markdown +## vX.Y.Z + +Helm vX.Y.Z is a feature release. This release, we focused on . Users are encouraged to upgrade for the best experience. + +The community keeps growing, and we'd love to see you there. + +- Join the discussion in [Kubernetes Slack](https://slack.k8s.io/): + - `#helm-users` for questions and just to hang out + - `#helm-dev` for discussing PRs, code, and bugs +- Hang out at the Public Developer Call: Thursday, 9:30 Pacific via [Zoom](https://zoom.us/j/4526666954) +- Test, debug, and contribute charts: [GitHub/kubernetes/charts](https://github.com/kubernetes/charts) + +## Installation and Upgrading + +Download Helm X.Y. The common platform binaries are here: + +- [OSX](https://storage.googleapis.com/kubernetes-helm/helm-vX.Y.Z-darwin-amd64.tar.gz) +- [Linux](https://storage.googleapis.com/kubernetes-helm/helm-vX.Y.Z-linux-amd64.tar.gz) +- [Windows](https://storage.googleapis.com/kubernetes-helm/helm-vX.Y.Z-windows-amd64.tar.gz) + +Once you have the client installed, upgrade Tiller with `helm init --upgrade`. + +The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get) on any system with `bash`. + +## What's Next + +- vX.Y.Z+1 will contain only bug fixes. +- vX.Y+1.Z is the next feature release. This release will focus on ... + +## Changelog + +- chore(*): bump version to v2.7.0 08c1144f5eb3e3b636d9775617287cc26e53dba4 (Adam Reese) +- fix circle not building tags f4f932fabd197f7e6d608c8672b33a483b4b76fa (Matthew Fisher) +``` + +The changelog at the bottom of the release notes can be generated with this command: + +```shell +PREVIOUS_RELEASE=vX.Y.Z +git log --no-merges --pretty=format:'- %s %H (%aN)' $RELEASE_NAME $PREVIOUS_RELEASE +``` + +Once finished, go into GitHub and edit the release notes for the tagged release with the notes written here. + +## 9. Evangelize + +Congratulations! You're done. Go grab yourself a $DRINK_OF_CHOICE. You've earned it. + +After enjoying a nice $DRINK_OF_CHOICE, go forth and announce the glad tidings of the new release in Slack and on Twitter. You should also notify any key partners in the helm community such as the homebrew formula maintainers, the owners of incubator projects (e.g. ChartMuseum) and any other interested parties. + +Optionally, write a blog post about the new release and showcase some of the new features on there! diff --git a/src/vendor/github.com/kubernetes/helm/docs/securing_installation.md b/src/vendor/github.com/kubernetes/helm/docs/securing_installation.md new file mode 100644 index 000000000..5c420242e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/securing_installation.md @@ -0,0 +1,111 @@ +# Securing your Helm Installation + +Helm is a powerful and flexible package-management and operations tool for Kubernetes. Installing it using the default installation command -- `helm init` -- quickly and easily installs **Tiller**, the server-side component with which Helm corresponds. + +This default installation applies **_no security configurations_**, however. It's completely appropriate to use this type of installation when you are working against a cluster with no or very few security concerns, such as local development with Minikube or with a cluster that is well-secured in a private network with no data-sharing or no other users or teams. If this is the case, then the default installation is fine, but remember: With great power comes great responsibility. Always use due diligence when deciding to use the default installation. + +## Who Needs Security Configurations? + +For the following types of clusters we strongly recommend that you apply the proper security configurations to Helm and Tiller to ensure the safety of the cluster, the data in it, and the network to which it is connected. + +- Clusters that are exposed to uncontrolled network environments: either untrusted network actors can access the cluster, or untrusted applications that can access the network environment. +- Clusters that are for many people to use -- _multitenant_ clusters -- as a shared environment +- Clusters that have access to or use high-value data or networks of any type + +Often, environments like these are referred to as _production grade_ or _production quality_ because the damage done to any company by misuse of the cluster can be profound for either customers, the company itself, or both. Once the risk of damage becomes high enough, you need to ensure the integrity of your cluster no matter what the actual risk. + +To configure your installation properly for your environment, you must: + +- Understand the security context of your cluster +- Choose the Best Practices you should apply to your helm installation + +The following assumes you have a Kubernetes configuration file (a _kubeconfig_ file) or one was given to you to access a cluster. + +## Understanding the Security Context of your Cluster + +`helm init` installs Tiller into the cluster in the `kube-system` namespace and without any RBAC rules applied. This is appropriate for local development and other private scenarios because it enables you to be productive immediately. It also enables you to continue running Helm with existing Kubernetes clusters that do not have role-based access control (RBAC) support until you can move your workloads to a more recent Kubernetes version. + +There are four main areas to consider when securing a tiller installation: + +1. Role-based access control, or RBAC +2. Tiller's gRPC endpoint and its usage by Helm +3. Tiller release information +4. Helm charts + +### RBAC + +Recent versions of Kubernetes employ a [role-based access control (or RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) system (as do modern operating systems) to help mitigate the damage that can done if credentials are misused or bugs exist. Even where an identity is hijacked, the identity has only so many permissions to a controlled space. This effectively adds a layer of security to limit the scope of any attack with that identity. + +Helm and Tiller are designed to install, remove, and modify logical applications that can contain many services interacting together. As a result, often its usefulness involves cluster-wide operations, which in a multitenant cluster means that great care must be taken with access to a cluster-wide Tiller installation to prevent improper activity. + +Specific users and teams -- developers, operators, system and network administrators -- will need their own portion of the cluster in which they can use Helm and Tiller without risking other portions of the cluster. This means using a Kubernetes cluster with RBAC enabled and Tiller configured to enforce them. For more information about using RBAC in Kubernetes, see [Using RBAC Authorization](rbac.md). + +#### Tiller and User Permissions + +Tiller in its current form does not provide a way to map user credentials to specific permissions within Kubernetes. When Tiller is running inside of the cluster, it operates with the permissions of its service account. If no service account name is supplied to Tiller, it runs with the default service account for that namespace. This means that all Tiller operations on that server are executed using the Tiller pod's credentials and permissions. + +To properly limit what Tiller itself can do, the standard Kubernetes RBAC mechanisms must be attached to Tiller, including Roles and RoleBindings that place explicit limits on what things a Tiller instance can install, and where. + +This situation may change in the future. While the community has several methods that might address this, at the moment performing actions using the rights of the client, instead of the rights of Tiller, is contingent upon the outcome of the Pod Identity Working Group, which has taken on the task of solving the problem in a general way. + + +### The Tiller gRPC Endpoint and TLS + +In the default installation the gRPC endpoint that Tiller offers is available inside the cluster (not external to the cluster) without authentication configuration applied. Without applying authentication, any process in the cluster can use the gRPC endpoint to perform operations inside the cluster. In a local or secured private cluster, this enables rapid usage and is normal. (When running outside the cluster, Helm authenticates through the Kubernetes API server to reach Tiller, leveraging existing Kubernetes authentication support.) + +Shared and production clusters -- for the most part -- should use Helm 2.7.2 at a minimum and configure TLS for each Tiller gRPC endpoint to ensure that within the cluster usage of gRPC endpoints is only for the properly authenticated identity for that endpoint. Doing so enables any number of Tiller instances to be deployed in any number of namespaces and yet no unauthenticated usage of any gRPC endpoint is possible. Finally, usa Helm `init` with the `--tiller-tls-verify` option to install Tiller with TLS enabled and to verify remote certificates, and all other Helm commands should use the `--tls` option. + +For more information about the proper steps to configure Tiller and use Helm properly with TLS configured, see [Using SSL between Helm and Tiller](tiller_ssl.md). + +When Helm clients are connecting from outside of the cluster, the security between the Helm client and the API server is managed by Kubernetes itself. You may want to ensure that this link is secure. Note that if you are using the TLS configuration recommended above, not even the Kubernetes API server has access to the unencrypted messages between the client and Tiller. + +### Tiller's Release Information + +For historical reasons, Tiller stores its release information in ConfigMaps. We suggest changing the default to Secrets. + +Secrets are the Kubernetes accepted mechanism for saving configuration data that is considered sensitive. While secrets don't themselves offer many protections, Kubernetes cluster management software often treats them differently than other objects. Thus, we suggest using secrets to store releases. + +Enabling this feature currently requires setting the `--storage=secret` flag in the tiller-deploy deployment. This entails directly modifying the deployment or using `helm init --override=...`, as no helm init flag is currently available to do this for you. For more information, see [Using --override](install.md#using---override). + +### Thinking about Charts + +Because of the relative longevity of Helm, the Helm chart ecosystem evolved without the immediate concern for cluster-wide control, and especially in the developer space this makes complete sense. However, charts are a kind of package that not only installs containers you may or may not have validated yourself, but it may also install into more than one namespace. + +As with all shared software, in a controlled or shared environment you must validate all software you install yourself _before_ you install it. If you have secured Tiller with TLS and have installed it with permissions to only one or a subset of namespaces, some charts may fail to install -- but in these environments, that is exactly what you want. If you need to use the chart, you may have to work with the creator or modify it yourself in order to use it securely in a multitenant cluster with proper RBAC rules applied. The `helm template` command renders the chart locally and displays the output. + +Once vetted, you can use Helm's provenance tools to [ensure the provenance and integrity of charts](provenance.md) that you use. + +### gRPC Tools and Secured Tiller Configurations + +Many very useful tools use the gRPC interface directly, and having been built against the default installation -- which provides cluster-wide access -- may fail once security configurations have been applied. RBAC policies are controlled by you or by the cluster operator, and either can be adjusted for the tool, or the tool can be configured to work properly within the constraints of specific RBAC policies applied to Tiller. The same may need to be done if the gRPC endpoint is secured: the tools need their own secure TLS configuration in order to use a specific Tiller instance. The combination of RBAC policies and a secured gRPC endpoint configured in conjunction with gRPC tools enables you to control your cluster environment as you should. + +## Best Practices for Securing Helm and Tiller + +The following guidelines reiterate the Best Practices for securing Helm and Tiller and using them correctly. + +1. Create a cluster with RBAC enabled +2. Configure each Tiller gRPC endpoint to use a separate TLS certificate +3. Release information should be a Kubernetes Secret +4. Install one Tiller per user, team, or other organizational entity with the `--service-account` flag, Roles, and RoleBindings +5. Use the `--tiller-tls-verify` option with `helm init` and the `--tls` flag with other Helm commands to enforce verification + +If these steps are followed, an example `helm init` command might look something like this: + +```bash +$ helm init \ +--tiller-tls \ +--tiller-tls-verify \ +--tiller-tls-ca-cert=ca.pem \ +--tiller-tls-cert=cert.pem \ +--tiller-tls-key=key.pem \ +--service-account=accountname +``` + +This command will start Tiller with both strong authentication over gRPC, and a service account to which RBAC policies have been applied. + + + + + + + diff --git a/src/vendor/github.com/kubernetes/helm/docs/tiller_ssl.md b/src/vendor/github.com/kubernetes/helm/docs/tiller_ssl.md new file mode 100644 index 000000000..d7f0166c4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/tiller_ssl.md @@ -0,0 +1,291 @@ +# Using SSL Between Helm and Tiller + +This document explains how to create strong SSL/TLS connections between Helm and +Tiller. The emphasis here is on creating an internal CA, and using both the +cryptographic and identity functions of SSL. + +> Support for TLS-based auth was introduced in Helm 2.3.0 + +Configuring SSL is considered an advanced topic, and knowledge of Helm and Tiller +is assumed. + +## Overview + +The Tiller authentication model uses client-side SSL certificates. Tiller itself +verifies these certificates using a certificate authority. Likewise, the client +also verifies Tiller's identity by certificate authority. + +There are numerous possible configurations for setting up certificates and authorities, +but the method we cover here will work for most situations. + +> As of Helm 2.7.2, Tiller _requires_ that the client certificate be validated +> by its CA. In prior versions, Tiller used a weaker validation strategy that +> allowed self-signed certificates. + +In this guide, we will show how to: + +- Create a private CA that is used to issue certificates for Tiller clients and + servers. +- Create a certificate for Tiller +- Create a certificate for the Helm client +- Create a Tiller instance that uses the certificate +- Configure the Helm client to use the CA and client-side certificate + +By the end of this guide, you should have a Tiller instance running that will +only accept connections from clients who can be authenticated by SSL certificate. + +## Generating Certificate Authorities and Certificates + +One way to generate SSL CAs is via the `openssl` command line tool. There are many +guides and best practices documents available online. This explanation is focused +on getting ready within a small amount of time. For production configurations, +we urge readers to read [the official documentation](https://www.openssl.org) and +consult other resources. + +### Generate a Certificate Authority + +The simplest way to generate a certificate authority is to run two commands: + +```console +$ openssl genrsa -out ./ca.key.pem 4096 +$ openssl req -key ca.key.pem -new -x509 -days 7300 -sha256 -out ca.cert.pem -extensions v3_ca +Enter pass phrase for ca.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:tiller +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller +Email Address []:tiller@example.com +``` + +Note that the data input above is _sample data_. You should customize to your own +specifications. + +The above will generate both a secret key and a CA. Note that these two files are +very important. The key in particular should be handled with particular care. + +Often, you will want to generate an intermediate signing key. For the sake of brevity, +we will be signing keys with our root CA. + +### Generating Certificates + +We will be generating two certificates, each representing a type of certificate: + +- One certificate is for Tiller. You will want one of these _per tiller host_ that + you run. +- One certificate is for the user. You will want one of these _per helm user_. + +Since the commands to generate these are the same, we'll be creating both at the +same time. The names will indicate their target. + +First, the Tiller key: + +```console +$ openssl genrsa -out ./tiller.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +..........................................................................................................................................................................................................................................................................................................................++ +............................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./tiller.key.pem: +Verifying - Enter pass phrase for ./tiller.key.pem: +``` + +Next, generate the Helm client's key: + +```console +$ openssl genrsa -out ./helm.key.pem 4096 +Generating RSA private key, 4096 bit long modulus +.....++ +......................................................................................................................................................................................++ +e is 65537 (0x10001) +Enter pass phrase for ./helm.key.pem: +Verifying - Enter pass phrase for ./helm.key.pem: +``` + +Again, for production use you will generate one client certificate for each user. + +Next we need to create certificates from these keys. For each certificate, this is +a two-step process of creating a CSR, and then creating the certificate. + +```console +$ openssl req -key tiller.key.pem -new -sha256 -out tiller.csr.pem +Enter pass phrase for tiller.key.pem: +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:US +State or Province Name (full name) [Some-State]:CO +Locality Name (eg, city) []:Boulder +Organization Name (eg, company) [Internet Widgits Pty Ltd]:Tiller Server +Organizational Unit Name (eg, section) []: +Common Name (e.g. server FQDN or YOUR name) []:tiller-server +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: +``` + +And we repeat this step for the Helm client certificate: + +```console +$ openssl req -key helm.key.pem -new -sha256 -out helm.csr.pem +# Answer the questions with your client user's info +``` + +(In rare cases, we've had to add the `-nodes` flag when generating the request.) + +Now we sign each of these CSRs with the CA certificate we created: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in tiller.csr.pem -out tiller.cert.pem +Signature ok +subject=/C=US/ST=CO/L=Boulder/O=Tiller Server/CN=tiller-server +Getting CA Private Key +Enter pass phrase for ca.key.pem: +``` + +And again for the client certificate: + +```console +$ openssl x509 -req -CA ca.cert.pem -CAkey ca.key.pem -CAcreateserial -in helm.csr.pem -out helm.cert.pem +``` + +At this point, the important files for us are these: + +``` +# The CA. Make sure the key is kept secret. +ca.cert.pem +ca.key.pem +# The Helm client files +helm.cert.pem +helm.key.pem +# The Tiller server files. +tiller.cert.pem +tiller.key.pem +``` + +Now we're ready to move on to the next steps. + +## Creating a Custom Tiller Installation + +Helm includes full support for creating a deployment configured for SSL. By specifying +a few flags, the `helm init` command can create a new Tiller installation complete +with all of our SSL configuration. + +To take a look at what this will generate, run this command: + +```console +$ helm init --dry-run --debug --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +The output will show you a Deployment, a Secret, and a Service. Your SSL information +will be preloaded into the Secret, which the Deployment will mount to pods as they +start up. + +If you want to customize the manifest, you can save that output to a file and then +use `kubectl create` to load it into your cluster. + +> We strongly recommend enabling RBAC on your cluster and adding [service accounts](rbac.md) +> with RBAC. + +Otherwise, you can remove the `--dry-run` and `--debug` flags. We also recommend +putting Tiller in a non-system namespace (`--tiller-namespace=something`) and enable +a service account (`--service-account=somename`). But for this example we will stay +with the basics: + +```console +$ helm init --tiller-tls --tiller-tls-cert ./tiller.cert.pem --tiller-tls-key ./tiller.key.pem --tiller-tls-verify --tls-ca-cert ca.cert.pem +``` + +In a minute or two it should be ready. We can check Tiller like this: + +```console +$ kubectl -n kube-system get deployment +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +... other stuff +tiller-deploy 1 1 1 1 2m +``` + +If there is a problem, you may want to use `kubectl get pods -n kube-system` to +find out what went wrong. With the SSL/TLS support, the most common problems all +have to do with improperly generated TLS certificates or accidentally swapping the +cert and the key. + +At this point, you should get a _failure_ when you run basic Helm commands: + +```console +$ helm ls +Error: transport is closing +``` + +This is because your Helm client does not have the correct certificate to authenticate +to Tiller. + +## Configuring the Helm Client + +The Tiller server is now running with TLS protection. It's time to configure the +Helm client to also perform TLS operations. + +For a quick test, we can specify our configuration manually. We'll run a normal +Helm command (`helm ls`), but with SSL/TLS enabled. + +```console +helm ls --tls --tls-ca-cert ca.cert.pem --tls-cert helm.cert.pem --tls-key helm.key.pem +``` + +This configuration sends our client-side certificate to establish identity, uses +the client key for encryption, and uses the CA certificate to validate the remote +Tiller's identity. + +Typing a line that is cumbersome, though. The shortcut is to move the key, +cert, and CA into `$HELM_HOME`: + +```console +$ cp ca.cert.pem $(helm home)/ca.pem +$ cp helm.cert.pem $(helm home)/cert.pem +$ cp helm.key.pem $(helm home)/key.pem +``` + +With this, you can simply run `helm ls --tls` to enable TLS. + +### Troubleshooting + +*Running a command, I get `Error: transport is closing`* + +This is almost always due to a configuration error in which the client is missing +a certificate (`--tls-cert`) or the certificate is bad. + +*I'm using a certificate, but get `Error: remote error: tls: bad certificate`* + +This means that Tiller's CA cannot verify your certificate. In the examples above, +we used a single CA to generate both the client and server certificates. In these +examples, the CA has _signed_ the client's certificate. We then load that CA +up to Tiller. So when the client certificate is sent to the server, Tiller +checks the client certificate against the CA. + +*If I use `--tls-verify` on the client, I get `Error: x509: certificate is valid for tiller-server, not localhost`* + +If you plan to use `--tls-verify` on the client, you will need to make sure that +the host name that Helm connects to matches the host name on the certificate. In +some cases this is awkward, since Helm will connect over localhost, or the FQDN is +not available for public resolution. + +## References + +https://github.com/denji/golang-tls +https://www.openssl.org/docs/ +https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html diff --git a/src/vendor/github.com/kubernetes/helm/docs/using_helm.md b/src/vendor/github.com/kubernetes/helm/docs/using_helm.md new file mode 100755 index 000000000..6bf7bc40a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/docs/using_helm.md @@ -0,0 +1,515 @@ +# Using Helm + +This guide explains the basics of using Helm (and Tiller) to manage +packages on your Kubernetes cluster. It assumes that you have already +[installed](install.md) the Helm client and the Tiller server (typically by `helm +init`). + +If you are simply interested in running a few quick commands, you may +wish to begin with the [Quickstart Guide](quickstart.md). This chapter +covers the particulars of Helm commands, and explains how to use Helm. + +## Three Big Concepts + +A *Chart* is a Helm package. It contains all of the resource definitions +necessary to run an application, tool, or service inside of a Kubernetes +cluster. Think of it like the Kubernetes equivalent of a Homebrew formula, +an Apt dpkg, or a Yum RPM file. + +A *Repository* is the place where charts can be collected and shared. +It's like Perl's [CPAN archive](http://www.cpan.org) or the +[Fedora Package Database](https://admin.fedoraproject.org/pkgdb/), but for +Kubernetes packages. + +A *Release* is an instance of a chart running in a Kubernetes cluster. +One chart can often be installed many times into the same cluster. And +each time it is installed, a new _release_ is created. Consider a MySQL +chart. If you want two databases running in your cluster, you can +install that chart twice. Each one will have its own _release_, which +will in turn have its own _release name_. + +With these concepts in mind, we can now explain Helm like this: + +Helm installs _charts_ into Kubernetes, creating a new _release_ for +each installation. And to find new charts, you can search Helm chart +_repositories_. + +## 'helm search': Finding Charts + +When you first install Helm, it is preconfigured to talk to the official +Kubernetes charts repository. This repository contains a number of +carefully curated and maintained charts. This chart repository is named +`stable` by default. + +You can see which charts are available by running `helm search`: + +``` +$ helm search +NAME VERSION DESCRIPTION +stable/drupal 0.3.2 One of the most versatile open source content m... +stable/jenkins 0.1.0 A Jenkins Helm chart for Kubernetes. +stable/mariadb 0.5.1 Chart for MariaDB +stable/mysql 0.1.0 Chart for MySQL +... +``` + +With no filter, `helm search` shows you all of the available charts. You +can narrow down your results by searching with a filter: + +``` +$ helm search mysql +NAME VERSION DESCRIPTION +stable/mysql 0.1.0 Chart for MySQL +stable/mariadb 0.5.1 Chart for MariaDB +``` + +Now you will only see the results that match your filter. + +Why is +`mariadb` in the list? Because its package description relates it to +MySQL. We can use `helm inspect chart` to see this: + +``` +$ helm inspect stable/mariadb +Fetched stable/mariadb to mariadb-0.5.1.tgz +description: Chart for MariaDB +engine: gotpl +home: https://mariadb.org +keywords: +- mariadb +- mysql +- database +- sql +... +``` + +Search is a good way to find available packages. Once you have found a +package you want to install, you can use `helm install` to install it. + +## 'helm install': Installing a Package + +To install a new package, use the `helm install` command. At its +simplest, it takes only one argument: The name of the chart. + +``` +$ helm install stable/mariadb +Fetched stable/mariadb-0.3.0 to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz +happy-panda +Last Deployed: Wed Sep 28 12:32:28 2016 +Namespace: default +Status: DEPLOYED + +Resources: +==> extensions/Deployment +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +happy-panda-mariadb 1 0 0 0 1s + +==> v1/Secret +NAME TYPE DATA AGE +happy-panda-mariadb Opaque 2 1s + +==> v1/Service +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +happy-panda-mariadb 10.0.0.70 3306/TCP 1s + + +Notes: +MariaDB can be accessed via port 3306 on the following DNS name from within your cluster: +happy-panda-mariadb.default.svc.cluster.local + +To connect to your database run the following command: + + kubectl run happy-panda-mariadb-client --rm --tty -i --image bitnami/mariadb --command -- mysql -h happy-panda-mariadb +``` + +Now the `mariadb` chart is installed. Note that installing a chart +creates a new _release_ object. The release above is named +`happy-panda`. (If you want to use your own release name, simply use the +`--name` flag on `helm install`.) + +During installation, the `helm` client will print useful information +about which resources were created, what the state of the release is, +and also whether there are additional configuration steps you can or +should take. + +Helm does not wait until all of the resources are running before it +exits. Many charts require Docker images that are over 600M in size, and +may take a long time to install into the cluster. + +To keep track of a release's state, or to re-read configuration +information, you can use `helm status`: + +``` +$ helm status happy-panda +Last Deployed: Wed Sep 28 12:32:28 2016 +Namespace: default +Status: DEPLOYED + +Resources: +==> v1/Service +NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE +happy-panda-mariadb 10.0.0.70 3306/TCP 4m + +==> extensions/Deployment +NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE +happy-panda-mariadb 1 1 1 1 4m + +==> v1/Secret +NAME TYPE DATA AGE +happy-panda-mariadb Opaque 2 4m + + +Notes: +MariaDB can be accessed via port 3306 on the following DNS name from within your cluster: +happy-panda-mariadb.default.svc.cluster.local + +To connect to your database run the following command: + + kubectl run happy-panda-mariadb-client --rm --tty -i --image bitnami/mariadb --command -- mysql -h happy-panda-mariadb +``` + +The above shows the current state of your release. + +### Customizing the Chart Before Installing + +Installing the way we have here will only use the default configuration +options for this chart. Many times, you will want to customize the chart +to use your preferred configuration. + +To see what options are configurable on a chart, use `helm inspect +values`: + +```console +helm inspect values stable/mariadb +Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz +## Bitnami MariaDB image version +## ref: https://hub.docker.com/r/bitnami/mariadb/tags/ +## +## Default: none +imageTag: 10.1.14-r3 + +## Specify a imagePullPolicy +## Default to 'Always' if imageTag is 'latest', else set to 'IfNotPresent' +## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images +## +# imagePullPolicy: + +## Specify password for root user +## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#setting-the-root-password-on-first-run +## +# mariadbRootPassword: + +## Create a database user +## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#creating-a-database-user-on-first-run +## +# mariadbUser: +# mariadbPassword: + +## Create a database +## ref: https://github.com/bitnami/bitnami-docker-mariadb/blob/master/README.md#creating-a-database-on-first-run +## +# mariadbDatabase: +``` + +You can then override any of these settings in a YAML formatted file, +and then pass that file during installation. + +```console +$ echo '{mariadbUser: user0, mariadbDatabase: user0db}' > config.yaml +$ helm install -f config.yaml stable/mariadb +``` + +The above will create a default MariaDB user with the name `user0`, and +grant this user access to a newly created `user0db` database, but will +accept all the rest of the defaults for that chart. + +There are two ways to pass configuration data during install: + +- `--values` (or `-f`): Specify a YAML file with overrides. This can be specified multiple times + and the rightmost file will take precedence +- `--set`: Specify overrides on the command line. + +If both are used, `--set` values are merged into `--values` with higher precedence. +Overrides specified with `--set` are persisted in a configmap. Values that have been +`--set` can be viewed for a given release with `helm get values `. +Values that have been `--set` can be cleared by running `helm upgrade` with `--reset-values` +specified. + +#### The Format and Limitations of `--set` + +The `--set` option takes zero or more name/value pairs. At its simplest, it is +used like this: `--set name=value`. The YAML equivalent of that is: + +```yaml +name: value +``` + +Multiple values are separated by `,` characters. So `--set a=b,c=d` becomes: + +```yaml +a: b +c: d +``` + +More complex expressions are supported. For example, `--set outer.inner=value` is +translated into this: +```yaml +outer: + inner: value +``` + +Lists can be expressed by enclosing values in `{` and `}`. For example, +`--set name={a, b, c}` translates to: + +```yaml +name: + - a + - b + - c +``` + +As of Helm 2.5.0, it is possible to access list items using an array index syntax. +For example, `--set servers[0].port=80` becomes: + +```yaml +servers: + - port: 80 +``` + +Multiple values can be set this way. The line `--set servers[0].port=80,servers[0].host=example` becomes: + +```yaml +servers: + - port: 80 + host: example +``` + +Sometimes you need to use special characters in your `--set` lines. You can use +a backslash to escape the characters; `--set name=value1\,value2` will become: + +```yaml +name: "value1,value2" +``` + +Similarly, you can escape dot sequences as well, which may come in handy when charts use the +`toYaml` function to parse annotations, labels and node selectors. The syntax for +`--set nodeSelector."kubernetes\.io/role"=master` becomes: + +```yaml +nodeSelector: + kubernetes.io/role: master +``` + +Deeply nested data structures can be difficult to express using `--set`. Chart +designers are encouraged to consider the `--set` usage when designing the format +of a `values.yaml` file. + +### More Installation Methods + +The `helm install` command can install from several sources: + +- A chart repository (as we've seen above) +- A local chart archive (`helm install foo-0.1.1.tgz`) +- An unpacked chart directory (`helm install path/to/foo`) +- A full URL (`helm install https://example.com/charts/foo-1.2.3.tgz`) + +## 'helm upgrade' and 'helm rollback': Upgrading a Release, and Recovering on Failure + +When a new version of a chart is released, or when you want to change +the configuration of your release, you can use the `helm upgrade` +command. + +An upgrade takes an existing release and upgrades it according to the +information you provide. Because Kubernetes charts can be large and +complex, Helm tries to perform the least invasive upgrade. It will only +update things that have changed since the last release. + +```console +$ helm upgrade -f panda.yaml happy-panda stable/mariadb +Fetched stable/mariadb-0.3.0.tgz to /Users/mattbutcher/Code/Go/src/k8s.io/helm/mariadb-0.3.0.tgz +happy-panda has been upgraded. Happy Helming! +Last Deployed: Wed Sep 28 12:47:54 2016 +Namespace: default +Status: DEPLOYED +... +``` + +In the above case, the `happy-panda` release is upgraded with the same +chart, but with a new YAML file: + +```yaml +mariadbUser: user1 +``` + +We can use `helm get values` to see whether that new setting took +effect. + +```console +$ helm get values happy-panda +mariadbUser: user1 +``` + +The `helm get` command is a useful tool for looking at a release in the +cluster. And as we can see above, it shows that our new values from +`panda.yaml` were deployed to the cluster. + +Now, if something does not go as planned during a release, it is easy to +roll back to a previous release using `helm rollback [RELEASE] [REVISION]`. + +```console +$ helm rollback happy-panda 1 +``` + +The above rolls back our happy-panda to its very first release version. +A release version is an incremental revision. Every time an install, +upgrade, or rollback happens, the revision number is incremented by 1. +The first revision number is always 1. And we can use `helm history [RELEASE]` +to see revision numbers for a certain release. + +## Helpful Options for Install/Upgrade/Rollback +There are several other helpful options you can specify for customizing the +behavior of Helm during an install/upgrade/rollback. Please note that this +is not a full list of cli flags. To see a description of all flags, just run +`helm --help`. + +- `--timeout`: A value in seconds to wait for Kubernetes commands to complete + This defaults to 300 (5 minutes) +- `--wait`: Waits until all Pods are in a ready state, PVCs are bound, Deployments + have minimum (`Desired` minus `maxUnavailable`) Pods in ready state and + Services have an IP address (and Ingress if a `LoadBalancer`) before + marking the release as successful. It will wait for as long as the + `--timeout` value. If timeout is reached, the release will be marked as + `FAILED`. Note: In scenario where Deployment has `replicas` set to 1 and + `maxUnavailable` is not set to 0 as part of rolling update strategy, + `--wait` will return as ready as it has satisfied the minimum Pod in ready condition. +- `--no-hooks`: This skips running hooks for the command +- `--recreate-pods` (only available for `upgrade` and `rollback`): This flag + will cause all pods to be recreated (with the exception of pods belonging to + deployments) + +## 'helm delete': Deleting a Release + +When it is time to uninstall or delete a release from the cluster, use +the `helm delete` command: + +``` +$ helm delete happy-panda +``` + +This will remove the release from the cluster. You can see all of your +currently deployed releases with the `helm list` command: + +``` +$ helm list +NAME VERSION UPDATED STATUS CHART +inky-cat 1 Wed Sep 28 12:59:46 2016 DEPLOYED alpine-0.1.0 +``` + +From the output above, we can see that the `happy-panda` release was +deleted. + +However, Helm always keeps records of what releases happened. Need to +see the deleted releases? `helm list --deleted` shows those, and `helm +list --all` shows all of the releases (deleted and currently deployed, +as well as releases that failed): + +```console +⇒ helm list --all +NAME VERSION UPDATED STATUS CHART +happy-panda 2 Wed Sep 28 12:47:54 2016 DELETED mariadb-0.3.0 +inky-cat 1 Wed Sep 28 12:59:46 2016 DEPLOYED alpine-0.1.0 +kindred-angelf 2 Tue Sep 27 16:16:10 2016 DELETED alpine-0.1.0 +``` + +Because Helm keeps records of deleted releases, a release name cannot be +re-used. (If you _really_ need to re-use a release name, you can use the +`--replace` flag, but it will simply re-use the existing release and +replace its resources.) + +Note that because releases are preserved in this way, you can rollback a +deleted resource, and have it re-activate. + +## 'helm repo': Working with Repositories + +So far, we've been installing charts only from the `stable` repository. +But you can configure `helm` to use other repositories. Helm provides +several repository tools under the `helm repo` command. + +You can see which repositories are configured using `helm repo list`: + +```console +$ helm repo list +NAME URL +stable https://kubernetes-charts.storage.googleapis.com +local http://localhost:8879/charts +mumoshu https://mumoshu.github.io/charts +``` + +And new repositories can be added with `helm repo add`: + +```console +$ helm repo add dev https://example.com/dev-charts +``` + +Because chart repositories change frequently, at any point you can make +sure your Helm client is up to date by running `helm repo update`. + +## Creating Your Own Charts + +The [Chart Development Guide](charts.md) explains how to develop your own +charts. But you can get started quickly by using the `helm create` +command: + +```console +$ helm create deis-workflow +Creating deis-workflow +``` + +Now there is a chart in `./deis-workflow`. You can edit it and create +your own templates. + +As you edit your chart, you can validate that it is well-formatted by +running `helm lint`. + +When it's time to package the chart up for distribution, you can run the +`helm package` command: + +```console +$ helm package deis-workflow +deis-workflow-0.1.0.tgz +``` + +And that chart can now easily be installed by `helm install`: + +```console +$ helm install ./deis-workflow-0.1.0.tgz +... +``` + +Charts that are archived can be loaded into chart repositories. See the +documentation for your chart repository server to learn how to upload. + +Note: The `stable` repository is managed on the [Kubernetes Charts +GitHub repository](https://github.com/kubernetes/charts). That project +accepts chart source code, and (after audit) packages those for you. + +## Tiller, Namespaces and RBAC +In some cases you may wish to scope Tiller or deploy multiple Tillers to a single cluster. Here are some best practices when operating in those circumstances. + +1. Tiller can be [installed](install.md) into any namespace. By default, it is installed into kube-system. You can run multiple Tillers provided they each run in their own namespace. +2. Limiting Tiller to only be able to install into specific namespaces and/or resource types is controlled by Kubernetes [RBAC](https://kubernetes.io/docs/admin/authorization/rbac/) roles and rolebindings. You can add a service account to Tiller when configuring Helm via `helm init --service-account `. You can find more information about that [here](rbac.md). +3. Release names are unique PER TILLER INSTANCE. +4. Charts should only contain resources that exist in a single namespace. +5. It is not recommended to have multiple Tillers configured to manage resources in the same namespace. + +## Conclusion + +This chapter has covered the basic usage patterns of the `helm` client, +including searching, installation, upgrading, and deleting. It has also +covered useful utility commands like `helm status`, `helm get`, and +`helm repo`. + +For more information on these commands, take a look at Helm's built-in +help: `helm help`. + +In the next chapter, we look at the process of developing charts. diff --git a/src/vendor/github.com/kubernetes/helm/glide.lock b/src/vendor/github.com/kubernetes/helm/glide.lock new file mode 100644 index 000000000..6c54c927c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/glide.lock @@ -0,0 +1,853 @@ +hash: 6837936360d447b64aa7a09d3c89c18ac5540b009a57fc4d3227af299bf40268 +updated: 2018-04-03T08:17:14.801847688-07:00 +imports: +- name: cloud.google.com/go + version: 3b1ae45394a234c385be014e9a488f2bb6eef821 + subpackages: + - compute/metadata + - internal +- name: github.com/aokoli/goutils + version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/asaskevich/govalidator + version: 7664702784775e51966f0885f5cd27435916517b +- name: github.com/Azure/go-ansiterm + version: 19f72df4d05d31cbe1c56bfc8045c96babff6c7e + subpackages: + - winterm +- name: github.com/Azure/go-autorest + version: d4e6b95c12a08b4de2d48b45d5b4d594e5d32fab + subpackages: + - autorest + - autorest/adal + - autorest/azure + - autorest/date +- name: github.com/beorn7/perks + version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 + subpackages: + - quantile +- name: github.com/BurntSushi/toml + version: b26d9c308763d68093482582cea63d69be07a0f0 +- name: github.com/cpuguy83/go-md2man + version: 71acacd42f85e5e82f70a55327789582a5200a90 + subpackages: + - md2man +- name: github.com/davecgh/go-spew + version: 782f4967f2dc4564575ca782fe2d04090b5faca8 + subpackages: + - spew +- name: github.com/dgrijalva/jwt-go + version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 +- name: github.com/docker/distribution + version: edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c + subpackages: + - digestset + - reference +- name: github.com/docker/docker + version: 4f3616fb1c112e206b88cb7a9922bf49067a7756 + subpackages: + - api + - api/types + - api/types/blkiodev + - api/types/container + - api/types/events + - api/types/filters + - api/types/image + - api/types/mount + - api/types/network + - api/types/registry + - api/types/strslice + - api/types/swarm + - api/types/swarm/runtime + - api/types/time + - api/types/versions + - api/types/volume + - client + - pkg/ioutils + - pkg/jsonlog + - pkg/jsonmessage + - pkg/longpath + - pkg/mount + - pkg/parsers + - pkg/stdcopy + - pkg/sysinfo + - pkg/system + - pkg/term + - pkg/term/windows + - pkg/tlsconfig +- name: github.com/docker/go-connections + version: 3ede32e2033de7505e6500d6c868c2b9ed9f169d + subpackages: + - nat + - sockets + - tlsconfig +- name: github.com/docker/go-units + version: 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1 +- name: github.com/docker/spdystream + version: 449fdfce4d962303d702fec724ef0ad181c92528 + subpackages: + - spdy +- name: github.com/evanphx/json-patch + version: 944e07253867aacae43c04b2e6a239005443f33a +- name: github.com/exponent-io/jsonpath + version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5 +- name: github.com/fatih/camelcase + version: f6a740d52f961c60348ebb109adde9f4635d7540 +- name: github.com/ghodss/yaml + version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee +- name: github.com/go-openapi/jsonpointer + version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 +- name: github.com/go-openapi/jsonreference + version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 +- name: github.com/go-openapi/spec + version: 1de3e0542de65ad8d75452a595886fdd0befb363 +- name: github.com/go-openapi/swag + version: f3f9494671f93fcff853e3c6e9e948b3eb71e590 +- name: github.com/gobwas/glob + version: 5ccd90ef52e1e632236f7326478d4faa74f99438 + subpackages: + - compiler + - match + - syntax + - syntax/ast + - syntax/lexer + - util/runes + - util/strings +- name: github.com/gogo/protobuf + version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 + subpackages: + - proto + - sortkeys +- name: github.com/golang/glog + version: 44145f04b68cf362d9c4df2182967c2275eaefed +- name: github.com/golang/groupcache + version: 02826c3e79038b59d737d3b1c0a1d937f71a4433 + subpackages: + - lru +- name: github.com/golang/protobuf + version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 + subpackages: + - proto + - ptypes + - ptypes/any + - ptypes/duration + - ptypes/timestamp +- name: github.com/google/btree + version: 7d79101e329e5a3adf994758c578dab82b90c017 +- name: github.com/google/gofuzz + version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- name: github.com/google/uuid + version: 064e2069ce9c359c118179501254f67d7d37ba24 +- name: github.com/googleapis/gnostic + version: 0c5108395e2debce0d731cf0287ddf7242066aba + subpackages: + - OpenAPIv2 + - compiler + - extensions +- name: github.com/gophercloud/gophercloud + version: 6da026c32e2d622cc242d32984259c77237aefe1 + subpackages: + - openstack + - openstack/identity/v2/tenants + - openstack/identity/v2/tokens + - openstack/identity/v3/tokens + - openstack/utils + - pagination +- name: github.com/gosuri/uitable + version: 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 + subpackages: + - util/strutil + - util/wordwrap +- name: github.com/gregjones/httpcache + version: 787624de3eb7bd915c329cba748687a3b22666a6 + subpackages: + - diskcache +- name: github.com/grpc-ecosystem/go-grpc-prometheus + version: 0c1b191dbfe51efdabe3c14b9f6f3b96429e0722 +- name: github.com/hashicorp/golang-lru + version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 + subpackages: + - simplelru +- name: github.com/howeyc/gopass + version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 +- name: github.com/huandu/xstrings + version: 3959339b333561bf62a38b424fd41517c2c90f40 +- name: github.com/imdario/mergo + version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/json-iterator/go + version: 13f86432b882000a51c6e610c620974462691a97 +- name: github.com/mailru/easyjson + version: 2f5df55504ebc322e4d52d34df6a1f5b503bf26d + subpackages: + - buffer + - jlexer + - jwriter +- name: github.com/MakeNowJust/heredoc + version: bb23615498cded5e105af4ce27de75b089cbe851 +- name: github.com/Masterminds/semver + version: 517734cc7d6470c0d07130e40fd40bdeb9bcd3fd +- name: github.com/Masterminds/sprig + version: 6b2a58267f6a8b1dc8e2eb5519b984008fa85e8c +- name: github.com/Masterminds/vcs + version: 3084677c2c188840777bff30054f2b553729d329 +- name: github.com/mattn/go-runewidth + version: d6bea18f789704b5f83375793155289da36a3c7f +- name: github.com/matttproud/golang_protobuf_extensions + version: fc2b8d3a73c4867e51861bbdd5ae3c1f0869dd6a + subpackages: + - pbutil +- name: github.com/mitchellh/go-wordwrap + version: ad45545899c7b13c020ea92b2072220eefad42b8 +- name: github.com/opencontainers/go-digest + version: a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb +- name: github.com/opencontainers/image-spec + version: 372ad780f63454fbbbbcc7cf80e5b90245c13e13 + subpackages: + - specs-go + - specs-go/v1 +- name: github.com/pborman/uuid + version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 +- name: github.com/peterbourgon/diskv + version: 5f041e8faa004a95c88a202771f4cc3e991971e6 +- name: github.com/prometheus/client_golang + version: c5b7fccd204277076155f10851dad72b76a49317 + subpackages: + - prometheus + - prometheus/promhttp +- name: github.com/prometheus/client_model + version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6 + subpackages: + - go +- name: github.com/prometheus/common + version: 13ba4ddd0caa9c28ca7b7bffe1dfa9ed8d5ef207 + subpackages: + - expfmt + - internal/bitbucket.org/ww/goautoneg + - model +- name: github.com/prometheus/procfs + version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259 + subpackages: + - xfs +- name: github.com/PuerkitoBio/purell + version: 8a290539e2e8629dbc4e6bad948158f790ec31f4 +- name: github.com/PuerkitoBio/urlesc + version: 5bd2802263f21d8788851d5305584c82a5c75d7e +- name: github.com/russross/blackfriday + version: 300106c228d52c8941d4b3de6054a6062a86dda3 +- name: github.com/shurcooL/sanitized_anchor_name + version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 +- name: github.com/sirupsen/logrus + version: 89742aefa4b206dcf400792f3bd35b542998eb3b +- name: github.com/spf13/cobra + version: f62e98d28ab7ad31d707ba837a966378465c7b57 + subpackages: + - doc +- name: github.com/spf13/pflag + version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 +- name: github.com/technosophos/moniker + version: ab470f5e105a44d0c87ea21bacd6a335c4816d83 +- name: golang.org/x/crypto + version: 81e90905daefcd6fd217b62423c0908922eadb30 + subpackages: + - cast5 + - ed25519 + - ed25519/internal/edwards25519 + - openpgp + - openpgp/armor + - openpgp/clearsign + - openpgp/elgamal + - openpgp/errors + - openpgp/packet + - openpgp/s2k + - pbkdf2 + - scrypt + - ssh/terminal +- name: golang.org/x/net + version: 1c05540f6879653db88113bc4a2b70aec4bd491f + subpackages: + - context + - context/ctxhttp + - http2 + - http2/hpack + - idna + - internal/timeseries + - lex/httplex + - trace +- name: golang.org/x/oauth2 + version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4 + subpackages: + - google + - internal + - jws + - jwt +- name: golang.org/x/sys + version: 43eea11bc92608addb41b8a406b0407495c106f6 + subpackages: + - unix + - windows +- name: golang.org/x/text + version: b19bf474d317b857955b12035d2c5acb57ce8b01 + subpackages: + - cases + - encoding + - encoding/internal + - encoding/internal/identifier + - encoding/unicode + - internal + - internal/tag + - internal/utf8internal + - language + - runes + - secure/bidirule + - secure/precis + - transform + - unicode/bidi + - unicode/norm + - width +- name: golang.org/x/time + version: f51c12702a4d776e4c1fa9b0fabab841babae631 + subpackages: + - rate +- name: google.golang.org/appengine + version: 12d5545dc1cfa6047a286d5e853841b6471f4c19 + subpackages: + - internal + - internal/app_identity + - internal/base + - internal/datastore + - internal/log + - internal/modules + - internal/remote_api + - internal/urlfetch + - urlfetch +- name: google.golang.org/genproto + version: 09f6ed296fc66555a25fe4ce95173148778dfa85 + subpackages: + - googleapis/rpc/status +- name: google.golang.org/grpc + version: 5ffe3083946d5603a0578721101dc8165b1d5b5f + subpackages: + - balancer + - codes + - connectivity + - credentials + - grpclb/grpc_lb_v1/messages + - grpclog + - health + - health/grpc_health_v1 + - internal + - keepalive + - metadata + - naming + - peer + - resolver + - stats + - status + - tap + - transport +- name: gopkg.in/inf.v0 + version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 +- name: gopkg.in/square/go-jose.v2 + version: f8f38de21b4dcd69d0413faf231983f5fd6634b1 + subpackages: + - cipher + - json + - jwt +- name: gopkg.in/yaml.v2 + version: 53feefa2559fb8dfa8d81baad31be332c97d6c77 +- name: k8s.io/api + version: c699ec51538f0cfd4afa8bfcfe1e0779cafbe666 + subpackages: + - admission/v1beta1 + - admissionregistration/v1alpha1 + - admissionregistration/v1beta1 + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - authentication/v1 + - authentication/v1beta1 + - authorization/v1 + - authorization/v1beta1 + - autoscaling/v1 + - autoscaling/v2beta1 + - batch/v1 + - batch/v1beta1 + - batch/v2alpha1 + - certificates/v1beta1 + - core/v1 + - events/v1beta1 + - extensions/v1beta1 + - imagepolicy/v1alpha1 + - networking/v1 + - policy/v1beta1 + - rbac/v1 + - rbac/v1alpha1 + - rbac/v1beta1 + - scheduling/v1alpha1 + - settings/v1alpha1 + - storage/v1 + - storage/v1alpha1 + - storage/v1beta1 +- name: k8s.io/apiextensions-apiserver + version: 898b0eda132e1aeac43a459785144ee4bf9b0a2e + subpackages: + - pkg/features +- name: k8s.io/apimachinery + version: 54101a56dda9a0962bc48751c058eb4c546dcbb9 + subpackages: + - pkg/api/equality + - pkg/api/errors + - pkg/api/meta + - pkg/api/resource + - pkg/api/validation + - pkg/apimachinery + - pkg/apimachinery/announced + - pkg/apimachinery/registered + - pkg/apis/meta/internalversion + - pkg/apis/meta/v1 + - pkg/apis/meta/v1/unstructured + - pkg/apis/meta/v1/validation + - pkg/apis/meta/v1beta1 + - pkg/conversion + - pkg/conversion/queryparams + - pkg/fields + - pkg/labels + - pkg/runtime + - pkg/runtime/schema + - pkg/runtime/serializer + - pkg/runtime/serializer/json + - pkg/runtime/serializer/protobuf + - pkg/runtime/serializer/recognizer + - pkg/runtime/serializer/streaming + - pkg/runtime/serializer/versioning + - pkg/selection + - pkg/types + - pkg/util/cache + - pkg/util/clock + - pkg/util/diff + - pkg/util/duration + - pkg/util/errors + - pkg/util/framer + - pkg/util/httpstream + - pkg/util/httpstream/spdy + - pkg/util/intstr + - pkg/util/json + - pkg/util/mergepatch + - pkg/util/net + - pkg/util/rand + - pkg/util/remotecommand + - pkg/util/runtime + - pkg/util/sets + - pkg/util/strategicpatch + - pkg/util/uuid + - pkg/util/validation + - pkg/util/validation/field + - pkg/util/wait + - pkg/util/yaml + - pkg/version + - pkg/watch + - third_party/forked/golang/json + - third_party/forked/golang/netutil + - third_party/forked/golang/reflect +- name: k8s.io/apiserver + version: ea53f8588c655568158b4ff53f5ec6fa4ebfc332 + subpackages: + - pkg/apis/audit + - pkg/authentication/authenticator + - pkg/authentication/serviceaccount + - pkg/authentication/user + - pkg/endpoints/request + - pkg/features + - pkg/util/feature + - pkg/util/flag +- name: k8s.io/client-go + version: 23781f4d6632d88e869066eaebb743857aa1ef9b + subpackages: + - discovery + - discovery/fake + - dynamic + - informers + - informers/admissionregistration + - informers/admissionregistration/v1alpha1 + - informers/admissionregistration/v1beta1 + - informers/apps + - informers/apps/v1 + - informers/apps/v1beta1 + - informers/apps/v1beta2 + - informers/autoscaling + - informers/autoscaling/v1 + - informers/autoscaling/v2beta1 + - informers/batch + - informers/batch/v1 + - informers/batch/v1beta1 + - informers/batch/v2alpha1 + - informers/certificates + - informers/certificates/v1beta1 + - informers/core + - informers/core/v1 + - informers/events + - informers/events/v1beta1 + - informers/extensions + - informers/extensions/v1beta1 + - informers/internalinterfaces + - informers/networking + - informers/networking/v1 + - informers/policy + - informers/policy/v1beta1 + - informers/rbac + - informers/rbac/v1 + - informers/rbac/v1alpha1 + - informers/rbac/v1beta1 + - informers/scheduling + - informers/scheduling/v1alpha1 + - informers/settings + - informers/settings/v1alpha1 + - informers/storage + - informers/storage/v1 + - informers/storage/v1alpha1 + - informers/storage/v1beta1 + - kubernetes + - kubernetes/fake + - kubernetes/scheme + - kubernetes/typed/admissionregistration/v1alpha1 + - kubernetes/typed/admissionregistration/v1alpha1/fake + - kubernetes/typed/admissionregistration/v1beta1 + - kubernetes/typed/admissionregistration/v1beta1/fake + - kubernetes/typed/apps/v1 + - kubernetes/typed/apps/v1/fake + - kubernetes/typed/apps/v1beta1 + - kubernetes/typed/apps/v1beta1/fake + - kubernetes/typed/apps/v1beta2 + - kubernetes/typed/apps/v1beta2/fake + - kubernetes/typed/authentication/v1 + - kubernetes/typed/authentication/v1/fake + - kubernetes/typed/authentication/v1beta1 + - kubernetes/typed/authentication/v1beta1/fake + - kubernetes/typed/authorization/v1 + - kubernetes/typed/authorization/v1/fake + - kubernetes/typed/authorization/v1beta1 + - kubernetes/typed/authorization/v1beta1/fake + - kubernetes/typed/autoscaling/v1 + - kubernetes/typed/autoscaling/v1/fake + - kubernetes/typed/autoscaling/v2beta1 + - kubernetes/typed/autoscaling/v2beta1/fake + - kubernetes/typed/batch/v1 + - kubernetes/typed/batch/v1/fake + - kubernetes/typed/batch/v1beta1 + - kubernetes/typed/batch/v1beta1/fake + - kubernetes/typed/batch/v2alpha1 + - kubernetes/typed/batch/v2alpha1/fake + - kubernetes/typed/certificates/v1beta1 + - kubernetes/typed/certificates/v1beta1/fake + - kubernetes/typed/core/v1 + - kubernetes/typed/core/v1/fake + - kubernetes/typed/events/v1beta1 + - kubernetes/typed/events/v1beta1/fake + - kubernetes/typed/extensions/v1beta1 + - kubernetes/typed/extensions/v1beta1/fake + - kubernetes/typed/networking/v1 + - kubernetes/typed/networking/v1/fake + - kubernetes/typed/policy/v1beta1 + - kubernetes/typed/policy/v1beta1/fake + - kubernetes/typed/rbac/v1 + - kubernetes/typed/rbac/v1/fake + - kubernetes/typed/rbac/v1alpha1 + - kubernetes/typed/rbac/v1alpha1/fake + - kubernetes/typed/rbac/v1beta1 + - kubernetes/typed/rbac/v1beta1/fake + - kubernetes/typed/scheduling/v1alpha1 + - kubernetes/typed/scheduling/v1alpha1/fake + - kubernetes/typed/settings/v1alpha1 + - kubernetes/typed/settings/v1alpha1/fake + - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1/fake + - kubernetes/typed/storage/v1alpha1 + - kubernetes/typed/storage/v1alpha1/fake + - kubernetes/typed/storage/v1beta1 + - kubernetes/typed/storage/v1beta1/fake + - listers/admissionregistration/v1alpha1 + - listers/admissionregistration/v1beta1 + - listers/apps/v1 + - listers/apps/v1beta1 + - listers/apps/v1beta2 + - listers/autoscaling/v1 + - listers/autoscaling/v2beta1 + - listers/batch/v1 + - listers/batch/v1beta1 + - listers/batch/v2alpha1 + - listers/certificates/v1beta1 + - listers/core/v1 + - listers/events/v1beta1 + - listers/extensions/v1beta1 + - listers/networking/v1 + - listers/policy/v1beta1 + - listers/rbac/v1 + - listers/rbac/v1alpha1 + - listers/rbac/v1beta1 + - listers/scheduling/v1alpha1 + - listers/settings/v1alpha1 + - listers/storage/v1 + - listers/storage/v1alpha1 + - listers/storage/v1beta1 + - pkg/apis/clientauthentication + - pkg/apis/clientauthentication/v1alpha1 + - pkg/version + - plugin/pkg/client/auth + - plugin/pkg/client/auth/azure + - plugin/pkg/client/auth/exec + - plugin/pkg/client/auth/gcp + - plugin/pkg/client/auth/oidc + - plugin/pkg/client/auth/openstack + - rest + - rest/fake + - rest/watch + - scale + - scale/scheme + - scale/scheme/appsint + - scale/scheme/appsv1beta1 + - scale/scheme/appsv1beta2 + - scale/scheme/autoscalingv1 + - scale/scheme/extensionsint + - scale/scheme/extensionsv1beta1 + - testing + - third_party/forked/golang/template + - tools/auth + - tools/cache + - tools/clientcmd + - tools/clientcmd/api + - tools/clientcmd/api/latest + - tools/clientcmd/api/v1 + - tools/metrics + - tools/pager + - tools/portforward + - tools/record + - tools/reference + - tools/remotecommand + - transport + - transport/spdy + - util/buffer + - util/cert + - util/exec + - util/flowcontrol + - util/homedir + - util/integer + - util/jsonpath + - util/retry + - util/workqueue +- name: k8s.io/kube-openapi + version: 50ae88d24ede7b8bad68e23c805b5d3da5c8abaf + subpackages: + - pkg/util/proto + - pkg/util/proto/validation +- name: k8s.io/kubernetes + version: a22f9fd34871d9dc9e5db2c02c713821d18ab2cd + subpackages: + - pkg/api/events + - pkg/api/legacyscheme + - pkg/api/pod + - pkg/api/ref + - pkg/api/resource + - pkg/api/service + - pkg/api/testapi + - pkg/api/v1/pod + - pkg/apis/admission + - pkg/apis/admission/install + - pkg/apis/admission/v1beta1 + - pkg/apis/admissionregistration + - pkg/apis/admissionregistration/install + - pkg/apis/admissionregistration/v1alpha1 + - pkg/apis/admissionregistration/v1beta1 + - pkg/apis/apps + - pkg/apis/apps/install + - pkg/apis/apps/v1 + - pkg/apis/apps/v1beta1 + - pkg/apis/apps/v1beta2 + - pkg/apis/authentication + - pkg/apis/authentication/install + - pkg/apis/authentication/v1 + - pkg/apis/authentication/v1beta1 + - pkg/apis/authorization + - pkg/apis/authorization/install + - pkg/apis/authorization/v1 + - pkg/apis/authorization/v1beta1 + - pkg/apis/autoscaling + - pkg/apis/autoscaling/install + - pkg/apis/autoscaling/v1 + - pkg/apis/autoscaling/v2beta1 + - pkg/apis/batch + - pkg/apis/batch/install + - pkg/apis/batch/v1 + - pkg/apis/batch/v1beta1 + - pkg/apis/batch/v2alpha1 + - pkg/apis/certificates + - pkg/apis/certificates/install + - pkg/apis/certificates/v1beta1 + - pkg/apis/componentconfig + - pkg/apis/componentconfig/install + - pkg/apis/componentconfig/v1alpha1 + - pkg/apis/core + - pkg/apis/core/helper + - pkg/apis/core/helper/qos + - pkg/apis/core/install + - pkg/apis/core/pods + - pkg/apis/core/v1 + - pkg/apis/core/v1/helper + - pkg/apis/core/v1/helper/qos + - pkg/apis/core/validation + - pkg/apis/events + - pkg/apis/events/install + - pkg/apis/events/v1beta1 + - pkg/apis/extensions + - pkg/apis/extensions/install + - pkg/apis/extensions/v1beta1 + - pkg/apis/imagepolicy + - pkg/apis/imagepolicy/install + - pkg/apis/imagepolicy/v1alpha1 + - pkg/apis/networking + - pkg/apis/networking/install + - pkg/apis/networking/v1 + - pkg/apis/policy + - pkg/apis/policy/install + - pkg/apis/policy/v1beta1 + - pkg/apis/rbac + - pkg/apis/rbac/install + - pkg/apis/rbac/v1 + - pkg/apis/rbac/v1alpha1 + - pkg/apis/rbac/v1beta1 + - pkg/apis/scheduling + - pkg/apis/scheduling/install + - pkg/apis/scheduling/v1alpha1 + - pkg/apis/settings + - pkg/apis/settings/install + - pkg/apis/settings/v1alpha1 + - pkg/apis/storage + - pkg/apis/storage/install + - pkg/apis/storage/util + - pkg/apis/storage/v1 + - pkg/apis/storage/v1alpha1 + - pkg/apis/storage/v1beta1 + - pkg/capabilities + - pkg/client/clientset_generated/internalclientset + - pkg/client/clientset_generated/internalclientset/fake + - pkg/client/clientset_generated/internalclientset/scheme + - pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion + - pkg/client/clientset_generated/internalclientset/typed/admissionregistration/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/apps/internalversion + - pkg/client/clientset_generated/internalclientset/typed/apps/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion + - pkg/client/clientset_generated/internalclientset/typed/authentication/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion + - pkg/client/clientset_generated/internalclientset/typed/authorization/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion + - pkg/client/clientset_generated/internalclientset/typed/autoscaling/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/batch/internalversion + - pkg/client/clientset_generated/internalclientset/typed/batch/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion + - pkg/client/clientset_generated/internalclientset/typed/certificates/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/core/internalversion + - pkg/client/clientset_generated/internalclientset/typed/core/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/events/internalversion + - pkg/client/clientset_generated/internalclientset/typed/events/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion + - pkg/client/clientset_generated/internalclientset/typed/extensions/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/networking/internalversion + - pkg/client/clientset_generated/internalclientset/typed/networking/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/policy/internalversion + - pkg/client/clientset_generated/internalclientset/typed/policy/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion + - pkg/client/clientset_generated/internalclientset/typed/rbac/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion + - pkg/client/clientset_generated/internalclientset/typed/scheduling/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion + - pkg/client/clientset_generated/internalclientset/typed/settings/internalversion/fake + - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion + - pkg/client/clientset_generated/internalclientset/typed/storage/internalversion/fake + - pkg/client/conditions + - pkg/cloudprovider + - pkg/controller + - pkg/controller/daemon + - pkg/controller/daemon/util + - pkg/controller/deployment/util + - pkg/controller/history + - pkg/controller/statefulset + - pkg/controller/volume/events + - pkg/controller/volume/persistentvolume + - pkg/controller/volume/persistentvolume/metrics + - pkg/credentialprovider + - pkg/features + - pkg/fieldpath + - pkg/kubectl + - pkg/kubectl/apps + - pkg/kubectl/categories + - pkg/kubectl/cmd/templates + - pkg/kubectl/cmd/testing + - pkg/kubectl/cmd/util + - pkg/kubectl/cmd/util/openapi + - pkg/kubectl/cmd/util/openapi/testing + - pkg/kubectl/cmd/util/openapi/validation + - pkg/kubectl/plugins + - pkg/kubectl/resource + - pkg/kubectl/scheme + - pkg/kubectl/util + - pkg/kubectl/util/hash + - pkg/kubectl/util/slice + - pkg/kubectl/util/term + - pkg/kubectl/util/transport + - pkg/kubectl/validation + - pkg/kubelet/apis + - pkg/kubelet/types + - pkg/master/ports + - pkg/printers + - pkg/printers/internalversion + - pkg/registry/rbac/validation + - pkg/scheduler/algorithm + - pkg/scheduler/algorithm/predicates + - pkg/scheduler/algorithm/priorities/util + - pkg/scheduler/api + - pkg/scheduler/schedulercache + - pkg/scheduler/util + - pkg/scheduler/volumebinder + - pkg/security/apparmor + - pkg/serviceaccount + - pkg/util/file + - pkg/util/goroutinemap + - pkg/util/goroutinemap/exponentialbackoff + - pkg/util/hash + - pkg/util/interrupt + - pkg/util/io + - pkg/util/labels + - pkg/util/metrics + - pkg/util/mount + - pkg/util/net/sets + - pkg/util/node + - pkg/util/nsenter + - pkg/util/parsers + - pkg/util/pointer + - pkg/util/slice + - pkg/util/taints + - pkg/version + - pkg/volume + - pkg/volume/util + - pkg/volume/util/fs + - pkg/volume/util/recyclerclient + - pkg/volume/util/types +- name: k8s.io/utils + version: aedf551cdb8b0119df3a19c65fde413a13b34997 + subpackages: + - clock + - exec + - exec/testing +- name: vbom.ml/util + version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394 + subpackages: + - sortorder +testImports: +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: e3a8ff8ce36581f87a15341206f205b1da467059 + subpackages: + - assert diff --git a/src/vendor/github.com/kubernetes/helm/glide.yaml b/src/vendor/github.com/kubernetes/helm/glide.yaml new file mode 100644 index 000000000..7fcb16d0b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/glide.yaml @@ -0,0 +1,65 @@ +package: k8s.io/helm +import: +- package: golang.org/x/net + subpackages: + - context +- package: github.com/spf13/cobra + version: f62e98d28ab7ad31d707ba837a966378465c7b57 +- package: github.com/spf13/pflag + version: 9ff6c6923cfffbcd502984b8e0c80539a94968b7 +- package: github.com/Masterminds/vcs + version: ~1.11.0 + # Pin version of mergo that is compatible with both sprig and Kubernetes +- package: github.com/imdario/mergo + version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- package: github.com/Masterminds/sprig + version: ^2.14.1 +- package: github.com/ghodss/yaml +- package: github.com/Masterminds/semver + version: ~1.3.1 +- package: github.com/technosophos/moniker +- package: github.com/golang/protobuf + version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 + subpackages: + - proto + - ptypes/any + - ptypes/timestamp +- package: google.golang.org/grpc + version: 1.7.2 +- package: github.com/gosuri/uitable +- package: github.com/asaskevich/govalidator + version: ^4.0.0 +- package: golang.org/x/crypto + subpackages: + - openpgp +# pin version of golang.org/x/sys that is compatible with golang.org/x/crypto +- package: golang.org/x/sys + version: 43eea11 + subpackages: + - unix + - windows +- package: github.com/gobwas/glob + version: ^0.2.1 +- package: github.com/evanphx/json-patch +- package: github.com/BurntSushi/toml + version: ~0.3.0 +- package: github.com/prometheus/client_golang + version: 0.8.0 +- package: github.com/grpc-ecosystem/go-grpc-prometheus + +- package: k8s.io/kubernetes + version: release-1.10 +- package: k8s.io/client-go + version: kubernetes-1.10.0 +- package: k8s.io/api + version: release-1.10 +- package: k8s.io/apimachinery + version: release-1.10 +- package: k8s.io/apiserver + version: release-1.10 + +testImports: +- package: github.com/stretchr/testify + version: ^1.1.4 + subpackages: + - assert diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities.go new file mode 100644 index 000000000..c87c0368e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities.go @@ -0,0 +1,71 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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" + "runtime" + + "k8s.io/apimachinery/pkg/version" + tversion "k8s.io/helm/pkg/proto/hapi/version" +) + +var ( + // DefaultVersionSet is the default version set, which includes only Core V1 ("v1"). + DefaultVersionSet = NewVersionSet("v1") + + // DefaultKubeVersion is the default kubernetes version + DefaultKubeVersion = &version.Info{ + Major: "1", + Minor: "9", + GitVersion: "v1.9.0", + GoVersion: runtime.Version(), + Compiler: runtime.Compiler, + Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), + } +) + +// Capabilities describes the capabilities of the Kubernetes cluster that Tiller is attached to. +type Capabilities struct { + // List of all supported API versions + APIVersions VersionSet + // KubeVerison is the Kubernetes version + KubeVersion *version.Info + // TillerVersion is the Tiller version + // + // This always comes from pkg/version.GetVersionProto(). + TillerVersion *tversion.Version +} + +// VersionSet is a set of Kubernetes API versions. +type VersionSet map[string]interface{} + +// NewVersionSet creates a new version set from a list of strings. +func NewVersionSet(apiVersions ...string) VersionSet { + vs := VersionSet{} + for _, v := range apiVersions { + vs[v] = struct{}{} + } + return vs +} + +// Has returns true if the version string is in the set. +// +// vs.Has("extensions/v1beta1") +func (v VersionSet) Has(apiVersion string) bool { + _, ok := v[apiVersion] + return ok +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities_test.go new file mode 100644 index 000000000..ac20f0038 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/capabilities_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 := NewVersionSet("v1", "extensions/v1beta1") + if d := len(vs); d != 2 { + t.Errorf("Expected 2 versions, got %d", d) + } + + if !vs.Has("extensions/v1beta1") { + t.Error("Expected to find extensions/v1beta1") + } + + 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") + } + if d := len(DefaultVersionSet); d != 1 { + t.Errorf("Expected only one version, got %d", d) + } +} + +func TestCapabilities(t *testing.T) { + cap := Capabilities{ + APIVersions: DefaultVersionSet, + } + + if !cap.APIVersions.Has("v1") { + t.Error("APIVersions should have v1") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile.go new file mode 100644 index 000000000..c2879cdae --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile.go @@ -0,0 +1,98 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// ApiVersionV1 is the API version number for version 1. +// +// This is ApiVersionV1 instead of APIVersionV1 to match the protobuf-generated name. +const ApiVersionV1 = "v1" // nolint + +// UnmarshalChartfile takes raw Chart.yaml data and unmarshals it. +func UnmarshalChartfile(data []byte) (*chart.Metadata, error) { + y := &chart.Metadata{} + err := yaml.Unmarshal(data, y) + if err != nil { + return nil, err + } + return y, nil +} + +// 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 + } + return UnmarshalChartfile(b) +} + +// SaveChartfile saves the given metadata as a Chart.yaml file at the given path. +// +// 'filename' should be the complete path and filename ('foo/Chart.yaml') +func SaveChartfile(filename string, cf *chart.Metadata) error { + out, err := yaml.Marshal(cf) + if err != nil { + return err + } + return 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, fmt.Errorf("%q is not a directory", dirName) + } + + chartYaml := filepath.Join(dirName, "Chart.yaml") + if _, err := os.Stat(chartYaml); os.IsNotExist(err) { + return false, fmt.Errorf("no Chart.yaml exists in directory %q", dirName) + } + + chartYamlContent, err := ioutil.ReadFile(chartYaml) + if err != nil { + return false, fmt.Errorf("cannot read Chart.Yaml in directory %q", dirName) + } + + chartContent, err := UnmarshalChartfile(chartYamlContent) + if err != nil { + return false, err + } + if chartContent == nil { + return false, errors.New("chart metadata (Chart.yaml) missing") + } + if chartContent.Name == "" { + return false, errors.New("invalid chart (Chart.yaml): name must not be empty") + } + + return true, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile_test.go new file mode 100755 index 000000000..5b36dc955 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/chartfile_test.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/helm/pkg/proto/hapi/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") + } + + // Api instead of API because it was generated via protobuf. + if f.ApiVersion != ApiVersionV1 { + t.Errorf("Expected API Version %q, got %q", 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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create.go new file mode 100644 index 000000000..ec84ba772 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create.go @@ -0,0 +1,410 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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/ioutil" + "os" + "path/filepath" + + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const ( + // ChartfileName is the default Chart file name. + ChartfileName = "Chart.yaml" + // ValuesfileName is the default values file name. + ValuesfileName = "values.yaml" + // TemplatesDir is the relative directory name for templates. + TemplatesDir = "templates" + // ChartsDir is the relative directory name for charts dependencies. + ChartsDir = "charts" + // IgnorefileName is the name of the Helm ignore file. + IgnorefileName = ".helmignore" + // IngressFileName is the name of the example ingress file. + IngressFileName = "ingress.yaml" + // DeploymentName is the name of the example deployment file. + DeploymentName = "deployment.yaml" + // ServiceName is the name of the example service file. + ServiceName = "service.yaml" + // NotesName is the name of the example NOTES.txt file. + NotesName = "NOTES.txt" + // HelpersName is the name of the example NOTES.txt file. + HelpersName = "_helpers.tpl" +) + +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 + tag: stable + pullPolicy: IfNotPresent + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + path: / + hosts: + - chart-example.local + 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: {} +` + +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 +*~ +# Various IDEs +.project +.idea/ +*.tmproj +` + +const defaultIngress = `{{- if .Values.ingress.enabled -}} +{{- $fullName := include ".fullname" . -}} +{{- $ingressPath := .Values.ingress.path -}} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + app: {{ template ".name" . }} + chart: {{ template ".chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- with .Values.ingress.annotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $fullName }} + servicePort: http + {{- end }} +{{- end }} +` + +const defaultDeployment = `apiVersion: apps/v1beta2 +kind: Deployment +metadata: + name: {{ template ".fullname" . }} + labels: + app: {{ template ".name" . }} + chart: {{ template ".chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + app: {{ template ".name" . }} + release: {{ .Release.Name }} + template: + metadata: + labels: + app: {{ template ".name" . }} + release: {{ .Release.Name }} + spec: + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + 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 | indent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: +{{ toYaml . | indent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: +{{ toYaml . | indent 8 }} + {{- end }} +` + +const defaultService = `apiVersion: v1 +kind: Service +metadata: + name: {{ template ".fullname" . }} + labels: + app: {{ template ".name" . }} + chart: {{ template ".chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + app: {{ template ".name" . }} + release: {{ .Release.Name }} +` + +const defaultNotes = `1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range .Values.ingress.hosts }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ . }}{{ $.Values.ingress.path }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template ".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 svc -w {{ template ".fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ template ".fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + 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={{ template ".name" . }},release={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl port-forward $POD_NAME 8080:80 +{{- end }} +` + +const defaultHelpers = `{{/* vim: set filetype=mustache: */}} +{{/* +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 -}} +` + +// CreateFrom creates a new chart, but scaffolds it from the src chart. +func CreateFrom(chartfile *chart.Metadata, dest string, src string) error { + schart, err := Load(src) + if err != nil { + return fmt.Errorf("could not load %s: %s", src, err) + } + + schart.Metadata = chartfile + + var updatedTemplates []*chart.Template + + for _, template := range schart.Templates { + newData := Transform(string(template.Data), "", schart.Metadata.Name) + updatedTemplates = append(updatedTemplates, &chart.Template{Name: template.Name, Data: newData}) + } + + schart.Templates = updatedTemplates + schart.Values = &chart.Config{Raw: string(Transform(schart.Values.Raw, "", schart.Metadata.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(chartfile *chart.Metadata, dir string) (string, error) { + path, err := filepath.Abs(dir) + if err != nil { + return path, err + } + + if fi, err := os.Stat(path); err != nil { + return path, err + } else if !fi.IsDir() { + return path, fmt.Errorf("no such directory %s", path) + } + + n := chartfile.Name + cdir := filepath.Join(path, n) + if fi, err := os.Stat(cdir); err == nil && !fi.IsDir() { + return cdir, fmt.Errorf("file %s already exists and is not a directory", cdir) + } + if err := os.MkdirAll(cdir, 0755); err != nil { + return cdir, err + } + + cf := filepath.Join(cdir, ChartfileName) + if _, err := os.Stat(cf); err != nil { + if err := SaveChartfile(cf, chartfile); err != nil { + return cdir, err + } + } + + for _, d := range []string{TemplatesDir, ChartsDir} { + if err := os.MkdirAll(filepath.Join(cdir, d), 0755); err != nil { + return cdir, err + } + } + + files := []struct { + path string + content []byte + }{ + { + // values.yaml + path: filepath.Join(cdir, ValuesfileName), + content: []byte(fmt.Sprintf(defaultValues, chartfile.Name)), + }, + { + // .helmignore + path: filepath.Join(cdir, IgnorefileName), + content: []byte(defaultIgnore), + }, + { + // ingress.yaml + path: filepath.Join(cdir, TemplatesDir, IngressFileName), + content: Transform(defaultIngress, "", chartfile.Name), + }, + { + // deployment.yaml + path: filepath.Join(cdir, TemplatesDir, DeploymentName), + content: Transform(defaultDeployment, "", chartfile.Name), + }, + { + // service.yaml + path: filepath.Join(cdir, TemplatesDir, ServiceName), + content: Transform(defaultService, "", chartfile.Name), + }, + { + // NOTES.txt + path: filepath.Join(cdir, TemplatesDir, NotesName), + content: Transform(defaultNotes, "", chartfile.Name), + }, + { + // _helpers.tpl + path: filepath.Join(cdir, TemplatesDir, HelpersName), + content: Transform(defaultHelpers, "", chartfile.Name), + }, + } + + for _, file := range files { + if _, err := os.Stat(file.path); err == nil { + // File exists and is okay. Skip it. + continue + } + if err := ioutil.WriteFile(file.path, file.content, 0644); err != nil { + return cdir, err + } + } + return cdir, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create_test.go new file mode 100644 index 000000000..e9af83ad2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/create_test.go @@ -0,0 +1,134 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "strings" + "testing" + + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestCreate(t *testing.T) { + tdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + cf := &chart.Metadata{Name: "foo"} + + c, err := Create(cf, tdir) + if err != nil { + t.Fatal(err) + } + + dir := filepath.Join(tdir, "foo") + + mychart, err := LoadDir(c) + if err != nil { + t.Fatalf("Failed to load newly created chart %q: %s", c, err) + } + + if mychart.Metadata.Name != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) + } + + for _, d := range []string{TemplatesDir, ChartsDir} { + if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { + t.Errorf("Expected %s dir: %s", d, err) + } else if !fi.IsDir() { + t.Errorf("Expected %s to be a directory.", d) + } + } + + for _, f := range []string{ChartfileName, ValuesfileName, IgnorefileName} { + if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a file.", f) + } + } + + for _, f := range []string{NotesName, DeploymentName, ServiceName, HelpersName} { + if fi, err := os.Stat(filepath.Join(dir, TemplatesDir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a file.", f) + } + } + +} + +func TestCreateFrom(t *testing.T) { + tdir, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + + cf := &chart.Metadata{Name: "foo"} + srcdir := "./testdata/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 := LoadDir(c) + if err != nil { + t.Fatalf("Failed to load newly created chart %q: %s", c, err) + } + + if mychart.Metadata.Name != "foo" { + t.Errorf("Expected name to be 'foo', got %q", mychart.Metadata.Name) + } + + for _, d := range []string{TemplatesDir, ChartsDir} { + if fi, err := os.Stat(filepath.Join(dir, d)); err != nil { + t.Errorf("Expected %s dir: %s", d, err) + } else if !fi.IsDir() { + t.Errorf("Expected %s to be a directory.", d) + } + } + + for _, f := range []string{ChartfileName, ValuesfileName, "requirements.yaml"} { + if fi, err := os.Stat(filepath.Join(dir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a file.", f) + } + } + + for _, f := range []string{"placeholder.tpl"} { + if fi, err := os.Stat(filepath.Join(dir, TemplatesDir, f)); err != nil { + t.Errorf("Expected %s file: %s", f, err) + } else if fi.IsDir() { + t.Errorf("Expected %s to be a file.", f) + } + } + + // Ensure we replace `` + if strings.Contains(mychart.Values.Raw, "") { + t.Errorf("Did not expect %s to be present in %s", "", mychart.Values.Raw) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/doc.go new file mode 100644 index 000000000..1190d968d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/doc.go @@ -0,0 +1,44 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 protocol buffer definition (pkg/proto/hapi/charts). +This packe 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 utilitites for working with those file formats. + +The preferred way of loading a chart is using 'chartutil.Load`: + + chart, err := chartutil.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 +'chartutil.LoadArchive()' will read in the data, uncompress it, and unpack it +into a Chart. + +When creating charts in memory, use the 'k8s.io/helm/pkg/proto/hapi/chart' +package directly. +*/ +package chartutil // import "k8s.io/helm/pkg/chartutil" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/expand.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/expand.go new file mode 100644 index 000000000..126e14e80 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/expand.go @@ -0,0 +1,84 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "io" + "os" + "path/filepath" +) + +// Expand uncompresses and extracts a chart into the specified directory. +func Expand(dir string, r io.Reader) error { + gr, err := gzip.NewReader(r) + if err != nil { + return err + } + defer gr.Close() + tr := tar.NewReader(gr) + for { + header, err := tr.Next() + if err == io.EOF { + break + } else if err != nil { + return err + } + + //split header name and create missing directories + d, _ := filepath.Split(header.Name) + fullDir := filepath.Join(dir, d) + _, err = os.Stat(fullDir) + if err != nil && d != "" { + if err := os.MkdirAll(fullDir, 0700); err != nil { + return err + } + } + + path := filepath.Clean(filepath.Join(dir, header.Name)) + info := header.FileInfo() + if info.IsDir() { + if err = os.MkdirAll(path, info.Mode()); err != nil { + return err + } + continue + } + + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode()) + if err != nil { + return err + } + _, err = io.Copy(file, tr) + if err != nil { + file.Close() + return err + } + file.Close() + } + 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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files.go new file mode 100644 index 000000000..a711a3366 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files.go @@ -0,0 +1,236 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "encoding/base64" + "encoding/json" + "path" + "strings" + + "github.com/ghodss/yaml" + + "github.com/BurntSushi/toml" + "github.com/gobwas/glob" + "github.com/golang/protobuf/ptypes/any" +) + +// 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 []*any.Any (the format for files in a chart.Chart), extract a map of files. +func NewFiles(from []*any.Any) Files { + files := map[string][]byte{} + if from != nil { + for _, f := range from { + files[f.TypeUrl] = f.Value + } + } + 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 { + v, ok := f[name] + if !ok { + return []byte{} + } + return v +} + +// 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 := 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 := 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") +} + +// 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 string(data) +} + +// 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 +} + +// 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 := map[string]interface{}{} + + if err := json.Unmarshal([]byte(str), &m); err != nil { + m["Error"] = err.Error() + } + return m +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files_test.go new file mode 100644 index 000000000..731c82e6f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/files_test.go @@ -0,0 +1,217 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + + "github.com/golang/protobuf/ptypes/any" + "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() []*any.Any { + a := []*any.Any{} + for _, c := range cases { + a = append(a, &any.Any{TypeUrl: c.path, Value: []byte(c.data)}) + } + return a +} + +func TestNewFiles(t *testing.T) { + files := NewFiles(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 := NewFiles(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 := NewFiles(getTestFiles()) + out := f.Glob("**/captain.txt").AsConfig() + as.Equal("captain.txt: The Captain\n", out) + + out = f.Glob("ship/**").AsConfig() + as.Equal("captain.txt: The Captain\nstowaway.txt: Legatt\n", out) +} + +func TestToSecret(t *testing.T) { + as := assert.New(t) + + f := NewFiles(getTestFiles()) + + out := f.Glob("ship/**").AsSecrets() + as.Equal("captain.txt: VGhlIENhcHRhaW4=\nstowaway.txt: TGVnYXR0\n", out) +} + +func TestLines(t *testing.T) { + as := assert.New(t) + + f := NewFiles(getTestFiles()) + + out := f.Lines("multiline/test.txt") + as.Len(out, 2) + + as.Equal("bar", out[0]) +} + +func TestToYaml(t *testing.T) { + expect := "foo: bar\n" + v := struct { + Foo string `json:"foo"` + }{ + Foo: "bar", + } + + if got := ToYaml(v); got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} + +func TestToToml(t *testing.T) { + expect := "foo = \"bar\"\n" + v := struct { + Foo string `toml:"foo"` + }{ + Foo: "bar", + } + + if got := ToToml(v); got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } + + // Regression for https://github.com/kubernetes/helm/issues/2271 + dict := map[string]map[string]string{ + "mast": { + "sail": "white", + }, + } + got := ToToml(dict) + expect = "[mast]\n sail = \"white\"\n" + if got != expect { + t.Errorf("Expected:\n%s\nGot\n%s\n", expect, got) + } +} + +func TestFromYaml(t *testing.T) { + doc := `hello: world +one: + two: three +` + dict := FromYaml(doc) + if err, ok := dict["Error"]; ok { + t.Fatalf("Parse error: %s", err) + } + + if len(dict) != 2 { + t.Fatal("expected two elements.") + } + + world := dict["hello"] + if world.(string) != "world" { + t.Fatal("Expected the world. Is that too much to ask?") + } + + // This should fail because we don't currently support lists: + doc2 := ` +- one +- two +- three +` + dict = FromYaml(doc2) + if _, ok := dict["Error"]; !ok { + t.Fatal("Expected parser error") + } +} + +func TestToJson(t *testing.T) { + expect := `{"foo":"bar"}` + v := struct { + Foo string `json:"foo"` + }{ + Foo: "bar", + } + + if got := ToJson(v); got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} + +func TestFromJson(t *testing.T) { + doc := `{ + "hello": "world", + "one": { + "two": "three" + } +} +` + dict := FromJson(doc) + if err, ok := dict["Error"]; ok { + t.Fatalf("Parse error: %s", err) + } + + if len(dict) != 2 { + t.Fatal("expected two elements.") + } + + world := dict["hello"] + if world.(string) != "world" { + t.Fatal("Expected the world. Is that too much to ask?") + } + + // This should fail because we don't currently support lists: + doc2 := ` +[ + "one", + "two", + "three" +] +` + dict = FromJson(doc2) + if _, ok := dict["Error"]; !ok { + t.Fatal("Expected parser error") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load.go new file mode 100644 index 000000000..c5246b8d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load.go @@ -0,0 +1,288 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/golang/protobuf/ptypes/any" + + "k8s.io/helm/pkg/ignore" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/sympath" +) + +// 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) { + fi, err := os.Stat(name) + if err != nil { + return nil, err + } + if fi.IsDir() { + if validChart, err := IsChartDir(name); !validChart { + return nil, err + } + return LoadDir(name) + } + return LoadFile(name) +} + +// BufferedFile represents an archive file buffered for later processing. +type BufferedFile struct { + Name string + Data []byte +} + +// LoadArchive loads from a reader containing a compressed tar archive. +func LoadArchive(in io.Reader) (*chart.Chart, error) { + unzipped, err := gzip.NewReader(in) + if err != nil { + return &chart.Chart{}, 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 &chart.Chart{}, err + } + + if hd.FileInfo().IsDir() { + // Use this instead of hd.Typeflag because we don't have to do any + // inference chasing. + 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.Replace(n, delimiter, "/", -1) + + if parts[0] == "Chart.yaml" { + return nil, errors.New("chart yaml not in base directory") + } + + if _, err := io.Copy(b, tr); err != nil { + return &chart.Chart{}, err + } + + files = append(files, &BufferedFile{Name: n, Data: b.Bytes()}) + b.Reset() + } + + if len(files) == 0 { + return nil, errors.New("no files in chart archive") + } + + return LoadFiles(files) +} + +// LoadFiles loads from in-memory files. +func LoadFiles(files []*BufferedFile) (*chart.Chart, error) { + c := &chart.Chart{} + subcharts := map[string][]*BufferedFile{} + + for _, f := range files { + if f.Name == "Chart.yaml" { + m, err := UnmarshalChartfile(f.Data) + if err != nil { + return c, err + } + c.Metadata = m + } else if f.Name == "values.toml" { + return c, errors.New("values.toml is illegal as of 2.0.0-alpha.2") + } else if f.Name == "values.yaml" { + c.Values = &chart.Config{Raw: string(f.Data)} + } else if strings.HasPrefix(f.Name, "templates/") { + c.Templates = append(c.Templates, &chart.Template{Name: f.Name, Data: f.Data}) + } else if strings.HasPrefix(f.Name, "charts/") { + if filepath.Ext(f.Name) == ".prov" { + c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data}) + continue + } + cname := strings.TrimPrefix(f.Name, "charts/") + if strings.IndexAny(cname, "._") == 0 { + // Ignore charts/ that start with . or _. + continue + } + parts := strings.SplitN(cname, "/", 2) + scname := parts[0] + subcharts[scname] = append(subcharts[scname], &BufferedFile{Name: cname, Data: f.Data}) + } else { + c.Files = append(c.Files, &any.Any{TypeUrl: f.Name, Value: f.Data}) + } + } + + // Ensure that we got a Chart.yaml file + if c.Metadata == nil { + return c, errors.New("chart metadata (Chart.yaml) missing") + } + if c.Metadata.Name == "" { + return c, errors.New("invalid chart (Chart.yaml): name must not be empty") + } + + for n, files := range subcharts { + var sc *chart.Chart + var err error + if strings.IndexAny(n, "_.") == 0 { + continue + } else if filepath.Ext(n) == ".tgz" { + file := files[0] + if file.Name != n { + return c, fmt.Errorf("error unpacking tar in %s: expected %s, got %s", c.Metadata.Name, n, file.Name) + } + // Untar the chart and add to c.Dependencies + b := bytes.NewBuffer(file.Data) + sc, err = LoadArchive(b) + } else { + // We have to trim the prefix off of every file, and ignore any file + // that is in charts/, but isn't actually a chart. + buff := make([]*BufferedFile, 0, len(files)) + for _, f := range files { + parts := strings.SplitN(f.Name, "/", 2) + if len(parts) < 2 { + continue + } + f.Name = parts[1] + buff = append(buff, f) + } + sc, err = LoadFiles(buff) + } + + if err != nil { + return c, fmt.Errorf("error unpacking %s in %s: %s", n, c.Metadata.Name, err) + } + + c.Dependencies = append(c.Dependencies, sc) + } + + return c, nil +} + +// 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() + + return LoadArchive(raw) +} + +// 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 + } + + data, err := ioutil.ReadFile(name) + if err != nil { + return fmt.Errorf("error reading %s: %s", n, err) + } + + 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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load_test.go new file mode 100644 index 000000000..454500489 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/load_test.go @@ -0,0 +1,253 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "path" + "testing" + + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestLoadDir(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyRequirements(t, c) +} + +func TestLoadFile(t *testing.T) { + c, err := Load("testdata/frobnitz-1.2.3.tgz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyFrobnitz(t, c) + verifyChart(t, c) + verifyRequirements(t, c) +} + +func TestLoadFiles(t *testing.T) { + goodFiles := []*BufferedFile{ + { + Name: ChartfileName, + 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: ValuesfileName, + Data: []byte(defaultValues), + }, + { + Name: path.Join("templates", DeploymentName), + Data: []byte(defaultDeployment), + }, + { + Name: path.Join("templates", ServiceName), + Data: []byte(defaultService), + }, + } + + c, err := LoadFiles(goodFiles) + if err != nil { + t.Errorf("Expected good files to be loaded, got %v", err) + } + + if c.Metadata.Name != "frobnitz" { + t.Errorf("Expected chart name to be 'frobnitz', got %s", c.Metadata.Name) + } + + if c.Values.Raw != defaultValues { + t.Error("Expected chart values to be populated with default values") + } + + if len(c.Templates) != 2 { + t.Errorf("Expected number of templates == 2, got %d", len(c.Templates)) + } + + c, err = LoadFiles([]*BufferedFile{}) + if err == nil { + t.Fatal("Expected err to be non-nil") + } + if err.Error() != "chart metadata (Chart.yaml) missing" { + t.Errorf("Expected chart metadata missing error, got '%s'", err.Error()) + } + + // legacy check + c, err = LoadFiles([]*BufferedFile{ + { + Name: "values.toml", + Data: []byte{}, + }, + }) + if err == nil { + t.Fatal("Expected err to be non-nil") + } + if err.Error() != "values.toml is illegal as of 2.0.0-alpha.2" { + t.Errorf("Expected values.toml to be illegal, got '%s'", err.Error()) + } +} + +// 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) + verifyRequirements(t, c) +} + +func verifyChart(t *testing.T, c *chart.Chart) { + if c.Metadata.Name == "" { + t.Fatalf("No chart metadata found on %v", c) + } + t.Logf("Verifying chart %s", c.Metadata.Name) + if len(c.Templates) != 1 { + t.Errorf("Expected 1 template, got %d", len(c.Templates)) + } + + numfiles := 8 + 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.TypeUrl) + } + } + + 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.Metadata.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.Metadata.Name] + if !ok { + t.Fatalf("Unknown dependency %s", dep.Metadata.Name) + } + if exp["version"] != dep.Metadata.Version { + t.Errorf("Expected %s version %s, got %s", dep.Metadata.Name, exp["version"], dep.Metadata.Version) + } + } + +} + +func verifyRequirements(t *testing.T, c *chart.Chart) { + r, err := LoadRequirements(c) + if err != nil { + t.Fatal(err) + } + if len(r.Dependencies) != 2 { + t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies)) + } + tests := []*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 := r.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 verifyRequirementsLock(t *testing.T, c *chart.Chart) { + r, err := LoadRequirementsLock(c) + if err != nil { + t.Fatal(err) + } + if len(r.Dependencies) != 2 { + t.Errorf("Expected 2 requirements, got %d", len(r.Dependencies)) + } + tests := []*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 := r.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) { + + verifyChartfile(t, c.Metadata, 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.") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements.go new file mode 100644 index 000000000..ce761a6fc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements.go @@ -0,0 +1,456 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "errors" + "log" + "strings" + "time" + + "github.com/ghodss/yaml" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/version" +) + +const ( + requirementsName = "requirements.yaml" + lockfileName = "requirements.lock" +) + +var ( + // ErrRequirementsNotFound indicates that a requirements.yaml is not found. + ErrRequirementsNotFound = errors.New(requirementsName + " not found") + // ErrLockfileNotFound indicates that a requirements.lock is not found. + ErrLockfileNotFound = errors.New(lockfileName + " not found") +) + +// 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"` +} + +// ErrNoRequirementsFile to detect error condition +type ErrNoRequirementsFile error + +// Requirements is a list of requirements for a chart. +// +// Requirements are charts upon which this chart depends. This expresses +// developer intent. +type Requirements struct { + Dependencies []*Dependency `json:"dependencies"` +} + +// RequirementsLock is a lock file for requirements. +// +// It represents the state that the dependencies should be in. +type RequirementsLock struct { + // Genderated is the date the lock file was last generated. + Generated time.Time `json:"generated"` + // Digest is a hash of the requirements file used to generate it. + Digest string `json:"digest"` + // Dependencies is the list of dependencies that this lock file has locked. + Dependencies []*Dependency `json:"dependencies"` +} + +// LoadRequirements loads a requirements file from an in-memory chart. +func LoadRequirements(c *chart.Chart) (*Requirements, error) { + var data []byte + for _, f := range c.Files { + if f.TypeUrl == requirementsName { + data = f.Value + } + } + if len(data) == 0 { + return nil, ErrRequirementsNotFound + } + r := &Requirements{} + return r, yaml.Unmarshal(data, r) +} + +// LoadRequirementsLock loads a requirements lock file. +func LoadRequirementsLock(c *chart.Chart) (*RequirementsLock, error) { + var data []byte + for _, f := range c.Files { + if f.TypeUrl == lockfileName { + data = f.Value + } + } + if len(data) == 0 { + return nil, ErrLockfileNotFound + } + r := &RequirementsLock{} + return r, yaml.Unmarshal(data, r) +} + +// ProcessRequirementsConditions disables charts based on condition path value in values +func ProcessRequirementsConditions(reqs *Requirements, cvals Values) { + var cond string + var conds []string + if reqs == nil || len(reqs.Dependencies) == 0 { + return + } + for _, r := range reqs.Dependencies { + var hasTrue, hasFalse bool + cond = string(r.Condition) + // check for list + if len(cond) > 0 { + if strings.Contains(cond, ",") { + conds = strings.Split(strings.TrimSpace(cond), ",") + } else { + conds = []string{strings.TrimSpace(cond)} + } + for _, c := range conds { + if len(c) > 0 { + // retrieve value + vv, err := cvals.PathValue(c) + if err == nil { + // if not bool, warn + if bv, ok := vv.(bool); ok { + if bv { + hasTrue = true + } else { + hasFalse = true + } + } 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) + + } + if vv != nil { + // got first value, break loop + break + } + } + } + if !hasTrue && hasFalse { + r.Enabled = false + } else if hasTrue { + r.Enabled = true + + } + } + + } + +} + +// ProcessRequirementsTags disables charts based on tags in values +func ProcessRequirementsTags(reqs *Requirements, cvals Values) { + vt, err := cvals.Table("tags") + if err != nil { + return + + } + if reqs == nil || len(reqs.Dependencies) == 0 { + return + } + for _, r := range reqs.Dependencies { + if len(r.Tags) > 0 { + tags := r.Tags + + var hasTrue, hasFalse bool + for _, k := range 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, aliasChart *Dependency) *chart.Chart { + var chartFound chart.Chart + for _, existingChart := range charts { + if existingChart == nil { + continue + } + if existingChart.Metadata == nil { + continue + } + if existingChart.Metadata.Name != aliasChart.Name { + continue + } + if !version.IsCompatibleRange(aliasChart.Version, existingChart.Metadata.Version) { + continue + } + chartFound = *existingChart + newMetadata := *existingChart.Metadata + if aliasChart.Alias != "" { + newMetadata.Name = aliasChart.Alias + } + chartFound.Metadata = &newMetadata + return &chartFound + } + return nil +} + +// ProcessRequirementsEnabled removes disabled charts from dependencies +func ProcessRequirementsEnabled(c *chart.Chart, v *chart.Config) error { + reqs, err := LoadRequirements(c) + if err != nil { + // if not just missing requirements file, return error + if nerr, ok := err.(ErrNoRequirementsFile); !ok { + return nerr + } + + // no requirements to process + return nil + } + + var chartDependencies []*chart.Chart + // If any dependency is not a part of requirements.yaml + // then this should be added to chartDependencies. + // However, if the dependency is already specified in requirements.yaml + // we should not add it, as it would be anyways processed from requirements.yaml + + for _, existingDependency := range c.Dependencies { + var dependencyFound bool + for _, req := range reqs.Dependencies { + if existingDependency.Metadata.Name == req.Name && version.IsCompatibleRange(req.Version, existingDependency.Metadata.Version) { + dependencyFound = true + break + } + } + if !dependencyFound { + chartDependencies = append(chartDependencies, existingDependency) + } + } + + for _, req := range reqs.Dependencies { + if chartDependency := getAliasDependency(c.Dependencies, req); chartDependency != nil { + chartDependencies = append(chartDependencies, chartDependency) + } + if req.Alias != "" { + req.Name = req.Alias + } + } + c.Dependencies = chartDependencies + + // set all to true + for _, lr := range reqs.Dependencies { + lr.Enabled = true + } + cvals, err := CoalesceValues(c, v) + if err != nil { + return err + } + // convert our values back into config + yvals, err := cvals.YAML() + if err != nil { + return err + } + cc := chart.Config{Raw: yvals} + // flag dependencies as enabled/disabled + ProcessRequirementsTags(reqs, cvals) + ProcessRequirementsConditions(reqs, cvals) + // make a map of charts to remove + rm := map[string]bool{} + for _, r := range reqs.Dependencies { + if !r.Enabled { + // remove disabled chart + rm[r.Name] = true + } + } + // 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) + } + + } + // recursively call self to process sub dependencies + for _, t := range cd { + err := ProcessRequirementsEnabled(t, &cc) + // if its not just missing requirements file, return error + if nerr, ok := err.(ErrNoRequirementsFile); !ok && err != nil { + return nerr + } + } + c.Dependencies = 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 + } + ap := strings.Split(path, ".") + if len(ap) == 0 { + return nil + } + n := []map[string]interface{}{} + // created nested map for each key, adding to slice + for _, v := range ap { + nm := make(map[string]interface{}) + nm[v] = make(map[string]interface{}) + n = append(n, nm) + } + // find the last key (map) and set our data + for i, d := range n { + for k := range d { + z := i + 1 + if z == len(n) { + n[i][k] = data + break + } + n[i][k] = n[z] + } + } + + return n[0] +} + +// getParents returns a slice of parent charts in reverse order. +func getParents(c *chart.Chart, out []*chart.Chart) []*chart.Chart { + if len(out) == 0 { + out = []*chart.Chart{c} + } + for _, ch := range c.Dependencies { + if len(ch.Dependencies) > 0 { + out = append(out, ch) + out = getParents(ch, out) + } + } + + return out +} + +// processImportValues merges values from child to parent based on the chart's dependencies' ImportValues field. +func processImportValues(c *chart.Chart) error { + reqs, err := LoadRequirements(c) + if err != nil { + return err + } + // combine chart values and empty config to get Values + cvals, err := CoalesceValues(c, &chart.Config{}) + if err != nil { + return err + } + b := make(map[string]interface{}, 0) + // import values from each dependency if specified in import-values + for _, r := range reqs.Dependencies { + if len(r.ImportValues) > 0 { + var outiv []interface{} + for _, riv := range r.ImportValues { + switch iv := riv.(type) { + case map[string]interface{}: + nm := map[string]string{ + "child": iv["child"].(string), + "parent": iv["parent"].(string), + } + outiv = append(outiv, nm) + s := r.Name + "." + nm["child"] + // get child table + vv, err := cvals.Table(s) + if err != nil { + log.Printf("Warning: ImportValues missing table: %v", err) + continue + } + // create value map from child to be merged into parent + vm := pathToMap(nm["parent"], vv.AsMap()) + b = coalesceTables(cvals, vm) + case string: + nm := map[string]string{ + "child": "exports." + iv, + "parent": ".", + } + outiv = append(outiv, nm) + s := r.Name + "." + nm["child"] + vm, err := cvals.Table(s) + 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 + } + } + b = coalesceTables(b, cvals) + y, err := yaml.Marshal(b) + if err != nil { + return err + } + + // set the new values + c.Values = &chart.Config{Raw: string(y)} + + return nil +} + +// ProcessRequirementsImportValues imports specified chart values from child to parent. +func ProcessRequirementsImportValues(c *chart.Chart) error { + pc := getParents(c, nil) + for i := len(pc) - 1; i >= 0; i-- { + processImportValues(pc[i]) + } + + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements_test.go new file mode 100644 index 000000000..24388f86c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/requirements_test.go @@ -0,0 +1,499 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "sort" + "testing" + + "strconv" + + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/version" +) + +func TestLoadRequirements(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyRequirements(t, c) +} + +func TestLoadRequirementsLock(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + verifyRequirementsLock(t, c) +} +func TestRequirementsTagsNonValue(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags with no effect + v := &chart.Config{Raw: "tags:\n nothinguseful: false\n\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsTagsDisabledL1(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags disabling a group + v := &chart.Config{Raw: "tags:\n front-end: false\n\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsTagsEnabledL1(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags disabling a group and enabling a different group + v := &chart.Config{Raw: "tags:\n front-end: false\n\n back-end: true\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart2", "subchartb", "subchartc"} + + verifyRequirementsEnabled(t, c, v, e) +} + +func TestRequirementsTagsDisabledL2(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags disabling only children, children still enabled since tag front-end=true in values.yaml + v := &chart.Config{Raw: "tags:\n subcharta: false\n\n subchartb: false\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsTagsDisabledL1Mixed(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags disabling all parents/children with additional tag re-enabling a parent + v := &chart.Config{Raw: "tags:\n front-end: false\n\n subchart1: true\n\n back-end: false\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsConditionsNonValue(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags with no effect + v := &chart.Config{Raw: "subchart1:\n nothinguseful: false\n\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subcharta", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsConditionsEnabledL1Both(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // conditions enabling the parent charts, but back-end (b, c) is still disabled via values.yaml + v := &chart.Config{Raw: "subchart1:\n enabled: true\nsubchart2:\n enabled: true\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsConditionsDisabledL1Both(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // conditions disabling the parent charts, effectively disabling children + v := &chart.Config{Raw: "subchart1:\n enabled: false\nsubchart2:\n enabled: false\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart"} + + verifyRequirementsEnabled(t, c, v, e) +} + +func TestRequirementsConditionsSecond(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // conditions a child using the second condition path of child's condition + v := &chart.Config{Raw: "subchart1:\n subcharta:\n enabled: false\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsCombinedDisabledL2(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags enabling a parent/child group with condition disabling one child + v := &chart.Config{Raw: "subchartc:\n enabled: false\ntags:\n back-end: true\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart", "subchart1", "subchart2", "subcharta", "subchartb", "subchartb"} + + verifyRequirementsEnabled(t, c, v, e) +} +func TestRequirementsCombinedDisabledL1(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + // tags will not enable a child if parent is explicitly disabled with condition + v := &chart.Config{Raw: "subchart1:\n enabled: false\ntags:\n front-end: true\n"} + // expected charts including duplicates in alphanumeric order + e := []string{"parentchart"} + + verifyRequirementsEnabled(t, c, v, e) +} + +func verifyRequirementsEnabled(t *testing.T, c *chart.Chart, v *chart.Config, e []string) { + out := []*chart.Chart{} + err := ProcessRequirementsEnabled(c, v) + if err != nil { + t.Errorf("Error processing enabled requirements %v", err) + } + out = extractCharts(c, out) + // build list of chart names + p := []string{} + for _, r := range out { + p = append(p, r.Metadata.Name) + } + //sort alphanumeric and compare to expectations + sort.Strings(p) + if len(p) != len(e) { + t.Errorf("Error slice lengths do not match got %v, expected %v", len(p), len(e)) + return + } + for i := range p { + if p[i] != e[i] { + t.Errorf("Error slice values do not match got %v, expected %v", p[i], e[i]) + } + } +} + +// extractCharts recursively searches chart dependencies returning all charts found +func extractCharts(c *chart.Chart, out []*chart.Chart) []*chart.Chart { + + if len(c.Metadata.Name) > 0 { + out = append(out, c) + } + for _, d := range c.Dependencies { + out = extractCharts(d, out) + } + return out +} +func TestProcessRequirementsImportValues(t *testing.T) { + c, err := Load("testdata/subpop") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + v := &chart.Config{Raw: ""} + + e := make(map[string]string) + + e["imported-chart1.SC1bool"] = "true" + e["imported-chart1.SC1float"] = "3.14" + e["imported-chart1.SC1int"] = "100" + e["imported-chart1.SC1string"] = "dollywood" + e["imported-chart1.SC1extra1"] = "11" + e["imported-chart1.SPextra1"] = "helm rocks" + e["imported-chart1.SC1extra1"] = "11" + + e["imported-chartA.SCAbool"] = "false" + e["imported-chartA.SCAfloat"] = "3.1" + e["imported-chartA.SCAint"] = "55" + e["imported-chartA.SCAstring"] = "jabba" + e["imported-chartA.SPextra3"] = "1.337" + e["imported-chartA.SC1extra2"] = "1.337" + e["imported-chartA.SCAnested1.SCAnested2"] = "true" + + e["imported-chartA-B.SCAbool"] = "false" + e["imported-chartA-B.SCAfloat"] = "3.1" + e["imported-chartA-B.SCAint"] = "55" + e["imported-chartA-B.SCAstring"] = "jabba" + + e["imported-chartA-B.SCBbool"] = "true" + e["imported-chartA-B.SCBfloat"] = "7.77" + e["imported-chartA-B.SCBint"] = "33" + e["imported-chartA-B.SCBstring"] = "boba" + e["imported-chartA-B.SPextra5"] = "k8s" + e["imported-chartA-B.SC1extra5"] = "tiller" + + e["overridden-chart1.SC1bool"] = "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"] = "jaberwocky" + 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"] = "jaberwocky" + 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" + + verifyRequirementsImportValues(t, c, v, e) +} +func verifyRequirementsImportValues(t *testing.T, c *chart.Chart, v *chart.Config, e map[string]string) { + + err := ProcessRequirementsImportValues(c) + if err != nil { + t.Errorf("Error processing import values requirements %v", err) + } + cv := c.GetValues() + cc, err := ReadValues([]byte(cv.Raw)) + if err != nil { + t.Errorf("Error reading import values %v", err) + } + for kk, vv := range e { + pv, err := cc.PathValue(kk) + if err != nil { + t.Fatalf("Error retrieving import values table %v %v", kk, err) + return + } + + switch pv.(type) { + case float64: + s := strconv.FormatFloat(pv.(float64), 'f', -1, 64) + if s != vv { + t.Errorf("Failed to match imported float value %v with expected %v", s, vv) + return + } + case bool: + b := strconv.FormatBool(pv.(bool)) + if b != vv { + t.Errorf("Failed to match imported bool value %v with expected %v", b, vv) + return + } + default: + if pv.(string) != vv { + t.Errorf("Failed to match imported string value %v with expected %v", pv, vv) + return + } + } + + } +} + +func TestGetAliasDependency(t *testing.T) { + c, err := Load("testdata/frobnitz") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + req, err := LoadRequirements(c) + if err != nil { + t.Fatalf("Failed to load requirement for testdata: %s", err) + } + if len(req.Dependencies) == 0 { + t.Fatalf("There are no requirements to test") + } + + // Success case + aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]) + if aliasChart == nil { + t.Fatalf("Failed to get dependency chart for alias %s", req.Dependencies[0].Name) + } + if req.Dependencies[0].Alias != "" { + if aliasChart.Metadata.Name != req.Dependencies[0].Alias { + t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Alias, aliasChart.Metadata.Name) + } + } else if aliasChart.Metadata.Name != req.Dependencies[0].Name { + t.Fatalf("Dependency chart name should be %s but got %s", req.Dependencies[0].Name, aliasChart.Metadata.Name) + } + + if req.Dependencies[0].Version != "" { + if !version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { + t.Fatalf("Dependency chart version is not in the compatible range") + } + + } + + // Failure case + req.Dependencies[0].Name = "something-else" + if aliasChart := getAliasDependency(c.Dependencies, req.Dependencies[0]); aliasChart != nil { + t.Fatalf("expected no chart but got %s", aliasChart.Metadata.Name) + } + + req.Dependencies[0].Version = "something else which is not in the compatible range" + if version.IsCompatibleRange(req.Dependencies[0].Version, aliasChart.Metadata.Version) { + t.Fatalf("Dependency chart version which is not in the compatible range should cause a failure other than a success ") + } + +} + +func TestDependentChartAliases(t *testing.T) { + c, err := Load("testdata/dependent-chart-alias") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if len(c.Dependencies) == 0 { + t.Fatal("There are no dependencies to run this test") + } + + origLength := len(c.Dependencies) + if err := ProcessRequirementsEnabled(c, c.Values); err != nil { + t.Fatalf("Expected no errors but got %q", err) + } + + if len(c.Dependencies) == origLength { + t.Fatal("Expected alias dependencies to be added, but did not got that") + } + + reqmts, err := LoadRequirements(c) + if err != nil { + t.Fatalf("Cannot load requirements for test chart, %v", err) + } + + if len(c.Dependencies) != len(reqmts.Dependencies) { + t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies)) + } + +} + +func TestDependentChartWithSubChartsAbsentInRequirements(t *testing.T) { + c, err := Load("testdata/dependent-chart-no-requirements-yaml") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if len(c.Dependencies) != 2 { + t.Fatalf("Expected 2 dependencies for this chart, but got %d", len(c.Dependencies)) + } + + origLength := len(c.Dependencies) + if err := ProcessRequirementsEnabled(c, c.Values); err != nil { + t.Fatalf("Expected no errors but got %q", err) + } + + if len(c.Dependencies) != origLength { + t.Fatal("Expected no changes in dependencies to be, but did something got changed") + } + +} + +func TestDependentChartWithSubChartsHelmignore(t *testing.T) { + if _, err := Load("testdata/dependent-chart-helmignore"); err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } +} + +func TestDependentChartsWithSubChartsSymlink(t *testing.T) { + c, err := Load("testdata/joonix") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + if c.Metadata.Name != "joonix" { + t.Fatalf("Unexpected chart name: %s", c.Metadata.Name) + } + if n := len(c.Dependencies); n != 1 { + t.Fatalf("Expected 1 dependency for this chart, but got %d", n) + } +} + +func TestDependentChartsWithSubchartsAllSpecifiedInRequirements(t *testing.T) { + c, err := Load("testdata/dependent-chart-with-all-in-requirements-yaml") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if len(c.Dependencies) == 0 { + t.Fatal("There are no dependencies to run this test") + } + + origLength := len(c.Dependencies) + if err := ProcessRequirementsEnabled(c, c.Values); err != nil { + t.Fatalf("Expected no errors but got %q", err) + } + + if len(c.Dependencies) != origLength { + t.Fatal("Expected no changes in dependencies to be, but did something got changed") + } + + reqmts, err := LoadRequirements(c) + if err != nil { + t.Fatalf("Cannot load requirements for test chart, %v", err) + } + + if len(c.Dependencies) != len(reqmts.Dependencies) { + t.Fatalf("Expected number of chart dependencies %d, but got %d", len(reqmts.Dependencies), len(c.Dependencies)) + } + +} + +func TestDependentChartsWithSomeSubchartsSpecifiedInRequirements(t *testing.T) { + c, err := Load("testdata/dependent-chart-with-mixed-requirements-yaml") + if err != nil { + t.Fatalf("Failed to load testdata: %s", err) + } + + if len(c.Dependencies) == 0 { + t.Fatal("There are no dependencies to run this test") + } + + origLength := len(c.Dependencies) + if err := ProcessRequirementsEnabled(c, c.Values); err != nil { + t.Fatalf("Expected no errors but got %q", err) + } + + if len(c.Dependencies) != origLength { + t.Fatal("Expected no changes in dependencies to be, but did something got changed") + } + + reqmts, err := LoadRequirements(c) + if err != nil { + t.Fatalf("Cannot load requirements for test chart, %v", err) + } + + if len(c.Dependencies) <= len(reqmts.Dependencies) { + t.Fatalf("Expected more dependencies than specified in requirements.yaml(%d), but got %d", len(reqmts.Dependencies), len(c.Dependencies)) + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save.go new file mode 100644 index 000000000..bff32dde5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save.go @@ -0,0 +1,219 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/proto/hapi/chart" +) + +var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") + +// SaveDir saves a chart as files in a directory. +func SaveDir(c *chart.Chart, dest string) error { + // Create the chart directory + outdir := filepath.Join(dest, c.Metadata.Name) + if err := os.Mkdir(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 + if c.Values != nil && len(c.Values.Raw) > 0 { + vf := filepath.Join(outdir, ValuesfileName) + if err := ioutil.WriteFile(vf, []byte(c.Values.Raw), 0755); err != nil { + return err + } + } + + for _, d := range []string{TemplatesDir, ChartsDir} { + if err := os.MkdirAll(filepath.Join(outdir, d), 0755); err != nil { + return err + } + } + + // Save templates + for _, f := range c.Templates { + n := filepath.Join(outdir, f.Name) + if err := ioutil.WriteFile(n, f.Data, 0755); err != nil { + return err + } + } + + // Save files + for _, f := range c.Files { + n := filepath.Join(outdir, f.TypeUrl) + + d := filepath.Dir(n) + if err := os.MkdirAll(d, 0755); err != nil { + return err + } + + if err := ioutil.WriteFile(n, f.Value, 0755); 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 err + } + } + return nil +} + +// Save creates an archived chart to the given directory. +// +// This takes an existing chart and a destination directory. +// +// If the directory is /foo, and the chart is named bar, with version 1.0.0, this +// will generate /foo/bar-1.0.0.tgz. +// +// This returns the absolute path to the chart archive file. +func Save(c *chart.Chart, outDir string) (string, error) { + // Create archive + if fi, err := os.Stat(outDir); err != nil { + return "", err + } else if !fi.IsDir() { + return "", fmt.Errorf("location %s is not a directory", outDir) + } + + if c.Metadata == nil { + return "", errors.New("no Chart.yaml data") + } + + cfile := c.Metadata + if cfile.Name == "" { + return "", errors.New("no chart name specified (Chart.yaml)") + } else if cfile.Version == "" { + return "", errors.New("no chart version specified (Chart.yaml)") + } + + filename := fmt.Sprintf("%s-%s.tgz", cfile.Name, cfile.Version) + filename = filepath.Join(outDir, filename) + if stat, err := os.Stat(filepath.Dir(filename)); os.IsNotExist(err) { + if err := os.MkdirAll(filepath.Dir(filename), 0755); !os.IsExist(err) { + return "", err + } + } else if !stat.IsDir() { + return "", fmt.Errorf("is not a directory: %s", filepath.Dir(filename)) + } + + 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 +} + +func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { + base := filepath.Join(prefix, c.Metadata.Name) + + // Save Chart.yaml + cdata, err := yaml.Marshal(c.Metadata) + if err != nil { + return err + } + if err := writeToTar(out, base+"/Chart.yaml", cdata); err != nil { + return err + } + + // Save values.yaml + if c.Values != nil && len(c.Values.Raw) > 0 { + if err := writeToTar(out, base+"/values.yaml", []byte(c.Values.Raw)); 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.TypeUrl) + if err := writeToTar(out, n, f.Value); err != nil { + return err + } + } + + // Save dependencies + for _, dep := range c.Dependencies { + if err := writeTarContents(out, dep, base+"/charts"); 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: name, + Mode: 0755, + Size: int64(len(body)), + } + if err := out.WriteHeader(h); err != nil { + return err + } + if _, err := out.Write(body); err != nil { + return err + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save_test.go new file mode 100644 index 000000000..5e1564299 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/save_test.go @@ -0,0 +1,114 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "strings" + "testing" + + "github.com/golang/protobuf/ptypes/any" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +func TestSave(t *testing.T) { + tmp, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "ahab", + Version: "1.2.3.4", + }, + Values: &chart.Config{ + Raw: "ship: Pequod", + }, + Files: []*any.Any{ + {TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")}, + }, + } + + where, err := Save(c, tmp) + if err != nil { + t.Fatalf("Failed to save: %s", err) + } + if !strings.HasPrefix(where, tmp) { + t.Fatalf("Expected %q to start with %q", where, tmp) + } + if !strings.HasSuffix(where, ".tgz") { + t.Fatalf("Expected %q to end with .tgz", where) + } + + c2, err := LoadFile(where) + if err != nil { + t.Fatal(err) + } + + if c2.Metadata.Name != c.Metadata.Name { + t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) + } + if c2.Values.Raw != c.Values.Raw { + t.Fatal("Values data did not match") + } + if len(c2.Files) != 1 || c2.Files[0].TypeUrl != "scheherazade/shahryar.txt" { + t.Fatal("Files data did not match") + } +} + +func TestSaveDir(t *testing.T) { + tmp, err := ioutil.TempDir("", "helm-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "ahab", + Version: "1.2.3.4", + }, + Values: &chart.Config{ + Raw: "ship: Pequod", + }, + Files: []*any.Any{ + {TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")}, + }, + } + + if err := SaveDir(c, tmp); err != nil { + t.Fatalf("Failed to save: %s", err) + } + + c2, err := LoadDir(tmp + "/ahab") + if err != nil { + t.Fatal(err) + } + + if c2.Metadata.Name != c.Metadata.Name { + t.Fatalf("Expected chart archive to have %q, got %q", c.Metadata.Name, c2.Metadata.Name) + } + if c2.Values.Raw != c.Values.Raw { + t.Fatal("Values data did not match") + } + if len(c2.Files) != 1 || c2.Files[0].TypeUrl != "scheherazade/shahryar.txt" { + t.Fatal("Files data did not match") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/Chart.yaml new file mode 100644 index 000000000..eeef737ff --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/Chart.yaml @@ -0,0 +1,4 @@ +name: albatross +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/values.yaml new file mode 100644 index 000000000..3121cd7ce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/albatross/values.yaml @@ -0,0 +1,4 @@ +albatross: "true" + +global: + author: Coleridge diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/chartfiletest.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/chartfiletest.yaml new file mode 100644 index 000000000..134cd1109 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/chartfiletest.yaml @@ -0,0 +1,20 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/coleridge.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/coleridge.yaml new file mode 100644 index 000000000..b6579628b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/coleridge.yaml @@ -0,0 +1,12 @@ +poet: "Coleridge" +title: "Rime of the Ancient Mariner" +stanza: ["at", "length", "did", "cross", "an", "Albatross"] + +mariner: + with: "crossbow" + shot: "ALBATROSS" + +water: + water: + where: "everywhere" + nor: "any drop to drink" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/Chart.yaml @@ -0,0 +1,17 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml new file mode 100644 index 000000000..aab6cddf7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/requirements.yaml @@ -0,0 +1,12 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-alias/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore new file mode 100644 index 000000000..8a71bc82e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/.helmignore @@ -0,0 +1,2 @@ +ignore/ +.* diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/Chart.yaml @@ -0,0 +1,17 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/.ignore_me new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-helmignore/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/Chart.yaml @@ -0,0 +1,17 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-no-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/Chart.yaml @@ -0,0 +1,17 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/requirements.yaml new file mode 100644 index 000000000..5eb0bc98b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/requirements.yaml @@ -0,0 +1,7 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-all-in-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore new file mode 100644 index 000000000..9973a57b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml new file mode 100644 index 000000000..7c071c27b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/Chart.yaml @@ -0,0 +1,17 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt new file mode 100644 index 000000000..2010438c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE new file mode 100644 index 000000000..6121943b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md new file mode 100644 index 000000000..8cf4cc3d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me new file mode 100644 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml new file mode 100644 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md new file mode 100644 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml new file mode 100644 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml new file mode 100644 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/requirements.yaml new file mode 100644 index 000000000..5f8bdc5a8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/requirements.yaml @@ -0,0 +1,4 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/dependent-chart-with-mixed-requirements-yaml/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz-1.2.3.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..fb21cd08fb08fd2f4e45e940634ce1cdb0421457 GIT binary patch literal 2070 zcmV+x2Dc zVQyr3R8em|NM&qo0PLOLZ`(K$$NQ|mVxYXPWl8>ZHInWHO%_{RHdt(P2P_VU3oVT; zB9y2lX*<4b{`UhZS$33ol{Svjr2IZKvPhA##1Cgk4&ABXlZ>kWbw4IVC~rkl_HN(u zecvAq2IjBt`}M#6&>vOD=6n#2g26l3`;9!Lxl}~F^ZlomQ~z?WL?p|&B8u`%jvWA! zah0IB!qs?vydZ3j4gg*&K}>=VGe5Bfok|6b7V zTmIY70USf|>S0P5Lc|R^QXfp|Y%WaS;5lV8cXiW;bCRO#I1(d6dc}pS6M$wwpiFaL zu4P+2Miu`G)0`27vO4lqzMrn3iGRNz*7zUvf}Z8S4gHLaak#nh47z_pj8iga3fv_Z zEOu{iEx>PD75|?0kLWDp0{2}D`@ZGB4IRKw>zQk3%NHo8tRXV1LXLBA0RNO^h7V=(;FE%cNXR(Mb*3W! z2`-TFTze9Z^Ai^k7bj2v&8GYZkcglX1jX|X(`g_u=aMQeR<;jnNfrMwkK{9T0*&^6 zaDV=PWchDJn?<@U4*;eb*K!z8E&#mrjaog6D*kE2GfysN&rnAb|AXPMZvXpzyZ+yb z4u8B(8I-HhM{eNx?vEdwL%Ep2b;>e1mW$buTQ1lymrHNi^SGFGL*Mtiaxrs@Y=4=> z0f4f!YeuuHhcXB6-@osg)FS(xJAx&RRdVEpgP{uv(pjR)4>Ue<6-woPZ~zWtl+lON z(sj`d`mk$G=`?|nGTEn8Nae!yksG?OGXIvv_x(-jL*IodW$eh!$YSMo@0KAk;#q78 z2?nN=enTk&V_nFhejp#dq0Y48U*I2eDp2?M(qlZj+Fu<_{P+C2{qOe%cKyE<#h7Cj zV-`^~0YIH{DW9;MX1I;w7+l}eG@6qeSN-9r9W!u{TLq>>&X+ z3rp;`tR4LCH5#~yfB#wm52z%26TJT_)}9p#&@Iz>aU@w;zaJNVy30~aSc zIgh`(H)!I2*sHDohkn0j`ENzF4Lnygi^uRQkDU}1iHRbc=>D4A&fB${C1+uP2~ zjaN8|#b*bAkqI(0kN`+fP(@~Uul$T+L8Brw6w8a}>(XmwmC4oZRXNHt@F)B+=c>qD z+s6dpYQMyjNjDNm6vplt+^ze#psBu@h-b09y>%U#W27$>sM~}3l`|Txbe&Z|^f@k2 zxZssJ{+pXDrt)&rgxPPj8SyL$Rpo$(rsg@g1x(|f*DJ<65l>U1pZLGs?u2Gt$q;k* z2lu`2`|f|*=zDkYzt@GpCjJNa=l@0{>;JbRv;MbU?OzfJGt5wEU|=q3HZv>qWiy3K z%@hh_<30)%JrZzIyR> zx56UjdO~8&$>pnG7u~^s`6(q*1y7y~XdM6h{TlzH(IB+^x1l@Z|KnmGta|EEp{H1Qt>y}JJ&27%?j4IRMAedB3fyXPEiZQ=j@_?NTJ zl#7%o?HgzttTUWo#E3xS@fPg_azcd!#bKh4rNkI$rZ}wlTvP^F>WlS7a0ITo&dxNv zh?@BiU&j91^}km174|>0_W$kZG5kNb{r7wQ`}W`R-->qH|M1lhZLJ=mI{#&u#$E#e zn(co*{&z6!+4%of^u_kSh~lX{029Dtdx}@9UHtDe0HAUFKdAFR^zHnAJKACYO(STY z0jOeuW>h8fiHXxwJY^k5vY9#B#~i`fP7yqtcJaSi&e`4>{&l|y*6jZWb^m|l+xfp% zv`x7E4S?@YyZGPdc7R6ze^CGZpOO9jzm~K+{(nN0;8H#+HhBKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml new file mode 100644 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/charts/mariner-4.3.2.tgz new file mode 100644 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/ignore/me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.lock b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.lock new file mode 100644 index 000000000..6fcc2ed9f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.yaml new file mode 100644 index 000000000..5eb0bc98b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/requirements.yaml @@ -0,0 +1,7 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/templates/template.tpl new file mode 100644 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/values.yaml new file mode 100644 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..692dd6aba5da8382262cd94fae736785709b339f GIT binary patch literal 2079 zcmV+)2;lc0iwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PLM@Z`-yO$NO5J;y`&j%aZ(bYc$;r+6*gh7Yu9e1%``@g_e#j zA{42Sv>o3p-+dq@KSXh-xpCDd<^M$^OB5-K{P7%;hwfC038(u1zfDMTtr$^R_f1Bm z4pt;*FOH)y48zf2VE=|;SpORi<7jhi&qw`H+R;}Oh;S}6 z(Nu6X_5cVMIzxGltMU4HLD(EU0KCzX*bDZkJWUGvAC!s;K88i)rKl1~3vEANW>f)H zKw#a-0xuIejv>>!7LEv-5_6fLeT04AYbr!{LZr7?zrQGmgPzxN{qN!5)~q^W2hhZS z)UWeDhz29ae;Yc1Gbk$@rj#Kh!lI+h!IaMC()JC3S2c6rG<~=rIr^TbFtMa>xbV#d zpd1KP*Zj8Iv(sVP!@tJ7U__(Z90j^~Ojppvf7p*}{EvF^(DC1feniFu+};Kj-9I43 zDVZ}3{!&Pm`geB@;AgEp{0GKUqBAZe9;b{({-f}L{f{HZe;X?6x$7wZMzx3kcNZTn z&)&Ze^o>4F8IAmp>h`}s>^uA4iZ+9E4wNfR7=u(K^BlPj?8rD_6uE|+tRSVTU}Ob4 zqbhJc#flL&}lkmJBgoLy_ff6X6itj)ng3b`MC_2ns1Br!DR13LseNaoT!sA*Xg;*;&-39 z8u{Nw1D7W{g-8$V4Vw5L_G;_@aois`{##LP1J5<(=@@<%sh6WBDbZvb-G6%<{DKUL zLK`f6ch|YS4NCX0{OkcRGC^hs5&#)Ws>$sBogYvxXi_GIa(VG$oqH{>>hk9CHYutz z@CW=p7rIPb+s6dp8^0tl%~>MRnEGSzcb?;t=H_7%!Bcg2=X|v`@&EeW z3FTeI5DWi1|7944{{PzOdmH&b!a`sZ|APnff1@yT{(md7>woLj{v}Z`Lypn_19L_B z%&yQ^%@nQ-Q&0j`Ir~?E=NS*;YHKI1Af*x$Gm_tKCn9FhzXC;VLB`*ZSZ>Y z)>gTt=wI^*N{$+px1PmQI&uU2=cB{;|Fp?~CjR57SNH#8xBtH#oxt0N#?w}I;GOJj z;s5pQr}v$ykU7!DH!wEXBzTJnBNDC0Tec1;2$c%7fQdO)3R9q53s{M{+!$bGZZY~M|IbH<@&9@3f7q+@AIEP0e>?i9{g0pg(AMd1 z=;QoX0UJjM0BE-V_4wb>u1mO(4kLVKkB%`%Fm_V}pH0pDZx?oU_lO_zi(t+E ze^mGXN0FQVYel<+yWar#_SDS(2($kiz+7iSj^T}9C}}!Fhq;rJlhbq2p8)^>|Nlfr J;bQ=3007~8JqQ2* literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/.helmignore new file mode 100755 index 000000000..9973a57b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/.helmignore @@ -0,0 +1 @@ +ignore/ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/Chart.yaml new file mode 100755 index 000000000..49df2a046 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/Chart.yaml @@ -0,0 +1,20 @@ +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 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/INSTALL.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/INSTALL.txt new file mode 100755 index 000000000..2010438c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/INSTALL.txt @@ -0,0 +1 @@ +This is an install document. The client may display this. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE new file mode 100755 index 000000000..6121943b1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/LICENSE @@ -0,0 +1 @@ +LICENSE placeholder. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/README.md new file mode 100755 index 000000000..8cf4cc3d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/README.md @@ -0,0 +1,11 @@ +# Frobnitz + +This is an example chart. + +## Usage + +This is an example. It has no usage. + +## Development + +For developer info, see the top-level repository. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/_ignore_me b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/_ignore_me new file mode 100755 index 000000000..2cecca682 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/_ignore_me @@ -0,0 +1 @@ +This should be ignored by the loader, but may be included in a chart. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/Chart.yaml new file mode 100755 index 000000000..38a4aaa54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/Chart.yaml @@ -0,0 +1,4 @@ +name: alpine +description: Deploy a basic Alpine Linux pod +version: 0.1.0 +home: https://k8s.io/helm diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/README.md new file mode 100755 index 000000000..a7c84fc41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.toml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml new file mode 100755 index 000000000..171e36156 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/Chart.yaml @@ -0,0 +1,4 @@ +name: mast1 +description: A Helm chart for Kubernetes +version: 0.1.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml new file mode 100755 index 000000000..42c39c262 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast1/values.yaml @@ -0,0 +1,4 @@ +# Default values for mast1. +# This is a YAML-formatted file. +# Declare name/value pairs to be passed into your templates. +# name = "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/charts/mast2-0.1.0.tgz new file mode 100755 index 0000000000000000000000000000000000000000..ced5a4a6adf946f76033b6ee584affc12433cb78 GIT binary patch literal 325 zcmV-L0lNMliwFRxBUV=c1MSw|YJ)Ho2Jl|{6o>BKov2ah-PkS$dy3RWS}syLpID4If6g{VtOEXtaBMKbNS zqRDw>=dBp!{dV&0PTP0q&C|N>lXXt-@itxw6Y{^`DeLnWW%?A)n7>C|RUhXsgbeu$ zF~=_IIe#g+SrMn$%(;J_|DcTCP^g0JS-aNm4}L!m8@i)M-5Y9`%Ajtv^fYa?9kkaj zJ8J8~B+f<7*=}6cSg*6cei`_&c>Y7mE>#=&?)@Lnf3ci@t|adNONjYC00000 X0000000000z?FFgy`&fL04M+ebFHRB literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml new file mode 100755 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/values.yaml new file mode 100755 index 000000000..6c2aab7ba --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: "my-alpine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/charts/mariner-4.3.2.tgz new file mode 100755 index 0000000000000000000000000000000000000000..3af333e76a3c268ce6d23f5ae8ba59387a75a38a GIT binary patch literal 1034 zcmV+l1oitLiwFRoe*ISf1MQf5Y!pQt$1j*vT_1mmq6v0Vv`Rzw_Pu%yR;*Eys*pfJ z2%2>6ZtiaDKCZKKh4VpUVhPw(Vq+5%p#1{`AAuCqVmyct4N85WAVyGp#n=>Wj1n4S z;p*((BjusRcz0-+&)sGA_I7u6_dCDuclIoZ4IANLpo|EDpsOnITP@cLl9Frl08!F) zQI-`+mw+HDk|+d#TF#Ryka7vc^i(WJNH|3z353tP9o;Mz`UG2>HDDfLsOK$)?XBLPk%{~bzM;vs=p>GasUXWKb3R2#PzqKg+d@d3b-h8BiKk1 z!?8nP9+;0z3q-t;0b&jY&8aZLHX_L7+7WjBjTBzyB`)E3N2#gdF81Xx{vn0>_f>Yw z69X6O|EeIVvL?{_R~21m{$B|S`eW3VGBC1`P25t)z?A;4N@wN2u8Au1|4I-=Nn}Tn z9Wjs_;sB@zxkP|w7!vHbE?oxzMoGsth=bE1kRT-KhJrz~0$NEE@e#)gp6Md~F2#hX z5qOaoSTy`MDJVw}6%*2EFGB=ep#M*v|4Cl`Gyg9?1^wHhnL;IZ{v1>Jzocru{;DFY zvZ8zXD}u!QzY@#>_o5g~nFQoUfIrdC4+@@}1r{d^7tl8ZOXofKKt27{yHO|#Vg~j8 zVi?2?l1PR9EFg|$)|=3d`%9eHLBxa@`N5JKXCMg;>;mF|u(#~G^mv9%zowlO21P6K z`p>0NjlUbqkkWIm|I;Rd5{?vdH?_;X^S7q6M{?qA4k~LcYf~K+m|0+Ut*A;=jm8X{kE*t&)SnE4r zM%A}7hwC=o@X3?4*}Ff!Z$VXtJ9Kn&ORKnfAk%L&Pun>D;)phO*KK{PKizeFOIz=n zy*o!wAB(P@-@O0Xt)Vxb?^^Wuz7^Z9s$0DG<(79l=RDI;yJg+Mmmb}CrBT z-!}KT=4?9KaOjJkYcgQjk@g!0$KO1e>6trl|4%m!{=B?u+4!Emw}w`2=pOv&TJ81s z9#V$gq1JEK%Ii?$Zt#20ucxXP^=-QPiQlpZ?i{{l`pMzO-oRHAJB1&st=E><&K!N! zdCK`A=yslR;D-|}568cpeRH7ta7WJ{hsPI;tY0|c!1o)c|1u-g@WGDm6TNL{-wPw* z(Wd>^e|==&5o^vX4U=!@9%pP?@baZ~f!i;ZpbQ3s!C){L3 + + Example icon + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/ignore/me.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/ignore/me.txt new file mode 100755 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.lock b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.lock new file mode 100755 index 000000000..6fcc2ed9f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.lock @@ -0,0 +1,8 @@ +dependencies: + - name: alpine + version: "0.1.0" + repository: https://example.com/charts + - name: mariner + version: "4.3.2" + repository: https://example.com/charts +digest: invalid diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.yaml new file mode 100755 index 000000000..5eb0bc98b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/requirements.yaml @@ -0,0 +1,7 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/templates/template.tpl b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/templates/template.tpl new file mode 100755 index 000000000..c651ee6a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/templates/template.tpl @@ -0,0 +1 @@ +Hello {{.Name | default "world"}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/values.yaml new file mode 100755 index 000000000..61f501258 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/frobnitz_backslash/values.yaml @@ -0,0 +1,6 @@ +# A values file contains configuration. + +name: "Some Name" + +section: + name: "Name in a section" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/genfrob.sh b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/genfrob.sh new file mode 100755 index 000000000..8f2cddec1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/genfrob.sh @@ -0,0 +1,12 @@ +#!/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 + +# Pack the frobnitz chart. +echo "Packing frobnitz" +tar --exclude=ignore/* -zcvf frobnitz-1.2.3.tgz frobnitz diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/Chart.yaml new file mode 100644 index 000000000..c3464c56e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: joonix +version: 1.2.3 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/charts/frobnitz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/charts/frobnitz new file mode 120000 index 000000000..fde1b78ac --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/joonix/charts/frobnitz @@ -0,0 +1 @@ +../../frobnitz \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/Chart.yaml new file mode 100644 index 000000000..4d52794c6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/Chart.yaml @@ -0,0 +1,4 @@ +name: mariner +description: A Helm chart for Kubernetes +version: 4.3.2 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/mariner/charts/albatross-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..0b66d27fe1bb83cde374420591ce39b5ee58543f GIT binary patch literal 347 zcmV-h0i^yPiwFRoe*ISf1MSw`YQrEH2H+j%Dx??CeJ1`u&PGoLy9(3VreJC@rm(x8 zX-fj5`=#kN_C6<`h|#?2r*V~u!ME-G-DHSJOUaI{I<=IGQ+vjEt_vzu!Kq-xg)E9Y zCa0UysB5DMX)2A0vFhTow&7yG)NQ?fXPomohW3vzL;j1}ggAdTjl1?Y(*ICGyq!PK zd7pnS3ay0{DSst&%Ac-1kxTx0{yWpyk`22cTiJFQw)?pCyOKSyN9!84>~{uR`@o*M z%7&edw#^PUw8u^~=X1(x-;Xn!Wk0%GbA5)&B41Dtb&pGEL74_#ol>prTw=m`( zZL@bK9qp@cfp6q5bA|kGVa)$vTxZ)U9snQJf0Fvu`%medQ2%dX$UhbD7&<%4vW@eV tab?Ds>0<3e$rj$(uw88|syhGx0000000000006*m_5v>lSt. +# 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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/Chart.yaml new file mode 100644 index 000000000..b725af916 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: moby +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/Chart.yaml new file mode 100644 index 000000000..d9a3bfd5f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: pequod +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/Chart.yaml new file mode 100644 index 000000000..c3cdf397d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: ahab +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml new file mode 100644 index 000000000..86c3f63aa --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/charts/ahab/values.yaml @@ -0,0 +1,2 @@ +scope: ahab +name: ahab diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/values.yaml new file mode 100644 index 000000000..d6e34b274 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/pequod/values.yaml @@ -0,0 +1,2 @@ +scope: pequod +name: pequod diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/Chart.yaml new file mode 100644 index 000000000..f6819c06d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: spouter +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/values.yaml new file mode 100644 index 000000000..f71d92a9f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/charts/spouter/values.yaml @@ -0,0 +1 @@ +scope: spouter diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/values.yaml new file mode 100644 index 000000000..54e1ce463 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/moby/values.yaml @@ -0,0 +1,9 @@ +scope: moby +name: moby +override: bad +top: nope +bottom: exists +right: exists +left: exists +front: exists +back: exists diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/Chart.yaml new file mode 100644 index 000000000..bbb0941c3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: parentchart +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/README.md b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/README.md new file mode 100644 index 000000000..e43fbfe9c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/README.md @@ -0,0 +1,18 @@ +## Subpop + +This chart is for testing the processing of enabled/disabled charts +via conditions and tags. + +Currently there are three levels: + +```` +parent +-1 tags: front-end, subchart1 +--A tags: front-end, subchartA +--B tags: front-end, subchartB +-2 tags: back-end, subchart2 +--B tags: back-end, subchartB +--C tags: back-end, subchartC +```` + +Tags and conditions are currently in requirements.yaml files. \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml new file mode 100644 index 000000000..f58d82818 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subchart1 +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml new file mode 100644 index 000000000..be3edcefb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subcharta +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml new file mode 100644 index 000000000..fdf75aa91 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + 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: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml new file mode 100644 index 000000000..f0381ae6a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartA/values.yaml @@ -0,0 +1,17 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# subchartA +service: + name: apache + type: ClusterIP + externalPort: 80 + internalPort: 80 +SCAdata: + SCAbool: false + SCAfloat: 3.1 + SCAint: 55 + SCAstring: "jabba" + SCAnested1: + SCAnested2: true + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml new file mode 100644 index 000000000..c3c6bbaf0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subchartb +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml new file mode 100644 index 000000000..fdf75aa91 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + 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: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml new file mode 100644 index 000000000..774fdd75c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/charts/subchartB/values.yaml @@ -0,0 +1,35 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 + +SCBdata: + SCBbool: true + SCBfloat: 7.77 + SCBint: 33 + SCBstring: "boba" + +exports: + SCBexported1: + SCBexported1A: + SCBexported1B: 1965 + + SCBexported2: + SCBexported2A: "blaster" + +global: + kolla: + nova: + api: + all: + port: 8774 + metadata: + all: + port: 8775 + + + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml new file mode 100644 index 000000000..abfe85e76 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/requirements.yaml @@ -0,0 +1,32 @@ +dependencies: + - name: subcharta + repository: http://localhost:10191 + version: 0.1.0 + condition: subcharta.enabled,subchart1.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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt new file mode 100644 index 000000000..4bdf443f6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/NOTES.txt @@ -0,0 +1 @@ +Sample notes for {{ .Chart.Name }} \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml new file mode 100644 index 000000000..3835a3d0b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + namespace: "{{ .Release.Namespace }}" + release-name: "{{ .Release.Name }}" + kube-version/major: "{{ .Capabilities.KubeVersion.Major }}" + kube-version/minor: "{{ .Capabilities.KubeVersion.Minor }}" + kube-version/gitversion: "v{{ .Capabilities.KubeVersion.Major }}.{{ .Capabilities.KubeVersion.Minor }}.0" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: {{ .Values.service.name }} + selector: + app: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml new file mode 100644 index 000000000..72d3fa5c8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart1/values.yaml @@ -0,0 +1,55 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +# subchart1 +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 + + +SC1data: + SC1bool: true + SC1float: 3.14 + SC1int: 100 + SC1string: "dollywood" + SC1extra1: 11 + +imported-chartA: + SC1extra2: 1.337 + +overridden-chartA: + SCAbool: true + SCAfloat: 3.14 + SCAint: 100 + SCAstring: "jabathehut" + 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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml new file mode 100644 index 000000000..2c76b0786 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subchart2 +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml new file mode 100644 index 000000000..c3c6bbaf0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subchartb +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml new file mode 100644 index 000000000..0935aadce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: subchart2-{{ .Chart.Name }} + labels: + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.externalPort }} + targetPort: {{ .Values.service.internalPort }} + protocol: TCP + name: subchart2-{{ .Values.service.name }} + selector: + app: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartB/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml new file mode 100644 index 000000000..dcc45c088 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: subchartc +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml new file mode 100644 index 000000000..fdf75aa91 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + 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: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/charts/subchartC/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/requirements.yaml new file mode 100644 index 000000000..1f0023a08 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/requirements.yaml @@ -0,0 +1,15 @@ +dependencies: + - name: subchartb + repository: http://localhost:10191 + version: 0.1.0 + condition: subchartb.enabled,subchart2.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/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml new file mode 100644 index 000000000..fdf75aa91 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + 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: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml new file mode 100644 index 000000000..5e5b21065 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/charts/subchart2/values.yaml @@ -0,0 +1,21 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml new file mode 100644 index 000000000..bbb0941c3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +description: A Helm chart for Kubernetes +name: parentchart +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml new file mode 100644 index 000000000..fdf75aa91 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Chart.Name }} + labels: + 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: {{ .Chart.Name }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/values.yaml new file mode 100644 index 000000000..4ed3b7ad3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/noreqs/values.yaml @@ -0,0 +1,26 @@ +# Default values for subchart. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. +replicaCount: 1 +image: + repository: nginx + tag: stable + pullPolicy: IfNotPresent +service: + name: nginx + type: ClusterIP + externalPort: 80 + internalPort: 80 +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + + +# switch-like +tags: + front-end: true + back-end: false diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/requirements.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/requirements.yaml new file mode 100644 index 000000000..a8eb0aace --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/requirements.yaml @@ -0,0 +1,31 @@ +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 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/values.yaml new file mode 100644 index 000000000..55e872d41 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/testdata/subpop/values.yaml @@ -0,0 +1,41 @@ +# 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: "jaberwocky" + SPextra4: true + +imported-chartA-B: + SPextra5: "k8s" + +overridden-chartA-B: + SCAbool: true + SCAfloat: 41.3 + SCAint: 808 + SCAstring: "jaberwocky" + SCBbool: false + SCBfloat: 1.99 + SCBint: 77 + SCBstring: "jango" + SPextra6: 111 + +tags: + front-end: true + back-end: false + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/transform.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/transform.go new file mode 100644 index 000000000..7cbb754fb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/transform.go @@ -0,0 +1,25 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "strings" + +// Transform performs a string replacement of the specified source for +// a given key with the replacement string +func Transform(src string, key string, replacement string) []byte { + return []byte(strings.Replace(src, key, replacement, -1)) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values.go new file mode 100644 index 000000000..66a2658d5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values.go @@ -0,0 +1,435 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "fmt" + "io" + "io/ioutil" + "log" + "strings" + + "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/timestamp" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// ErrNoTable indicates that a chart does not have a matching table. +type ErrNoTable error + +// ErrNoValue indicates that Values does not contain a key with a value +type ErrNoValue error + +// 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) { + names := strings.Split(name, ".") + table := v + var err error + + for _, n := range names { + table, err = tableLookup(table, n) + if err != nil { + return table, err + } + } + 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 v == nil || 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 { + //return yaml.NewEncoder(w).Encode(v) + 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(fmt.Errorf("no table named %q (%v)", simple, v)) + } + 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 + } + + var e ErrNoTable = fmt.Errorf("no table named %q", simple) + return map[string]interface{}{}, e +} + +// 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 +} + +// 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) +} + +// 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 *chart.Config) (Values, error) { + cvals := Values{} + // Parse values if not nil. We merge these at the top level because + // the passed-in values are in the same namespace as the parent chart. + if vals != nil { + evals, err := ReadValues([]byte(vals.Raw)) + if err != nil { + return cvals, err + } + cvals, err = coalesce(chrt, evals) + if err != nil { + return cvals, err + } + } + + var err error + cvals, err = coalesceDeps(chrt, cvals) + return cvals, err +} + +// coalesce coalesces the dest values and the chart values, giving priority to the dest values. +// +// This is a helper function for CoalesceValues. +func coalesce(ch *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { + var err error + dest, err = coalesceValues(ch, dest) + if err != nil { + return dest, err + } + coalesceDeps(ch, dest) + return dest, nil +} + +// coalesceDeps coalesces the dependencies of the given chart. +func coalesceDeps(chrt *chart.Chart, dest map[string]interface{}) (map[string]interface{}, error) { + for _, subchart := range chrt.Dependencies { + if c, ok := dest[subchart.Metadata.Name]; !ok { + // If dest doesn't already have the key, create it. + dest[subchart.Metadata.Name] = map[string]interface{}{} + } else if !istable(c) { + return dest, fmt.Errorf("type mismatch on %s: %t", subchart.Metadata.Name, c) + } + if dv, ok := dest[subchart.Metadata.Name]; ok { + dvmap := dv.(map[string]interface{}) + + // Get globals out of dest and merge them into dvmap. + coalesceGlobals(dvmap, dest) + + var err error + // Now coalesce the rest of the values. + dest[subchart.Metadata.Name], err = coalesce(subchart, dvmap) + 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(dest, src map[string]interface{}) map[string]interface{} { + var dg, sg map[string]interface{} + + if destglob, ok := dest[GlobalKey]; !ok { + dg = map[string]interface{}{} + } else if dg, ok = destglob.(map[string]interface{}); !ok { + log.Printf("warning: skipping globals because destination %s is not a table.", GlobalKey) + return dg + } + + if srcglob, ok := src[GlobalKey]; !ok { + sg = map[string]interface{}{} + } else if sg, ok = srcglob.(map[string]interface{}); !ok { + log.Printf("warning: skipping globals because source %s is not a table.", GlobalKey) + return dg + } + + // 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 { + if destvmap, ok := destv.(map[string]interface{}); ok { + // Basically, we reverse order of coalesce here to merge + // top-down. + coalesceTables(vv, destvmap) + dg[key] = vv + continue + } else { + log.Printf("Conflict: cannot merge map onto non-map for %q. Skipping.", key) + } + } else { + // Here there is no merge. We're just adding. + dg[key] = vv + } + } else if dv, ok := dg[key]; ok && istable(dv) { + // It's not clear if this condition can actually ever trigger. + log.Printf("key %s is table. Skipping", key) + continue + } + // TODO: Do we need to do any additional checking on the value? + dg[key] = val + } + dest[GlobalKey] = dg + return dest +} + +func copyMap(src map[string]interface{}) map[string]interface{} { + dest := make(map[string]interface{}, len(src)) + for k, v := range src { + dest[k] = v + } + return dest +} + +// coalesceValues builds up a values map for a particular chart. +// +// Values in v will override the values in the chart. +func coalesceValues(c *chart.Chart, v map[string]interface{}) (map[string]interface{}, error) { + // If there are no values in the chart, we just return the given values + if c.Values == nil || c.Values.Raw == "" { + return v, nil + } + + nv, err := ReadValues([]byte(c.Values.Raw)) + if err != nil { + // On error, we return just the overridden values. + // FIXME: We should log this error. It indicates that the YAML data + // did not parse. + return v, fmt.Errorf("error reading default values (%s): %s", c.Values.Raw, err) + } + + for key, val := range nv { + 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 { + log.Printf("warning: skipped value for %s: Not a table.", key) + continue + } + // Because v has higher precedence than nv, dest values override src + // values. + coalesceTables(dest, src) + } + } else { + // If the key is not in v, copy it from nv. + v[key] = val + } + } + return v, nil +} + +// coalesceTables merges a source map into a destination map. +// +// dest is considered authoritative. +func coalesceTables(dst, src map[string]interface{}) map[string]interface{} { + // Because dest has higher precedence than src, dest values override src + // values. + for key, val := range src { + if istable(val) { + if innerdst, ok := dst[key]; !ok { + dst[key] = val + } else if istable(innerdst) { + coalesceTables(innerdst.(map[string]interface{}), val.(map[string]interface{})) + } else { + log.Printf("warning: cannot overwrite table with non table for %s (%v)", key, val) + } + continue + } else if dv, ok := dst[key]; ok && istable(dv) { + log.Printf("warning: destination for %s is a table. Ignoring non-table value %v", key, val) + continue + } else if !ok { // <- ok is still in scope from preceding conditional. + dst[key] = val + continue + } + } + return dst +} + +// ReleaseOptions represents the additional release options needed +// for the composition of the final values struct +type ReleaseOptions struct { + Name string + Time *timestamp.Timestamp + Namespace string + IsUpgrade bool + IsInstall bool + Revision int +} + +// ToRenderValues composes the struct from the data coming from the Releases, Charts and Values files +// +// WARNING: This function is deprecated for Helm > 2.1.99 Use ToRenderValuesCaps() instead. It will +// remain in the codebase to stay SemVer compliant. +// +// In Helm 3.0, this will be changed to accept Capabilities as a fourth parameter. +func ToRenderValues(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions) (Values, error) { + caps := &Capabilities{APIVersions: DefaultVersionSet} + return ToRenderValuesCaps(chrt, chrtVals, options, caps) +} + +// ToRenderValuesCaps 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 ToRenderValuesCaps(chrt *chart.Chart, chrtVals *chart.Config, options ReleaseOptions, caps *Capabilities) (Values, error) { + + top := map[string]interface{}{ + "Release": map[string]interface{}{ + "Name": options.Name, + "Time": options.Time, + "Namespace": options.Namespace, + "IsUpgrade": options.IsUpgrade, + "IsInstall": options.IsInstall, + "Revision": options.Revision, + "Service": "Tiller", + }, + "Chart": chrt.Metadata, + "Files": NewFiles(chrt.Files), + "Capabilities": caps, + } + + vals, err := CoalesceValues(chrt, chrtVals) + if err != nil { + return top, err + } + + top["Values"] = vals + return top, nil +} + +// istable is a special-purpose function to see if the present thing matches the definition of a YAML table. +func istable(v interface{}) bool { + _, ok := v.(map[string]interface{}) + return ok +} + +// PathValue takes a path that traverses a YAML structure and returns the value at the end of that path. +// The path starts at the root of the YAML structure and is comprised of YAML keys separated by periods. +// Given the following YAML data the value at path "chapter.one.title" is "Loomings". +// +// chapter: +// one: +// title: "Loomings" +func (v Values) PathValue(ypath string) (interface{}, error) { + if len(ypath) == 0 { + return nil, errors.New("YAML path string cannot be zero length") + } + yps := strings.Split(ypath, ".") + if len(yps) == 1 { + // if exists must be root key not table + vals := v.AsMap() + k := yps[0] + if _, ok := vals[k]; ok && !istable(vals[k]) { + // key found + return vals[yps[0]], nil + } + // key not found + return nil, ErrNoValue(fmt.Errorf("%v is not a value", k)) + } + // join all elements of YAML path except last to get string table path + ypsLen := len(yps) + table := yps[:ypsLen-1] + st := strings.Join(table, ".") + // get the last element as a string key + key := yps[ypsLen-1:] + sk := string(key[0]) + // get our table for table path + t, err := v.Table(st) + if err != nil { + //no table + return nil, ErrNoValue(fmt.Errorf("%v is not a value", sk)) + } + // check table for key and ensure value is not a table + if k, ok := t[sk]; ok && !istable(k) { + // key found + return k, nil + } + + // key not found + return nil, ErrNoValue(fmt.Errorf("key not found: %s", sk)) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values_test.go b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values_test.go new file mode 100644 index 000000000..d9b03c21a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/chartutil/values_test.go @@ -0,0 +1,459 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "encoding/json" + "fmt" + "testing" + "text/template" + + "github.com/golang/protobuf/ptypes/any" + + kversion "k8s.io/apimachinery/pkg/version" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/timeconv" + "k8s.io/helm/pkg/version" +) + +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 TestToRenderValuesCaps(t *testing.T) { + + chartValues := ` +name: al Rashid +where: + city: Basrah + title: caliph +` + overideValues := ` +name: Haroun +where: + city: Baghdad + date: 809 CE +` + + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "test"}, + Templates: []*chart.Template{}, + Values: &chart.Config{Raw: chartValues}, + Dependencies: []*chart.Chart{ + { + Metadata: &chart.Metadata{Name: "where"}, + Values: &chart.Config{Raw: ""}, + }, + }, + Files: []*any.Any{ + {TypeUrl: "scheherazade/shahryar.txt", Value: []byte("1,001 Nights")}, + }, + } + v := &chart.Config{Raw: overideValues} + + o := ReleaseOptions{ + Name: "Seven Voyages", + Time: timeconv.Now(), + Namespace: "al Basrah", + IsInstall: true, + Revision: 5, + } + + caps := &Capabilities{ + APIVersions: DefaultVersionSet, + TillerVersion: version.GetVersionProto(), + KubeVersion: &kversion.Info{Major: "1"}, + } + + res, err := ToRenderValuesCaps(c, v, o, caps) + 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 rev := relmap["Revision"]; rev.(int) != 5 { + t.Errorf("Expected release revision %d, got %q", 5, rev) + } + if relmap["IsUpgrade"].(bool) { + t.Error("Expected upgrade to be false.") + } + if !relmap["IsInstall"].(bool) { + t.Errorf("Expected install to be true.") + } + if data := res["Files"].(Files)["scheherazade/shahryar.txt"]; string(data) != "1,001 Nights" { + t.Errorf("Expected file '1,001 Nights', got %q", string(data)) + } + if !res["Capabilities"].(*Capabilities).APIVersions.Has("v1") { + t.Error("Expected Capabilities to have v1 as an API") + } + if res["Capabilities"].(*Capabilities).TillerVersion.SemVer == "" { + t.Error("Expected Capabilities to have a Tiller version") + } + if res["Capabilities"].(*Capabilities).KubeVersion.Major != "1" { + t.Error("Expected Capabilities to have a Kube version") + } + + var vals Values + 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)) + if err := tt.Execute(&b, v); err != nil { + return "", err + } + return b.String(), nil +} + +// ref: http://www.yaml.org/spec/1.2/spec.html#id2803362 +var testCoalesceValuesYaml = ` +top: yup +bottom: null +right: Null +left: NULL +front: ~ +back: "" + +global: + name: Ishmael + subject: Queequeg + nested: + boat: true + +pequod: + global: + name: Stinky + harpooner: Tashtego + nested: + boat: false + sail: true + ahab: + scope: whale +` + +func TestCoalesceValues(t *testing.T) { + tchart := "testdata/moby" + c, err := LoadDir(tchart) + if err != nil { + t.Fatal(err) + } + + tvals := &chart.Config{Raw: testCoalesceValuesYaml} + + v, err := CoalesceValues(c, tvals) + 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.global.name}}", "Ishmael"}, + {"{{.pequod.ahab.global.subject}}", "Queequeg"}, + {"{{.pequod.ahab.global.harpooner}}", "Tashtego"}, + {"{{.pequod.global.name}}", "Ishmael"}, + {"{{.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}}", ""}, + } + + 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) + } + } +} + +func TestCoalesceTables(t *testing.T) { + dst := map[string]interface{}{ + "name": "Ishmael", + "address": map[string]interface{}{ + "street": "123 Spouter Inn Ct.", + "city": "Nantucket", + }, + "details": map[string]interface{}{ + "friends": []string{"Tashtego"}, + }, + "boat": "pequod", + } + src := map[string]interface{}{ + "occupation": "whaler", + "address": map[string]interface{}{ + "state": "MA", + "street": "234 Spouter Inn Ct.", + }, + "details": "empty", + "boat": map[string]interface{}{ + "mast": true, + }, + } + + // 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 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"]) + } +} +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.doesntexist"); err == nil { + t.Errorf("Non-existent key should return error: %s\n%v", err, d) + } + if _, err := d.PathValue("chapter.doesntexist.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/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader.go b/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader.go new file mode 100644 index 000000000..59b9d4d75 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader.go @@ -0,0 +1,357 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strings" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/urlutil" +) + +// 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 + // HelmHome is the $HELM_HOME. + HelmHome helmpath.Home + // Getter collection for the operation + Getters getter.Providers + // Chart repository username + Username string + // Chart repository password + Password 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, g, err := c.ResolveChartVersion(ref, version) + if err != nil { + return "", nil, err + } + + data, err := g.Get(u.String()) + if err != nil { + return "", nil, err + } + + name := filepath.Base(u.Path) + destfile := filepath.Join(dest, name) + if err := ioutil.WriteFile(destfile, data.Bytes(), 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, fmt.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 := ioutil.WriteFile(provfile, body.Bytes(), 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 +} + +// ResolveChartVersion resolves a chart reference to a URL. +// +// It returns the URL as well as a preconfigured repo.Getter that can fetch +// the URL. +// +// A reference may be an HTTP 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, getter.Getter, error) { + u, err := url.Parse(ref) + if err != nil { + return nil, nil, fmt.Errorf("invalid chart URL format: %s", ref) + } + + rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile()) + if err != nil { + return u, nil, 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 { + getterConstructor, err := c.Getters.ByScheme(u.Scheme) + if err != nil { + return u, nil, err + } + getter, err := getterConstructor(ref, "", "", "") + return u, getter, err + } + return u, nil, err + } + r, err := repo.NewChartRepository(rc, c.Getters) + c.setCredentials(r) + // 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 return. + return u, r.Client, err + } + + // See if it's of the form: repo/path_to_chart + p := strings.SplitN(u.Path, "/", 2) + if len(p) < 2 { + return u, nil, fmt.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, nil, err + } + + r, err := repo.NewChartRepository(rc, c.Getters) + if err != nil { + return u, nil, err + } + c.setCredentials(r) + + // Next, we need to load the index, and actually look up the chart. + i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) + if err != nil { + return u, r.Client, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) + } + + cv, err := i.Get(chartName, version) + if err != nil { + return u, r.Client, fmt.Errorf("chart %q matching %s not found in %s index. (try 'helm repo update'). %s", chartName, version, r.Config.Name, err) + } + + if len(cv.URLs) == 0 { + return u, r.Client, fmt.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, r.Client, fmt.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, r.Client, err + } + q := repoURL.Query() + // We need a trailing slash for ResolveReference to work, but make sure there isn't already one + repoURL.Path = strings.TrimSuffix(repoURL.Path, "/") + "/" + u = repoURL.ResolveReference(u) + u.RawQuery = q.Encode() + return u, r.Client, err + } + + return u, r.Client, nil +} + +// If HttpGetter is used, this method sets the configured repository credentials on the HttpGetter. +func (c *ChartDownloader) setCredentials(r *repo.ChartRepository) { + if t, ok := r.Client.(*getter.HttpGetter); ok { + t.SetCredentials(c.getRepoCredentials(r)) + } +} + +// If this ChartDownloader is not configured to use credentials, and the chart repository sent as an argument is, +// then the repository's configured credentials are returned. +// Else, this ChartDownloader's credentials are returned. +func (c *ChartDownloader) getRepoCredentials(r *repo.ChartRepository) (username, password string) { + username = c.Username + password = c.Password + if r != nil && r.Config != nil { + if username == "" { + username = r.Config.Username + } + if password == "" { + password = r.Config.Password + } + } + return +} + +// 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 string, keyring string) (*provenance.Verification, error) { + // For now, error out if it's not a tar file. + if fi, err := os.Stat(path); err != nil { + return nil, err + } else if fi.IsDir() { + return nil, errors.New("unpacked charts cannot be verified") + } else if !isTar(path) { + return nil, errors.New("chart must be a tgz file") + } + + provfile := path + ".prov" + if _, err := os.Stat(provfile); err != nil { + return nil, fmt.Errorf("could not load provenance file %s: %s", provfile, err) + } + + sig, err := provenance.NewFromKeyring(keyring, "") + if err != nil { + return nil, fmt.Errorf("failed to load keyring: %s", err) + } + 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.ToLower(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, fmt.Errorf("no URL found for repository %s", name) + } + return rc, nil + } + } + return nil, fmt.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.RepoFile) (*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 + } + + i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(r.Config.Name)) + if err != nil { + return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) + } + + 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 +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader_test.go b/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader_test.go new file mode 100644 index 000000000..80efa77e8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/chart_downloader_test.go @@ -0,0 +1,315 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/repo/repotest" +) + +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: "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{ + HelmHome: helmpath.Home("testdata/helmhome"), + Out: os.Stderr, + Getters: getter.All(environment.EnvSettings{}), + } + + 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 %s", tt.name, err) + continue + } + if got := u.String(); got != tt.expect { + t.Errorf("%s: expected %s, got %s", tt.name, tt.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 TestDownload(t *testing.T) { + expect := "Call me Ishmael" + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, expect) + })) + defer srv.Close() + + provider, err := getter.ByScheme("http", environment.EnvSettings{}) + if err != nil { + t.Fatal("No http provider found") + } + + g, err := provider.New(srv.URL, "", "", "") + if err != nil { + t.Fatal(err) + } + got, err := g.Get(srv.URL) + if err != nil { + t.Fatal(err) + } + + if got.String() != expect { + t.Errorf("Expected %q, got %q", expect, got.String()) + } + + // test with server backed by basic auth + 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) + } + fmt.Fprint(w, expect) + })) + + defer basicAuthSrv.Close() + + u, _ := url.ParseRequestURI(basicAuthSrv.URL) + httpgetter, err := getter.NewHTTPGetter(u.String(), "", "", "") + if err != nil { + t.Fatal(err) + } + httpgetter.SetCredentials("username", "password") + 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 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) { + tmp, err := ioutil.TempDir("", "helm-downloadto-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + hh := helmpath.Home(tmp) + dest := filepath.Join(hh.String(), "dest") + configDirectories := []string{ + hh.String(), + hh.Repository(), + hh.Cache(), + dest, + } + for _, p := range configDirectories { + if fi, err := os.Stat(p); err != nil { + if err := os.MkdirAll(p, 0755); err != nil { + t.Fatalf("Could not create %s: %s", p, err) + } + } else if !fi.IsDir() { + t.Fatalf("%s must be a directory", p) + } + } + + // Set up a fake repo + srv := repotest.NewServer(tmp) + defer srv.Stop() + if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { + t.Error(err) + return + } + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + c := ChartDownloader{ + HelmHome: hh, + Out: os.Stderr, + Verify: VerifyAlways, + Keyring: "testdata/helm-test-key.pub", + Getters: getter.All(environment.EnvSettings{}), + } + cname := "/signtest-0.1.0.tgz" + where, v, err := c.DownloadTo(srv.URL()+cname, "", dest) + if err != nil { + t.Error(err) + return + } + + 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) + return + } +} + +func TestDownloadTo_VerifyLater(t *testing.T) { + tmp, err := ioutil.TempDir("", "helm-downloadto-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + + hh := helmpath.Home(tmp) + dest := filepath.Join(hh.String(), "dest") + configDirectories := []string{ + hh.String(), + hh.Repository(), + hh.Cache(), + dest, + } + for _, p := range configDirectories { + if fi, err := os.Stat(p); err != nil { + if err := os.MkdirAll(p, 0755); err != nil { + t.Fatalf("Could not create %s: %s", p, err) + } + } else if !fi.IsDir() { + t.Fatalf("%s must be a directory", p) + } + } + + // Set up a fake repo + srv := repotest.NewServer(tmp) + defer srv.Stop() + if _, err := srv.CopyCharts("testdata/*.tgz*"); err != nil { + t.Error(err) + return + } + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + c := ChartDownloader{ + HelmHome: hh, + Out: os.Stderr, + Verify: VerifyLater, + Getters: getter.All(environment.EnvSettings{}), + } + cname := "/signtest-0.1.0.tgz" + where, _, err := c.DownloadTo(srv.URL()+cname, "", dest) + if err != nil { + t.Error(err) + return + } + + 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.Error(err) + return + } + if _, err := os.Stat(filepath.Join(dest, cname+".prov")); err != nil { + t.Error(err) + return + } +} + +func TestScanReposForURL(t *testing.T) { + hh := helmpath.Home("testdata/helmhome") + c := ChartDownloader{ + HelmHome: hh, + Out: os.Stderr, + Verify: VerifyLater, + Getters: getter.All(environment.EnvSettings{}), + } + + u := "http://example.com/alpine-0.2.0.tgz" + rf, err := repo.LoadRepositoriesFile(c.HelmHome.RepositoryFile()) + 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/src/vendor/github.com/kubernetes/helm/pkg/downloader/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/downloader/doc.go new file mode 100644 index 000000000..fb54936b8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 (like +HELM_HOME). This library contains many functions that depend on a specific +filesystem layout. +*/ +package downloader diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager.go b/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager.go new file mode 100644 index 000000000..89a839b54 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager.go @@ -0,0 +1,656 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "errors" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "sync" + + "github.com/Masterminds/semver" + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/repo" + "k8s.io/helm/pkg/resolver" + "k8s.io/helm/pkg/urlutil" +) + +// 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 + // HelmHome is the $HELM_HOME directory + HelmHome helmpath.Home + // 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 +} + +// 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, err := chartutil.LoadRequirementsLock(c) + if err != nil { + return m.Update() + } + + // A lock must accompany a requirements.yaml file. + req, err := chartutil.LoadRequirements(c) + if err != nil { + return fmt.Errorf("requirements.yaml cannot be opened: %s", err) + } + if sum, err := resolver.HashReq(req); err != nil || sum != lock.Digest { + return fmt.Errorf("requirements.lock is out of sync with requirements.yaml") + } + + // 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/ + if err := m.downloadAll(lock.Dependencies); err != nil { + return err + } + + return nil +} + +// Update updates a local charts directory. +// +// It first reads the requirements.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 requirements file is found, we consider this a successful + // completion. + req, err := chartutil.LoadRequirements(c) + if err != nil { + if err == chartutil.ErrRequirementsNotFound { + fmt.Fprintf(m.Out, "No requirements found in %s/charts.\n", m.ChartPath) + return nil + } + return err + } + + // Hash requirements.yaml + hash, err := resolver.HashReq(req) + if err != nil { + return err + } + + // Check that all of the repos we're dependent on actually exist and + // the repo index names. + repoNames, err := m.getRepoNames(req.Dependencies) + if err != nil { + return err + } + + // For each repo in the file, update the cached copy of that repo + 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 + // requirements the requirements.yaml + lock, err := m.resolve(req, repoNames, hash) + 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 + } + + // If the lock file hasn't changed, don't write a new one. + oldLock, err := chartutil.LoadRequirementsLock(c) + if err == nil && oldLock.Digest == lock.Digest { + return nil + } + + // Finally, we need to write the lockfile. + return writeLock(m.ChartPath, lock) +} + +func (m *Manager) loadChartDir() (*chart.Chart, error) { + if fi, err := os.Stat(m.ChartPath); err != nil { + return nil, fmt.Errorf("could not find %s: %s", m.ChartPath, err) + } else if !fi.IsDir() { + return nil, errors.New("only unpacked charts can be updated") + } + return chartutil.LoadDir(m.ChartPath) +} + +// resolve takes a list of requirements and translates them into an exact version to download. +// +// This returns a lock file, which has all of the requirements normalized to a specific version. +func (m *Manager) resolve(req *chartutil.Requirements, repoNames map[string]string, hash string) (*chartutil.RequirementsLock, error) { + res := resolver.New(m.ChartPath, m.HelmHome) + return res.Resolve(req, repoNames, hash) +} + +// 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 []*chartutil.Dependency) error { + repos, err := m.loadChartRepositories() + if err != nil { + return err + } + + destPath := filepath.Join(m.ChartPath, "charts") + tmpPath := filepath.Join(m.ChartPath, "tmpcharts") + + // Create 'charts' directory if it doesn't already exist. + if fi, err := os.Stat(destPath); err != nil { + if err := os.MkdirAll(destPath, 0755); err != nil { + return err + } + } else if !fi.IsDir() { + return fmt.Errorf("%q is not a directory", destPath) + } + + if err := os.Rename(destPath, tmpPath); err != nil { + return fmt.Errorf("Unable to move current charts to tmp dir: %v", err) + } + + if err := os.MkdirAll(destPath, 0755); err != nil { + return err + } + + fmt.Fprintf(m.Out, "Saving %d charts\n", len(deps)) + var saveError error + for _, dep := range deps { + 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) + if err != nil { + saveError = err + break + } + dep.Version = ver + continue + } + + fmt.Fprintf(m.Out, "Downloading %s from repo %s\n", dep.Name, dep.Repository) + + // Any failure to resolve/download a chart should fail: + // https://github.com/kubernetes/helm/issues/1439 + churl, username, password, err := findChartURL(dep.Name, dep.Version, dep.Repository, repos) + if err != nil { + saveError = fmt.Errorf("could not find %s: %s", churl, err) + break + } + + dl := ChartDownloader{ + Out: m.Out, + Verify: m.Verify, + Keyring: m.Keyring, + HelmHome: m.HelmHome, + Getters: m.Getters, + Username: username, + Password: password, + } + + if _, _, err := dl.DownloadTo(churl, "", destPath); err != nil { + saveError = fmt.Errorf("could not download %s: %s", churl, err) + break + } + } + + if saveError == nil { + fmt.Fprintln(m.Out, "Deleting outdated charts") + for _, dep := range deps { + if err := m.safeDeleteDep(dep.Name, tmpPath); err != nil { + return err + } + } + if err := move(tmpPath, destPath); err != nil { + return err + } + if err := os.RemoveAll(tmpPath); err != nil { + return fmt.Errorf("Failed to remove %v: %v", tmpPath, err) + } + } else { + fmt.Fprintln(m.Out, "Save error occurred: ", saveError) + fmt.Fprintln(m.Out, "Deleting newly downloaded charts, restoring pre-update state") + for _, dep := range deps { + if err := m.safeDeleteDep(dep.Name, destPath); err != nil { + return err + } + } + if err := os.RemoveAll(destPath); err != nil { + return fmt.Errorf("Failed to remove %v: %v", destPath, err) + } + if err := os.Rename(tmpPath, destPath); err != nil { + return fmt.Errorf("Unable to move current charts to tmp dir: %v", err) + } + return saveError + } + return nil +} + +// safeDeleteDep deletes any versions of the given dependency in the given directory. +// +// It does this by first matching the file name to an expected pattern, then loading +// the file to verify that it is a chart with the same name as the given name. +// +// Because it requires tar file introspection, it is more intensive than a basic delete. +// +// This will only return errors that should stop processing entirely. Other errors +// will emit log messages or be ignored. +func (m *Manager) safeDeleteDep(name, dir string) error { + files, err := filepath.Glob(filepath.Join(dir, name+"-*.tgz")) + if err != nil { + // Only for ErrBadPattern + return err + } + for _, fname := range files { + ch, err := chartutil.LoadFile(fname) + if err != nil { + fmt.Fprintf(m.Out, "Could not verify %s for deletion: %s (Skipping)", fname, err) + continue + } + if ch.Metadata.Name != name { + // This is not the file you are looking for. + 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 []*chartutil.Dependency) error { + rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) + if err != nil { + return err + } + repos := rf.Repositories + + // Verify that all repositories referenced in the deps are actually known + // by Helm. + missing := []string{} + for _, dd := range deps { + // If repo is from local path, continue + if strings.HasPrefix(dd.Repository, "file://") { + continue + } + + found := false + if dd.Repository == "" { + found = true + } else { + for _, repo := range repos { + if urlutil.Equal(repo.URL, strings.TrimSuffix(dd.Repository, "/")) { + found = true + } + } + } + if !found { + missing = append(missing, dd.Repository) + } + } + if len(missing) > 0 { + return fmt.Errorf("no repository definition for %s. Please add the missing repos via 'helm repo add'", strings.Join(missing, ", ")) + } + return nil +} + +// getRepoNames returns the repo names of the referenced deps which can be used to fetch the cahced index file. +func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string, error) { + rf, err := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) + if err != 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 { + // 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 + } + + 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 { + missing = append(missing, dd.Repository) + } + } + if len(missing) > 0 { + 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 stable +repository, use "https://kubernetes-charts.storage.googleapis.com/" or "@stable" instead of +"stable". 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 := repo.LoadRepositoriesFile(m.HelmHome.RepositoryFile()) + if err != nil { + return err + } + repos := rf.Repositories + if len(repos) > 0 { + // This prints warnings straight to out. + if err := m.parallelRepoUpdate(repos); err != nil { + return err + } + } + return nil +} + +func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) error { + out := m.Out + fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...") + 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(m.HelmHome.Cache()); err != nil { + fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err) + } else { + fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", r.Config.Name) + } + wg.Done() + }(r) + } + wg.Wait() + fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈") + 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 findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (url, username, password string, err error) { + 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 + return + } + } + err = fmt.Errorf("chart %s not found in %s", name, repoURL) + return +} + +// 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, fmt.Errorf("Base URL failed to parse: %s", err) + } + + 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{} + repoyaml := m.HelmHome.RepositoryFile() + + // Load repositories.yaml file + rf, err := repo.LoadRepositoriesFile(repoyaml) + if err != nil { + return indices, fmt.Errorf("failed to load %s: %s", repoyaml, err) + } + + for _, re := range rf.Repositories { + lname := re.Name + cacheindex := m.HelmHome.CacheIndex(lname) + index, err := repo.LoadIndexFile(cacheindex) + 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 *chartutil.RequirementsLock) error { + data, err := yaml.Marshal(lock) + if err != nil { + return err + } + dest := filepath.Join(chartpath, "requirements.lock") + return ioutil.WriteFile(dest, data, 0644) +} + +// archive a dep chart from local directory and save it into charts/ +func tarFromLocalDir(chartpath string, name string, repo string, version string) (string, error) { + destPath := filepath.Join(chartpath, "charts") + + if !strings.HasPrefix(repo, "file://") { + return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo) + } + + origPath, err := resolver.GetLocalPath(repo, chartpath) + if err != nil { + return "", err + } + + ch, err := chartutil.LoadDir(origPath) + if err != nil { + return "", err + } + + constraint, err := semver.NewConstraint(version) + if err != nil { + return "", fmt.Errorf("dependency %s has an invalid version/constraint format: %s", name, err) + } + + 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 "", fmt.Errorf("can't get a valid version for dependency %s", name) +} + +// move files from tmppath to destpath +func move(tmpPath, destPath string) error { + files, _ := ioutil.ReadDir(tmpPath) + for _, file := range files { + filename := file.Name() + tmpfile := filepath.Join(tmpPath, filename) + destfile := filepath.Join(destPath, filename) + if err := os.Rename(tmpfile, destfile); err != nil { + return fmt.Errorf("Unable to move local charts to charts dir: %v", err) + } + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager_test.go b/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager_test.go new file mode 100644 index 000000000..1ff2a9c17 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/manager_test.go @@ -0,0 +1,170 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "reflect" + "testing" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm/helmpath" +) + +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"}, + } + + 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) { + b := bytes.NewBuffer(nil) + m := &Manager{ + Out: b, + HelmHome: helmpath.Home("testdata/helmhome"), + } + repos, err := m.loadChartRepositories() + if err != nil { + t.Fatal(err) + } + + name := "alpine" + version := "0.1.0" + repoURL := "http://example.com/charts" + + churl, username, password, err := findChartURL(name, version, repoURL, repos) + if err != nil { + t.Fatal(err) + } + if churl != "https://kubernetes-charts.storage.googleapis.com/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) + } +} + +func TestGetRepoNames(t *testing.T) { + b := bytes.NewBuffer(nil) + m := &Manager{ + Out: b, + HelmHome: helmpath.Home("testdata/helmhome"), + } + tests := []struct { + name string + req []*chartutil.Dependency + expect map[string]string + err bool + }{ + { + name: "no repo definition failure", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "http://example.com/test"}, + }, + err: true, + }, + { + name: "no repo definition failure -- stable repo", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "stable"}, + }, + err: true, + }, + { + name: "no repo definition failure", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "http://example.com"}, + }, + expect: map[string]string{"oedipus-rex": "testing"}, + }, + { + name: "repo from local path", + req: []*chartutil.Dependency{ + {Name: "local-dep", Repository: "file://./testdata/signtest"}, + }, + expect: map[string]string{"local-dep": "file://./testdata/signtest"}, + }, + { + name: "repo alias (alias:)", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "alias:testing"}, + }, + expect: map[string]string{"oedipus-rex": "testing"}, + }, + { + name: "repo alias (@)", + req: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "@testing"}, + }, + expect: map[string]string{"oedipus-rex": "testing"}, + }, + } + + for _, tt := range tests { + l, err := m.getRepoNames(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) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.pub b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.pub new file mode 100644 index 0000000000000000000000000000000000000000..38714f25adaf701b08e11fd559a587074bbde0e4 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.secret b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/helm-test-key.secret new file mode 100644 index 0000000000000000000000000000000000000000..a966aef93ed97d01d764f29940738df6df2d9d24 GIT binary patch literal 2545 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 N|NqlRq0#^l007yS;m800 literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest-0.1.0.tgz.prov b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest-0.1.0.tgz.prov new file mode 100644 index 000000000..94235399a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest-0.1.0.tgz.prov @@ -0,0 +1,20 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +description: A Helm chart for Kubernetes +name: signtest +version: 0.1.0 + +... +files: + signtest-0.1.0.tgz: sha256:dee72947753628425b82814516bdaa37aef49f25e8820dd2a6e15a33a007823b +-----BEGIN PGP SIGNATURE----- + +wsBcBAEBCgAQBQJXomNHCRCEO7+YH8GHYgAALywIAG1Me852Fpn1GYu8Q1GCcw4g +l2k7vOFchdDwDhdSVbkh4YyvTaIO3iE2Jtk1rxw+RIJiUr0eLO/rnIJuxZS8WKki +DR1LI9J1VD4dxN3uDETtWDWq7ScoPsRY5mJvYZXC8whrWEt/H2kfqmoA9LloRPWp +flOE0iktA4UciZOblTj6nAk3iDyjh/4HYL4a6tT0LjjKI7OTw4YyHfjHad1ywVCz +9dMUc1rPgTnl+fnRiSPSrlZIWKOt1mcQ4fVrU3nwtRUwTId2k8FtygL0G6M+Y6t0 +S6yaU7qfk9uTxkdkUF7Bf1X3ukxfe+cNBC32vf4m8LY4NkcYfSqK2fGtQsnVr6s= +=NyOM +-----END PGP SIGNATURE----- \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/.helmignore new file mode 100644 index 000000000..435b756d8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/.helmignore @@ -0,0 +1,5 @@ +# 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/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/Chart.yaml new file mode 100644 index 000000000..90964b44a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +name: signtest +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/Chart.yaml new file mode 100644 index 000000000..6fbb27f18 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/Chart.yaml @@ -0,0 +1,6 @@ +description: Deploy a basic Alpine Linux pod +home: https://k8s.io/helm +name: alpine +sources: +- https://github.com/kubernetes/helm +version: 0.1.0 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/README.md b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/README.md new file mode 100644 index 000000000..5bd595747 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/README.md @@ -0,0 +1,9 @@ +This example was generated using the command `helm create alpine`. + +The `templates/` directory contains a very simple pod resource with a +couple of parameters. + +The `values.yaml` file contains the default values for the +`alpine-pod.yaml` template. + +You can install this example using `helm install docs/examples/alpine`. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml new file mode 100644 index 000000000..08cf3c2c1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/templates/alpine-pod.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: {{.Release.Name}}-{{.Chart.Name}} + labels: + heritage: {{.Release.Service}} + chartName: {{.Chart.Name}} + chartVersion: {{.Chart.Version | quote}} + annotations: + "helm.sh/created": "{{.Release.Time.Seconds}}" +spec: + restartPolicy: {{default "Never" .restart_policy}} + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/values.yaml new file mode 100644 index 000000000..bb6c06ae4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/alpine/values.yaml @@ -0,0 +1,2 @@ +# The pod name +name: my-alpine diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/templates/pod.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/templates/pod.yaml new file mode 100644 index 000000000..9b00ccaf7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/templates/pod.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: signtest +spec: + restartPolicy: Never + containers: + - name: waiter + image: "alpine:3.3" + command: ["/bin/sleep","9000"] diff --git a/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/downloader/testdata/signtest/values.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/engine/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/engine/doc.go new file mode 100644 index 000000000..53c4084b0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/engine/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 template engine as a Tiller Engine. + +Tiller provides a simple interface for taking a Chart and rendering its templates. +The 'engine' package implements this interface using Go's built-in 'text/template' +package. +*/ +package engine // import "k8s.io/helm/pkg/engine" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/engine/engine.go b/src/vendor/github.com/kubernetes/helm/pkg/engine/engine.go new file mode 100644 index 000000000..7a940fc84 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/engine/engine.go @@ -0,0 +1,356 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "fmt" + "path" + "sort" + "strings" + "text/template" + + "github.com/Masterminds/sprig" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// Engine is an implementation of 'cmd/tiller/environment'.Engine that uses Go templates. +type Engine struct { + // FuncMap contains the template functions that will be passed to each + // render call. This may only be modified before the first call to Render. + FuncMap template.FuncMap + // If strict is enabled, template rendering will fail if a template references + // a value that was not passed in. + Strict bool + CurrentTemplates map[string]renderable +} + +// New creates a new Go template Engine instance. +// +// The FuncMap is initialized here. You may modify the FuncMap _prior to_ the +// first invocation of Render. +// +// The FuncMap sets all of the Sprig functions except for those that provide +// access to the underlying OS (env, expandenv). +func New() *Engine { + f := FuncMap() + return &Engine{ + FuncMap: f, + } +} + +// 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": This is late-bound in Engine.Render(). The version +// included in the FuncMap is a placeholder. +// - "required": This is late-bound in Engine.Render(). The version +// included in the FuncMap is a placeholder. +// - "tpl": This is 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": chartutil.ToToml, + "toYaml": chartutil.ToYaml, + "fromYaml": chartutil.FromYaml, + "toJson": chartutil.ToJson, + "fromJson": chartutil.FromJson, + + // 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" }, + "required": func(string, interface{}) interface{} { return "not implemented" }, + "tpl": func(string, interface{}) interface{} { return "not implemented" }, + } + + for k, v := range extra { + f[k] = v + } + + return f +} + +// 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) { + // Render the charts + tmap := allTemplates(chrt, values) + e.CurrentTemplates = tmap + return e.render(tmap) +} + +// 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 +} + +// alterFuncMap takes the Engine's FuncMap and adds context-specific functions. +// +// The resulting FuncMap is only valid for the passed-in template. +func (e *Engine) alterFuncMap(t *template.Template) template.FuncMap { + // Clone the func map because we are adding context-specific functions. + var funcMap template.FuncMap = map[string]interface{}{} + for k, v := range e.FuncMap { + funcMap[k] = v + } + + // Add the 'include' function here so we can close over t. + funcMap["include"] = func(name string, data interface{}) (string, error) { + buf := bytes.NewBuffer(nil) + if err := t.ExecuteTemplate(buf, name, data); err != nil { + return "", err + } + return buf.String(), nil + } + + // Add the 'required' function here + funcMap["required"] = func(warn string, val interface{}) (interface{}, error) { + if val == nil { + return val, fmt.Errorf(warn) + } else if _, ok := val.(string); ok { + if val == "" { + return val, fmt.Errorf(warn) + } + } + return val, nil + } + + // 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 "", fmt.Errorf("Cannot retrieve Template.Basepath from values inside tpl function: %s (%s)", tpl, err.Error()) + } + + r := renderable{ + tpl: tpl, + vals: vals, + basePath: basePath.(string), + } + + templates := map[string]renderable{} + templateName, err := vals.PathValue("Template.Name") + if err != nil { + return "", fmt.Errorf("Cannot retrieve Template.Name from values inside tpl function: %s (%s)", tpl, err.Error()) + } + + templates[templateName.(string)] = r + + result, err := e.render(templates) + if err != nil { + return "", fmt.Errorf("Error during tpl function execution for %q: %s", tpl, err.Error()) + } + return result[templateName.(string)], nil + } + + return funcMap +} + +// render takes a map of templates/values and renders them. +func (e *Engine) render(tpls 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 = fmt.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") + } + + funcMap := e.alterFuncMap(t) + + // 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) + + files := []string{} + + for _, fname := range keys { + r := tpls[fname] + t = t.New(fname).Funcs(funcMap) + if _, err := t.Parse(r.tpl); err != nil { + return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) + } + files = append(files, fname) + } + + // Adding the engine's currentTemplates to the template context + // so they can be referenced in the tpl function + for fname, r := range e.CurrentTemplates { + if t.Lookup(fname) == nil { + t = t.New(fname).Funcs(funcMap) + if _, err := t.Parse(r.tpl); err != nil { + return map[string]string{}, fmt.Errorf("parse error in %q: %s", fname, err) + } + } + } + + rendered = make(map[string]string, len(files)) + var buf bytes.Buffer + for _, file := range files { + // 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(file), "_") { + continue + } + // At render time, add information about the template that is being rendered. + vals := tpls[file].vals + vals["Template"] = map[string]interface{}{"Name": file, "BasePath": tpls[file].basePath} + if err := t.ExecuteTemplate(&buf, file, vals); err != nil { + return map[string]string{}, fmt.Errorf("render error in %q: %s", file, 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[file] = strings.Replace(buf.String(), "", "", -1) + buf.Reset() + } + + return rendered, nil +} + +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 := map[string]renderable{} + recAllTpls(c, templates, vals, true, "") + 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, parentVals chartutil.Values, top bool, parentID string) { + // This should never evaluate to a nil map. That will cause problems when + // values are appended later. + cvals := chartutil.Values{} + if top { + // If this is the top of the rendering tree, assume that parentVals + // is already resolved to the authoritative values. + cvals = parentVals + } else if c.Metadata != nil && c.Metadata.Name != "" { + // If there is a {{.Values.ThisChart}} in the parent metadata, + // copy that into the {{.Values}} for this template. + newVals := chartutil.Values{} + if vs, err := parentVals.Table("Values"); err == nil { + if tmp, err := vs.Table(c.Metadata.Name); err == nil { + newVals = tmp + } + } + + cvals = map[string]interface{}{ + "Values": newVals, + "Release": parentVals["Release"], + "Chart": c.Metadata, + "Files": chartutil.NewFiles(c.Files), + "Capabilities": parentVals["Capabilities"], + } + } + + newParentID := c.Metadata.Name + if parentID != "" { + // We artificially reconstruct the chart path to child templates. This + // creates a namespaced filename that can be used to track down the source + // of a particular template declaration. + newParentID = path.Join(parentID, "charts", newParentID) + } + + for _, child := range c.Dependencies { + recAllTpls(child, templates, cvals, false, newParentID) + } + for _, t := range c.Templates { + templates[path.Join(newParentID, t.Name)] = renderable{ + tpl: string(t.Data), + vals: cvals, + basePath: path.Join(newParentID, "templates"), + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/engine/engine_test.go b/src/vendor/github.com/kubernetes/helm/pkg/engine/engine_test.go new file mode 100644 index 000000000..8ffb3d87c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/engine/engine_test.go @@ -0,0 +1,566 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "sync" + "testing" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" + + "github.com/golang/protobuf/ptypes/any" +) + +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.Errorf("expected %q, got %q at index %d\n\tExp: %#v\n\tGot: %#v", e, got[i], i, expect, got) + } + } +} + +func TestEngine(t *testing.T) { + e := New() + + // Forbidden because they allow access to the host OS. + forbidden := []string{"env", "expandenv"} + for _, f := range forbidden { + if _, ok := e.FuncMap[f]; ok { + t.Errorf("Forbidden function %s exists in FuncMap.", f) + } + } +} + +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"} + 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.Template{ + {Name: "templates/test1", Data: []byte("{{.outer | title }} {{.inner | title}}")}, + {Name: "templates/test2", Data: []byte("{{.global.callme | lower }}")}, + {Name: "templates/test3", Data: []byte("{{.noValue}}")}, + }, + Values: &chart.Config{ + Raw: "outer: DEFAULT\ninner: DEFAULT", + }, + } + + vals := &chart.Config{ + Raw: ` +outer: spouter +inner: inn +global: + callme: Ishmael +`} + + e := New() + v, err := chartutil.CoalesceValues(c, vals) + if err != nil { + t.Fatalf("Failed to coalesce values: %s", err) + } + out, err := e.Render(c, v) + if err != nil { + t.Errorf("Failed to render templates: %s", err) + } + + expect := "Spouter Inn" + if out["moby/templates/test1"] != expect { + t.Errorf("Expected %q, got %q", expect, out["test1"]) + } + + expect = "ishmael" + if out["moby/templates/test2"] != expect { + t.Errorf("Expected %q, got %q", expect, out["test2"]) + } + expect = "" + if out["moby/templates/test3"] != expect { + t.Errorf("Expected %q, got %q", expect, out["test3"]) + } + + if _, err := e.Render(c, v); err != nil { + t.Errorf("Unexpected error: %s", err) + } +} + +func TestRenderInternals(t *testing.T) { + // Test the internals of the rendering tool. + e := New() + + 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 := e.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() + var wg sync.WaitGroup + for i := 0; i < 20; i++ { + wg.Add(1) + go func(i int) { + fname := "my/file/name" + tt := fmt.Sprintf("expect-%d", i) + v := chartutil.Values{"val": tt} + tpls := map[string]renderable{fname: {tpl: `{{.val}}`, vals: v}} + out, err := e.render(tpls) + if err != nil { + t.Errorf("Failed to render %s: %s", tt, err) + } + if out[fname] != tt { + t.Errorf("Expected %q, got %q", tt, out[fname]) + } + wg.Done() + }(i) + } + wg.Wait() +} + +func TestAllTemplates(t *testing.T) { + ch1 := &chart.Chart{ + Metadata: &chart.Metadata{Name: "ch1"}, + Templates: []*chart.Template{ + {Name: "templates/foo", Data: []byte("foo")}, + {Name: "templates/bar", Data: []byte("bar")}, + }, + Dependencies: []*chart.Chart{ + { + Metadata: &chart.Metadata{Name: "laboratory mice"}, + Templates: []*chart.Template{ + {Name: "templates/pinky", Data: []byte("pinky")}, + {Name: "templates/brain", Data: []byte("brain")}, + }, + Dependencies: []*chart.Chart{{ + Metadata: &chart.Metadata{Name: "same thing we do every night"}, + Templates: []*chart.Template{ + {Name: "templates/innermost", Data: []byte("innermost")}, + }}, + }, + }, + }, + } + + var v chartutil.Values + tpls := allTemplates(ch1, v) + if len(tpls) != 5 { + t.Errorf("Expected 5 charts, got %d", len(tpls)) + } +} + +func TestRenderDependency(t *testing.T) { + e := New() + deptpl := `{{define "myblock"}}World{{end}}` + toptpl := `Hello {{template "myblock"}}` + ch := &chart.Chart{ + Metadata: &chart.Metadata{Name: "outerchart"}, + Templates: []*chart.Template{ + {Name: "templates/outer", Data: []byte(toptpl)}, + }, + Dependencies: []*chart.Chart{ + { + Metadata: &chart.Metadata{Name: "innerchart"}, + Templates: []*chart.Template{ + {Name: "templates/inner", Data: []byte(deptpl)}, + }, + }, + }, + } + + out, err := e.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) { + e := New() + + innerpath := "templates/inner.tpl" + outerpath := "templates/outer.tpl" + // Ensure namespacing rules are working. + deepestpath := "templates/inner.tpl" + checkrelease := "templates/release.tpl" + + deepest := &chart.Chart{ + Metadata: &chart.Metadata{Name: "deepest"}, + Templates: []*chart.Template{ + {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: &chart.Config{Raw: `what: "milkshake"`}, + } + + inner := &chart.Chart{ + Metadata: &chart.Metadata{Name: "herrick"}, + Templates: []*chart.Template{ + {Name: innerpath, Data: []byte(`Old {{.Values.who}} is still a-flyin'`)}, + }, + Values: &chart.Config{Raw: `who: "Robert"`}, + Dependencies: []*chart.Chart{deepest}, + } + + outer := &chart.Chart{ + Metadata: &chart.Metadata{Name: "top"}, + Templates: []*chart.Template{ + {Name: outerpath, Data: []byte(`Gather ye {{.Values.what}} while ye may`)}, + }, + Values: &chart.Config{ + Raw: ` +what: stinkweed +who: me +herrick: + who: time`, + }, + Dependencies: []*chart.Chart{inner}, + } + + injValues := chart.Config{ + Raw: ` +what: rosebuds +herrick: + deepest: + what: flower +global: + 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 := e.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]) + } +} + +func TestRenderBuiltinValues(t *testing.T) { + inner := &chart.Chart{ + Metadata: &chart.Metadata{Name: "Latium"}, + Templates: []*chart.Template{ + {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"}}`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + Files: []*any.Any{ + {TypeUrl: "author", Value: []byte("Virgil")}, + {TypeUrl: "book/title.txt", Value: []byte("Aeneid")}, + }, + } + + outer := &chart.Chart{ + Metadata: &chart.Metadata{Name: "Troy"}, + Templates: []*chart.Template{ + {Name: "templates/Aeneas", Data: []byte(`{{.Template.Name}}{{.Chart.Name}}{{.Release.Name}}`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{inner}, + } + + inject := chartutil.Values{ + "Values": &chart.Config{Raw: ""}, + "Chart": outer.Metadata, + "Release": chartutil.Values{ + "Name": "Aeneid", + }, + } + + t.Logf("Calculated values: %v", outer) + + out, err := New().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/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(t *testing.T) { + c := &chart.Chart{ + Metadata: &chart.Metadata{Name: "conrad"}, + Templates: []*chart.Template{ + {Name: "templates/quote", Data: []byte(`{{include "conrad/templates/_partial" . | indent 2}} dead.`)}, + {Name: "templates/_partial", Data: []byte(`{{.Release.Name}} - he`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + + v := chartutil.Values{ + "Values": &chart.Config{Raw: ""}, + "Chart": c.Metadata, + "Release": chartutil.Values{ + "Name": "Mistah Kurtz", + }, + } + + out, err := New().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) + } + + reqChart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "conan"}, + Templates: []*chart.Template{ + {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!`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + + reqValues := chartutil.Values{ + "Values": chartutil.Values{ + "who": "us", + "bases": 2, + }, + "Chart": reqChart.Metadata, + "Release": chartutil.Values{ + "Name": "That 90s meme", + }, + } + + outReq, err := New().Render(reqChart, reqValues) + if err != nil { + t.Fatal(err) + } + + expectStr := "All your base are belong to us" + if gotStr := outReq["conan/templates/quote"]; gotStr != expectStr { + t.Errorf("Expected %q, got %q (%v)", expectStr, gotStr, outReq) + } + expectNum := "All 2 of them!" + if gotNum := outReq["conan/templates/bases"]; gotNum != expectNum { + t.Errorf("Expected %q, got %q (%v)", expectNum, gotNum, outReq) + } + + tplChart := &chart.Chart{ + Metadata: &chart.Metadata{Name: "TplFunction"}, + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value}}" .}}`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + + tplValues := chartutil.Values{ + "Values": chartutil.Values{ + "value": "myvalue", + }, + "Chart": tplChart.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + outTpl, err := New().Render(tplChart, tplValues) + if err != nil { + t.Fatal(err) + } + + expectTplStr := "Evaluate tpl Value: myvalue" + if gotStrTpl := outTpl["TplFunction/templates/base"]; gotStrTpl != expectTplStr { + t.Errorf("Expected %q, got %q (%v)", expectTplStr, gotStrTpl, outTpl) + } + + tplChartWithFunction := &chart.Chart{ + Metadata: &chart.Metadata{Name: "TplFunction"}, + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`Evaluate tpl {{tpl "Value: {{ .Values.value | quote}}" .}}`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + + tplValuesWithFunction := chartutil.Values{ + "Values": chartutil.Values{ + "value": "myvalue", + }, + "Chart": tplChartWithFunction.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + outTplWithFunction, err := New().Render(tplChartWithFunction, tplValuesWithFunction) + if err != nil { + t.Fatal(err) + } + + expectTplStrWithFunction := "Evaluate tpl Value: \"myvalue\"" + if gotStrTplWithFunction := outTplWithFunction["TplFunction/templates/base"]; gotStrTplWithFunction != expectTplStrWithFunction { + t.Errorf("Expected %q, got %q (%v)", expectTplStrWithFunction, gotStrTplWithFunction, outTplWithFunction) + } + + tplChartWithInclude := &chart.Chart{ + Metadata: &chart.Metadata{Name: "TplFunction"}, + Templates: []*chart.Template{ + {Name: "templates/base", Data: []byte(`{{ tpl "{{include ` + "`" + `TplFunction/templates/_partial` + "`" + ` . | quote }}" .}}`)}, + {Name: "templates/_partial", Data: []byte(`{{.Template.Name}}`)}, + }, + Values: &chart.Config{Raw: ``}, + Dependencies: []*chart.Chart{}, + } + tplValueWithInclude := chartutil.Values{ + "Values": chartutil.Values{ + "value": "myvalue", + }, + "Chart": tplChartWithInclude.Metadata, + "Release": chartutil.Values{ + "Name": "TestRelease", + }, + } + + outTplWithInclude, err := New().Render(tplChartWithInclude, tplValueWithInclude) + if err != nil { + t.Fatal(err) + } + + expectedTplStrWithInclude := "\"TplFunction/templates/base\"" + if gotStrTplWithInclude := outTplWithInclude["TplFunction/templates/base"]; gotStrTplWithInclude != expectedTplStrWithInclude { + t.Errorf("Expected %q, got %q (%v)", expectedTplStrWithInclude, gotStrTplWithInclude, outTplWithInclude) + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/doc.go new file mode 100644 index 000000000..fe51e4967 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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/src/vendor/github.com/kubernetes/helm/pkg/getter/getter.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/getter.go new file mode 100644 index 000000000..ca018884a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/getter.go @@ -0,0 +1,98 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/helm/pkg/helm/environment" +) + +// Getter is an interface to support GET to the specified URL. +type Getter interface { + //Get file content by url string + Get(url string) (*bytes.Buffer, error) +} + +// Constructor is the function for every getter which creates a specific instance +// according to the configuration +type Constructor func(URL, CertFile, KeyFile, CAFile string) (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) (Constructor, error) { + for _, pp := range p { + if pp.Provides(scheme) { + return pp.New, nil + } + } + return nil, fmt.Errorf("scheme %q not supported", scheme) +} + +// All finds all of the registered getters as a list of Provider instances. +// Currently the build-in http/https getter and the discovered +// plugins with downloader notations are collected. +func All(settings environment.EnvSettings) Providers { + result := Providers{ + { + Schemes: []string{"http", "https"}, + New: newHTTPGetter, + }, + } + pluginDownloaders, _ := collectPlugins(settings) + result = append(result, pluginDownloaders...) + return result +} + +// ByScheme returns a getter for the given scheme. +// +// If the scheme is not supported, this will return an error. +func ByScheme(scheme string, settings environment.EnvSettings) (Provider, error) { + // Q: What do you call a scheme string who's the boss? + // A: Bruce Schemestring, of course. + a := All(settings) + for _, p := range a { + if p.Provides(scheme) { + return p, nil + } + } + return Provider{}, fmt.Errorf("scheme %q not supported", scheme) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/getter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/getter_test.go new file mode 100644 index 000000000..6d38a0d28 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/getter_test.go @@ -0,0 +1,81 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 ( + "os" + "testing" +) + +func TestProvider(t *testing.T) { + p := Provider{ + []string{"one", "three"}, + func(h, e, l, m string) (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(h, e, l, m string) (Getter, error) { return nil, nil }}, + {[]string{"two", "four"}, func(h, e, l, m string) (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) { + oldhh := os.Getenv("HELM_HOME") + defer os.Setenv("HELM_HOME", oldhh) + os.Setenv("HELM_HOME", "") + + env := hh(false) + + all := All(env) + if len(all) != 3 { + t.Errorf("expected 3 providers (default plus two plugins), got %d", len(all)) + } + + if _, err := all.ByScheme("test2"); err != nil { + t.Error(err) + } +} + +func TestByScheme(t *testing.T) { + oldhh := os.Getenv("HELM_HOME") + defer os.Setenv("HELM_HOME", oldhh) + os.Setenv("HELM_HOME", "") + + env := hh(false) + if _, err := ByScheme("test", env); err != nil { + t.Error(err) + } + if _, err := ByScheme("https", env); err != nil { + t.Error(err) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter.go new file mode 100644 index 000000000..3c20e35e1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter.go @@ -0,0 +1,108 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "io" + "net/http" + "strings" + + "k8s.io/helm/pkg/tlsutil" + "k8s.io/helm/pkg/urlutil" + "k8s.io/helm/pkg/version" +) + +//HttpGetter is the efault HTTP(/S) backend handler +// TODO: change the name to HTTPGetter in Helm 3 +type HttpGetter struct { //nolint + client *http.Client + username string + password string +} + +//SetCredentials sets the credentials for the getter +func (g *HttpGetter) SetCredentials(username, password string) { + g.username = username + g.password = password +} + +//Get performs a Get from repo.Getter and returns the body. +func (g *HttpGetter) Get(href string) (*bytes.Buffer, error) { + return g.get(href) +} + +func (g *HttpGetter) get(href string) (*bytes.Buffer, error) { + buf := bytes.NewBuffer(nil) + + // 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("GET", href, nil) + if err != nil { + return buf, err + } + req.Header.Set("User-Agent", "Helm/"+strings.TrimPrefix(version.GetVersion(), "v")) + + if g.username != "" && g.password != "" { + req.SetBasicAuth(g.username, g.password) + } + + resp, err := g.client.Do(req) + if err != nil { + return buf, err + } + if resp.StatusCode != 200 { + return buf, fmt.Errorf("Failed to fetch %s : %s", href, resp.Status) + } + + _, err = io.Copy(buf, resp.Body) + resp.Body.Close() + return buf, err +} + +// newHTTPGetter constructs a valid http/https client as Getter +func newHTTPGetter(URL, CertFile, KeyFile, CAFile string) (Getter, error) { + return NewHTTPGetter(URL, CertFile, KeyFile, CAFile) +} + +// NewHTTPGetter constructs a valid http/https client as HttpGetter +func NewHTTPGetter(URL, CertFile, KeyFile, CAFile string) (*HttpGetter, error) { + var client HttpGetter + if CertFile != "" && KeyFile != "" { + tlsConf, err := tlsutil.NewClientTLS(CertFile, KeyFile, CAFile) + if err != nil { + return &client, fmt.Errorf("can't create TLS config for client: %s", err.Error()) + } + tlsConf.BuildNameToCertificate() + + sni, err := urlutil.ExtractHostname(URL) + if err != nil { + return &client, err + } + tlsConf.ServerName = sni + + client.client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: tlsConf, + Proxy: http.ProxyFromEnvironment, + }, + } + } else { + client.client = http.DefaultClient + } + return &client, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter_test.go new file mode 100644 index 000000000..fe3fde22a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/httpgetter_test.go @@ -0,0 +1,48 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 ( + "net/http" + "path/filepath" + "testing" +) + +func TestHTTPGetter(t *testing.T) { + g, err := newHTTPGetter("http://example.com", "", "", "") + if err != nil { + t.Fatal(err) + } + + if hg, ok := g.(*HttpGetter); !ok { + t.Fatal("Expected newHTTPGetter to produce an httpGetter") + } else if hg.client != http.DefaultClient { + t.Fatal("Expected newHTTPGetter to return a default HTTP client.") + } + + // Test with SSL: + cd := "../../testdata" + join := filepath.Join + ca, pub, priv := join(cd, "ca.pem"), join(cd, "crt.pem"), join(cd, "key.pem") + g, err = newHTTPGetter("http://example.com/", pub, priv, ca) + if err != nil { + t.Fatal(err) + } + + if _, ok := g.(*HttpGetter); !ok { + t.Fatal("Expected newHTTPGetter to produce an httpGetter") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter.go new file mode 100644 index 000000000..c747eef7f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "os" + "os/exec" + "path/filepath" + + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/plugin" +) + +// collectPlugins scans for getter plugins. +// This will load plugins according to the environment. +func collectPlugins(settings environment.EnvSettings) (Providers, error) { + plugins, err := plugin.FindPlugins(settings.PluginDirs()) + 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 + certFile, keyFile, cAFile string + settings environment.EnvSettings + name string + base string +} + +// Get runs downloader plugin command +func (p *pluginGetter) Get(href string) (*bytes.Buffer, error) { + argv := []string{p.certFile, p.keyFile, p.cAFile, href} + prog := exec.Command(filepath.Join(p.base, p.command), 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, fmt.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 environment.EnvSettings, name, base string) Constructor { + return func(URL, CertFile, KeyFile, CAFile string) (Getter, error) { + result := &pluginGetter{ + command: command, + certFile: CertFile, + keyFile: KeyFile, + cAFile: CAFile, + settings: settings, + name: name, + base: base, + } + return result, nil + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter_test.go new file mode 100644 index 000000000..f1fe9bf29 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/plugingetter_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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 ( + "os" + "path/filepath" + "strings" + "testing" + + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/helm/helmpath" +) + +func hh(debug bool) environment.EnvSettings { + apath, err := filepath.Abs("./testdata") + if err != nil { + panic(err) + } + hp := helmpath.Home(apath) + return environment.EnvSettings{ + Home: hp, + Debug: debug, + } +} + +func TestCollectPlugins(t *testing.T) { + // Reset HELM HOME to testdata. + oldhh := os.Getenv("HELM_HOME") + defer os.Setenv("HELM_HOME", oldhh) + os.Setenv("HELM_HOME", "") + + env := hh(false) + 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) { + oldhh := os.Getenv("HELM_HOME") + defer os.Setenv("HELM_HOME", oldhh) + os.Setenv("HELM_HOME", "") + + env := hh(false) + pg := newPluginGetter("echo", env, "test", ".") + g, err := pg("test://foo/bar", "", "", "") + 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) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/get.sh b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/get.sh new file mode 100755 index 000000000..cdd992369 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/get.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo ENVIRONMENT +env + +echo "" +echo ARGUMENTS +echo $@ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/plugin.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/plugin.yaml new file mode 100644 index 000000000..d1b929e3f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter/plugin.yaml @@ -0,0 +1,15 @@ +name: "testgetter" +version: "0.1.0" +usage: "Fetch a package from a test:// source" +description: |- + Print the environment that the plugin was given, then exit. + + This registers the test:// protocol. + +command: "$HELM_PLUGIN_DIR/get.sh" +ignoreFlags: true +downloaders: +#- command: "$HELM_PLUGIN_DIR/get.sh" +- command: "echo" + protocols: + - "test" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/get.sh b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/get.sh new file mode 100755 index 000000000..cdd992369 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/get.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +echo ENVIRONMENT +env + +echo "" +echo ARGUMENTS +echo $@ diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/plugin.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/plugin.yaml new file mode 100644 index 000000000..f1a527ef9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/plugins/testgetter2/plugin.yaml @@ -0,0 +1,10 @@ +name: "testgetter2" +version: "0.1.0" +usage: "Fetch a different package from a test2:// source" +description: "Handle test2 scheme" +command: "$HELM_PLUGIN_DIR/get.sh" +ignoreFlags: true +downloaders: +- command: "echo" + protocols: + - "test2" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/local-index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/local-index.yaml new file mode 120000 index 000000000..ed068e99e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/local-index.yaml @@ -0,0 +1 @@ +repository/local/index.yaml \ No newline at end of file diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/stable-index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/stable-index.yaml new file mode 100644 index 000000000..bf187e3df --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/cache/stable-index.yaml @@ -0,0 +1,7852 @@ +apiVersion: v1 +entries: + aws-cluster-autoscaler: + - apiVersion: v1 + created: 2017-04-28T00:18:30.071110236Z + description: Scales worker nodes within autoscaling groups. + digest: 291eaabbf54913e71cda39d42a6e9c4c493a3672a5b0b5183ea1991a76d6c317 + engine: gotpl + icon: https://github.com/kubernetes/kubernetes/blob/master/logo/logo.png + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: aws-cluster-autoscaler + sources: + - https://github.com/kubernetes/contrib/tree/master/cluster-autoscaler/cloudprovider/aws + urls: + - https://kubernetes-charts.storage.googleapis.com/aws-cluster-autoscaler-0.2.1.tgz + version: 0.2.1 + - apiVersion: v1 + created: 2017-03-02T18:48:30.418071154Z + description: Scales worker nodes within autoscaling groups. + digest: 52ee58b51619f641d0f6df4c778bcd068242379a1a040a269f0fc93867b79b13 + engine: gotpl + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: aws-cluster-autoscaler + sources: + - https://github.com/kubernetes/contrib/tree/master/cluster-autoscaler/cloudprovider/aws + urls: + - https://kubernetes-charts.storage.googleapis.com/aws-cluster-autoscaler-0.2.0.tgz + version: 0.2.0 + chaoskube: + - created: 2017-04-28T00:18:30.071358212Z + description: Chaoskube periodically kills random pods in your Kubernetes cluster. + digest: c90ff57a6205c725520dc600b439fc8eda120c3d8d4d4b7c9feee60bf62629cb + engine: gotpl + home: https://github.com/linki/chaoskube + maintainers: + - email: linki+kubernetes.io@posteo.de + name: Martin Linkhorst + name: chaoskube + sources: + - https://github.com/linki/chaoskube + urls: + - https://kubernetes-charts.storage.googleapis.com/chaoskube-0.5.0.tgz + version: 0.5.0 + - created: 2017-03-14T23:48:31.878196764Z + description: Chaoskube periodically kills random pods in your Kubernetes cluster. + digest: cc211be37255f2fdf7cc74022f51473c2c4af98c06b0871ab8c9b8341ee9d349 + engine: gotpl + home: https://github.com/linki/chaoskube + maintainers: + - email: linki+kubernetes.io@posteo.de + name: Martin Linkhorst + name: chaoskube + sources: + - https://github.com/linki/chaoskube + urls: + - https://kubernetes-charts.storage.googleapis.com/chaoskube-0.4.0.tgz + version: 0.4.0 + - created: 2017-02-13T04:18:31.525717582Z + description: A Helm chart for chaoskube + digest: 6e6466b2a7294853fbad6a1dc5526e7fd6eb75bafd035748259511ccf49f9c47 + engine: gotpl + home: https://github.com/linki/chaoskube + maintainers: + - email: linki+helm.sh@posteo.de + name: Martin Linkhorst + name: chaoskube + sources: + - https://github.com/linki/chaoskube + urls: + - https://kubernetes-charts.storage.googleapis.com/chaoskube-0.3.1.tgz + version: 0.3.1 + - created: 2017-02-13T21:18:28.251529085Z + description: Chaoskube periodically kills random pods in your Kubernetes cluster. + digest: 6439a906666fc62e7aeb28186ce2f4a730216941163edd799176cc30616e713a + engine: gotpl + home: https://github.com/linki/chaoskube + maintainers: + - email: linki+kubernetes.io@posteo.de + name: Martin Linkhorst + name: chaoskube + sources: + - https://github.com/linki/chaoskube + urls: + - https://kubernetes-charts.storage.googleapis.com/chaoskube-0.3.1-1.tgz + version: 0.3.1-1 + chronograf: + - created: 2017-04-28T00:18:30.071786276Z + description: Open-source web application written in Go and React.js that provides + the tools to visualize your monitoring data and easily create alerting and automation + rules. + digest: 5f6c0ada37696c624ebdfad1703666b91a84dedea81cbae4335109e7046c9f86 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/chronograf/ + keywords: + - chronograf + - visualizaion + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: chronograf + urls: + - https://kubernetes-charts.storage.googleapis.com/chronograf-0.2.0.tgz + version: 0.2.0 + - created: 2017-03-22T23:03:29.448801697Z + description: Open-source web application written in Go and React.js that provides + the tools to visualize your monitoring data and easily create alerting and automation + rules. + digest: 6bc90a815f7fc513bfa2b4d1a56a03e53444bfd08b742e0d0f1ff9f3b5db52f7 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/chronograf/ + keywords: + - chronograf + - visualizaion + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: chronograf + urls: + - https://kubernetes-charts.storage.googleapis.com/chronograf-0.1.2.tgz + version: 0.1.2 + - created: 2017-02-13T04:18:31.52604543Z + description: Chart for Chronograf + digest: 985fa74feb6ed111220ca7a8c5da529accd42def9d75f56c0c82902631bcf15f + engine: gotpl + home: https://www.influxdata.com/time-series-platform/chronograf/ + keywords: + - chronograf + - visualizaion + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: chronograf + urls: + - https://kubernetes-charts.storage.googleapis.com/chronograf-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-28T00:33:31.060189495Z + description: Chart for Chronograf + digest: ea03695da15a018e84d05e0fd97d581f3fd348d8461aa098cd221b5010176a35 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/chronograf/ + keywords: + - chronograf + - visualizaion + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: chronograf + urls: + - https://kubernetes-charts.storage.googleapis.com/chronograf-0.1.0.tgz + version: 0.1.0 + cockroachdb: + - created: 2017-04-28T00:18:30.07217685Z + description: CockroachDB is a scalable, survivable, strongly-consistent SQL database. + digest: c51bf6c3d07067b80ca8661ff8460b2bb7d25d242f153045668ba95566fc8069 + home: https://www.cockroachlabs.com + icon: https://raw.githubusercontent.com/cockroachdb/cockroach/master/docs/media/cockroach_db.png + maintainers: + - email: alex@cockroachlabs.com + name: Alex Robinson + name: cockroachdb + sources: + - https://github.com/cockroachdb/cockroach + urls: + - https://kubernetes-charts.storage.googleapis.com/cockroachdb-0.2.2.tgz + version: 0.2.2 + - created: 2017-02-13T04:18:31.526409785Z + description: CockroachDB Helm chart for Kubernetes. + digest: 0eec741613e00f7092ec81469f919cd79fec52a22e8685063c0b0d8fd6570c37 + home: https://www.cockroachlabs.com + maintainers: + - email: alex@cockroachlabs.com + name: Alex Robinson + name: cockroachdb + sources: + - https://github.com/cockroachdb/cockroach + urls: + - https://kubernetes-charts.storage.googleapis.com/cockroachdb-0.2.1.tgz + version: 0.2.1 + - created: 2017-01-27T21:48:32.059935168Z + description: CockroachDB Helm chart for Kubernetes. + digest: 7b41add319a997fd3d862d6649b707313f59ac6fa137842db65230342baff619 + home: https://www.cockroachlabs.com + maintainers: + - email: alex@cockroachlabs.com + name: Alex Robinson + name: cockroachdb + sources: + - https://github.com/cockroachdb/cockroach + urls: + - https://kubernetes-charts.storage.googleapis.com/cockroachdb-0.2.0.tgz + version: 0.2.0 + concourse: + - created: 2017-04-28T00:18:30.074578589Z + description: Concourse is a simple and scalable CI system. + digest: b7ea57e289002deba839f52acf6a5919870ab99910f12bcc6edadd4ae5651826 + engine: gotpl + home: https://concourse.ci/ + icon: https://avatars1.githubusercontent.com/u/7809479 + keywords: + - ci + - concourse + - concourse.ci + maintainers: + - email: frodenas@gmail.com + name: Ferran Rodenas + name: concourse + sources: + - https://github.com/concourse/bin + - https://github.com/kubernetes/charts + urls: + - https://kubernetes-charts.storage.googleapis.com/concourse-0.1.3.tgz + version: 0.1.3 + - created: 2017-02-14T19:03:29.77100439Z + description: Concourse is a simple and scalable CI system. + digest: 5f8ed4eb5b0acf8da7b34772714154322405b796553a33fc6ffd779e0a556003 + engine: gotpl + home: https://concourse.ci/ + icon: https://avatars1.githubusercontent.com/u/7809479 + keywords: + - ci + - concourse + - concourse.ci + maintainers: + - email: frodenas@gmail.com + name: Ferran Rodenas + name: concourse + sources: + - https://github.com/concourse/bin + - https://github.com/kubernetes/charts + urls: + - https://kubernetes-charts.storage.googleapis.com/concourse-0.1.2.tgz + version: 0.1.2 + - created: 2017-02-13T21:18:28.254273427Z + description: Concourse is a simple and scalable CI system. + digest: cba53dadd21dbd85b31a1a522a5eaeb49cadfa595ba0762c02dca04905d35f20 + engine: gotpl + home: https://concourse.ci/ + icon: https://avatars1.githubusercontent.com/u/7809479 + keywords: + - ci + - concourse + - concourse.ci + maintainers: + - email: frodenas@gmail.com + name: Ferran Rodenas + name: concourse + sources: + - https://github.com/concourse/bin + - https://github.com/kubernetes/charts + urls: + - https://kubernetes-charts.storage.googleapis.com/concourse-0.1.1.tgz + version: 0.1.1 + - created: 2017-02-10T23:18:26.003782261Z + description: Concourse Helm chart for Kubernetes. + digest: 08e6b4c56357ce15dfd66b0fad8c2df37664877b16b4a0afcbaeb301ddcc7432 + engine: gotpl + home: https://concourse.ci/ + keywords: + - ci + - concourse + - concourse.ci + maintainers: + - email: frodenas@gmail.com + name: Ferran Rodenas + name: concourse + sources: + - https://github.com/concourse/bin + - https://github.com/kubernetes/charts + urls: + - https://kubernetes-charts.storage.googleapis.com/concourse-0.1.0.tgz + version: 0.1.0 + consul: + - created: 2017-04-28T00:18:30.075038045Z + description: Highly available and distributed service discovery and key-value + store designed with support for the modern data center to make distributed systems + and configuration easy. + digest: 7e0093709abc7a2c475e4e8c14e856d9834f88683b4a9e8c6099f4e0ad7e434e + home: https://github.com/hashicorp/consul + icon: https://www.consul.io/assets/images/logo_large-475cebb0.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + name: consul + sources: + - https://github.com/kelseyhightower/consul-on-kubernetes + urls: + - https://kubernetes-charts.storage.googleapis.com/consul-0.2.2.tgz + version: 0.2.2 + - created: 2017-04-26T14:48:28.225526691Z + description: Highly available and distributed service discovery and key-value + store designed with support for the modern data center to make distributed systems + and configuration easy. + digest: 03daa04827bf93627b7087001c1d2424337d268a5875a51d8db4bb4e53bbc7db + home: https://github.com/hashicorp/consul + icon: https://www.consul.io/assets/images/logo_large-475cebb0.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + name: consul + sources: + - https://github.com/kelseyhightower/consul-on-kubernetes + urls: + - https://kubernetes-charts.storage.googleapis.com/consul-0.2.0.tgz + version: 0.2.0 + coredns: + - created: 2017-04-28T00:18:30.075393511Z + description: CoreDNS is a DNS server that chains middleware and provides Kubernetes + DNS Services + digest: adbdc4a8895f7c2e7cca64c2dcf36ddfffeff1115a3ade32011ec82b60be119b + home: https://coredns.io + icon: https://raw.githubusercontent.com/coredns/logo/master/Icon/CoreDNS_Colour_Icon.svg + keywords: + - coredns + - dns + - kubedns + maintainers: + - email: hello@acale.ph + name: Acaleph + - email: shashidhara.huawei@gmail.com + name: Shashidhara TD + name: coredns + sources: + - https://github.com/coredns/coredns + urls: + - https://kubernetes-charts.storage.googleapis.com/coredns-0.1.0.tgz + version: 0.1.0 + datadog: + - created: 2017-04-28T00:18:30.075800677Z + description: DataDog Agent + digest: bc559f013b738704089c4964a268c447b22e82181e9fa9e8219d46d40b388709 + home: https://www.datadoghq.com + icon: https://datadog-live.imgix.net/img/dd_logo_70x75.png + keywords: + - monitoring + - alerting + - metric + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: datadog + sources: + - https://app.datadoghq.com/account/settings#agent/kubernetes + - https://github.com/DataDog/docker-dd-agent + urls: + - https://kubernetes-charts.storage.googleapis.com/datadog-0.3.0.tgz + version: 0.3.0 + - created: 2017-04-06T11:33:26.056402381Z + description: DataDog Agent + digest: b32c28e76004eedf5c160936ccf35adca3a150ae1d0b491df52d017725b5ee90 + home: https://www.datadoghq.com + icon: https://datadog-live.imgix.net/img/dd_logo_70x75.png + keywords: + - monitoring + - alerting + - metric + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: datadog + sources: + - https://app.datadoghq.com/account/settings#agent/kubernetes + - https://github.com/DataDog/docker-dd-agent + urls: + - https://kubernetes-charts.storage.googleapis.com/datadog-0.2.1.tgz + version: 0.2.1 + - created: 2017-02-11T03:18:26.518137684Z + description: DataDog Agent + digest: d534bdcf4644d88ebfa70c58e57aafed41b75da4264042d4975f70d091e2b493 + keywords: + - monitoring + - alerting + - metric + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: datadog + sources: + - https://app.datadoghq.com/account/settings#agent/kubernetes + - https://github.com/DataDog/docker-dd-agent + urls: + - https://kubernetes-charts.storage.googleapis.com/datadog-0.2.0.tgz + version: 0.2.0 + - created: 2017-01-04T00:48:19.731877862Z + description: DataDog Agent + digest: 694c1d036d92c8bb60638f7bd66144a991a807dc879bedacf8a5d32e9d72081a + keywords: + - monitoring + - alerting + - metric + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: datadog + sources: + - https://app.datadoghq.com/account/settings#agent/kubernetes + - https://github.com/DataDog/docker-dd-agent + urls: + - https://kubernetes-charts.storage.googleapis.com/datadog-0.1.0.tgz + version: 0.1.0 + dokuwiki: + - created: 2017-04-28T00:18:30.076184541Z + description: DokuWiki is a standards-compliant, simple to use wiki optimized for + creating documentation. It is targeted at developer teams, workgroups, and small + companies. All data is stored in plain text files, so no database is required. + digest: 5cfff9542341a391abf9029dd9b42e7c44813c520ef0301ce62e9c08586ceca2 + engine: gotpl + home: http://www.dokuwiki.org/ + icon: https://bitnami.com/assets/stacks/dokuwiki/img/dokuwiki-stack-110x117.png + keywords: + - dokuwiki + - wiki + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: dokuwiki + sources: + - https://github.com/bitnami/bitnami-docker-dokuwiki + urls: + - https://kubernetes-charts.storage.googleapis.com/dokuwiki-0.1.4.tgz + version: 0.1.4 + - created: 2017-04-13T05:18:28.897680481Z + description: DokuWiki is a standards-compliant, simple to use wiki optimized for + creating documentation. It is targeted at developer teams, workgroups, and small + companies. All data is stored in plain text files, so no database is required. + digest: 3c46f9d9196bbf975711b2bb7c889fd3df1976cc57c3c120c7374d721da0e240 + engine: gotpl + home: http://www.dokuwiki.org/ + icon: https://bitnami.com/assets/stacks/dokuwiki/img/dokuwiki-stack-110x117.png + keywords: + - dokuwiki + - wiki + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: dokuwiki + sources: + - https://github.com/bitnami/bitnami-docker-dokuwiki + urls: + - https://kubernetes-charts.storage.googleapis.com/dokuwiki-0.1.3.tgz + version: 0.1.3 + - created: 2017-03-02T19:33:28.170205427Z + description: DokuWiki is a standards-compliant, simple to use wiki optimized for + creating documentation. It is targeted at developer teams, workgroups, and small + companies. All data is stored in plain text files, so no database is required. + digest: f533bc20e08179a49cca77b175f897087dc3f2c57e6c89ecbd7264ab5975d66a + engine: gotpl + home: http://www.dokuwiki.org/ + icon: https://bitnami.com/assets/stacks/dokuwiki/img/dokuwiki-stack-110x117.png + keywords: + - dokuwiki + - wiki + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: dokuwiki + sources: + - https://github.com/bitnami/bitnami-docker-dokuwiki + urls: + - https://kubernetes-charts.storage.googleapis.com/dokuwiki-0.1.2.tgz + version: 0.1.2 + - created: 2017-02-01T02:18:29.116555882Z + description: DokuWiki is a standards-compliant, simple to use wiki optimized for + creating documentation. It is targeted at developer teams, workgroups, and small + companies. All data is stored in plain text files, so no database is required. + digest: 34a926398cfafbf426ff468167ef49577252e260ebce5df33380e6e67b79fe59 + engine: gotpl + home: http://www.dokuwiki.org/ + icon: https://bitnami.com/assets/stacks/dokuwiki/img/dokuwiki-stack-110x117.png + keywords: + - dokuwiki + - wiki + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: dokuwiki + sources: + - https://github.com/bitnami/bitnami-docker-dokuwiki + urls: + - https://kubernetes-charts.storage.googleapis.com/dokuwiki-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-28T00:33:31.06436596Z + description: DokuWiki is a standards-compliant, simple to use wiki optimized for + creating documentation. It is targeted at developer teams, workgroups, and small + companies. All data is stored in plain text files, so no database is required. + digest: 6825fbacb709cf05901985561be10ba9473a379488d99b71d1590d33f5a81374 + engine: gotpl + home: http://www.dokuwiki.org/ + icon: https://bitnami.com/assets/stacks/dokuwiki/img/dokuwiki-stack-110x117.png + keywords: + - dokuwiki + - wiki + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: dokuwiki + sources: + - https://github.com/bitnami/bitnami-docker-dokuwiki + urls: + - https://kubernetes-charts.storage.googleapis.com/dokuwiki-0.1.0.tgz + version: 0.1.0 + drupal: + - created: 2017-04-28T00:18:30.076853626Z + description: One of the most versatile open source content management systems. + digest: db95c255b19164c5051eb75a6860f3775a1011399a62b27e474cd9ebee0cb578 + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.5.1.tgz + version: 0.5.1 + - created: 2017-04-24T19:18:29.642780033Z + description: One of the most versatile open source content management systems. + digest: 84c13154a9aeb7215dc0d98e9825207207e69ca870f3d54b273bfc2d34699f61 + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.5.0.tgz + version: 0.5.0 + - created: 2017-04-11T17:18:28.883487907Z + description: One of the most versatile open source content management systems. + digest: 17d0bfdcdf5a1a650941343c76b6b928d76d3332fece127c502e91f9597f419e + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-23T01:48:29.309045867Z + description: One of the most versatile open source content management systems. + digest: 317674c89762e0b54156b734203ee93638dd7a25df35120c5cab45546814d89b + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-16T13:33:30.828606332Z + description: One of the most versatile open source content management systems. + digest: 24c4f187b50c0e961cc2cacf6e6b2ce6d6b225c73637c578e001bebd9b3f5d48 + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-02T19:33:28.170963773Z + description: One of the most versatile open source content management systems. + digest: 7fcea4684a3d520454aeaa10beb9f9b1789c09c097680fc484954084f283feb3 + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-27T17:03:27.765392204Z + description: One of the most versatile open source content management systems. + digest: adb23bc71125b9691b407a47dadf4298de3516805218813b56067967e39db7d8 + engine: gotpl + home: http://www.drupal.org/ + icon: https://bitnami.com/assets/stacks/drupal/img/drupal-stack-220x234.png + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.2.tgz + version: 0.4.2 + - created: 2017-02-11T00:18:52.26723498Z + description: One of the most versatile open source content management systems. + digest: 5de529e25767e8a37b8d6f413daa0fe99f5c304e48ddcfa8adb4d8c7a0496aa7 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-28T00:33:31.065139372Z + description: One of the most versatile open source content management systems. + digest: a35dbf9d470972cc2461de3e0a8fcf2fec8d0adc04f5a0f1e924505f22c714d7 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.4.0.tgz + version: 0.4.0 + - created: 2017-01-04T00:48:19.73297256Z + description: One of the most versatile open source content management systems. + digest: a62d686d6bd47643dfa489e395dda89286954f785123a43a88db7ef34f3ea48d + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.10.tgz + version: 0.3.10 + - created: 2016-12-15T00:48:24.005322531Z + description: One of the most versatile open source content management systems. + digest: 2c189424bda94eeebb7e6370e96884f09bdfa81498cb93ac4723d24c92a3938e + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.9.tgz + version: 0.3.9 + - created: 2016-12-09T18:48:20.182097412Z + description: One of the most versatile open source content management systems. + digest: 3596f47c5dcaa7a975d1c4cb7bf7ef6790c9ad8dda41a5a329e30c1ea8a40d11 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.8.tgz + version: 0.3.8 + - created: 2016-11-21T19:48:21.904806991Z + description: One of the most versatile open source content management systems. + digest: 78b2bb3717be63dccb02ea06b711ca7cf7869848b296b880099c6264e86d86d3 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.7.tgz + version: 0.3.7 + - created: 2016-11-08T15:03:20.739400722Z + description: One of the most versatile open source content management systems. + digest: 5508b29e20a5d609f76319869774f008dcc4bed13bbbc7ed40546bc9af8c7cd7 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.6.tgz + version: 0.3.6 + - created: 2016-11-03T19:33:29.11780736Z + description: One of the most versatile open source content management systems. + digest: 023a282c93f8411fb81bb4fff7820c1337aad0586ccf7dae55bdbed515ad8b05 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.5.tgz + version: 0.3.5 + - created: 2016-10-21T19:18:18.619010562Z + description: One of the most versatile open source content management systems. + digest: 9bdaa53f7a9e82c9b32c7ac9b34b84fd142671732a54423a2dcdc893c4162801 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.4.tgz + version: 0.3.4 + - created: 2016-10-19T00:03:14.027652488Z + description: One of the most versatile open source content management systems. + digest: 25650526abc1036398dbb314d77a0062cbb644b2c5791a258fb863fdaad5093d + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.3.tgz + version: 0.3.3 + - created: 2016-10-19T00:03:14.027073479Z + description: One of the most versatile open source content management systems. + digest: 13d5d32d316c08359221d230004dd2adc0152362e87abcc0d61ea191241fa69f + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.2.tgz + version: 0.3.2 + - created: 2016-10-19T00:03:14.025451665Z + description: One of the most versatile open source content management systems. + digest: b3f09ecd191f8c06275c96d9af4d77a97c94355525864201e9baf151b17bd5a7 + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.1.tgz + version: 0.3.1 + - created: 2016-10-19T00:03:14.024557743Z + description: One of the most versatile open source content management systems. + digest: c56fc55b93b0dead65af7b81bbd54befd5115860698ca475baa5acb178a12e5a + engine: gotpl + home: http://www.drupal.org/ + keywords: + - drupal + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: drupal + sources: + - https://github.com/bitnami/bitnami-docker-drupal + urls: + - https://kubernetes-charts.storage.googleapis.com/drupal-0.3.0.tgz + version: 0.3.0 + etcd-operator: + - apiVersion: v1 + created: 2017-04-28T00:18:30.077181321Z + description: CoreOS etcd-operator Helm chart for Kubernetes + digest: 1eb39b2f0ca26762eb13fc8cb577be741f7bb9d3162ab9c4547bb5176383bc2b + home: https://github.com/coreos/etcd-operator + icon: https://raw.githubusercontent.com/coreos/etcd/master/logos/etcd-horizontal-color.png + maintainers: + - email: chance.zibolski@coreos.com + name: Chance Zibolski + - email: lachlan@deis.com + name: Lachlan Evenson + name: etcd-operator + sources: + - https://github.com/coreos/etcd-operator + urls: + - https://kubernetes-charts.storage.googleapis.com/etcd-operator-0.2.0.tgz + version: 0.2.0 + - apiVersion: v1 + created: 2017-03-08T19:18:29.84391993Z + description: CoreOS etcd-operator Helm chart for Kubernetes + digest: 4a65fe6c61e731b373395e524f160eb4ced32a22cbfb3ff5d406b1c8bc3ae3c7 + home: https://github.com/coreos/etcd-operator + icon: https://raw.githubusercontent.com/coreos/etcd/master/logos/etcd-horizontal-color.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + name: etcd-operator + sources: + - https://github.com/coreos/etcd-operator + urls: + - https://kubernetes-charts.storage.googleapis.com/etcd-operator-0.1.1.tgz + version: 0.1.1 + - apiVersion: v1 + created: 2017-02-11T03:18:26.519796919Z + description: CoreOS etcd-operator Helm chart for Kubernetes + digest: 9bf72369082c4bad154171b3b68ad435331d6d07ae6d00e79e859f2a1599017b + icon: https://github.com/coreos/etcd/blob/master/logos/etcd-horizontal-color.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + name: etcd-operator + sources: + - https://github.com/coreos/etcd-operator + urls: + - https://kubernetes-charts.storage.googleapis.com/etcd-operator-0.1.0.tgz + version: 0.1.0 + factorio: + - created: 2017-04-28T00:18:30.077542134Z + description: Factorio dedicated server. + digest: cdc44bc00d42020a7a4df154cdc5cc7ae148aa8d2a3f97b1e18ac1fc42853dc1 + home: https://www.factorio.com/ + icon: https://us1.factorio.com/assets/img/factorio-logo.png + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.2.0.tgz + version: 0.2.0 + - created: 2017-03-15T11:48:36.74226142Z + description: Factorio dedicated server. + digest: 5a60defdd8ac6f2276950662ba75f65d20c9e37edc85149012a2c84146eb6cff + home: https://www.factorio.com/ + icon: https://us1.factorio.com/assets/img/factorio-logo.png + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.1.4.tgz + version: 0.1.4 + - created: 2017-02-13T04:18:31.530714209Z + description: Factorio dedicated server. + digest: 26244a5e1b5f992cdbef73ef9c7ffcaa52af538912fa299210f72275f2c756c9 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.1.3.tgz + version: 0.1.3 + - created: 2017-01-28T00:33:31.066072163Z + description: Factorio dedicated server. + digest: d6f4e42b82713c2da69e1ddcfce4a04fad31d4af915629d8e83e54b90a822f9a + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.1.2.tgz + version: 0.1.2 + - created: 2016-12-02T09:03:20.175832035Z + description: Factorio dedicated server. + digest: 3cc1f83669fd1d97eb96e76ba4821e8664350a4c310d3a14e62be18cc09e59b7 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.1.1.tgz + version: 0.1.1 + - created: 2016-11-07T18:33:21.243890443Z + description: Factorio dedicated server. + digest: 8edc1340cd99225a769b5843a677896c0d54a079133f9759211a1839bc7bb3eb + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: factorio + sources: + - https://github.com/games-on-k8s/docker-factorio + urls: + - https://kubernetes-charts.storage.googleapis.com/factorio-0.1.0.tgz + version: 0.1.0 + gcloud-endpoints: + - created: 2017-04-28T00:18:30.077956448Z + description: Develop, deploy, protect and monitor your APIs with Google Cloud + Endpoints. + digest: 172b56d0343c560f06e18858038e2096c910b37075a90f4303f77a79342b56fd + engine: gotpl + home: https://cloud.google.com/endpoints/ + keywords: + - google + - endpoints + - nginx + - gcloud + - proxy + - authentication + - monitoring + - api + - swagger + - open api + maintainers: + - email: mtucker@deis.com + name: Matt Tucker + name: gcloud-endpoints + urls: + - https://kubernetes-charts.storage.googleapis.com/gcloud-endpoints-0.1.0.tgz + version: 0.1.0 + gcloud-sqlproxy: + - created: 2017-04-28T00:18:30.078355277Z + description: Google Cloud SQL Proxy + digest: 1115afe0958f1ed01e568ec4c35b48ac0094704165b8808634ea5331c0d6da7d + engine: gotpl + home: https://cloud.google.com/sql/docs/postgres/sql-proxy + keywords: + - google + - cloud + - postgresql + - mysql + - sql + - sqlproxy + maintainers: + - email: rmocius@gmail.com + name: Rimas Mocevicius + name: gcloud-sqlproxy + sources: + - https://github.com/rimusz/charts + urls: + - https://kubernetes-charts.storage.googleapis.com/gcloud-sqlproxy-0.1.0.tgz + version: 0.1.0 + ghost: + - created: 2017-04-28T00:18:30.079159648Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 91d195c99e00b2801eafef5c23fcf9ced218bb29c7097f08139e2bdc80e38a0f + engine: gotpl + home: http://www.ghost.org/ + icon: https://bitnami.com/assets/stacks/ghost/img/ghost-stack-220x234.png + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.7.tgz + version: 0.4.7 + - created: 2017-04-06T10:48:26.261677382Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 6342a95aeef40690430c2e80b167fbb116a632746cdca49cfac4cbd535eadb22 + engine: gotpl + home: http://www.ghost.org/ + icon: https://bitnami.com/assets/stacks/ghost/img/ghost-stack-220x234.png + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-08T19:03:31.719494672Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 8998a9a4e75e777edb6f06c05b45d461daebba09021161af3bef523efd193b15 + engine: gotpl + home: http://www.ghost.org/ + icon: https://bitnami.com/assets/stacks/ghost/img/ghost-stack-220x234.png + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.5.tgz + version: 0.4.5 + - created: 2017-02-27T17:03:27.767416735Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: e44c9a53355086ded1832f769dca515b863337ad118ba618ef97f37b3ef84030 + engine: gotpl + home: http://www.ghost.org/ + icon: https://bitnami.com/assets/stacks/ghost/img/ghost-stack-220x234.png + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-11T00:18:52.2688126Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: b0c94a93c88fde68bb4fc78e92691d46cd2eb4d32cbac011e034565fbd35d46b + engine: gotpl + home: http://www.ghost.org/ + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-29T22:48:35.944809746Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 791ccb42b62d56d50c72b37db3282eb3f2af75d667a25542d76c7991004eb822 + engine: gotpl + home: http://www.ghost.org/ + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.342008382Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 331a2b145bfdb39b626313cda7dc539f32dbda5149893957589c5406317fca53 + engine: gotpl + home: http://www.ghost.org/ + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.183434341Z + description: A simple, powerful publishing platform that allows you to share your + stories with the world + digest: 804227af037082a0f5c3c579feb9e24eb3682449e78876971c93a109bc716f40 + engine: gotpl + home: http://www.ghost.org/ + keywords: + - ghost + - blog + - http + - web + - application + - nodejs + - javascript + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: ghost + sources: + - https://github.com/bitnami/bitnami-docker-ghost + urls: + - https://kubernetes-charts.storage.googleapis.com/ghost-0.4.0.tgz + version: 0.4.0 + gitlab-ce: + - created: 2017-04-28T00:18:30.080184798Z + description: GitLab Community Edition + digest: df5e36c64bf1b8e2b77609c1cd9c717df47c290777a005ebf0edbe42d1f0ac70 + home: https://about.gitlab.com + icon: https://gitlab.com/uploads/group/avatar/6543/gitlab-logo-square.png + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.7.tgz + version: 0.1.7 + - created: 2017-04-11T16:33:29.173232912Z + description: GitLab Community Edition + digest: 80094520d1bee55c7e25ad740bbfe3740814f389e6221b2fa536f77aabba9b37 + home: https://about.gitlab.com + icon: https://gitlab.com/uploads/group/avatar/6543/gitlab-logo-square.png + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.6.tgz + version: 0.1.6 + - created: 2017-03-23T20:48:32.107763732Z + description: GitLab Community Edition + digest: 20c0895dd3c5c1edbc0e3be4687f0d0874599cd0c53e49560f9f0a91506957ce + home: https://about.gitlab.com + icon: https://gitlab.com/uploads/group/avatar/6543/gitlab-logo-square.png + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.5.tgz + version: 0.1.5 + - created: 2017-02-14T02:48:28.88667289Z + description: GitLab Community Edition + digest: e05d4de559597760d59ca684fab107abcc0968b3260a77b16099d31e0b00cd74 + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.4.tgz + version: 0.1.4 + - created: 2017-02-13T20:18:28.097071087Z + description: GitLab Community Edition + digest: a2fef3fd8d3187f8a4242d66435488bce4193ae3f2db8d3ec14da1c4373c2519 + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.3.tgz + version: 0.1.3 + - created: 2017-01-26T03:18:35.336711333Z + description: GitLab Community Edition + digest: 3ca821c4e3bec2fe7541b95f94503875c508517e2f1200368268511e254df360 + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.2.tgz + version: 0.1.2 + - created: 2017-01-13T20:48:31.520266166Z + description: GitLab Community Edition + digest: d78cfc8cc2e262c49e1aee3af046509b92b022a5cd4b522778e846ddcd808d9b + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.1.tgz + version: 0.1.1 + - created: 2016-12-15T21:18:24.667256782Z + description: GitLab Community Edition + digest: 2c84a056e3f6d66a6aed763b2f4a26c1f4275eb3f6ca64798962a070809c6272 + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: gitlab-ce + sources: + - https://hub.docker.com/r/gitlab/gitlab-ce/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ce-0.1.0.tgz + version: 0.1.0 + gitlab-ee: + - created: 2017-04-28T00:18:30.081242553Z + description: GitLab Enterprise Edition + digest: 3fa791ba74b0e43c05178b6eb16fb21eede5bb046e9870b854db38654768de09 + home: https://about.gitlab.com + icon: https://gitlab.com/uploads/group/avatar/6543/gitlab-logo-square.png + keywords: + - git + - ci + - deploy + - issue tracker + - code review + - wiki + maintainers: + - email: contact@quent.in + name: Quentin Rousseau + name: gitlab-ee + sources: + - https://hub.docker.com/r/gitlab/gitlab-ee/ + - https://docs.gitlab.com/omnibus/ + urls: + - https://kubernetes-charts.storage.googleapis.com/gitlab-ee-0.1.6.tgz + version: 0.1.6 + grafana: + - created: 2017-04-28T00:18:30.082782593Z + description: The leading tool for querying and visualizing time series and metrics. + digest: 721c85c6407ef534dc0d2366af3092f63229d51158abb124496efbe1a224907b + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.3.1.tgz + version: 0.3.1 + - created: 2017-04-14T01:18:27.369088748Z + description: The leading tool for querying and visualizing time series and metrics. + digest: 8f1db00e769d13c2435841b9b89b2045653039ca377c4547f46b33ec566f60ef + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.3.0.tgz + version: 0.3.0 + - created: 2017-04-05T18:03:30.376700685Z + description: The leading tool for querying and visualizing time series and metrics. + digest: e428e33b249f2261882632cc658c36d50df81460c091fd29404a3b3d16886416 + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.5.tgz + version: 0.2.5 + - created: 2017-03-16T23:33:31.578792832Z + description: The leading tool for querying and visualizing time series and metrics. + digest: 2fefc51028c411641e5bf85b799fc8e608c7646c7054cfaa2149d4e83610d60a + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.4.tgz + version: 0.2.4 + - created: 2017-03-14T23:48:31.886602615Z + description: The leading tool for querying and visualizing time series and metrics. + digest: 7fac33dcd25d8ac60071e56968db133ecfa4f796732f92044d1f7714495840fd + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.3.tgz + version: 0.2.3 + - created: 2017-02-13T22:33:28.777518221Z + description: The leading tool for querying and visualizing time series and metrics. + digest: 037158b80733838ab2ad84928c999997ab76b5383cbeb4cce6c174fe5bc5ae8d + engine: gotpl + home: https://grafana.net + icon: https://raw.githubusercontent.com/grafana/grafana/master/public/img/logo_transparent_400x.png + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.2.tgz + version: 0.2.2 + - created: 2017-02-13T04:18:31.532928133Z + description: A Helm chart for Kubernetes + digest: e49f08bd26843eb0449304c72fd9be3e65de0d8647457f39807f9aa296ba9b6a + engine: gotpl + home: https://grafana.net + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.1.tgz + version: 0.2.1 + - created: 2017-01-29T23:03:26.897254812Z + description: A Helm chart for Kubernetes + digest: 283be1abb811d005b3759f65761e4465e79f2031be8a47b3d87256e88888f047 + engine: gotpl + home: https://grafana.net + maintainers: + - email: zanhsieh@gmail.com + name: Ming Hsieh + name: grafana + sources: + - https://github.com/grafana/grafana + urls: + - https://kubernetes-charts.storage.googleapis.com/grafana-0.2.0.tgz + version: 0.2.0 + influxdb: + - created: 2017-04-28T00:18:30.083302575Z + description: Scalable datastore for metrics, events, and real-time analytics. + digest: c34d6d57a1c4f5cdf1d1eb869231b27d2c080c7d219ab330f43fd9fd795b8d40 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + sources: + - https://github.com/influxdata/influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.4.0.tgz + version: 0.4.0 + - created: 2017-03-23T01:19:01.442621577Z + description: Scalable datastore for metrics, events, and real-time analytics. + digest: 76522d9156e4939669344cc72a9674ce16ccc7049b06c03eaa0822ed4ce59254 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + sources: + - https://github.com/influxdata/influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.3.0.tgz + version: 0.3.0 + - created: 2017-03-17T05:33:29.077307939Z + description: Scalable datastore for metrics, events, and real-time analytics. + digest: 499e87e9a0cfb2452107eaabc5e81bb5382554731b12e20dac791d81d492044e + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + sources: + - https://github.com/influxdata/influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.2.1.tgz + version: 0.2.1 + - created: 2017-02-13T17:03:30.109119817Z + description: Scalable datastore for metrics, events, and real-time analytics. + digest: e78ce7b2aac9538e5f6087fc77e5e30ab41a2a54d9bb1381b02830dd3324f0a9 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + sources: + - https://github.com/influxdata/influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.1.2.tgz + version: 0.1.2 + - created: 2017-02-13T04:33:52.294695041Z + description: Chart for InfluxDB + digest: 3341f3cca2d60540b219390688ac600ec605a331975cd402d6489969a0346aec + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-28T00:48:33.328518706Z + description: Chart for InfluxDB + digest: a64fad23b1d02c8f14955f652f60a36ca2bc9f01fb5f4d80cd88bcf9f96a7300 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/influxdb/ + keywords: + - influxdb + - database + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: influxdb + urls: + - https://kubernetes-charts.storage.googleapis.com/influxdb-0.1.0.tgz + version: 0.1.0 + jasperreports: + - created: 2017-04-28T00:18:30.084006378Z + description: The JasperReports server can be used as a stand-alone or embedded + reporting and BI server that offers web-based reporting, analytic tools and + visualization, and a dashboard feature for compiling multiple custom views + digest: 8a649026f55b2fa1e743c93fd331e127e66b49c4d7f20116a2bb06e5937f4828 + engine: gotpl + home: http://community.jaspersoft.com/project/jasperreports-server + icon: https://bitnami.com/assets/stacks/jasperserver/img/jasperserver-stack-110x117.png + keywords: + - business intelligence + - java + - jasper + - reporting + - analytic + - visualization + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: jasperreports + sources: + - https://github.com/bitnami/bitnami-docker-jasperreports + urls: + - https://kubernetes-charts.storage.googleapis.com/jasperreports-0.1.5.tgz + version: 0.1.5 + - created: 2017-04-06T10:18:27.580953879Z + description: The JasperReports server can be used as a stand-alone or embedded + reporting and BI server that offers web-based reporting, analytic tools and + visualization, and a dashboard feature for compiling multiple custom views + digest: d4a62f7ace55256852e5c650a56ccf671633c4f223180d304cfb03b9cd7993aa + engine: gotpl + home: http://community.jaspersoft.com/project/jasperreports-server + icon: https://bitnami.com/assets/stacks/jasperserver/img/jasperserver-stack-110x117.png + keywords: + - business intelligence + - java + - jasper + - reporting + - analytic + - visualization + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: jasperreports + sources: + - https://github.com/bitnami/bitnami-docker-jasperreports + urls: + - https://kubernetes-charts.storage.googleapis.com/jasperreports-0.1.4.tgz + version: 0.1.4 + - created: 2017-03-08T19:03:31.722665089Z + description: The JasperReports server can be used as a stand-alone or embedded + reporting and BI server that offers web-based reporting, analytic tools and + visualization, and a dashboard feature for compiling multiple custom views + digest: 99af0fca7ef1c475b239f2c8fc2dee6b040ea76b3c30bba1431f358df873aa49 + engine: gotpl + home: http://community.jaspersoft.com/project/jasperreports-server + icon: https://bitnami.com/assets/stacks/jasperserver/img/jasperserver-stack-110x117.png + keywords: + - business intelligence + - java + - jasper + - reporting + - analytic + - visualization + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: jasperreports + sources: + - https://github.com/bitnami/bitnami-docker-jasperreports + urls: + - https://kubernetes-charts.storage.googleapis.com/jasperreports-0.1.3.tgz + version: 0.1.3 + - created: 2017-02-13T21:33:27.741967589Z + description: The JasperReports server can be used as a stand-alone or embedded + reporting and BI server that offers web-based reporting, analytic tools and + visualization, and a dashboard feature for compiling multiple custom views + digest: f01e53d1b89c4fb1fcd9702cd5f4e48d77607aed65f249e1f94b8b21f7eef3f4 + engine: gotpl + home: http://community.jaspersoft.com/project/jasperreports-server + icon: https://bitnami.com/assets/stacks/jasperserver/img/jasperserver-stack-110x117.png + keywords: + - business intelligence + - java + - jasper + - reporting + - analytic + - visualization + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: jasperreports + sources: + - https://github.com/bitnami/bitnami-docker-jasperreports + urls: + - https://kubernetes-charts.storage.googleapis.com/jasperreports-0.1.2.tgz + version: 0.1.2 + - created: 2017-01-28T01:33:32.784491819Z + description: The JasperReports server can be used as a stand-alone or embedded + reporting and BI server that offers web-based reporting, analytic tools and + visualization, and a dashboard feature for compiling multiple custom views + digest: 5cc4af8c88691d7030602c97be2ccbc125ef11129b361da0aa236a127c31b965 + engine: gotpl + home: http://community.jaspersoft.com/project/jasperreports-server + icon: https://bitnami.com/assets/stacks/jasperserver/img/jasperserver-stack-110x117.png + keywords: + - business intelligence + - java + - jasper + - reporting + - analytic + - visualization + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: jasperreports + sources: + - https://github.com/bitnami/bitnami-docker-jasperreports + urls: + - https://kubernetes-charts.storage.googleapis.com/jasperreports-0.1.1.tgz + version: 0.1.1 + jenkins: + - created: 2017-04-28T00:18:30.084552323Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: e8bb6edabe1af4db4f67e2939b88fa45416167612349a3a32bcf786c529a18e7 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.6.0.tgz + version: 0.6.0 + - created: 2017-04-24T21:03:29.315054715Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 0c962935ead654ed9422c75978195b9ec893b1e1909b38cdbc15e3dc24863f7e + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.5.1.tgz + version: 0.5.1 + - created: 2017-04-19T16:48:30.580850385Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 338a9265e567f75469c4e227f583de14b7ab38da137b1d4fd5eee24ddea05724 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.5.0.tgz + version: 0.5.0 + - created: 2017-04-13T19:03:30.206806842Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 66cb4ab56bdac82c98eea4ceaf0fbcd9bb7c5c446f21bb396c3ce4246a76f423 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.4.1.tgz + version: 0.4.1 + - created: 2017-04-13T05:33:26.915479665Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 109c67f85696136b5629126952d5eb4196ca3cf36524976adf7babd3b8631782 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.4.0.tgz + version: 0.4.0 + - created: 2017-04-13T05:18:28.907645759Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: afa4583548e2e89617e21b91ef014285f060ad4a5355741260d72e27bb8eb4d3 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.3.1.tgz + version: 0.3.1 + - created: 2017-03-30T07:48:56.453711055Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: e9c5280a7cc57b16c51e70af9e4bf8b10f6525daf191c22a3a050a1791e5f7c7 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.3.0.tgz + version: 0.3.0 + - created: 2017-03-22T22:18:33.707478335Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: f5cf5bafd797d65bbbb55ff0b31935123d3f7ac283e703ac0b9df73b016edf59 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.2.0.tgz + version: 0.2.0 + - created: 2017-03-15T09:48:31.97803944Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: f8167252e615a10eb155087d6666985e8eb083781faa0485c0413de8c13deeb8 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.15.tgz + version: 0.1.15 + - created: 2017-03-14T13:33:36.105867963Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 6c5039f7ab62e7f74bb902f5804814f66733c535908b4ffae1cf759efa3c6f24 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.14.tgz + version: 0.1.14 + - created: 2017-02-13T22:18:28.949796594Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 90aa0fe9bac319690c871327dff6fee85e7de117e961dfa95ba12abedec4e99b + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.13.tgz + version: 0.1.13 + - created: 2017-02-13T21:48:52.587710548Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 92224a4dcf96772652e91cee6369a08f21f4ba7d1513ee458b5724720f7045ca + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.12.tgz + version: 0.1.12 + - created: 2017-02-13T21:03:28.21318035Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: c75cadf6bc32c90cfc8a61c1f233884004670c8583edefcfcaa43b5573422923 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.10.tgz + version: 0.1.10 + - created: 2017-02-13T17:03:30.11276321Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 47f32f975f942607a4b4ca05bd804ece5e2381840508d049251ca692e83080c0 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.9.tgz + version: 0.1.9 + - created: 2017-02-13T04:18:31.534794405Z + description: Open source continuous integration server. It supports multiple SCM + tools including CVS, Subversion and Git. It can execute Apache Ant and Apache + Maven-based projects as well as arbitrary scripts. + digest: 0764541a4165016e68bef6de1f405964b58ebb2b43b4d738f4bbbbad794b5df8 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.8.tgz + version: 0.1.8 + - created: 2017-01-27T21:48:32.069740444Z + description: A Jenkins Helm chart for Kubernetes. + digest: c29819a435e9474277846492930e910c9f7c0605717421c4efe411ce476283ab + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.7.tgz + version: 0.1.7 + - created: 2017-01-04T00:48:19.7378014Z + description: A Jenkins Helm chart for Kubernetes. + digest: 58ddd6442c8534de79a8d5eaca48263c744ca9fa6e9af7ceb28d1fda9b88ddb5 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.6.tgz + version: 0.1.6 + - created: 2016-12-19T22:03:51.103057889Z + description: A Jenkins Helm chart for Kubernetes. + digest: 2c8dff77b7ad736ac54e5335f5d52bfacaf7ea2ad97558da507b7f5bb9f4d549 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.5.tgz + version: 0.1.5 + - created: 2016-12-09T18:48:20.183887307Z + description: A Jenkins Helm chart for Kubernetes. + digest: 0b7016624acec8d66f6d919611e8f1c9853a5475c9801c3da6e50b7ce044ed57 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.4.tgz + version: 0.1.4 + - created: 2016-12-05T18:33:22.028569484Z + description: A Jenkins Helm chart for Kubernetes. + digest: 82b53a4c90ac2cf71bb41d870939ce1e980192e1407ba8825051c0ed98c04906 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.1.tgz + version: 0.1.1 + - created: 2016-10-19T00:03:14.028672648Z + description: A Jenkins Helm chart for Kubernetes. + digest: ea97fbfeaf9b9701d71dbc2b6fa50bff25a33f0565233d81170a6ac225d22ab4 + home: https://jenkins.io/ + icon: https://wiki.jenkins-ci.org/download/attachments/2916393/logo.png + maintainers: + - email: lachlan@deis.com + name: Lachlan Evenson + - email: viglesias@google.com + name: Vic Iglesias + name: jenkins + sources: + - https://github.com/jenkinsci/jenkins + - https://github.com/jenkinsci/docker-jnlp-slave + urls: + - https://kubernetes-charts.storage.googleapis.com/jenkins-0.1.0.tgz + version: 0.1.0 + joomla: + - created: 2017-04-28T00:18:30.085219154Z + description: PHP content management system (CMS) for publishing web content + digest: 6f9934487533f325515f4877b3af1306c87d64bf3ece9d4bd875289cd73b340d + engine: gotpl + home: http://www.joomla.org/ + icon: https://bitnami.com/assets/stacks/joomla/img/joomla-stack-220x234.png + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.6.tgz + version: 0.4.6 + - created: 2017-04-06T11:03:25.397962583Z + description: PHP content management system (CMS) for publishing web content + digest: f9dedab2fc2dbf170cf45b2c230baa6d20aad9a6f8ccfcb09c459602fc5213dc + engine: gotpl + home: http://www.joomla.org/ + icon: https://bitnami.com/assets/stacks/joomla/img/joomla-stack-220x234.png + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-02T19:33:28.178324258Z + description: PHP content management system (CMS) for publishing web content + digest: 1e067e459873ae832d54ff516a3420f7f0e16ecd8f72f4c4f02be22e47702077 + engine: gotpl + home: http://www.joomla.org/ + icon: https://bitnami.com/assets/stacks/joomla/img/joomla-stack-220x234.png + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-27T16:48:32.159029074Z + description: PHP content management system (CMS) for publishing web content + digest: 9a99b15e83e18955eb364985cd545659f1176ef203ac730876dfe39499edfb18 + engine: gotpl + home: http://www.joomla.org/ + icon: https://bitnami.com/assets/stacks/joomla/img/joomla-stack-220x234.png + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-11T00:18:52.272598223Z + description: PHP content management system (CMS) for publishing web content + digest: 07c3a16eb674ffc74fe5b2b16191b8bb24c63bdae9bce9710bda1999920c46fc + engine: gotpl + home: http://www.joomla.org/ + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.345952551Z + description: PHP content management system (CMS) for publishing web content + digest: 95fbe272015941544609eee90b3bffd5172bfdec10be13636510caa8478a879e + engine: gotpl + home: http://www.joomla.org/ + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.18539038Z + description: PHP content management system (CMS) for publishing web content + digest: 0a01ea051ec15274932c8d82076c1a9fd62584b0fb916a81372319bef223c20e + engine: gotpl + home: http://www.joomla.org/ + keywords: + - joomla + - cms + - blog + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: joomla + sources: + - https://github.com/bitnami/bitnami-docker-joomla + urls: + - https://kubernetes-charts.storage.googleapis.com/joomla-0.4.0.tgz + version: 0.4.0 + kapacitor: + - created: 2017-04-28T00:18:30.085544137Z + description: InfluxDB's native data processing engine. It can process both stream + and batch data from InfluxDB. + digest: f484ef5acbc3ad68bb765adb1fd6c74920b9265371d3379e43a47ec266dc8449 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/kapacitor/ + keywords: + - kapacitor + - stream + - etl + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: kapacitor + sources: + - https://github.com/influxdata/kapacitor + urls: + - https://kubernetes-charts.storage.googleapis.com/kapacitor-0.2.2.tgz + version: 0.2.2 + - created: 2017-03-22T21:33:33.841225941Z + description: InfluxDB's native data processing engine. It can process both stream + and batch data from InfluxDB. + digest: c592b3bf2dec92c2273057b984b7289c17032dc578ea0f2ec86aeb5e838a7047 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/kapacitor/ + keywords: + - kapacitor + - stream + - etl + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: kapacitor + sources: + - https://github.com/influxdata/kapacitor + urls: + - https://kubernetes-charts.storage.googleapis.com/kapacitor-0.2.1.tgz + version: 0.2.1 + - created: 2017-02-13T21:48:52.58883187Z + description: InfluxDB's native data processing engine. It can process both stream + and batch data from InfluxDB. + digest: 18971c9c5b3805830f72fdfacd6c1dfc173db4797994f61b3666296b67abe70a + engine: gotpl + home: https://www.influxdata.com/time-series-platform/kapacitor/ + keywords: + - kapacitor + - stream + - etl + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: kapacitor + sources: + - https://github.com/influxdata/kapacitor + urls: + - https://kubernetes-charts.storage.googleapis.com/kapacitor-0.1.2.tgz + version: 0.1.2 + - created: 2017-02-13T21:03:28.215149313Z + description: Chart for Chronograf + digest: e0d29608602df25a9352629afd3fd7615e01a23549e1d1eba8101ee1b725e528 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/kapacitor/ + keywords: + - kapacitor + - stream + - etl + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: kapacitor + urls: + - https://kubernetes-charts.storage.googleapis.com/kapacitor-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-28T01:33:32.786133788Z + description: Chart for Chronograf + digest: efde65696634d3bf1dc4c9caae44e68f7ed8c7599aab809c8cdd0fde7e4f9efc + engine: gotpl + home: https://www.influxdata.com/time-series-platform/kapacitor/ + keywords: + - kapacitor + - stream + - etl + - timeseries + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: kapacitor + urls: + - https://kubernetes-charts.storage.googleapis.com/kapacitor-0.1.0.tgz + version: 0.1.0 + kube-lego: + - apiVersion: v1 + created: 2017-04-28T00:18:30.085897046Z + description: Automatically requests certificates from Let's Encrypt + digest: adc8f0fe1c244e5abda2d918afe9153d47648b2acb779adaf796fa5a7266ea6b + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.8.tgz + version: 0.1.8 + - apiVersion: v1 + created: 2017-03-20T20:03:37.426644363Z + description: Automatically requests certificates from Let's Encrypt + digest: 0110b8c36f6ad016684ef904b5e514751ffa7a89ba7b88cf4a8376c889e03693 + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.7.tgz + version: 0.1.7 + - apiVersion: v1 + created: 2017-03-02T19:33:28.179029307Z + description: Automatically requests certificates from Let's Encrypt + digest: 275265fda80ccc62376ebd3a200bbea94b105cbf3481efcc726d6a33d73da8d0 + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.6.tgz + version: 0.1.6 + - apiVersion: v1 + created: 2017-02-27T17:33:27.264070807Z + description: Automatically requests certificates from Let's Encrypt + digest: 62de296cc21c6b286097de9ac389033e18a4cd3cf414cd97b7a02d4c3be14d6d + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.5.tgz + version: 0.1.5 + - apiVersion: v1 + created: 2017-02-14T15:48:28.578398517Z + description: Automatically requests certificates from Let's Encrypt + digest: 1bc993b68eb51fb00e4eb18a65df9e2182f895bd46134d6ddfdc0dd3b6432a98 + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.4.tgz + version: 0.1.4 + - apiVersion: v1 + created: 2017-02-11T00:48:25.354730418Z + description: Automatically requests certificates from Let's Encrypt + digest: d4b3694951c2bb42b356217a8f12870bd3806dac4de3390056279998fd634c34 + engine: gotpl + keywords: + - kube-lego + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube-lego + sources: + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-lego-0.1.3.tgz + version: 0.1.3 + kube-ops-view: + - created: 2017-04-28T00:18:30.086143954Z + description: Kubernetes Operational View - read-only system dashboard for multiple + K8s clusters + digest: 9e7b62f2d781aaacee801e91efe4d6b6ef6df9d511c0d6fb2d6e7977c8d0a06e + home: https://github.com/hjacobs/kube-ops-view + icon: https://raw.githubusercontent.com/hjacobs/kube-ops-view/master/kube-ops-view-logo.png + keywords: + - kubernetes + - dashboard + - operations + maintainers: + - email: henning@jacobs1.de + name: Henning Jacobs + name: kube-ops-view + sources: + - https://github.com/hjacobs/kube-ops-view + urls: + - https://kubernetes-charts.storage.googleapis.com/kube-ops-view-0.2.0.tgz + version: 0.2.0 + kube2iam: + - created: 2017-04-28T00:18:30.086423576Z + description: Provide IAM credentials to pods based on annotations. + digest: 2035d8dfae5733fa475914694cd060e28954b1f5c930b6c4800ee165d23abc46 + engine: gotpl + keywords: + - kube2iam + - aws + - iam + - security + maintainers: + - email: jm.carp@gmail.com + name: Josh Carp + - email: michael.haselton@gmail.com + name: Michael Haselton + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube2iam + sources: + - https://github.com/jtblin/kube2iam + urls: + - https://kubernetes-charts.storage.googleapis.com/kube2iam-0.2.1.tgz + version: 0.2.1 + - created: 2017-03-02T18:48:30.431985789Z + description: Provide IAM credentials to pods based on annotations. + digest: 5d2226fb101b800fc5a6edb5566d329880c604d75f4245da55f47027bcf5546e + engine: gotpl + keywords: + - kube2iam + - aws + - iam + - security + maintainers: + - email: jm.carp@gmail.com + name: Josh Carp + - email: michael.haselton@gmail.com + name: Michael Haselton + - email: mgoodness@gmail.com + name: Michael Goodness + name: kube2iam + sources: + - https://github.com/jtblin/kube2iam + urls: + - https://kubernetes-charts.storage.googleapis.com/kube2iam-0.2.0.tgz + version: 0.2.0 + - created: 2017-02-13T17:03:30.115672844Z + description: Provide IAM credentials to containers running inside a kubernetes + cluster based on annotations. + digest: 63d913fb8f31c287b1f1d45d310517c0b22597ecdaef19d7ad2520bff990969a + engine: gotpl + keywords: + - kube2iam + - aws + - iam + - security + maintainers: + - email: jm.carp@gmail.com + name: Josh Carp + - email: michael.haselton@gmail.com + name: Michael Haselton + name: kube2iam + sources: + - https://github.com/jtblin/kube2iam + urls: + - https://kubernetes-charts.storage.googleapis.com/kube2iam-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-19T00:33:27.789172853Z + description: Provide IAM credentials to containers running inside a kubernetes + cluster based on annotations. + digest: b4de5bddfa0af6737ea91d9b3a9bcfc3a0e5a656045e06038505613b214120fc + engine: gotpl + keywords: + - kube2iam + - aws + - iam + - security + maintainers: + - email: jm.carp@gmail.com + name: Josh Carp + - email: michael.haselton@gmail.com + name: Michael Haselton + name: kube2iam + sources: + - https://github.com/jtblin/kube2iam + urls: + - https://kubernetes-charts.storage.googleapis.com/kube2iam-0.1.0.tgz + version: 0.1.0 + linkerd: + - apiVersion: v1 + created: 2017-04-28T00:18:30.086763621Z + description: Service mesh for cloud native apps + digest: 9341ea67647129999beadb49f1a33131a187569581095907f902daffa73dd73b + home: https://linkerd.io/ + icon: https://pbs.twimg.com/profile_images/690258997237014528/KNgQd9GL_400x400.png + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: linkerd + sources: + - https://github.com/BuoyantIO/linkerd + urls: + - https://kubernetes-charts.storage.googleapis.com/linkerd-0.2.0.tgz + version: 0.2.0 + - apiVersion: v1 + created: 2017-03-27T13:18:34.232799345Z + description: Service mesh for cloud native apps + digest: f9f2287d026c6de3a522bdcffdff061f81a8e64274da4acefdc9d2177b603570 + home: https://linkerd.io/ + icon: https://pbs.twimg.com/profile_images/690258997237014528/KNgQd9GL_400x400.png + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: linkerd + sources: + - https://github.com/BuoyantIO/linkerd + urls: + - https://kubernetes-charts.storage.googleapis.com/linkerd-0.1.1.tgz + version: 0.1.1 + - apiVersion: v1 + created: 2017-02-13T04:33:52.299092507Z + description: Service mesh for cloud native apps + digest: 147759e4982f1dffce894e0d4242fb914d21014700a7d9891e8dc352318633fa + home: https://linkerd.io/ + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: linkerd + sources: + - https://github.com/BuoyantIO/linkerd + urls: + - https://kubernetes-charts.storage.googleapis.com/linkerd-0.1.0.tgz + version: 0.1.0 + locust: + - created: 2017-04-28T00:18:30.087097677Z + description: A modern load testing framework + digest: eb91b0e3c0b618cf5ad0f24d2685fe4086bc6f497685e58ad8a64032c4e82b7a + home: http://locust.io + icon: https://pbs.twimg.com/profile_images/1867636195/locust-logo-orignal.png + maintainers: + - email: vincent.drl@gmail.com + name: Vincent De Smet + name: locust + sources: + - https://github.com/honestbee/distributed-load-testing + urls: + - https://kubernetes-charts.storage.googleapis.com/locust-0.1.2.tgz + version: 0.1.2 + - created: 2017-04-20T16:18:33.345004534Z + description: A chart for locust distributed load testing + digest: 40a353b38823eaa4954dcb70ae92858671a16fb8d00ac9677a36912aab6b629a + name: locust + sources: + - https://github.com/honestbee/distributed-load-testing + urls: + - https://kubernetes-charts.storage.googleapis.com/locust-0.1.1.tgz + version: 0.1.1 + - created: 2017-04-11T16:03:29.689097384Z + description: A chart for locust distributed load testing + digest: 2cd175e8c8d66625ca3608046d5764662b53e3760aa260a934d3d2ee35148c49 + name: locust + sources: + - https://github.com/honestbee/distributed-load-testing + urls: + - https://kubernetes-charts.storage.googleapis.com/locust-0.1.0.tgz + version: 0.1.0 + magento: + - created: 2017-04-28T00:18:30.087794886Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: 2d7ebf934ca0e742e248753da6502f05929b51329a42d0d4b3db4e1824381f11 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-31T19:33:30.42890495Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: a289a051d6d2e8cf833398d7d63fcb1fd953d202f0755800eebe568804fce525 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-09T19:33:31.142591331Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: 311a98182281b981f60fbd446b42f6af4b86e0bac9445bf2248b1b47dfce5933 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-02T19:33:28.180428252Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: fccfa89493a977a2f898a5e6d3c6fa4fda15faf0dc0a9e48772744a32c2b1d24 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-27T16:48:32.161186722Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: fb33cafa4874855419ea3178dc0660a856ed6f8b581b984f38e86701d54e54a6 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.349108265Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: c597107a5cf512d616e5e4134e562f8fcc6e79ad1f78241e9a40ded77e77ef62 + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-04T00:48:19.74048297Z + description: A feature-rich flexible e-commerce solution. It includes transaction + options, multi-store functionality, loyalty programs, product categorization + and shopper filtering, promotion rules, and more. + digest: 526115583b8ae741f6819c2a0d3e719a6622a63a0a2e9918e56d2ebaa6ad498b + engine: gotpl + home: https://magento.com/ + icon: https://bitnami.com/assets/stacks/magento/img/magento-stack-110x117.png + keywords: + - magento + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: magento + sources: + - https://github.com/bitnami/bitnami-docker-magento + urls: + - https://kubernetes-charts.storage.googleapis.com/magento-0.4.0.tgz + version: 0.4.0 + mariadb: + - created: 2017-04-28T00:18:30.088188975Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: bc13b8aa3728de5595bb83ea8a5c21f9234e169352739d8e72f04d1ed2558508 + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.6.0.tgz + version: 0.6.0 + - created: 2017-04-03T22:33:26.672780802Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: 23b81e8bc8cd8577a47a73578b56f68a2a3c15640e0a0fb0ac7f1646845bc0b0 + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.14.tgz + version: 0.5.14 + - created: 2017-03-23T19:18:29.910295788Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: a4c8e19714e2a3d29eae03c63a63a7343b3e4f62df2956603acca7eb42ab15de + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.13.tgz + version: 0.5.13 + - created: 2017-03-17T22:48:29.250381078Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: 8d5cd0e79d675baa79ab4f999da8e10037ae4c2158e434b7b60d4ff345f1d976 + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.12.tgz + version: 0.5.12 + - created: 2017-03-16T13:33:30.840089178Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: 0841b64dbe2fe499bd3554fa9b2074be0ed6535e9f87d3ea2c13bcfc2616717b + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.11.tgz + version: 0.5.11 + - created: 2017-03-15T23:03:34.130536152Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: 587d7ef318ee1229d14e3b783c3e825f90f274f56b2eb62f1c7126f69fec6fc2 + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.10.tgz + version: 0.5.10 + - created: 2017-03-08T19:03:31.728285865Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. MariaDB Server is intended for mission-critical, heavy-load + production systems as well as for embedding into mass-deployed software. + digest: cfec18e7be68a616a5e4944c21ed0b65dab1701cf7182d0d1bdea83df6a77ab1 + engine: gotpl + home: https://mariadb.org + icon: https://bitnami.com/assets/stacks/mariadb/img/mariadb-stack-220x234.png + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.9.tgz + version: 0.5.9 + - created: 2017-02-11T00:18:52.27620633Z + description: Chart for MariaDB + digest: 33990fb57b2fafb4396498e02a8083f6a476cf0b465a4a52905a67aab73f3f43 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.8.tgz + version: 0.5.8 + - created: 2017-01-31T01:03:26.972704179Z + description: Chart for MariaDB + digest: 177a882b4248d6ebb2e72e125a3be775085bf082e4eb8f4359fee9e8640ede50 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.7.tgz + version: 0.5.7 + - created: 2017-01-27T22:33:30.411626628Z + description: Chart for MariaDB + digest: 730d179ab0d5922494e3c21594ce7b4f118f7fe796071dfcfdbbc2714ada7d15 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.6.tgz + version: 0.5.6 + - created: 2016-12-15T23:18:25.557355931Z + description: Chart for MariaDB + digest: c7811fad8165eb5d57eb55ad4e58d63e949d2f710c0fbbcd28cca11bb6af6de6 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.5.tgz + version: 0.5.5 + - created: 2016-12-09T18:48:20.185787408Z + description: Chart for MariaDB + digest: c6b73c5146d085a202f838741526fe5fdc2fae55b8f5f17c0c42fd195c65311f + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.4.tgz + version: 0.5.4 + - created: 2016-11-23T00:33:20.016012859Z + description: Chart for MariaDB + digest: 38786f4f6fe4fb7a2dcbc38aa0bfea72e40e4d9766e1679942af60e25b5ba9bd + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.3.tgz + version: 0.5.3 + - created: 2016-11-03T19:33:29.120356141Z + description: Chart for MariaDB + digest: 8eb24c65fbdb75711a8ffd8f8f6ed206951cdc3a24d74a355121bc11c6c02f3b + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.2.tgz + version: 0.5.2 + - created: 2016-10-19T00:03:14.031007257Z + description: Chart for MariaDB + digest: 6544dbf62586f570762a3a469f0086fe595379454fb46a53d001206a0e282268 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.1.tgz + version: 0.5.1 + - created: 2016-10-19T00:03:14.030598543Z + description: Chart for MariaDB + digest: c9f67f8140b3e7243d479f819d4ec8b2e992ee462b8fa579b920e86066955312 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.5.0.tgz + version: 0.5.0 + - created: 2016-10-19T00:03:14.03012509Z + description: Chart for MariaDB + digest: fe8adb0730567ad8cd63501ac18b178ec2a9a590d43dd7ad91fd2d5fcf6114be + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.4.0.tgz + version: 0.4.0 + - created: 2016-10-19T00:03:14.029775557Z + description: Chart for MariaDB + digest: 47fe08909187da025e7e86e9534a736df11e2629c5c123178113fe776992e9e7 + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.3.1.tgz + version: 0.3.1 + - created: 2016-10-19T00:03:14.02942396Z + description: Chart for MariaDB + digest: 7354d2672b3983e98fe5c31d96d2c3d9c73edefe642eb5e1981484804315c4bc + engine: gotpl + home: https://mariadb.org + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mariadb + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.3.0.tgz + version: 0.3.0 + mediawiki: + - created: 2017-04-28T00:18:30.088860566Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 0e51822c5547895109a5b41ce426c77f62d0434b40f3021afee8471ab976a6f5 + engine: gotpl + home: http://www.mediawiki.org/ + icon: https://bitnami.com/assets/stacks/mediawiki/img/mediawiki-stack-220x234.png + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.6.tgz + version: 0.4.6 + - created: 2017-04-06T10:18:27.586263816Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 0e419c2c5d87997f94a32da6597af3f3b52120dc1ec682dcbb6b238fb4825e06 + engine: gotpl + home: http://www.mediawiki.org/ + icon: https://bitnami.com/assets/stacks/mediawiki/img/mediawiki-stack-220x234.png + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-08T19:03:31.729226516Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 6f4dde26737f7f1aa63ffda6c259ce388e3a3509225f90f334bfc3f0f7617bc1 + engine: gotpl + home: http://www.mediawiki.org/ + icon: https://bitnami.com/assets/stacks/mediawiki/img/mediawiki-stack-220x234.png + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-13T04:18:31.539427547Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 0ba52b8c4c9e0bee3eb76fe625d2dc88729a1cdf41ace9d13cd4abc5b477cfb8 + engine: gotpl + home: http://www.mediawiki.org/ + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-21T00:18:31.350311075Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: f49df3e17f97b238743aad0376eb9db7e4a9bca3829a3a65d7bbb349344a73be + engine: gotpl + home: http://www.mediawiki.org/ + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.2.tgz + version: 0.4.2 + - created: 2016-12-15T21:18:24.670966268Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 339a90050d5cf4216140409349a356aa7cd8dc95e2cbdca06e4fdd11e87aa963 + engine: gotpl + home: http://www.mediawiki.org/ + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.186416136Z + description: Extremely powerful, scalable software and a feature-rich wiki implementation + that uses PHP to process and display data stored in a database. + digest: 9617f13f51f5bb016a072f2a026c627420721a1c5b7cd22f32d6cd0c90f34eda + engine: gotpl + home: http://www.mediawiki.org/ + keywords: + - mediawiki + - wiki + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mediawiki + sources: + - https://github.com/bitnami/bitnami-docker-mediawiki + urls: + - https://kubernetes-charts.storage.googleapis.com/mediawiki-0.4.0.tgz + version: 0.4.0 + memcached: + - created: 2017-04-28T00:18:30.090586195Z + description: Free & open source, high-performance, distributed memory object caching + system. + digest: 36ceb2767094598171b2851ecda54bd43d862b9b81aa4b294f3d8c8d59ddd79c + engine: gotpl + home: http://memcached.org/ + icon: https://upload.wikimedia.org/wikipedia/en/thumb/2/27/Memcached.svg/1024px-Memcached.svg.png + keywords: + - memcached + - cache + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: memcached + sources: + - https://github.com/docker-library/memcached + urls: + - https://kubernetes-charts.storage.googleapis.com/memcached-0.4.1.tgz + version: 0.4.1 + - created: 2017-02-13T04:18:31.539713999Z + description: Chart for Memcached + digest: 2b918dd8129a9d706e58b3de459004e3367c05a162d3e3cdb031cb6818d5f820 + engine: gotpl + home: http://memcached.org/ + keywords: + - memcached + - cache + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: memcached + sources: + - https://github.com/docker-library/memcached + urls: + - https://kubernetes-charts.storage.googleapis.com/memcached-0.4.0.tgz + version: 0.4.0 + minecraft: + - created: 2017-04-28T00:18:30.09103508Z + description: Minecraft server + digest: bc18a8356092c19f39ce94ff34b2160650a7bebca5723fd2e2f4e350c38793c0 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: minecraft + sources: + - https://hub.docker.com/r/itzg/minecraft-server/~/dockerfile/ + - https://github.com/itzg/dockerfiles + urls: + - https://kubernetes-charts.storage.googleapis.com/minecraft-0.1.3.tgz + version: 0.1.3 + - created: 2017-03-16T23:33:31.588714127Z + description: Minecraft server + digest: 5a42451d7c2f69b7b77c40e91ef60ca284798bcab8aa5b97b1f3f2559612d443 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: minecraft + sources: + - https://hub.docker.com/r/itzg/minecraft-server/~/dockerfile/ + - https://github.com/itzg/dockerfiles + urls: + - https://kubernetes-charts.storage.googleapis.com/minecraft-0.1.2.tgz + version: 0.1.2 + - created: 2017-01-30T23:33:28.437640114Z + description: Minecraft server + digest: 87c2ef91c8feaee680bb26ba16362c6b366429e0f54119c40dbf7a5ce3f22553 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: minecraft + sources: + - https://hub.docker.com/r/itzg/minecraft-server/~/dockerfile/ + - https://github.com/itzg/dockerfiles + urls: + - https://kubernetes-charts.storage.googleapis.com/minecraft-0.1.1.tgz + version: 0.1.1 + - created: 2016-12-05T21:03:20.22190695Z + description: Minecraft server + digest: 2cd423a28eb8a0186fba171cc17dcbe6f5a53d1a99e4078f9713afed3b61cce6 + keywords: + - game + - server + maintainers: + - email: gtaylor@gc-taylor.com + name: Greg Taylor + name: minecraft + sources: + - https://hub.docker.com/r/itzg/minecraft-server/~/dockerfile/ + - https://github.com/itzg/dockerfiles + urls: + - https://kubernetes-charts.storage.googleapis.com/minecraft-0.1.0.tgz + version: 0.1.0 + minio: + - apiVersion: v1 + created: 2017-04-28T00:18:30.091526674Z + description: Distributed object storage server built for cloud applications and + devops. + digest: 12eb77d0d25971c86a4df25297eea8d579b4b5dc6599d16f340f553977c53914 + home: https://minio.io + icon: https://www.minio.io/logo/img/logo-dark.svg + keywords: + - storage + - object-storage + - S3 + maintainers: + - email: hello@acale.ph + name: Acaleph + - email: hello@minio.io + name: Minio + name: minio + sources: + - https://github.com/minio/minio + urls: + - https://kubernetes-charts.storage.googleapis.com/minio-0.1.0.tgz + version: 0.1.0 + - apiVersion: v1 + created: 2017-03-16T23:48:28.876000842Z + description: Distributed object storage server built for cloud applications and + devops. + digest: d19b721fcb5f0566cce4a259e296b957ba982e87343947e1cbdbe979c770378d + home: https://minio.io + icon: https://www.minio.io/logo/img/logo-dark.svg + keywords: + - storage + - object-storage + - S3 + maintainers: + - email: hello@acale.ph + name: Acaleph + - email: hello@minio.io + name: Minio + name: minio + sources: + - https://github.com/minio/minio + urls: + - https://kubernetes-charts.storage.googleapis.com/minio-0.0.4.tgz + version: 0.0.4 + - apiVersion: v1 + created: 2017-02-13T04:33:52.302413618Z + description: A Minio Helm chart for Kubernetes + digest: cd58148df0776329fe5f518c542759565cab29dbefbc43f6b0ffdac86c9b31a9 + home: https://minio.io + keywords: + - storage + - object-storage + - S3 + maintainers: + - email: hello@acale.ph + name: Acaleph + - email: hello@minio.io + name: Minio + name: minio + sources: + - https://github.com/minio/minio + urls: + - https://kubernetes-charts.storage.googleapis.com/minio-0.0.3.tgz + version: 0.0.3 + - apiVersion: v1 + created: 2017-02-03T20:18:29.15659791Z + description: A Minio Helm chart for Kubernetes + digest: 699003bf2ef4cbb570580887da980412c1b9d6d173636de5def6053c7ba29a2a + home: https://minio.io + keywords: + - storage + - object-storage + - S3 + maintainers: + - email: hello@acale.ph + name: Acaleph + - email: hello@minio.io + name: Minio + name: minio + sources: + - https://github.com/minio/minio + urls: + - https://kubernetes-charts.storage.googleapis.com/minio-0.0.2.tgz + version: 0.0.2 + - apiVersion: v1 + created: 2017-01-12T02:36:05.500400572Z + description: A Minio Helm chart for Kubernetes + digest: aed17de3622988f8366126e158c740535c8d3bc55a0a85a3dcfabf07ac1395e9 + home: https://minio.io + keywords: + - storage + - object-storage + - S3 + maintainers: + - email: hello@acale.ph + name: Acaleph + name: minio + sources: + - https://github.com/minio/minio + urls: + - https://kubernetes-charts.storage.googleapis.com/minio-0.0.1.tgz + version: 0.0.1 + mongodb: + - created: 2017-04-28T00:18:30.091865909Z + description: NoSQL document-oriented database that stores JSON-like documents + with dynamic schemas, simplifying the integration of data in content-driven + applications. + digest: 979d36b208be9b266c70860d4fe1f9e5130d9d60b3bcbd893132452648dfe27f + engine: gotpl + home: https://mongodb.org + icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.9.tgz + version: 0.4.9 + - created: 2017-04-06T10:33:26.324740815Z + description: NoSQL document-oriented database that stores JSON-like documents + with dynamic schemas, simplifying the integration of data in content-driven + applications. + digest: cdc9fc28ff9139fcc6be015177e481f9d3765d6af284dce1999fc334cd7ef3a4 + engine: gotpl + home: https://mongodb.org + icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.8.tgz + version: 0.4.8 + - created: 2017-03-08T19:03:31.731176002Z + description: NoSQL document-oriented database that stores JSON-like documents + with dynamic schemas, simplifying the integration of data in content-driven + applications. + digest: 7d334e12acf9327f58f9a890e884a61ad760da2b1081d4a79b5680bee055cdbd + engine: gotpl + home: https://mongodb.org + icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.7.tgz + version: 0.4.7 + - created: 2017-02-14T03:48:27.566728756Z + description: NoSQL document-oriented database that stores JSON-like documents + with dynamic schemas, simplifying the integration of data in content-driven + applications. + digest: 27f9071cb81e9d3745776861f453db80b6ab6bf4507809f2e5c59e8a34d44939 + engine: gotpl + home: https://mongodb.org + icon: https://bitnami.com/assets/stacks/mongodb/img/mongodb-stack-220x234.png + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.6.tgz + version: 0.4.6 + - created: 2017-02-10T23:18:26.017406698Z + description: Chart for MongoDB + digest: 27a78b0c6300f4567975af18a3ca145940a716a53de42ed89c75872788d2848b + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.5.tgz + version: 0.4.5 + - created: 2017-01-30T23:33:28.438574863Z + description: Chart for MongoDB + digest: 9bcc0e2aa1d7d8f8c2ce43ef9284af39e5794214d185577ed1baff07c1a019f4 + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.4.tgz + version: 0.4.4 + - created: 2017-01-13T20:48:31.52900213Z + description: Chart for MongoDB + digest: be548ec183b8c44f031504864b85b7a0d48d745fa450bf733f89646c9cbbda44 + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-03T17:48:20.740456601Z + description: Chart for MongoDB + digest: 027dd50ff545309506daa0636a62d633071379040f8be080a403cb6d67399b0d + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.2.tgz + version: 0.4.2 + - created: 2016-12-15T00:48:24.012807516Z + description: Chart for MongoDB + digest: 42e8e56c715ea3bd2b8f9c188f5a9aec48a87654fb5215c35728ddf6c33c7437 + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.187403991Z + description: Chart for MongoDB + digest: d5eabbe99b03b4f7f71c461580564e3d965c2602bfd1be4dd09f1c54c8e7e9db + engine: gotpl + home: https://mongodb.org + keywords: + - mongodb + - database + - nosql + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: mongodb + sources: + - https://github.com/bitnami/bitnami-docker-mongodb + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-0.4.0.tgz + version: 0.4.0 + mongodb-replicaset: + - created: 2017-04-28T00:18:30.092344824Z + description: NoSQL document-oriented database that stores JSON-like documents + with dynamic schemas, simplifying the integration of data in content-driven + applications. + digest: 98592fbb471eb98d3c59deb83ed9bd2a808e8df972b21ff183d04fa4659e9a39 + home: https://github.com/mongodb/mongo + icon: https://webassets.mongodb.com/_com_assets/cms/mongodb-logo-rgb-j6w271g1xn.jpg + maintainers: + - email: ramanathana@google.com + name: Anirudh Ramanathan + name: mongodb-replicaset + sources: + - https://github.com/mongodb/mongo + urls: + - https://kubernetes-charts.storage.googleapis.com/mongodb-replicaset-1.0.0.tgz + version: 1.0.0 + moodle: + - created: 2017-04-28T00:18:30.093014917Z + description: Moodle is a learning platform designed to provide educators, administrators + and learners with a single robust, secure and integrated system to create personalised + learning environments + digest: 386bff8ce61cf61961daf8ed6d68a76cd3a360560a08c1fca80bcbd897f11270 + engine: gotpl + home: http://www.moodle.org/ + icon: https://bitnami.com/assets/stacks/moodle/img/moodle-stack-110x117.png + keywords: + - moodle + - learning + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: moodle + sources: + - https://github.com/bitnami/bitnami-docker-moodle + urls: + - https://kubernetes-charts.storage.googleapis.com/moodle-0.1.4.tgz + version: 0.1.4 + - created: 2017-03-31T19:33:30.433327839Z + description: Moodle is a learning platform designed to provide educators, administrators + and learners with a single robust, secure and integrated system to create personalised + learning environments + digest: bd85420a7cefd82e9d96088591601f832ecc60016d6389dbcde51a2050327a66 + engine: gotpl + home: http://www.moodle.org/ + icon: https://bitnami.com/assets/stacks/moodle/img/moodle-stack-110x117.png + keywords: + - moodle + - learning + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: moodle + sources: + - https://github.com/bitnami/bitnami-docker-moodle + urls: + - https://kubernetes-charts.storage.googleapis.com/moodle-0.1.3.tgz + version: 0.1.3 + - created: 2017-03-26T18:03:33.791615833Z + description: Moodle is a learning platform designed to provide educators, administrators + and learners with a single robust, secure and integrated system to create personalised + learning environments + digest: 8656c544a71fa8cc4ac23380e999e072740ec8e481a22aff86517d8362e70121 + engine: gotpl + home: http://www.moodle.org/ + icon: https://bitnami.com/assets/stacks/moodle/img/moodle-stack-110x117.png + keywords: + - moodle + - learning + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: moodle + sources: + - https://github.com/bitnami/bitnami-docker-moodle + urls: + - https://kubernetes-charts.storage.googleapis.com/moodle-0.1.2.tgz + version: 0.1.2 + mysql: + - created: 2017-04-28T00:18:30.093353908Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. + digest: 2de7736158326da89faed2e5af952b4c7288f61bd96377ef345a99b4b271a3e7 + engine: gotpl + home: https://www.mysql.com/ + icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.6.tgz + version: 0.2.6 + - created: 2017-04-13T05:18:28.91694371Z + description: Fast, reliable, scalable, and easy to use open-source relational + database system. + digest: 66b9a6787a36c72095c24499a45f2d3bfc700e31420a8926d09c029604ac4c98 + engine: gotpl + home: https://www.mysql.com/ + icon: https://www.mysql.com/common/logos/logo-mysql-170x115.png + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.5.tgz + version: 0.2.5 + - created: 2017-02-13T04:18:31.541320579Z + description: Chart for MySQL + digest: 3653a2d111ca60287ffbf10cbbb3c268dcb8666422bf5518d37adb9b2f131f7c + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.4.tgz + version: 0.2.4 + - created: 2017-01-27T22:33:30.41494884Z + description: Chart for MySQL + digest: 1244814f3490d23172a923e52339ad8b126f3037483db462591405863884e7ce + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.3.tgz + version: 0.2.3 + - created: 2016-12-21T19:33:19.335178436Z + description: Chart for MySQL + digest: 5dfdd6301aa5c7424c5ecc649ac3038ea72afd0e25d4c350c79e8ba84fe15faf + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.2.tgz + version: 0.2.2 + - created: 2016-12-09T18:48:20.187750412Z + description: Chart for MySQL + digest: 3dd20c2ed2faf64b9f940e813b18d7fa1c18c1b86101de453c7c836940e05680 + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.1.tgz + version: 0.2.1 + - created: 2016-11-04T14:18:20.013918541Z + description: Chart for MySQL + digest: 9723417e4d71713ed87b701eff52a1a74abea64c3cf852833b1e9bb96ff6fd55 + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.2.0.tgz + version: 0.2.0 + - created: 2016-11-03T03:18:19.715713217Z + description: Chart for MySQL + digest: fef93423760265f8bb3aedf9a922ed0169e018071ea467f22c17527006ae6a60 + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.1.2.tgz + version: 0.1.2 + - created: 2016-10-19T18:33:14.866729383Z + description: Chart for MySQL + digest: 3cb4495336e12d4fea28a1f7e3f02bbb18249582227866088cf6ac89587a2527 + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.1.1.tgz + version: 0.1.1 + - created: 2016-10-19T00:03:14.031801762Z + description: Chart for MySQL + digest: cba3eff1710520dbfbbeca2b0f9a754e0ddc172eb83ce51211606387955a6572 + engine: gotpl + home: https://www.mysql.com/ + keywords: + - mysql + - database + - sql + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: mysql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/mysql-0.1.0.tgz + version: 0.1.0 + namerd: + - apiVersion: v1 + created: 2017-04-28T00:18:30.093626863Z + description: Service that manages routing for multiple linkerd instances + digest: 52164352b00a196135a608198d12748d063965cfde016dc8a4fdc9021587bbeb + home: https://linkerd.io/in-depth/namerd/ + icon: https://pbs.twimg.com/profile_images/690258997237014528/KNgQd9GL_400x400.png + maintainers: + - email: knoxville@gmail.com + name: Sean Knox + name: namerd + sources: + - https://github.com/linkerd/linkerd + urls: + - https://kubernetes-charts.storage.googleapis.com/namerd-0.1.0.tgz + version: 0.1.0 + nginx-ingress: + - created: 2017-04-28T00:18:30.094151336Z + description: An nginx Ingress controller that uses ConfigMap to store the nginx + configuration. + digest: 2d6183d368b3993b4fdfd7108531067b928083b3db6dc30eab3f9c3a8212df9b + engine: gotpl + icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/500px-Nginx_logo.svg.png + keywords: + - ingress + - nginx + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + - email: chance.zibolski@coreos.com + name: Chance Zibolski + name: nginx-ingress + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-ingress-0.3.2.tgz + version: 0.3.2 + - created: 2017-04-03T22:33:26.678796256Z + description: An nginx Ingress controller that uses ConfigMap to store the nginx + configuration. + digest: 4e4632273eb5db95e525a8899b9f6f1697db241c2ff1ccb7456e0fc916bb75c2 + engine: gotpl + icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/500px-Nginx_logo.svg.png + keywords: + - ingress + - nginx + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + - email: chance.zibolski@coreos.com + name: Chance Zibolski + name: nginx-ingress + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-ingress-0.3.1.tgz + version: 0.3.1 + - created: 2017-03-10T17:03:37.208481141Z + description: An nginx Ingress controller that uses ConfigMap to store the nginx + configuration. + digest: 731823c88a849f945f405c192d92daf27afad37af76befb1eb92251240de29d7 + engine: gotpl + icon: https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Nginx_logo.svg/500px-Nginx_logo.svg.png + keywords: + - ingress + - nginx + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + - email: mgoodness@gmail.com + name: Michael Goodness + name: nginx-ingress + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-ingress-0.3.0.tgz + version: 0.3.0 + nginx-lego: + - created: 2017-04-28T00:18:30.094506147Z + description: Chart for nginx-ingress-controller and kube-lego + digest: da173cc1a9313ea0b11f5fb7aa67a20a2ac797b2f129a079c06284e8a9765f21 + engine: gotpl + keywords: + - kube-lego + - nginx-ingress-controller + - nginx + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + name: nginx-lego + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-lego-0.2.1.tgz + version: 0.2.1 + - created: 2017-03-02T18:48:30.43738595Z + description: Chart for nginx-ingress-controller and kube-lego + digest: 9a8ea81371900d2c7931b0143f991ef0fde41038d26a968628008aef14ec08ef + engine: gotpl + keywords: + - kube-lego + - nginx-ingress-controller + - nginx + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + name: nginx-lego + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-lego-0.2.0.tgz + version: 0.2.0 + - created: 2017-01-12T02:52:37.835917781Z + description: Chart for nginx-ingress-controller and kube-lego + digest: 2dee942e546940beb8301a94a1a51e22c7a38b4c5592701528149dfaa83e1bb6 + engine: gotpl + keywords: + - kube-lego + - nginx-ingress-controller + - nginx + - letsencrypt + maintainers: + - email: jack.zampolin@gmail.com + name: Jack Zampolin + name: nginx-lego + sources: + - https://github.com/kubernetes/contrib/tree/master/ingress/controllers/nginx + - https://github.com/jetstack/kube-lego/tree/master/examples/nginx + urls: + - https://kubernetes-charts.storage.googleapis.com/nginx-lego-0.1.0.tgz + version: 0.1.0 + odoo: + - created: 2017-04-28T00:18:30.095144247Z + description: A suite of web based open source business apps. + digest: 439b9160c3226a0cf6b848a5ac921307e5100ffa36e03e3697619a6c968eec3b + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.5.0.tgz + version: 0.5.0 + - created: 2017-04-20T11:48:30.777572861Z + description: A suite of web based open source business apps. + digest: 4aeba5cf600c9552d3a0ace00b4880aa27d8b2c2aec1b7dc9ebad04f2152d165 + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.5.tgz + version: 0.4.5 + - created: 2017-04-19T19:48:30.479799003Z + description: A suite of web based open source business apps. + digest: 74dff526f84c50445e0cc0fb602679c68941e9b93d9410ce66a6f3713a71f39d + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-23T21:18:31.859026332Z + description: A suite of web based open source business apps. + digest: 6bdd86f83c41f079218105162f1e86cf572d005174c0688a2201c59d20cf1e55 + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.3.tgz + version: 0.4.3 + - created: 2017-03-23T00:18:30.533565303Z + description: A suite of web based open source business apps. + digest: 9a7723d94d17ff18347074d5a1d6b42530466a8a4882981178f4856138f44072 + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.2.tgz + version: 0.4.2 + - created: 2017-03-17T22:48:29.256461745Z + description: A suite of web based open source business apps. + digest: 1339b6efbc6b4df849c460ddbcade9f81ede3a6a800e9566f54acc1c52dccb4b + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.1.tgz + version: 0.4.1 + - created: 2017-03-08T19:03:31.734764598Z + description: A suite of web based open source business apps. + digest: 15b1d5339086427990fb3d0ee8f65e768b398195f2db63b0356e6c79f4d4b981 + engine: gotpl + home: https://www.odoo.com/ + icon: https://bitnami.com/assets/stacks/odoo/img/odoo-stack-110x117.png + keywords: + - odoo + - crm + - www + - http + - web + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: odoo + sources: + - https://github.com/bitnami/bitnami-docker-odoo + urls: + - https://kubernetes-charts.storage.googleapis.com/odoo-0.4.0.tgz + version: 0.4.0 + opencart: + - created: 2017-04-28T00:18:30.095906073Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: a22a7ee46a419b2bc4dd0ba66c3fa294e15f86722944a7bc3cc56cb24f3fa737 + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.5.tgz + version: 0.4.5 + - created: 2017-04-06T11:18:27.44256999Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: 0abb5c30e0eef2a06b36b83b0c11f4bcf3df14ea416c5e49cb821c78e6783bce + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-02T19:33:28.187180475Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: 1a80dfcec98c190b8710d7db47c22c3882a05a046b3d767c84094cef983a1556 + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-23T02:33:30.963491381Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: d3092c4ed82db569937e435d3dc6bcddce420540bf340dd54a554a57b62c6aaa + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.354317974Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: afbaa2517e811990bc4b31495a4634b6399615493cf344215a5658de2f33575b + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-04T00:48:19.745672724Z + description: A free and open source e-commerce platform for online merchants. + It provides a professional and reliable foundation for a successful online store. + digest: f0e46cf67f8594c6d92f02fad4a23fdf7aa94bdb145bfde39436e17f0a8930f5 + engine: gotpl + home: https://opencart.com/ + icon: https://bitnami.com/assets/stacks/opencart/img/opencart-stack-110x117.png + keywords: + - opencart + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: opencart + sources: + - https://github.com/bitnami/bitnami-docker-opencart + urls: + - https://kubernetes-charts.storage.googleapis.com/opencart-0.4.0.tgz + version: 0.4.0 + openvpn: + - apiVersion: v1 + created: 2017-04-28T00:18:30.096266269Z + description: A Helm chart to install an openvpn server inside a kubernetes cluster. Certificate + generation is also part of the deployment, and this chart will generate client + keys as needed. + digest: 403a80ed3f204442afe4236e275035bf39f9e1f6af8d48153d6698f51efeb16d + home: https://openvpn.net/index.php/open-source.html + icon: https://forums.openvpn.net/styles/openvpn/theme/images/ovpnlogo.png + keywords: + - openvpn + - vpn + - tunnel + - network + - service + - connectivity + - encryption + maintainers: + - email: john.felten@gmail.com + name: John Felten + name: openvpn + urls: + - https://kubernetes-charts.storage.googleapis.com/openvpn-1.0.2.tgz + version: 1.0.2 + - apiVersion: v1 + created: 2017-04-06T10:18:27.592699587Z + description: A Helm chart to install an openvpn server inside a kubernetes cluster. Certificate + generation is also part of the deployment, and this chart will generate client + keys as needed. + digest: c92674b7c6391e412864a3aa73af1a2d89f3b4e768bb459118b8e75b9750fc10 + home: https://openvpn.net/index.php/open-source.html + icon: https://forums.openvpn.net/styles/openvpn/theme/images/ovpnlogo.png + keywords: + - openvpn + - vpn + - tunnel + - network + - service + - connectivity + - encryption + maintainers: + - email: john.felten@gmail.com + name: John Felten + name: openvpn + urls: + - https://kubernetes-charts.storage.googleapis.com/openvpn-1.0.1.tgz + version: 1.0.1 + - apiVersion: v1 + created: 2017-01-27T21:48:32.078827588Z + description: A Helm chart to install an openvpn server inside a kubernetes cluster. Certificate + generation is also part of the deployment, and this chart will generate client + keys as needed. + digest: a12cfddce900c8a4ef6cea0cef5426d5fb23c657cdab433f9307f6d048273f6b + home: https://openvpn.net/index.php/open-source.html + icon: https://forums.openvpn.net/styles/openvpn/theme/images/ovpnlogo.png + keywords: + - openvpn + - vpn + - tunnel + - network + - service + - connectivity + - encryption + maintainers: + - email: john.felten@gmail.com + name: John Felten + name: openvpn + urls: + - https://kubernetes-charts.storage.googleapis.com/openvpn-1.0.0.tgz + version: 1.0.0 + orangehrm: + - created: 2017-04-28T00:18:30.097427186Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: d796649de3610a41a9f92b0ee3f5ce327a250d1b86d7a4c76fd46a31144c59e5 + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.5.tgz + version: 0.4.5 + - created: 2017-04-12T21:03:28.668395677Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: 9c954806dfd415ee07cbcc6ee5e82eeeebeb765ecfe173ba80c0003b1d0be504 + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-18T18:33:36.171180792Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: 87483fe0c39f63c61549a85103b9423b30ac4bfe2b19a7af083eaf7dd3491ce2 + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.3.tgz + version: 0.4.3 + - created: 2017-03-02T19:33:28.188778055Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: d03e9f261dd50212f6c2c0ecc3a84cbabda89493d8bfc28ad95c6c9a6a195af9 + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.2.tgz + version: 0.4.2 + - created: 2017-02-23T02:33:30.964778113Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: b5e5ed301b5321a99a7147173ec6fa7cb471879a585b9af0e8ac2741f0409e48 + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.1.tgz + version: 0.4.1 + - created: 2017-02-01T00:48:29.581953712Z + description: OrangeHRM is a free HR management system that offers a wealth of + modules to suit the needs of your business. + digest: 4ca6ccafc5bc408cb9e61139b8914468e0d8eeed7bffb62eea51c6e40697e8dd + engine: gotpl + home: https://www.orangehrm.com + icon: https://bitnami.com/assets/stacks/orangehrm/img/orangehrm-stack-110x117.png + keywords: + - orangehrm + - http + - https + - web + - application + - php + - human resources + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: orangehrm + sources: + - https://github.com/bitnami/bitnami-docker-orangehrm + urls: + - https://kubernetes-charts.storage.googleapis.com/orangehrm-0.4.0.tgz + version: 0.4.0 + osclass: + - created: 2017-04-28T00:18:30.098620601Z + description: Osclass is a php script that allows you to quickly create and manage + your own free classifieds site. + digest: 23472bb37c094dad3122a96f6fd6a3967f1d41547121ecf2622b557f2fc5da8b + engine: gotpl + home: https://osclass.org/ + icon: https://bitnami.com/assets/stacks/osclass/img/osclass-stack-110x117.png + keywords: + - osclass + - classifieds + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: osclass + sources: + - https://github.com/bitnami/bitnami-docker-osclass + urls: + - https://kubernetes-charts.storage.googleapis.com/osclass-0.4.1.tgz + version: 0.4.1 + - created: 2017-02-23T02:33:30.965596248Z + description: Osclass is a php script that allows you to quickly create and manage + your own free classifieds site. + digest: 7c21cef0b281e3da5b3c9b60940c1b010c34fe866c95615b7d0afd10c480178c + engine: gotpl + home: https://osclass.org/ + icon: https://bitnami.com/assets/stacks/osclass/img/osclass-stack-110x117.png + keywords: + - osclass + - classifieds + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: osclass + sources: + - https://github.com/bitnami/bitnami-docker-osclass + urls: + - https://kubernetes-charts.storage.googleapis.com/osclass-0.4.0.tgz + version: 0.4.0 + owncloud: + - created: 2017-04-28T00:18:30.099329236Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 9beee22ffa4b4eae5e6f8da5734e08a9455a063299b33d4350dea91050ae9a25 + engine: gotpl + home: https://owncloud.org/ + icon: https://bitnami.com/assets/stacks/owncloud/img/owncloud-stack-220x234.png + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.7.tgz + version: 0.4.7 + - created: 2017-04-20T16:18:33.358391215Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 97b3574369c1d690ce8787588668070451c559ac06b6b69942e9905cb6ec94ad + engine: gotpl + home: https://owncloud.org/ + icon: https://bitnami.com/assets/stacks/owncloud/img/owncloud-stack-220x234.png + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-23T21:18:31.86258413Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: fb78c2ea47996c6781ddfe2dca6aea87cbb8b895e1fc5fea404aa28735d4a2ec + engine: gotpl + home: https://owncloud.org/ + icon: https://bitnami.com/assets/stacks/owncloud/img/owncloud-stack-220x234.png + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-08T19:03:31.738784117Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 07e18bccf9dc0a8fcff42e97c422520530c2c5057c2a9c14767842609bcaa7ac + engine: gotpl + home: https://owncloud.org/ + icon: https://bitnami.com/assets/stacks/owncloud/img/owncloud-stack-220x234.png + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-11T03:18:26.536480213Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 4e534bdc4be184c59fbcbcff4641f2bfa341cd7f4ed3668912cd47c1336f5dea + engine: gotpl + home: https://owncloud.org/ + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-30T23:48:29.172608299Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: b41eb1fc1eea6311f3ad15b459f99b77e96b944b81ea50ebccc941191d496141 + engine: gotpl + home: https://owncloud.org/ + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.35624854Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 89901fc256d6a4f878d77b51bf23eb77358dedb040052b34a09cbe9ca607487f + engine: gotpl + home: https://owncloud.org/ + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-21T18:33:20.278724682Z + description: A file sharing server that puts the control and security of your + own data back into your hands. + digest: 7ec2dfe27bf42e8ee2e0672964decc2fbec94ec4fb60af2f3e9e91e13fdae08a + engine: gotpl + home: https://owncloud.org/ + keywords: + - owncloud + - storage + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: owncloud + sources: + - https://github.com/bitnami/bitnami-docker-owncloud + urls: + - https://kubernetes-charts.storage.googleapis.com/owncloud-0.4.0.tgz + version: 0.4.0 + percona: + - created: 2017-04-28T00:18:30.099763589Z + description: free, fully compatible, enhanced, open source drop-in replacement + for MySQL + digest: b10837a3de2492c2826aaf9ab740314ffa3f8c6110568b0a88e142791abb0e96 + engine: gotpl + home: https://www.percona.com/ + icon: https://www.percona.com/sites/all/themes/percona2015/logo.png + keywords: + - mysql + - percona + - database + - sql + maintainers: + - email: patg@patg.net + name: Patrick Galbraith + name: percona + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/percona + urls: + - https://kubernetes-charts.storage.googleapis.com/percona-0.1.0.tgz + version: 0.1.0 + phabricator: + - created: 2017-04-28T00:18:30.100508372Z + description: Collection of open source web applications that help software companies + build better software. + digest: 6c53fef9ac9c246983a26d2e8b9e8f032b87706c09667edb9f9bb62dfa822e88 + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.5.tgz + version: 0.4.5 + - created: 2017-04-06T10:33:26.333625406Z + description: Collection of open source web applications that help software companies + build better software. + digest: ef626563185a4b41ff7da03a9bd5548903061c6140d2d65300e4b1c1d39a2f28 + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-02T19:33:28.191377992Z + description: Collection of open source web applications that help software companies + build better software. + digest: 544373e984918da349351f05fdb7a8cf0c695726890bcd4cc348e786e1c55d57 + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-23T02:33:30.967143259Z + description: Collection of open source web applications that help software companies + build better software. + digest: 657b118031c1513b95268915ef4f68f5453f2b21ca5a919e9a370facb439dfe5 + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.358403062Z + description: Collection of open source web applications that help software companies + build better software. + digest: 77603dd383c0674e829fa4aa2ca0d33ce61884df6a3ce68f9e165e7802e7eacc + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-04T00:48:19.747640048Z + description: Collection of open source web applications that help software companies + build better software. + digest: e4f0f5322bac2a2bed822eb8065eec6e29716310af1623975f1ad0ef2b448c66 + engine: gotpl + home: https://www.phacility.com/phabricator/ + icon: https://bitnami.com/assets/stacks/phabricator/img/phabricator-stack-110x117.png + keywords: + - phabricator + - http + - https + - web + - application + - collaboration + - project management + - bug tracking + - code review + - wiki + - git + - mercurial + - subversion + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phabricator + sources: + - https://github.com/bitnami/bitnami-docker-phabricator + urls: + - https://kubernetes-charts.storage.googleapis.com/phabricator-0.4.0.tgz + version: 0.4.0 + phpbb: + - created: 2017-04-28T00:18:30.10119319Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: d4e97de68d28acd69cb655c7fa44cb8829367ce3a7b20ecbf151f1bb2b10d1cb + engine: gotpl + home: https://www.phpbb.com/ + icon: https://bitnami.com/assets/stacks/phpbb/img/phpbb-stack-220x234.png + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.7.tgz + version: 0.4.7 + - created: 2017-04-13T05:18:28.926003844Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: 30732d5c200ef312a50f5a89a222a959c99b0a08af87f0c312c3699bb2c56082 + engine: gotpl + home: https://www.phpbb.com/ + icon: https://bitnami.com/assets/stacks/phpbb/img/phpbb-stack-220x234.png + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-02T19:33:28.19218227Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: ed994a70f454f325cf677bdcc0a09aa66b01c7343a624a3304b88148eafa0dd4 + engine: gotpl + home: https://www.phpbb.com/ + icon: https://bitnami.com/assets/stacks/phpbb/img/phpbb-stack-220x234.png + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.5.tgz + version: 0.4.5 + - created: 2017-02-23T02:33:30.967854461Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: f50e4b1eb4bf8b2e6815b664e3cfff4bfd7979b650bf1efa709b79c92a8e5dd0 + engine: gotpl + home: https://www.phpbb.com/ + icon: https://bitnami.com/assets/stacks/phpbb/img/phpbb-stack-220x234.png + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-13T17:03:30.134235119Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: c41705e34490d1941adda128b166ef1a327cceb7a447113d2633652f3c85363a + engine: gotpl + home: https://www.phpbb.com/ + icon: https://bitnami.com/assets/stacks/phpbb/img/phpbb-stack-220x234.png + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-13T04:18:31.547850917Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: 77f6dfa8f277b003ba0b8e2ca6d046f6b6526ee7db05e304cbb9924c9fd7d194 + engine: gotpl + home: https://www.phpbb.com/ + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-31T00:03:26.18262303Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: cd6dde525898d91f5f39541ee205e79ba5b8eb7a5b99585141cc88c3a63f5aed + engine: gotpl + home: https://www.phpbb.com/ + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.188802837Z + description: Community forum that supports the notion of users and groups, file + attachments, full-text search, notifications and more. + digest: 7e5e8828fdbba9ebc83c95baac4937e569e881d8b68bc79974a0f5d8a2883e13 + engine: gotpl + home: https://www.phpbb.com/ + keywords: + - phpbb + - forum + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: phpbb + sources: + - https://github.com/bitnami/bitnami-docker-phpbb + urls: + - https://kubernetes-charts.storage.googleapis.com/phpbb-0.4.0.tgz + version: 0.4.0 + postgresql: + - created: 2017-04-28T00:18:30.101575321Z + description: Object-relational database management system (ORDBMS) with an emphasis + on extensibility and on standards-compliance. + digest: 60224116f1e803bbf744cb2b3c2d621cfadef709ef0a8c6ed08cbed0f5415915 + engine: gotpl + home: https://www.postgresql.org/ + icon: https://www.postgresql.org/media/img/about/press/elephant.png + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.6.0.tgz + version: 0.6.0 + - created: 2017-03-16T23:33:31.598678756Z + description: Object-relational database management system (ORDBMS) with an emphasis + on extensibility and on standards-compliance. + digest: abfc2c516cce613f9042eb5d202b01f6766240f54bb3632df9378be033711b30 + engine: gotpl + home: https://www.postgresql.org/ + icon: https://www.postgresql.org/media/img/about/press/elephant.png + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.5.1.tgz + version: 0.5.1 + - created: 2017-03-15T11:18:31.676836517Z + description: Object-relational database management system (ORDBMS) with an emphasis + on extensibility and on standards-compliance. + digest: 29a90e1b52577e04a42da3ac7464b1b1cf9572b359d7825a375aad122659db55 + engine: gotpl + home: https://www.postgresql.org/ + icon: https://www.postgresql.org/media/img/about/press/elephant.png + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.5.0.tgz + version: 0.5.0 + - created: 2017-03-15T09:48:31.994004661Z + description: Object-relational database management system (ORDBMS) with an emphasis + on extensibility and on standards-compliance. + digest: 0ca09fbfd539d5258026fcaf7b640f88295271f88773acfa540dcd55cfaf6036 + engine: gotpl + home: https://www.postgresql.org/ + icon: https://www.postgresql.org/media/img/about/press/elephant.png + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.4.0.tgz + version: 0.4.0 + - created: 2017-02-13T21:33:27.762374795Z + description: Object-relational database management system (ORDBMS) with an emphasis + on extensibility and on standards-compliance. + digest: b07b7c12f13731ebc3019c11601351863030ad3933f72fb7925f3c0de39e23f4 + engine: gotpl + home: https://www.postgresql.org/ + icon: https://www.postgresql.org/media/img/about/press/elephant.png + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.3.4.tgz + version: 0.3.4 + - created: 2017-02-13T04:18:31.548242286Z + description: Chart for PostgreSQL + digest: 80685774a539b9efa27ea82337e9dd9fccd436688a84e2609d59a68a336d8f18 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.3.3.tgz + version: 0.3.3 + - created: 2017-02-06T17:48:28.059659169Z + description: Chart for PostgreSQL + digest: d8306a710181a440672795d0b5850e6851ae28b3ecb4cf5f92126c9f533700d5 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.3.2.tgz + version: 0.3.2 + - created: 2017-01-28T00:18:32.756495622Z + description: Chart for PostgreSQL + digest: 7b4e2b838ccb2e96c26f0b18d75b2cba224a634925abacaeee1b053a3db40f72 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.3.1.tgz + version: 0.3.1 + - created: 2017-01-26T17:18:33.808053693Z + description: Chart for PostgreSQL + digest: b8e412ddd2f2648efbaa84f85c924e5b94cba0393fb93ea607fdcab3132b7d2e + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + - name: databus23 + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.3.0.tgz + version: 0.3.0 + - created: 2016-12-21T18:33:20.280731983Z + description: Chart for PostgreSQL + digest: a0516b4e5b83d9dd4af936859582878683183c6f72e9dddb73c9ff0fd280b972 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.2.2.tgz + version: 0.2.2 + - created: 2016-12-19T22:48:20.03810957Z + description: Chart for PostgreSQL + digest: 8d79ed44bb8477cdbbf8a3eb5a08eef787a147334b30d35a81f8ee43d07eb9ee + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.2.1.tgz + version: 0.2.1 + - created: 2016-12-05T20:48:21.242600076Z + description: Chart for PostgreSQL + digest: 713d7ee250af7f914188d889ba3cb3065b4c36565b6f68ca8f55cd5340bda213 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.2.0.tgz + version: 0.2.0 + - created: 2016-11-08T15:03:20.745475633Z + description: Chart for PostgreSQL + digest: c79411d63ad872d0f6d034de9818cef565dbde3ac5f018ee8349305f286031a8 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.1.1.tgz + version: 0.1.1 + - created: 2016-11-04T14:18:20.014925806Z + description: Chart for PostgreSQL + digest: c984e3efb97da1b841c6a07b1e8fd32638b59de42e5b062690b8c9956be58959 + engine: gotpl + home: https://www.postgresql.org/ + keywords: + - postgresql + - postgres + - database + - sql + maintainers: + - name: swordbeta + name: postgresql + sources: + - https://github.com/kubernetes/charts + - https://github.com/docker-library/postgres + urls: + - https://kubernetes-charts.storage.googleapis.com/postgresql-0.1.0.tgz + version: 0.1.0 + prestashop: + - created: 2017-04-28T00:18:30.102289986Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: fd575ef671455aa2c85ed91643419219bad86f8766b5775c1e869837cfbef6e5 + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.6.tgz + version: 0.4.6 + - created: 2017-04-06T11:03:25.414976585Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: 17eb689a1b04c31c1c78fa1b411f938eaad74bbd1ae78e4eecd0755770156043 + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-23T01:19:01.462007867Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: ff84426385c8abb40d0f4f7e2cfc7fec70b0ae38ca32de832ccbb2b26f74787b + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-08T19:03:31.741573757Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: 4beeb9d17c713b8065f9167d11672862fe2d1eeb5fded4d4ecd395a379825bbc + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.3.tgz + version: 0.4.3 + - created: 2017-02-13T17:03:30.136404798Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: fd8c2be6fd9348d2c5bda428ac61f0745974ebd7a4b58ad5d975d8d21888f1c4 + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.2.tgz + version: 0.4.2 + - created: 2017-01-21T00:18:31.360326923Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: eba227b32cb9f503c4fd41609d705019372f7936c69febaf2c4200735910e333 + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-04T00:48:19.749415831Z + description: A popular open source ecommerce solution. Professional tools are + easily accessible to increase online sales including instant guest checkout, + abandoned cart reminders and automated Email marketing. + digest: dd80da4612c962f5d02e2f52f7e0facf42a661d6d6f5eec36d7a3980e2a685d3 + engine: gotpl + home: https://prestashop.com/ + icon: https://bitnami.com/assets/stacks/prestashop/img/prestashop-stack-110x117.png + keywords: + - prestashop + - e-commerce + - http + - web + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: prestashop + sources: + - https://github.com/bitnami/bitnami-docker-prestashop + urls: + - https://kubernetes-charts.storage.googleapis.com/prestashop-0.4.0.tgz + version: 0.4.0 + prometheus: + - created: 2017-04-28T00:18:30.103009703Z + description: Prometheus is a monitoring system and time series database. + digest: 54cd97a780b0af5cd86d84455a29ec2f30765cfbf5c56f9eedf0c30539e325ec + engine: gotpl + home: https://prometheus.io/ + icon: https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + - https://github.com/kubernetes/kube-state-metrics + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-3.0.2.tgz + version: 3.0.2 + - created: 2017-04-14T01:18:27.389282119Z + description: Prometheus is a monitoring system and time series database. + digest: 8240b1319bb4b1382f69e3ae753fce599185a7f21a399c7ed15108138cb6e793 + engine: gotpl + home: https://prometheus.io/ + icon: https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + - https://github.com/kubernetes/kube-state-metrics + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-3.0.1.tgz + version: 3.0.1 + - created: 2017-03-23T19:03:32.048982259Z + description: Prometheus is a monitoring system and time series database. + digest: a6a49f3aaf7768dd84acfec96f95cab74a4ed1b68f95f23a9248d6926e8a2ba7 + engine: gotpl + home: https://prometheus.io/ + icon: https://raw.githubusercontent.com/prometheus/prometheus.github.io/master/assets/prometheus_logo-cb55bb5c346.png + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + - https://github.com/kubernetes/kube-state-metrics + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-3.0.0.tgz + version: 3.0.0 + - created: 2017-03-20T20:18:33.636286259Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: 0bbefe8b7732b400320007e4f8898518ffcafd3371114be24ca8ded424fe60b3 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-2.0.4.tgz + version: 2.0.4 + - created: 2017-02-13T04:18:31.549501389Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: 0f54774b8b258a8e126f09a66749b15c0691e0a330b65f397d58418b0fa0210c + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-2.0.3.tgz + version: 2.0.3 + - created: 2017-02-03T20:18:29.168136057Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: f1f457be370a944f3c703ceecc35664aa00f7a243730ca9e110bc18f1ed3ab9a + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-2.0.2.tgz + version: 2.0.2 + - created: 2017-02-01T02:18:29.14318599Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: eebea40b9e86c6dfb92048b0f63d47b11021ab0df437e2b13bc0fd1fc121e8d6 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-2.0.1.tgz + version: 2.0.1 + - created: 2017-01-19T19:18:27.372125459Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: 0ae9e1c2ec6e3a6e2148f01e174bbbdd02a5797b4136e5de784383bca9bff938 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-2.0.0.tgz + version: 2.0.0 + - created: 2017-01-12T02:52:37.843602967Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: cd8d763bdfe5d7c3c0e9a38f9741a2ef5de1c7c57a0c43a4407e70e2f6232dc9 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-1.4.2.tgz + version: 1.4.2 + - created: 2017-01-04T00:48:19.750279814Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: 7209dafe19488487a8a151129deff24fe174d9734ea2c1629dd52bee183a8ad2 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-1.4.1.tgz + version: 1.4.1 + - created: 2016-12-09T18:48:20.189907856Z + description: A Prometheus Helm chart for Kubernetes. Prometheus is a monitoring + system and time series database. + digest: f6b4c948e408471b51ff6361e0d0f5afc801ee8141aae5002111ffbc12c68895 + engine: gotpl + home: https://prometheus.io/ + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: prometheus + sources: + - https://github.com/prometheus/alertmanager + - https://github.com/prometheus/prometheus + urls: + - https://kubernetes-charts.storage.googleapis.com/prometheus-1.3.1.tgz + version: 1.3.1 + rabbitmq: + - created: 2017-04-28T00:18:30.103417484Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 1b4339d3f866cdc57072ce60fc3a579a5f4233aa6a4cb81254dcb9ddc0fabc33 + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.5.1.tgz + version: 0.5.1 + - created: 2017-04-06T10:33:26.336868116Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 60c103b6cd7c57d5bf25588c0d1fcbbe0790ad50968d1947ce11e4b7ae1b2e6e + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.5.0.tgz + version: 0.5.0 + - created: 2017-04-03T22:33:26.690098498Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 2f9b4afaffbe72c99c640de71b33e161e9d11386c287bab9028af19d1548bfa8 + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-18T18:33:36.178174406Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 833f7c8b0db5eeee50005fe88db8f1aaa899e5cc7875e18473a77162425756be + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.4.tgz + version: 0.4.4 + - created: 2017-03-16T13:33:30.85508759Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: bcfcbfa446f75dc1ca93f9a7d2ccc36731ef41f1dd5615e251344af0d6a1ba83 + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.3.tgz + version: 0.4.3 + - created: 2017-03-08T19:03:31.743135112Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 7ad8ba0ff9d1b57778ffe60812be9087ad4fac27e8696fad4c9eba9a2529fdba + engine: gotpl + home: https://www.rabbitmq.com + icon: https://bitnami.com/assets/stacks/rabbitmq/img/rabbitmq-stack-220x234.png + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.2.tgz + version: 0.4.2 + - created: 2017-02-13T04:18:31.549853753Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 323ca950152028ecfa421b78ba0b9282265f39b934b07649b239be4d9f2dc10a + engine: gotpl + home: https://www.rabbitmq.com + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-31T00:03:26.184733426Z + description: Open source message broker software that implements the Advanced + Message Queuing Protocol (AMQP) + digest: 9bd9655f974dc3b2666c141718b65c7786e91c533ffee1784428a6d48cb458ca + engine: gotpl + home: https://www.rabbitmq.com + keywords: + - rabbitmq + - message queue + - AMQP + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: rabbitmq + sources: + - https://github.com/bitnami/bitnami-docker-rabbitmq + urls: + - https://kubernetes-charts.storage.googleapis.com/rabbitmq-0.4.0.tgz + version: 0.4.0 + redis: + - created: 2017-04-28T00:18:30.103747837Z + description: Open source, advanced key-value store. It is often referred to as + a data structure server since keys can contain strings, hashes, lists, sets + and sorted sets. + digest: 72af23bdc2aee61a6cc75e6b7fe3677d1f08ec98afbf8b6fccb3922c9bde47aa + engine: gotpl + home: http://redis.io/ + icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.5.1.tgz + version: 0.5.1 + - created: 2017-04-03T22:18:27.809077961Z + description: Open source, advanced key-value store. It is often referred to as + a data structure server since keys can contain strings, hashes, lists, sets + and sorted sets. + digest: 84fdd07b6f56e7771696e7a707456808cb925f2a6311bf85e1bd027e5b2d364d + engine: gotpl + home: http://redis.io/ + icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.5.0.tgz + version: 0.5.0 + - created: 2017-03-23T15:48:30.415372004Z + description: Open source, advanced key-value store. It is often referred to as + a data structure server since keys can contain strings, hashes, lists, sets + and sorted sets. + digest: 83ace7583e93e763b781d74c940d0966d7317d2b1665eaed35be9ca73dcace5e + engine: gotpl + home: http://redis.io/ + icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-02T19:33:28.196397881Z + description: Open source, advanced key-value store. It is often referred to as + a data structure server since keys can contain strings, hashes, lists, sets + and sorted sets. + digest: 7891aef2647fd00ca93cd6894720a6307d3fdd275f912eb6a05fcbb6b7009c13 + engine: gotpl + home: http://redis.io/ + icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.5.tgz + version: 0.4.5 + - created: 2017-02-13T20:18:28.115940614Z + description: Open source, advanced key-value store. It is often referred to as + a data structure server since keys can contain strings, hashes, lists, sets + and sorted sets. + digest: e8cf2f96a6931397adf372857a6a0da161e7e9eb0cf91f565399d20b26144be1 + engine: gotpl + home: http://redis.io/ + icon: https://bitnami.com/assets/stacks/redis/img/redis-stack-220x234.png + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-13T04:18:31.550184203Z + description: Chart for Redis + digest: b4cb9b2e0811a83ce269dc06c25a05fe31deb799018eba418232b2c3f4b18b12 + engine: gotpl + home: http://redis.io/ + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-28T02:03:32.495597111Z + description: Chart for Redis + digest: 160dab504021716867790c3b1ea5c6e4afcaf865d9b8569707e123bc4d1536dc + engine: gotpl + home: http://redis.io/ + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.2.tgz + version: 0.4.2 + - created: 2016-12-09T18:48:20.191261198Z + description: Chart for Redis + digest: 7132d9ddecaf4a890e5177c401228fa031f52e927393063f8d6c5a0881b281a3 + engine: gotpl + home: http://redis.io/ + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.1.tgz + version: 0.4.1 + - created: 2016-10-31T16:33:19.644247028Z + description: Chart for Redis + digest: cef59b98a3607bf0f40560c724fd36a84e5f29498031a36c0f2f80369c35d9c4 + engine: gotpl + home: http://redis.io/ + keywords: + - redis + - keyvalue + - database + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redis + sources: + - https://github.com/bitnami/bitnami-docker-redis + urls: + - https://kubernetes-charts.storage.googleapis.com/redis-0.4.0.tgz + version: 0.4.0 + redmine: + - created: 2017-04-28T00:18:30.104404263Z + description: A flexible project management web application. + digest: 65419e8af0bff73d1c4da279cbb4d2abdaf99a714dabae01b27f67b917d4efc1 + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.4.2.tgz + version: 0.4.2 + - created: 2017-04-13T05:18:28.930142135Z + description: A flexible project management web application. + digest: 3edad0332b7aa4ff2c33729f0e49e7c58c0ad06108d66774d1f1583b8e4ccddf + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.4.1.tgz + version: 0.4.1 + - created: 2017-03-31T19:33:30.446728632Z + description: A flexible project management web application. + digest: facd552d60b39c5f6d37e8cf88f453bcae418a08eee0c401f15d15872e1e3601 + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.4.0.tgz + version: 0.4.0 + - created: 2017-03-14T23:48:31.907843022Z + description: A flexible project management web application. + digest: a21733ee877ad579f8b5be03d5a35008816d64dd56e0ca6482a7c0686fcdfe09 + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.11.tgz + version: 0.3.11 + - created: 2017-03-08T19:03:31.745197966Z + description: A flexible project management web application. + digest: 30253b618b47801a076c6cdd8a9ff93e1e4401e0189e88576553802b224e2775 + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.10.tgz + version: 0.3.10 + - created: 2017-02-13T21:33:27.767502945Z + description: A flexible project management web application. + digest: aa8a3b1be968e99c7a61ad0b7c1d13934562b9c30eeec0b3a3683063b9d38c7b + engine: gotpl + home: http://www.redmine.org/ + icon: https://bitnami.com/assets/stacks/redmine/img/redmine-stack-220x234.png + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.9.tgz + version: 0.3.9 + - created: 2017-02-10T23:18:26.028438027Z + description: A flexible project management web application. + digest: 51f4e834b5d2eb4ab66468e6996419bb20aa4d96ebe35a3663bc8b2c494694e6 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.8.tgz + version: 0.3.8 + - created: 2017-01-31T00:18:28.517014253Z + description: A flexible project management web application. + digest: d9e7c4c47c853413107330d4fc0ad44e9bc3be90057ca722d28042b73f244fe5 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.7.tgz + version: 0.3.7 + - created: 2016-12-15T21:18:24.678305914Z + description: A flexible project management web application. + digest: 98d9c8c7f241a9418bed6862f7c82295d5d8158cd1702907ced7150e46530768 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.6.tgz + version: 0.3.6 + - created: 2016-12-05T21:03:20.228049572Z + description: A flexible project management web application. + digest: ae1c2ced129d05cdae28e1fe9c2bed53ded35cd77d96fc1b26f810d334c601e3 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.5.tgz + version: 0.3.5 + - created: 2016-11-03T19:33:29.122956769Z + description: A flexible project management web application. + digest: c591dea135ef93f4af1a05961333125167ae551cf2b666363fe76b5a7ad9f806 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.4.tgz + version: 0.3.4 + - created: 2016-10-21T19:18:18.621573514Z + description: A flexible project management web application. + digest: da6a8cb8c355a93ae11d9312be9eca51966d2288eafe96b6724e6154d000b8c3 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.3.tgz + version: 0.3.3 + - created: 2016-10-19T00:03:14.035726608Z + description: A flexible project management web application. + digest: 052a0a97ff279db43f06c5ceeabfc5bd26f2e5f4f7ce7c24fdbcf761f97af84e + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.2.tgz + version: 0.3.2 + - created: 2016-10-19T00:03:14.034750035Z + description: A flexible project management web application. + digest: 88cf358644be274866ec5e88199c257e18a35fc8bbe97417658b9a0ea1e4a260 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.1.tgz + version: 0.3.1 + - created: 2016-10-19T00:03:14.033766322Z + description: A flexible project management web application. + digest: f4815d35cbf9f8bb72c051ee528958b9c6f48b1f3bf8b3fdceaadd90d1b88068 + engine: gotpl + home: http://www.redmine.org/ + keywords: + - redmine + - project management + - www + - http + - web + - application + - ruby + - rails + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: redmine + sources: + - https://github.com/bitnami/bitnami-docker-redmine + urls: + - https://kubernetes-charts.storage.googleapis.com/redmine-0.3.0.tgz + version: 0.3.0 + sapho: + - apiVersion: v1 + created: 2017-04-28T00:18:30.105942339Z + description: A micro application development and integration platform that enables + organizations to create and deliver secure micro applications that tie into + existing business systems and track changes to key business data. + digest: 6c499f9875c07b508d23b081ffd991a5737a0acaf1c75def55dbb2dc07bf40ea + engine: gotpl + home: http://www.sapho.com + icon: https://www.sapho.com/wp-content/uploads/2016/04/sapho-logotype.svg + maintainers: + - email: support@sapho.com + name: Sapho + name: sapho + sources: + - https://bitbucket.org/sapho/ops-docker-tomcat/src + - https://hub.docker.com/r/sapho/ops-docker-tomcat + - https://github.com/kubernetes/charts/tree/master/stable/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/sapho-0.1.5.tgz + version: 0.1.5 + - apiVersion: v1 + created: 2017-02-13T04:33:52.314506112Z + description: A micro application development and integration platform that enables + organizations to create and deliver secure micro applications that tie into + existing business systems and track changes to key business data. + digest: abe8e15b8e51369d6d05033177efb524139d3352794e201003d2e3fce3d0669d + engine: gotpl + maintainers: + - email: support@sapho.com + name: Sapho + name: sapho + sources: + - https://bitbucket.org/sapho/ops-docker-tomcat/src + - https://hub.docker.com/r/sapho/ops-docker-tomcat + - https://github.com/kubernetes/charts/tree/master/stable/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/sapho-0.1.4.tgz + version: 0.1.4 + - apiVersion: v1 + created: 2017-01-31T00:18:28.518904Z + description: A micro application development and integration platform that enables + organizations to create and deliver secure micro applications that tie into + existing business systems and track changes to key business data. + digest: d93ff20d61a35de8ab23d5d118c177184a6b8b0578a39ba7d101f818a8742851 + engine: gotpl + maintainers: + - email: support@sapho.com + name: Sapho + name: sapho + sources: + - https://bitbucket.org/sapho/ops-docker-tomcat/src + - https://hub.docker.com/r/sapho/ops-docker-tomcat + - https://github.com/kubernetes/charts/tree/master/stable/mysql + urls: + - https://kubernetes-charts.storage.googleapis.com/sapho-0.1.3.tgz + version: 0.1.3 + selenium: + - created: 2017-04-28T00:18:30.106455441Z + description: Chart for selenium grid + digest: 0e03cf36738e83b3e6ae7384c0ffdeb4ee4b694f0c0a025eb15106acb189b8d2 + engine: gotpl + home: http://www.seleniumhq.org/ + icon: http://docs.seleniumhq.org/images/big-logo.png + keywords: + - qa + maintainers: + - email: techops@adaptly.com + name: Philip Champon (flah00) + name: selenium + sources: + - https://github.com/SeleniumHQ/docker-selenium + urls: + - https://kubernetes-charts.storage.googleapis.com/selenium-0.1.0.tgz + version: 0.1.0 + sensu: + - apiVersion: v1 + created: 2017-04-28T00:18:30.107064065Z + description: Sensu monitoring framework backed by the Redis transport + digest: 56c74a8de76074cfb021057112cf46d11d8b77f9ef5f6ec5d0877698c9931dfa + engine: gotpl + home: https://sensuapp.org/ + icon: https://raw.githubusercontent.com/sensu/sensu/master/sensu-logo.png + keywords: + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: sensu + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-sensu + - https://github.com/sensu/sensu + urls: + - https://kubernetes-charts.storage.googleapis.com/sensu-0.1.2.tgz + version: 0.1.2 + - apiVersion: v1 + created: 2017-03-17T05:18:29.12808256Z + description: Sensu monitoring framework backed by the Redis transport + digest: bb8781a9693f3b6df9389b3098a6298658127df2e86ad8156788602f541f33c3 + engine: gotpl + home: https://sensuapp.org/ + icon: https://raw.githubusercontent.com/sensu/sensu/master/sensu-logo.png + keywords: + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: sensu + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-sensu + - https://github.com/sensu/sensu + urls: + - https://kubernetes-charts.storage.googleapis.com/sensu-0.1.1.tgz + version: 0.1.1 + - apiVersion: v1 + created: 2016-12-21T23:33:22.277352049Z + description: Sensu monitoring framework backed by the Redis transport + digest: 4592387df52c4110a3a313820dbea81e8bf0252845e8c08ad7c71bce9a92831c + engine: gotpl + home: https://sensuapp.org/ + icon: https://raw.githubusercontent.com/sensu/sensu/master/sensu-logo.png + keywords: + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: sensu + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-sensu + - https://github.com/sensu/sensu + urls: + - https://kubernetes-charts.storage.googleapis.com/sensu-0.1.0.tgz + version: 0.1.0 + spark: + - created: 2017-04-28T00:18:30.107369986Z + description: Fast and general-purpose cluster computing system. + digest: d37ec7d7530a5836eeeb5ff54110d594efe188ce8175a7c2e3b50e5d9f5af9bc + home: http://spark.apache.org + icon: http://spark.apache.org/images/spark-logo-trademark.png + maintainers: + - email: lachlan.evenson@gmail.com + name: Lachlan Evenson + name: spark + sources: + - https://github.com/kubernetes/kubernetes/tree/master/examples/spark + - https://github.com/apache/spark + urls: + - https://kubernetes-charts.storage.googleapis.com/spark-0.1.4.tgz + version: 0.1.4 + - created: 2017-03-09T19:03:32.57258203Z + description: Fast and general-purpose cluster computing system. + digest: 1cea71eb812c7ea6d566ad34247ad8d1c7b2a460b908748372618a94f035d974 + home: http://spark.apache.org + icon: http://spark.apache.org/images/spark-logo-trademark.png + maintainers: + - email: lachlan.evenson@gmail.com + name: Lachlan Evenson + name: spark + sources: + - https://github.com/kubernetes/kubernetes/tree/master/examples/spark + - https://github.com/apache/spark + urls: + - https://kubernetes-charts.storage.googleapis.com/spark-0.1.3.tgz + version: 0.1.3 + - created: 2017-02-13T04:33:52.317122021Z + description: A Apache Spark Helm chart for Kubernetes. Apache Spark is a fast + and general-purpose cluster computing system + digest: fd5559299116691e56c85f60be46e3b1d1a647973f4dfd6c0d87d0b0274a349b + home: http://spark.apache.org/ + maintainers: + - email: lachlan.evenson@gmail.com + name: Lachlan Evenson + name: spark + sources: + - https://github.com/kubernetes/kubernetes/tree/master/examples/spark + - https://github.com/apache/spark + urls: + - https://kubernetes-charts.storage.googleapis.com/spark-0.1.2.tgz + version: 0.1.2 + - created: 2017-01-27T21:48:32.088621169Z + description: A Apache Spark Helm chart for Kubernetes. Apache Spark is a fast + and general-purpose cluster computing system + digest: 884cc07e4710011476db63017b48504cc00b00faf461cdfe83aac40f0fd33e49 + home: http://spark.apache.org/ + maintainers: + - email: lachlan.evenson@gmail.com + name: Lachlan Evenson + name: spark + sources: + - https://github.com/kubernetes/kubernetes/tree/master/examples/spark + - https://github.com/apache/spark + urls: + - https://kubernetes-charts.storage.googleapis.com/spark-0.1.1.tgz + version: 0.1.1 + spartakus: + - created: 2017-04-28T00:18:30.107681212Z + description: Collect information about Kubernetes clusters to help improve the + project. + digest: 7db8a6ac7280c8d112b533b2653cfa8ed43d8517a4cf31d28e24d5761d8c6b80 + engine: gotpl + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: spartakus + sources: + - https://github.com/kubernetes-incubator/spartakus + urls: + - https://kubernetes-charts.storage.googleapis.com/spartakus-1.1.1.tgz + version: 1.1.1 + - created: 2017-03-02T18:48:30.451198217Z + description: Collect information about Kubernetes clusters to help improve the + project. + digest: 84720960919addcce5b608717eca0218b7f6cd9edbf77a52ddc0747e51037936 + engine: gotpl + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: spartakus + sources: + - https://github.com/kubernetes-incubator/spartakus + urls: + - https://kubernetes-charts.storage.googleapis.com/spartakus-1.1.0.tgz + version: 1.1.0 + - created: 2017-02-13T17:03:30.144830851Z + description: A Spartakus Helm chart for Kubernetes. Spartakus aims to collect + information about Kubernetes clusters. + digest: 1c202628cd57e01cb324ee6e9457b52d1e1a5fd665f99d4bb25bd17c92c438e9 + engine: gotpl + maintainers: + - email: mgoodness@gmail.com + name: Michael Goodness + name: spartakus + sources: + - https://github.com/kubernetes-incubator/spartakus + urls: + - https://kubernetes-charts.storage.googleapis.com/spartakus-1.0.0.tgz + version: 1.0.0 + spinnaker: + - apiVersion: v1 + created: 2017-04-28T00:18:30.109150773Z + description: Open source, multi-cloud continuous delivery platform for releasing + software changes with high velocity and confidence. + digest: a06ae1d7452e19824110cbb3270c5b7bfc4acf10af23e072e442b81fe26b1dc5 + home: http://spinnaker.io/ + icon: https://pbs.twimg.com/profile_images/669205226994319362/O7OjwPrh_400x400.png + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: spinnaker + sources: + - https://github.com/spinnaker + - https://github.com/viglesiasce/images + urls: + - https://kubernetes-charts.storage.googleapis.com/spinnaker-0.1.1.tgz + version: 0.1.1 + - apiVersion: v1 + created: 2017-02-13T20:48:27.29021219Z + description: A Helm chart for Kubernetes + digest: cc44efeace9d645b2ea824b017986d86b6b3a50fcd94e86199e0e6849eb02731 + home: http://spinnaker.io/ + maintainers: + - email: viglesias@google.com + name: Vic Iglesias + name: spinnaker + sources: + - https://github.com/spinnaker + - https://github.com/viglesiasce/images + urls: + - https://kubernetes-charts.storage.googleapis.com/spinnaker-0.1.0.tgz + version: 0.1.0 + sumokube: + - created: 2017-04-28T00:18:30.109952763Z + description: Sumologic Log Collector + digest: 2f4f5cfc4c1d40cd24085497041fd701f72d4f15cb55241bfb998da82b05c7b9 + keywords: + - monitoring + - logging + maintainers: + - email: jdumars+github@gmail.com + name: Jason DuMars + - email: knoxville+github@gmail.com + name: Sean Knox + name: sumokube + sources: + - https://github.com/SumoLogic/sumologic-collector-docker + urls: + - https://kubernetes-charts.storage.googleapis.com/sumokube-0.1.1.tgz + version: 0.1.1 + - created: 2017-01-27T21:48:32.092039665Z + description: Sumologic Log Collector + digest: 5b173be9b7dc0e1d48a7cd11015b9c405666a40420a290c5fb54e4f8718b4fc0 + keywords: + - monitoring + - logging + maintainers: + - email: jdumars+github@gmail.com + name: Jason DuMars + - email: knoxville+github@gmail.com + name: Sean Knox + name: sumokube + sources: + - https://github.com/SumoLogic/sumologic-collector-docker + urls: + - https://kubernetes-charts.storage.googleapis.com/sumokube-0.1.0.tgz + version: 0.1.0 + telegraf: + - created: 2017-04-28T00:18:30.110460492Z + description: Telegraf is an agent written in Go for collecting, processing, aggregating, + and writing metrics. + digest: 850b4b7543a3dd7f5d33ba65d9098fe4f361981f49452a40ce9774850b4285e3 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/telegraf/ + keywords: + - telegraf + - collector + - timeseries + - influxdata + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: telegraf + urls: + - https://kubernetes-charts.storage.googleapis.com/telegraf-0.2.0.tgz + version: 0.2.0 + - created: 2017-02-13T21:48:52.617397285Z + description: Telegraf is an agent written in Go for collecting, processing, aggregating, + and writing metrics. + digest: 1f74106455808d45d16742f6d7d02164eb328a40dd9699dfa4511b33efaf14e9 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/telegraf/ + keywords: + - telegraf + - collector + - timeseries + - influxdata + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: telegraf + urls: + - https://kubernetes-charts.storage.googleapis.com/telegraf-0.1.1.tgz + version: 0.1.1 + - created: 2017-02-11T03:18:26.54678474Z + description: Chart for Telegraf Kubernetes deployments + digest: 52fa68fd948ee675a5d1a5ffff22d98e293ee37569a8fa56a4022f51e9507184 + engine: gotpl + home: https://www.influxdata.com/time-series-platform/telegraf/ + keywords: + - telegraf + - collector + - timeseries + - influxdata + maintainers: + - email: jack@influxdb.com + name: Jack Zampolin + name: telegraf + urls: + - https://kubernetes-charts.storage.googleapis.com/telegraf-0.1.0.tgz + version: 0.1.0 + testlink: + - created: 2017-04-28T00:18:30.111130012Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 8cffc761a9e6618bc015cec3721964192e909dfaae92a9bb79c4471424c74128 + engine: gotpl + home: http://www.testlink.org/ + icon: https://bitnami.com/assets/stacks/testlink/img/testlink-stack-220x234.png + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.6.tgz + version: 0.4.6 + - created: 2017-03-23T18:18:30.028234531Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 08f7104671364ff6bd43270659733ea97a4adc06181f8a5c3027ac3d0078e51c + engine: gotpl + home: http://www.testlink.org/ + icon: https://bitnami.com/assets/stacks/testlink/img/testlink-stack-220x234.png + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.5.tgz + version: 0.4.5 + - created: 2017-03-08T19:03:31.751542723Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 7861921ff159f1be6834acfc3e5c139382a8c6461b20a45c4b1561985827c865 + engine: gotpl + home: http://www.testlink.org/ + icon: https://bitnami.com/assets/stacks/testlink/img/testlink-stack-220x234.png + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.4.tgz + version: 0.4.4 + - created: 2017-02-11T03:18:26.547570032Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 2c7188d5f1a9fb03c71b2e2d693dfbef9a739ae8889d9eb38854900cf066077b + engine: gotpl + home: http://www.testlink.org/ + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.3.tgz + version: 0.4.3 + - created: 2017-01-21T00:18:31.369288453Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 78f6a9cfe1843b8ea99489d8b4c801f84271ee25827ad044989ed0df21ac086b + engine: gotpl + home: http://www.testlink.org/ + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.2.tgz + version: 0.4.2 + - created: 2016-12-15T21:18:24.679744308Z + description: Web-based test management system that facilitates software quality + assurance. + digest: 9edb2777c6db4794885a2c7531a28436774edc248aad3a26007bca4076058143 + engine: gotpl + home: http://www.testlink.org/ + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.1.tgz + version: 0.4.1 + - created: 2016-12-09T18:48:20.193151472Z + description: Web-based test management system that facilitates software quality + assurance. + digest: df216a31082cdf15867ee9a17b107e4006e9e0a20b79425889b695c4c46fb0c1 + engine: gotpl + home: http://www.testlink.org/ + keywords: + - testlink + - testing + - http + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: testlink + sources: + - https://github.com/bitnami/bitnami-docker-testlink + urls: + - https://kubernetes-charts.storage.googleapis.com/testlink-0.4.0.tgz + version: 0.4.0 + traefik: + - apiVersion: v1 + created: 2017-04-28T00:18:30.111646123Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 4019610a5fb1defcc5bc90532cb19c986999114f7de4aef3f0272e6c7ed1b914 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.2.1-a.tgz + version: 1.2.1-a + - apiVersion: v1 + created: 2017-03-31T19:33:30.456523182Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: ba0ade25b34f419ad0790b220fb7277a046d48bc76e1c726f66ba535b51d4f63 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-h.tgz + version: 1.1.2-h + - apiVersion: v1 + created: 2017-03-16T23:33:31.610346236Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 9aa401aee6da3b4afc5cc3f8be7ff9f74bf424743ca72a7a7b91a7105d9781b6 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-g.tgz + version: 1.1.2-g + - apiVersion: v1 + created: 2017-02-27T17:18:28.185706737Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 5bb7b98b962098808e3b73f604592bc4c6e6245e0074fa0c99308fc04bf766b8 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-f.tgz + version: 1.1.2-f + - apiVersion: v1 + created: 2017-02-13T22:18:28.973464794Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: ae467c4bee7364d17de2583d33031d0eeb2ef55e7962a7db0245d692e65479e1 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-e.tgz + version: 1.1.2-e + - apiVersion: v1 + created: 2017-02-13T21:33:27.776086791Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 399d74bcd8ab26f2de10894d83b59d413752797789b9fe9568e17f7b564f5f75 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-d.tgz + version: 1.1.2-d + - apiVersion: v1 + created: 2017-02-03T19:33:30.806247527Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: e225511060509d9cf3e38eaafd93af9ee994f8ed99c40a25500f4a1d06851841 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-c.tgz + version: 1.1.2-c + - apiVersion: v1 + created: 2017-02-01T02:18:29.153394653Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 9cc02b2e43c901c92aa560b4f85e325f04635d052035418f3b27b06bdd571ae9 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-b.tgz + version: 1.1.2-b + - apiVersion: v1 + created: 2017-01-28T00:18:32.767314879Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 9bae960964d5062dd4c412ad7daf6f6f9e8dd070264aa3f44c831c817fc26b7d + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.2-a.tgz + version: 1.1.2-a + - apiVersion: v1 + created: 2017-01-03T17:48:20.753425335Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: e8ab4576505091785b27084e4f4e4f02f1ee3f1744d9842ec086457baabe8b85 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.1-a.tgz + version: 1.1.1-a + - apiVersion: v1 + created: 2016-11-23T00:33:20.024479934Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 6664534aab03a22531602a415ca14a72e932b08fe1feab8866cc55ba18b77dc8 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.0-rc3-a.tgz + version: 1.1.0-rc3-a + - apiVersion: v1 + created: 2016-11-30T22:03:20.721274307Z + description: A Traefik based Kubernetes ingress controller with Let's Encrypt + support + digest: 2828d7284839baee1fb36f823ce4e2574a4b675b7f4f74e921a4685f4cee28c2 + engine: gotpl + home: http://traefik.io/ + icon: http://traefik.io/traefik.logo.png + keywords: + - traefik + - ingress + - acme + - letsencrypt + maintainers: + - email: engineering@deis.com + name: Deis + name: traefik + sources: + - https://github.com/containous/traefik + - https://github.com/krancour/charts/tree/master/traefik + urls: + - https://kubernetes-charts.storage.googleapis.com/traefik-1.1.0-a.tgz + version: 1.1.0-a + uchiwa: + - apiVersion: v1 + created: 2017-04-28T00:18:30.112528335Z + description: Dashboard for the Sensu monitoring framework + digest: b9b7186c2e53d4049c4b0ef9ba9c89ded7de36bf920653b63f6ea725253354d6 + engine: gotpl + home: https://uchiwa.io/ + icon: https://uchiwa.io/img/favicon.png + keywords: + - uchiwa + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: uchiwa + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-uchiwa + - https://github.com/sensu/uchiwa + urls: + - https://kubernetes-charts.storage.googleapis.com/uchiwa-0.2.1.tgz + version: 0.2.1 + - apiVersion: v1 + created: 2017-03-17T06:03:29.101091523Z + description: Dashboard for the Sensu monitoring framework + digest: 9bee21cd61e56e08f58c1ba130e0a4af1a1d62a8d7921f9408509bd501494403 + engine: gotpl + home: https://uchiwa.io/ + icon: https://uchiwa.io/img/favicon.png + keywords: + - uchiwa + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: uchiwa + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-uchiwa + - https://github.com/sensu/uchiwa + urls: + - https://kubernetes-charts.storage.googleapis.com/uchiwa-0.2.0.tgz + version: 0.2.0 + - apiVersion: v1 + created: 2017-01-18T23:03:27.817024829Z + description: Dashboard for the Sensu monitoring framework + digest: 868d7e58adb2fead4ed9e4be17e2017c2d1c55d265b2a579625e787e6f15f4d5 + engine: gotpl + home: https://uchiwa.io/ + icon: https://uchiwa.io/img/favicon.png + keywords: + - uchiwa + - sensu + - monitoring + maintainers: + - email: shane.starcher@gmail.com + name: Shane Starcher + name: uchiwa + sources: + - https://github.com/kubernetes/charts + - https://github.com/sstarcher/docker-uchiwa + - https://github.com/sensu/uchiwa + urls: + - https://kubernetes-charts.storage.googleapis.com/uchiwa-0.1.0.tgz + version: 0.1.0 + wordpress: + - created: 2017-04-28T00:18:30.114169329Z + description: Web publishing platform for building blogs and websites. + digest: 8df4b37c471d43b5b3955ecadcc0da1dad31ba28a93ae0b74be5fc94debf2876 + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.6.0.tgz + version: 0.6.0 + - created: 2017-04-03T22:33:26.700088102Z + description: Web publishing platform for building blogs and websites. + digest: 4413a17258eaca753252174a219ba9081283a406375d8ae49e5c1f3313c6619a + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.5.2.tgz + version: 0.5.2 + - created: 2017-03-23T21:18:31.877594706Z + description: Web publishing platform for building blogs and websites. + digest: 3e408baaa5110edfd730603bd5d49d7a8c222f49c7e9de1bd168b564463d57d9 + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.5.1.tgz + version: 0.5.1 + - created: 2017-03-16T13:33:30.866725941Z + description: Web publishing platform for building blogs and websites. + digest: 40c767b4b2b7d494ea6da7a20a9fe58e76896a0bdad7c6c569f9d8cdab71f2e3 + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.5.0.tgz + version: 0.5.0 + - created: 2017-03-14T23:48:31.917245657Z + description: Web publishing platform for building blogs and websites. + digest: 306220e3c19f1360644eade517a2a8ca422e8f9ec6ea9c65181ce8fc9797772f + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.4.3.tgz + version: 0.4.3 + - created: 2017-03-08T19:03:31.755452536Z + description: Web publishing platform for building blogs and websites. + digest: 0689b452d3c9a9bee6e5c84b48172c68de6eedc253223b96ab6500ad88a5de40 + engine: gotpl + home: http://www.wordpress.com/ + icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.4.2.tgz + version: 0.4.2 + - created: 2017-02-13T04:33:52.323397093Z + description: Web publishing platform for building blogs and websites. + digest: 0fc412dea55069b368183afefb74342001a91a7f3a0e9126a921581d7740d61c + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.4.1.tgz + version: 0.4.1 + - created: 2017-01-28T00:18:32.769124587Z + description: Web publishing platform for building blogs and websites. + digest: 2f4a5d65350b36a6481c4c3d619f713835f091821d3f56c38c718061628ff712 + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.4.0.tgz + version: 0.4.0 + - created: 2017-01-04T00:48:19.757447587Z + description: Web publishing platform for building blogs and websites. + digest: f62b6f1728a33c5d59dd24dc6fb984f13d2dffac2bc6eec01724501e66ffc6a0 + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.3.4.tgz + version: 0.3.4 + - created: 2016-12-15T00:48:24.021239603Z + description: Web publishing platform for building blogs and websites. + digest: 0c86b7cec5877a3c3c55d919b2f02ae52340c953afd9dc541ae0280bc23fe9aa + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.3.3.tgz + version: 0.3.3 + - created: 2016-12-09T18:48:20.19465733Z + description: Web publishing platform for building blogs and websites. + digest: 589e49370cb09f6d9ddb3ceba3b21f52697570cd4b40aff891a660c5daaa9bec + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.3.2.tgz + version: 0.3.2 + - created: 2016-10-21T19:18:18.622178432Z + description: Web publishing platform for building blogs and websites. + digest: e70a072dcbb7252becc8899f54de8cb5977ceaea47197919c3990a6896adc350 + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.3.1.tgz + version: 0.3.1 + - created: 2016-10-19T00:03:14.037631856Z + description: Web publishing platform for building blogs and websites. + digest: 1c44515f02fb34b722dce1d8cf5fed0dfbbd2f8c03d63b335211b7bcb12b6dea + engine: gotpl + home: http://www.wordpress.com/ + keywords: + - wordpress + - cms + - blog + - http + - web + - application + - php + maintainers: + - email: containers@bitnami.com + name: Bitnami + name: wordpress + sources: + - https://github.com/bitnami/bitnami-docker-wordpress + urls: + - https://kubernetes-charts.storage.googleapis.com/wordpress-0.3.0.tgz + version: 0.3.0 +generated: 2017-04-28T00:18:30.070608132Z diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/local/index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/local/index.yaml new file mode 100644 index 000000000..efcf30c21 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/local/index.yaml @@ -0,0 +1,3 @@ +apiVersion: v1 +entries: {} +generated: 2017-04-28T12:34:38.900985501-06:00 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/repositories.yaml b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/repositories.yaml new file mode 100644 index 000000000..1d884a0c7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/getter/testdata/repository/repositories.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +generated: 2017-04-28T12:34:38.551693035-06:00 +repositories: +- caFile: "" + cache: repository/cache/stable-index.yaml + certFile: "" + keyFile: "" + name: stable + url: https://kubernetes-charts.storage.googleapis.com +- caFile: "" + cache: repository/cache/local-index.yaml + certFile: "" + keyFile: "" + name: local + url: http://127.0.0.1:8879/charts diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/client.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/client.go new file mode 100644 index 000000000..43e9f4daf --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/client.go @@ -0,0 +1,522 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm // import "k8s.io/helm/pkg/helm" + +import ( + "fmt" + "io" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" + + healthpb "google.golang.org/grpc/health/grpc_health_v1" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" + rls "k8s.io/helm/pkg/proto/hapi/services" +) + +// maxMsgSize use 20MB as the default message size limit. +// grpc library default is 4MB +const maxMsgSize = 1024 * 1024 * 20 + +// Client manages client side of the Helm-Tiller protocol. +type Client struct { + opts options +} + +// NewClient creates a new client. +func NewClient(opts ...Option) *Client { + var c Client + // set some sane defaults + c.Option(ConnectTimeout(5)) + return c.Option(opts...) +} + +// Option configures the Helm client with the provided options. +func (h *Client) Option(opts ...Option) *Client { + for _, opt := range opts { + opt(&h.opts) + } + return h +} + +// ListReleases lists the current releases. +func (h *Client) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.listReq + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.list(ctx, req) +} + +// InstallRelease loads a chart from chstr, installs it, and returns the release response. +func (h *Client) InstallRelease(chstr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + // load the chart to install + chart, err := chartutil.Load(chstr) + if err != nil { + return nil, err + } + + return h.InstallReleaseFromChart(chart, ns, opts...) +} + +// InstallReleaseFromChart installs a new chart and returns the release response. +func (h *Client) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + // apply the install options + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.instReq + req.Chart = chart + req.Namespace = ns + req.DryRun = reqOpts.dryRun + req.DisableHooks = reqOpts.disableHooks + req.ReuseName = reqOpts.reuseName + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values) + if err != nil { + return nil, err + } + err = chartutil.ProcessRequirementsImportValues(req.Chart) + if err != nil { + return nil, err + } + + return h.install(ctx, req) +} + +// DeleteRelease uninstalls a named release and returns the response. +func (h *Client) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { + // apply the uninstall options + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + + if reqOpts.dryRun { + // In the dry run case, just see if the release exists + r, err := h.ReleaseContent(rlsName) + if err != nil { + return &rls.UninstallReleaseResponse{}, err + } + return &rls.UninstallReleaseResponse{Release: r.Release}, nil + } + + req := &reqOpts.uninstallReq + req.Name = rlsName + req.DisableHooks = reqOpts.disableHooks + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.delete(ctx, req) +} + +// UpdateRelease loads a chart from chstr and updates a release to a new/different chart. +func (h *Client) UpdateRelease(rlsName string, chstr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + // load the chart to update + chart, err := chartutil.Load(chstr) + if err != nil { + return nil, err + } + + return h.UpdateReleaseFromChart(rlsName, chart, opts...) +} + +// UpdateReleaseFromChart updates a release to a new/different chart. +func (h *Client) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + // apply the update options + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.updateReq + req.Chart = chart + req.DryRun = reqOpts.dryRun + req.Name = rlsName + req.DisableHooks = reqOpts.disableHooks + req.Recreate = reqOpts.recreate + req.Force = reqOpts.force + req.ResetValues = reqOpts.resetValues + req.ReuseValues = reqOpts.reuseValues + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + err := chartutil.ProcessRequirementsEnabled(req.Chart, req.Values) + if err != nil { + return nil, err + } + err = chartutil.ProcessRequirementsImportValues(req.Chart) + if err != nil { + return nil, err + } + + return h.update(ctx, req) +} + +// GetVersion returns the server version. +func (h *Client) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &rls.GetVersionRequest{} + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.version(ctx, req) +} + +// RollbackRelease rolls back a release to the previous version. +func (h *Client) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.rollbackReq + req.Recreate = reqOpts.recreate + req.Force = reqOpts.force + req.DisableHooks = reqOpts.disableHooks + req.DryRun = reqOpts.dryRun + req.Name = rlsName + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.rollback(ctx, req) +} + +// ReleaseStatus returns the given release's status. +func (h *Client) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.statusReq + req.Name = rlsName + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.status(ctx, req) +} + +// ReleaseContent returns the configuration for a given release. +func (h *Client) ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + req := &reqOpts.contentReq + req.Name = rlsName + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.content(ctx, req) +} + +// ReleaseHistory returns a release's revision history. +func (h *Client) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + + req := &reqOpts.histReq + req.Name = rlsName + ctx := NewContext() + + if reqOpts.before != nil { + if err := reqOpts.before(ctx, req); err != nil { + return nil, err + } + } + return h.history(ctx, req) +} + +// RunReleaseTest executes a pre-defined test on a release. +func (h *Client) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { + reqOpts := h.opts + for _, opt := range opts { + opt(&reqOpts) + } + + req := &reqOpts.testReq + req.Name = rlsName + ctx := NewContext() + + return h.test(ctx, req) +} + +// PingTiller pings the Tiller pod and ensure's that it is up and running +func (h *Client) PingTiller() error { + ctx := NewContext() + return h.ping(ctx) +} + +// connect returns a gRPC connection to Tiller or error. The gRPC dial options +// are constructed here. +func (h *Client) connect(ctx context.Context) (conn *grpc.ClientConn, err error) { + opts := []grpc.DialOption{ + grpc.WithBlock(), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + // Send keepalive every 30 seconds to prevent the connection from + // getting closed by upstreams + Time: time.Duration(30) * time.Second, + }), + grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize)), + } + switch { + case h.opts.useTLS: + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(h.opts.tlsConfig))) + default: + opts = append(opts, grpc.WithInsecure()) + } + ctx, cancel := context.WithTimeout(ctx, h.opts.connectTimeout) + defer cancel() + if conn, err = grpc.DialContext(ctx, h.opts.host, opts...); err != nil { + return nil, err + } + return conn, nil +} + +// Executes tiller.ListReleases RPC. +func (h *Client) list(ctx context.Context, req *rls.ListReleasesRequest) (*rls.ListReleasesResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + s, err := rlc.ListReleases(ctx, req) + if err != nil { + return nil, err + } + var resp *rls.ListReleasesResponse + for { + r, err := s.Recv() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if resp == nil { + resp = r + continue + } + resp.Releases = append(resp.Releases, r.GetReleases()[0]) + } + return resp, nil +} + +// Executes tiller.InstallRelease RPC. +func (h *Client) install(ctx context.Context, req *rls.InstallReleaseRequest) (*rls.InstallReleaseResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.InstallRelease(ctx, req) +} + +// Executes tiller.UninstallRelease RPC. +func (h *Client) delete(ctx context.Context, req *rls.UninstallReleaseRequest) (*rls.UninstallReleaseResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.UninstallRelease(ctx, req) +} + +// Executes tiller.UpdateRelease RPC. +func (h *Client) update(ctx context.Context, req *rls.UpdateReleaseRequest) (*rls.UpdateReleaseResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.UpdateRelease(ctx, req) +} + +// Executes tiller.RollbackRelease RPC. +func (h *Client) rollback(ctx context.Context, req *rls.RollbackReleaseRequest) (*rls.RollbackReleaseResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.RollbackRelease(ctx, req) +} + +// Executes tiller.GetReleaseStatus RPC. +func (h *Client) status(ctx context.Context, req *rls.GetReleaseStatusRequest) (*rls.GetReleaseStatusResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.GetReleaseStatus(ctx, req) +} + +// Executes tiller.GetReleaseContent RPC. +func (h *Client) content(ctx context.Context, req *rls.GetReleaseContentRequest) (*rls.GetReleaseContentResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.GetReleaseContent(ctx, req) +} + +// Executes tiller.GetVersion RPC. +func (h *Client) version(ctx context.Context, req *rls.GetVersionRequest) (*rls.GetVersionResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.GetVersion(ctx, req) +} + +// Executes tiller.GetHistory RPC. +func (h *Client) history(ctx context.Context, req *rls.GetHistoryRequest) (*rls.GetHistoryResponse, error) { + c, err := h.connect(ctx) + if err != nil { + return nil, err + } + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + return rlc.GetHistory(ctx, req) +} + +// Executes tiller.TestRelease RPC. +func (h *Client) test(ctx context.Context, req *rls.TestReleaseRequest) (<-chan *rls.TestReleaseResponse, <-chan error) { + errc := make(chan error, 1) + c, err := h.connect(ctx) + if err != nil { + errc <- err + return nil, errc + } + + ch := make(chan *rls.TestReleaseResponse, 1) + go func() { + defer close(errc) + defer close(ch) + defer c.Close() + + rlc := rls.NewReleaseServiceClient(c) + s, err := rlc.RunReleaseTest(ctx, req) + if err != nil { + errc <- err + return + } + + for { + msg, err := s.Recv() + if err == io.EOF { + return + } + if err != nil { + errc <- err + return + } + ch <- msg + } + }() + + return ch, errc +} + +// Executes tiller.Ping RPC. +func (h *Client) ping(ctx context.Context) error { + c, err := h.connect(ctx) + if err != nil { + return err + } + defer c.Close() + + healthClient := healthpb.NewHealthClient(c) + resp, err := healthClient.Check(ctx, &healthpb.HealthCheckRequest{Service: "Tiller"}) + if err != nil { + return err + } + switch resp.GetStatus() { + case healthpb.HealthCheckResponse_SERVING: + return nil + case healthpb.HealthCheckResponse_NOT_SERVING: + return fmt.Errorf("tiller is not serving requests at this time, Please try again later") + default: + return fmt.Errorf("tiller healthcheck returned an unknown status") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/client_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/client_test.go new file mode 100644 index 000000000..95e044499 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/client_test.go @@ -0,0 +1,34 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm + +import ( + "testing" + "time" +) + +func TestNewClient(t *testing.T) { + helmClient := NewClient() + if helmClient.opts.connectTimeout != 5*time.Second { + t.Errorf("expected default timeout duration to be 5 seconds, got %v", helmClient.opts.connectTimeout) + } + + helmClient = NewClient(ConnectTimeout(60)) + if helmClient.opts.connectTimeout != time.Minute { + t.Errorf("expected timeout duration to be 1 minute, got %v", helmClient.opts.connectTimeout) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment.go new file mode 100644 index 000000000..2980e6dc9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 environment describes the operating environment for Tiller. + +Tiller's environment encapsulates all of the service dependencies Tiller has. +These dependencies are expressed as interfaces so that alternate implementations +(mocks, etc.) can be easily generated. +*/ +package environment + +import ( + "os" + "path/filepath" + + "github.com/spf13/pflag" + + "k8s.io/client-go/util/homedir" + "k8s.io/helm/pkg/helm/helmpath" +) + +// DefaultHelmHome is the default HELM_HOME. +var DefaultHelmHome = filepath.Join(homedir.HomeDir(), ".helm") + +// EnvSettings describes all of the environment settings. +type EnvSettings struct { + // TillerHost is the host and port of Tiller. + TillerHost string + // TillerConnectionTimeout is the duration (in seconds) helm will wait to establish a connection to tiller. + TillerConnectionTimeout int64 + // TillerNamespace is the namespace in which Tiller runs. + TillerNamespace string + // Home is the local path to the Helm home directory. + Home helmpath.Home + // Debug indicates whether or not Helm is running in Debug mode. + Debug bool + // KubeContext is the name of the kubeconfig context. + KubeContext string +} + +// AddFlags binds flags to the given flagset. +func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { + fs.StringVar((*string)(&s.Home), "home", DefaultHelmHome, "location of your Helm config. Overrides $HELM_HOME") + fs.StringVar(&s.TillerHost, "host", "", "address of Tiller. Overrides $HELM_HOST") + fs.StringVar(&s.KubeContext, "kube-context", "", "name of the kubeconfig context to use") + fs.BoolVar(&s.Debug, "debug", false, "enable verbose output") + fs.StringVar(&s.TillerNamespace, "tiller-namespace", "kube-system", "namespace of Tiller") + fs.Int64Var(&s.TillerConnectionTimeout, "tiller-connection-timeout", int64(300), "the duration (in seconds) Helm will wait to establish a connection to tiller") +} + +// Init sets values from the environment. +func (s *EnvSettings) Init(fs *pflag.FlagSet) { + for name, envar := range envMap { + setFlagFromEnv(name, envar, fs) + } +} + +// PluginDirs is the path to the plugin directories. +func (s EnvSettings) PluginDirs() string { + if d, ok := os.LookupEnv("HELM_PLUGIN"); ok { + return d + } + return s.Home.Plugins() +} + +// envMap maps flag names to envvars +var envMap = map[string]string{ + "debug": "HELM_DEBUG", + "home": "HELM_HOME", + "host": "HELM_HOST", + "tiller-namespace": "TILLER_NAMESPACE", +} + +func setFlagFromEnv(name, envar string, fs *pflag.FlagSet) { + if fs.Changed(name) { + return + } + if v, ok := os.LookupEnv(envar); ok { + fs.Set(name, v) + } +} + +// Deprecated +const ( + HomeEnvVar = "HELM_HOME" + PluginEnvVar = "HELM_PLUGIN" + PluginDisableEnvVar = "HELM_NO_PLUGINS" + HostEnvVar = "HELM_HOST" + DebugEnvVar = "HELM_DEBUG" +) diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment_test.go new file mode 100644 index 000000000..8f0caa388 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/environment/environment_test.go @@ -0,0 +1,134 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 environment + +import ( + "os" + "strings" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" + + "github.com/spf13/pflag" +) + +func TestEnvSettings(t *testing.T) { + tests := []struct { + name string + + // input + args []string + envars map[string]string + + // expected values + home, host, ns, kcontext, plugins string + debug bool + }{ + { + name: "defaults", + args: []string{}, + home: DefaultHelmHome, + plugins: helmpath.Home(DefaultHelmHome).Plugins(), + ns: "kube-system", + }, + { + name: "with flags set", + args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, + home: "/foo", + plugins: helmpath.Home("/foo").Plugins(), + host: "here", + ns: "myns", + debug: true, + }, + { + name: "with envvars set", + args: []string{}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns"}, + home: "/bar", + plugins: helmpath.Home("/bar").Plugins(), + host: "there", + ns: "yourns", + debug: true, + }, + { + name: "with flags and envvars set", + args: []string{"--home", "/foo", "--host=here", "--debug", "--tiller-namespace=myns"}, + envars: map[string]string{"HELM_HOME": "/bar", "HELM_HOST": "there", "HELM_DEBUG": "1", "TILLER_NAMESPACE": "yourns", "HELM_PLUGIN": "glade"}, + home: "/foo", + plugins: "glade", + host: "here", + ns: "myns", + debug: true, + }, + } + + cleanup := resetEnv() + defer cleanup() + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for k, v := range tt.envars { + os.Setenv(k, v) + } + + flags := pflag.NewFlagSet("testing", pflag.ContinueOnError) + + settings := &EnvSettings{} + settings.AddFlags(flags) + flags.Parse(tt.args) + + settings.Init(flags) + + if settings.Home != helmpath.Home(tt.home) { + t.Errorf("expected home %q, got %q", tt.home, settings.Home) + } + if settings.PluginDirs() != tt.plugins { + t.Errorf("expected plugins %q, got %q", tt.plugins, settings.PluginDirs()) + } + if settings.TillerHost != tt.host { + t.Errorf("expected host %q, got %q", tt.host, settings.TillerHost) + } + if settings.Debug != tt.debug { + t.Errorf("expected debug %t, got %t", tt.debug, settings.Debug) + } + if settings.TillerNamespace != tt.ns { + t.Errorf("expected tiller-namespace %q, got %q", tt.ns, settings.TillerNamespace) + } + if settings.KubeContext != tt.kcontext { + t.Errorf("expected kube-context %q, got %q", tt.kcontext, settings.KubeContext) + } + + cleanup() + }) + } +} + +func resetEnv() func() { + origEnv := os.Environ() + + // ensure any local envvars do not hose us + for _, e := range envMap { + os.Unsetenv(e) + } + + return func() { + for _, pair := range origEnv { + kv := strings.SplitN(pair, "=", 2) + os.Setenv(kv[0], kv[1]) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/fake.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/fake.go new file mode 100644 index 000000000..0a9e77c44 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/fake.go @@ -0,0 +1,277 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm // import "k8s.io/helm/pkg/helm" + +import ( + "errors" + "fmt" + "math/rand" + "sync" + + "github.com/golang/protobuf/ptypes/timestamp" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + rls "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/proto/hapi/version" +) + +// FakeClient implements Interface +type FakeClient struct { + Rels []*release.Release + Responses map[string]release.TestRun_Status + Opts options +} + +// Option returns the fake release client +func (c *FakeClient) Option(opts ...Option) Interface { + for _, opt := range opts { + opt(&c.Opts) + } + return c +} + +var _ Interface = &FakeClient{} +var _ Interface = (*FakeClient)(nil) + +// ListReleases lists the current releases +func (c *FakeClient) ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) { + resp := &rls.ListReleasesResponse{ + Count: int64(len(c.Rels)), + Releases: c.Rels, + } + return resp, nil +} + +// InstallRelease creates a new release and returns a InstallReleaseResponse containing that release +func (c *FakeClient) InstallRelease(chStr, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + chart := &chart.Chart{} + return c.InstallReleaseFromChart(chart, ns, opts...) +} + +// InstallReleaseFromChart adds a new MockRelease to the fake client and returns a InstallReleaseResponse containing that release +func (c *FakeClient) InstallReleaseFromChart(chart *chart.Chart, ns string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) { + for _, opt := range opts { + opt(&c.Opts) + } + + releaseName := c.Opts.instReq.Name + + // Check to see if the release already exists. + rel, err := c.ReleaseStatus(releaseName, nil) + if err == nil && rel != nil { + return nil, errors.New("cannot re-use a name that is still in use") + } + + release := ReleaseMock(&MockReleaseOptions{Name: releaseName, Namespace: ns}) + c.Rels = append(c.Rels, release) + + return &rls.InstallReleaseResponse{ + Release: release, + }, nil +} + +// DeleteRelease deletes a release from the FakeClient +func (c *FakeClient) DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) { + for i, rel := range c.Rels { + if rel.Name == rlsName { + c.Rels = append(c.Rels[:i], c.Rels[i+1:]...) + return &rls.UninstallReleaseResponse{ + Release: rel, + }, nil + } + } + + return nil, fmt.Errorf("No such release: %s", rlsName) +} + +// GetVersion returns a fake version +func (c *FakeClient) GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) { + return &rls.GetVersionResponse{ + Version: &version.Version{ + SemVer: "1.2.3-fakeclient+testonly", + }, + }, nil +} + +// UpdateRelease returns an UpdateReleaseResponse containing the updated release, if it exists +func (c *FakeClient) UpdateRelease(rlsName string, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + return c.UpdateReleaseFromChart(rlsName, &chart.Chart{}, opts...) +} + +// UpdateReleaseFromChart returns an UpdateReleaseResponse containing the updated release, if it exists +func (c *FakeClient) UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) { + // Check to see if the release already exists. + rel, err := c.ReleaseContent(rlsName, nil) + if err != nil { + return nil, err + } + + return &rls.UpdateReleaseResponse{Release: rel.Release}, nil +} + +// RollbackRelease returns nil, nil +func (c *FakeClient) RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) { + return nil, nil +} + +// ReleaseStatus returns a release status response with info from the matching release name. +func (c *FakeClient) ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) { + for _, rel := range c.Rels { + if rel.Name == rlsName { + return &rls.GetReleaseStatusResponse{ + Name: rel.Name, + Info: rel.Info, + Namespace: rel.Namespace, + }, nil + } + } + return nil, fmt.Errorf("No such release: %s", rlsName) +} + +// ReleaseContent returns the configuration for the matching release name in the fake release client. +func (c *FakeClient) ReleaseContent(rlsName string, opts ...ContentOption) (resp *rls.GetReleaseContentResponse, err error) { + for _, rel := range c.Rels { + if rel.Name == rlsName { + return &rls.GetReleaseContentResponse{ + Release: rel, + }, nil + } + } + return resp, fmt.Errorf("No such release: %s", rlsName) +} + +// ReleaseHistory returns a release's revision history. +func (c *FakeClient) ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) { + return &rls.GetHistoryResponse{Releases: c.Rels}, nil +} + +// RunReleaseTest executes a pre-defined tests on a release +func (c *FakeClient) RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) { + + results := make(chan *rls.TestReleaseResponse) + errc := make(chan error, 1) + + go func() { + var wg sync.WaitGroup + for m, s := range c.Responses { + wg.Add(1) + + go func(msg string, status release.TestRun_Status) { + defer wg.Done() + results <- &rls.TestReleaseResponse{Msg: msg, Status: status} + }(m, s) + } + + wg.Wait() + close(results) + close(errc) + }() + + return results, errc +} + +// PingTiller pings the Tiller pod and ensure's that it is up and running +func (c *FakeClient) PingTiller() error { + return nil +} + +// 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 int32 + Chart *chart.Chart + StatusCode release.Status_Code + Namespace string +} + +// ReleaseMock creates a mock release object based on options set by MockReleaseOptions. This function should typically not be used outside of testing. +func ReleaseMock(opts *MockReleaseOptions) *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + + name := opts.Name + if name == "" { + name = "testrelease-" + string(rand.Intn(100)) + } + + var version int32 = 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", + }, + Templates: []*chart.Template{ + {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, + }, + } + } + + scode := release.Status_DEPLOYED + if opts.StatusCode > 0 { + scode = opts.StatusCode + } + + return &release.Release{ + Name: name, + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: scode}, + Description: "Release mock", + }, + Chart: ch, + Config: &chart.Config{Raw: `name: "value"`}, + Version: version, + Namespace: namespace, + Hooks: []*release.Hook{ + { + Name: "pre-install-hook", + Kind: "Job", + Path: "pre-install-hook.yaml", + Manifest: MockHookTemplate, + LastRun: &date, + Events: []release.Hook_Event{release.Hook_PRE_INSTALL}, + }, + }, + Manifest: MockManifest, + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/fake_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/fake_test.go new file mode 100644 index 000000000..9c0a53759 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/fake_test.go @@ -0,0 +1,283 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm + +import ( + "reflect" + "testing" + + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + rls "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestFakeClient_ReleaseStatus(t *testing.T) { + releasePresent := ReleaseMock(&MockReleaseOptions{Name: "release-present"}) + releaseNotPresent := ReleaseMock(&MockReleaseOptions{Name: "release-not-present"}) + + type fields struct { + Rels []*release.Release + } + type args struct { + rlsName string + opts []StatusOption + } + tests := []struct { + name string + fields fields + args args + want *rls.GetReleaseStatusResponse + wantErr bool + }{ + { + name: "Get a single release that exists", + fields: fields{ + Rels: []*release.Release{ + releasePresent, + }, + }, + args: args{ + rlsName: releasePresent.Name, + opts: nil, + }, + want: &rls.GetReleaseStatusResponse{ + Name: releasePresent.Name, + Info: releasePresent.Info, + Namespace: releasePresent.Namespace, + }, + + wantErr: false, + }, + { + name: "Get a release that does not exist", + fields: fields{ + Rels: []*release.Release{ + releasePresent, + }, + }, + args: args{ + rlsName: releaseNotPresent.Name, + opts: nil, + }, + want: nil, + wantErr: true, + }, + { + name: "Get a single release that exists from list", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin", Namespace: "default"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir", Namespace: "default"}), + releasePresent, + }, + }, + args: args{ + rlsName: releasePresent.Name, + opts: nil, + }, + want: &rls.GetReleaseStatusResponse{ + Name: releasePresent.Name, + Info: releasePresent.Info, + Namespace: releasePresent.Namespace, + }, + + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.ReleaseStatus(tt.args.rlsName, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.ReleaseStatus() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.ReleaseStatus() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestFakeClient_InstallReleaseFromChart(t *testing.T) { + installChart := &chart.Chart{} + type fields struct { + Rels []*release.Release + } + type args struct { + ns string + opts []InstallOption + } + tests := []struct { + name string + fields fields + args args + want *rls.InstallReleaseResponse + relsAfter []*release.Release + wantErr bool + }{ + { + name: "Add release to an empty list.", + fields: fields{ + Rels: []*release.Release{}, + }, + args: args{ + ns: "default", + opts: []InstallOption{ReleaseName("new-release")}, + }, + want: &rls.InstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + wantErr: false, + }, + { + name: "Try to add a release where the name already exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + }, + args: args{ + ns: "default", + opts: []InstallOption{ReleaseName("new-release")}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "new-release"}), + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.InstallReleaseFromChart(installChart, tt.args.ns, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.InstallReleaseFromChart() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.InstallReleaseFromChart() = %v, want %v", got, tt.want) + } + if !reflect.DeepEqual(c.Rels, tt.relsAfter) { + t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter) + } + }) + } +} + +func TestFakeClient_DeleteRelease(t *testing.T) { + type fields struct { + Rels []*release.Release + } + type args struct { + rlsName string + opts []DeleteOption + } + tests := []struct { + name string + fields fields + args args + want *rls.UninstallReleaseResponse + relsAfter []*release.Release + wantErr bool + }{ + { + name: "Delete a release that exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "trepid-tapir", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + }, + want: &rls.UninstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + wantErr: false, + }, + { + name: "Delete a release that does not exist.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "release-that-does-not-exists", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "angry-dolphin"}), + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + want: nil, + wantErr: true, + }, + { + name: "Delete when only 1 item exists.", + fields: fields{ + Rels: []*release.Release{ + ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + }, + args: args{ + rlsName: "trepid-tapir", + opts: []DeleteOption{}, + }, + relsAfter: []*release.Release{}, + want: &rls.UninstallReleaseResponse{ + Release: ReleaseMock(&MockReleaseOptions{Name: "trepid-tapir"}), + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &FakeClient{ + Rels: tt.fields.Rels, + } + got, err := c.DeleteRelease(tt.args.rlsName, tt.args.opts...) + if (err != nil) != tt.wantErr { + t.Errorf("FakeClient.DeleteRelease() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("FakeClient.DeleteRelease() = %v, want %v", got, tt.want) + } + + if !reflect.DeepEqual(c.Rels, tt.relsAfter) { + t.Errorf("FakeClient.InstallReleaseFromChart() rels = %v, expected %v", got, tt.relsAfter) + } + }) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/helm_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/helm_test.go new file mode 100644 index 000000000..2b0436581 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/helm_test.go @@ -0,0 +1,363 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm // import "k8s.io/helm/pkg/helm" + +import ( + "errors" + "path/filepath" + "reflect" + "testing" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + + "k8s.io/helm/pkg/chartutil" + cpb "k8s.io/helm/pkg/proto/hapi/chart" + rls "k8s.io/helm/pkg/proto/hapi/release" + tpb "k8s.io/helm/pkg/proto/hapi/services" +) + +// Path to example charts relative to pkg/helm. +const chartsDir = "../../docs/examples/" + +// Sentinel error to indicate to the Helm client to not send the request to Tiller. +var errSkip = errors.New("test: skip") + +// Verify each ReleaseListOption is applied to a ListReleasesRequest correctly. +func TestListReleases_VerifyOptions(t *testing.T) { + // Options testdata + var limit = 2 + var offset = "offset" + var filter = "filter" + var sortBy = int32(2) + var sortOrd = int32(1) + var codes = []rls.Status_Code{ + rls.Status_FAILED, + rls.Status_DELETED, + rls.Status_DEPLOYED, + rls.Status_SUPERSEDED, + } + var namespace = "namespace" + + // Expected ListReleasesRequest message + exp := &tpb.ListReleasesRequest{ + Limit: int64(limit), + Offset: offset, + Filter: filter, + SortBy: tpb.ListSort_SortBy(sortBy), + SortOrder: tpb.ListSort_SortOrder(sortOrd), + StatusCodes: codes, + Namespace: namespace, + } + + // Options used in ListReleases + ops := []ReleaseListOption{ + ReleaseListSort(sortBy), + ReleaseListOrder(sortOrd), + ReleaseListLimit(limit), + ReleaseListOffset(offset), + ReleaseListFilter(filter), + ReleaseListStatuses(codes), + ReleaseListNamespace(namespace), + } + + // BeforeCall option to intercept Helm client ListReleasesRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.ListReleasesRequest: + t.Logf("ListReleasesRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type ListReleasesRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + + if _, err := client.ListReleases(ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.listReq.Filter) +} + +// Verify each InstallOption is applied to an InstallReleaseRequest correctly. +func TestInstallRelease_VerifyOptions(t *testing.T) { + // Options testdata + var disableHooks = true + var releaseName = "test" + var namespace = "default" + var reuseName = true + var dryRun = true + var chartName = "alpine" + var chartPath = filepath.Join(chartsDir, chartName) + var overrides = []byte("key1=value1,key2=value2") + + // Expected InstallReleaseRequest message + exp := &tpb.InstallReleaseRequest{ + Chart: loadChart(t, chartName), + Values: &cpb.Config{Raw: string(overrides)}, + DryRun: dryRun, + Name: releaseName, + DisableHooks: disableHooks, + Namespace: namespace, + ReuseName: reuseName, + } + + // Options used in InstallRelease + ops := []InstallOption{ + ValueOverrides(overrides), + InstallDryRun(dryRun), + ReleaseName(releaseName), + InstallReuseName(reuseName), + InstallDisableHooks(disableHooks), + } + + // BeforeCall option to intercept Helm client InstallReleaseRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.InstallReleaseRequest: + t.Logf("InstallReleaseRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type InstallReleaseRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.InstallRelease(chartPath, namespace, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.instReq.Name) +} + +// Verify each DeleteOptions is applied to an UninstallReleaseRequest correctly. +func TestDeleteRelease_VerifyOptions(t *testing.T) { + // Options testdata + var releaseName = "test" + var disableHooks = true + var purgeFlag = true + + // Expected DeleteReleaseRequest message + exp := &tpb.UninstallReleaseRequest{ + Name: releaseName, + Purge: purgeFlag, + DisableHooks: disableHooks, + } + + // Options used in DeleteRelease + ops := []DeleteOption{ + DeletePurge(purgeFlag), + DeleteDisableHooks(disableHooks), + } + + // BeforeCall option to intercept Helm client DeleteReleaseRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.UninstallReleaseRequest: + t.Logf("UninstallReleaseRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type UninstallReleaseRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.DeleteRelease(releaseName, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.uninstallReq.Name) +} + +// Verify each UpdateOption is applied to an UpdateReleaseRequest correctly. +func TestUpdateRelease_VerifyOptions(t *testing.T) { + // Options testdata + var chartName = "alpine" + var chartPath = filepath.Join(chartsDir, chartName) + var releaseName = "test" + var disableHooks = true + var overrides = []byte("key1=value1,key2=value2") + var dryRun = false + + // Expected UpdateReleaseRequest message + exp := &tpb.UpdateReleaseRequest{ + Name: releaseName, + Chart: loadChart(t, chartName), + Values: &cpb.Config{Raw: string(overrides)}, + DryRun: dryRun, + DisableHooks: disableHooks, + } + + // Options used in UpdateRelease + ops := []UpdateOption{ + UpgradeDryRun(dryRun), + UpdateValueOverrides(overrides), + UpgradeDisableHooks(disableHooks), + } + + // BeforeCall option to intercept Helm client UpdateReleaseRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.UpdateReleaseRequest: + t.Logf("UpdateReleaseRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type UpdateReleaseRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.UpdateRelease(releaseName, chartPath, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.updateReq.Name) +} + +// Verify each RollbackOption is applied to a RollbackReleaseRequest correctly. +func TestRollbackRelease_VerifyOptions(t *testing.T) { + // Options testdata + var disableHooks = true + var releaseName = "test" + var revision = int32(2) + var dryRun = true + + // Expected RollbackReleaseRequest message + exp := &tpb.RollbackReleaseRequest{ + Name: releaseName, + DryRun: dryRun, + Version: revision, + DisableHooks: disableHooks, + } + + // Options used in RollbackRelease + ops := []RollbackOption{ + RollbackDryRun(dryRun), + RollbackVersion(revision), + RollbackDisableHooks(disableHooks), + } + + // BeforeCall option to intercept Helm client RollbackReleaseRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.RollbackReleaseRequest: + t.Logf("RollbackReleaseRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type RollbackReleaseRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.RollbackRelease(releaseName, ops...); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.rollbackReq.Name) +} + +// Verify each StatusOption is applied to a GetReleaseStatusRequest correctly. +func TestReleaseStatus_VerifyOptions(t *testing.T) { + // Options testdata + var releaseName = "test" + var revision = int32(2) + + // Expected GetReleaseStatusRequest message + exp := &tpb.GetReleaseStatusRequest{ + Name: releaseName, + Version: revision, + } + + // BeforeCall option to intercept Helm client GetReleaseStatusRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.GetReleaseStatusRequest: + t.Logf("GetReleaseStatusRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type GetReleaseStatusRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.ReleaseStatus(releaseName, StatusReleaseVersion(revision)); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.statusReq.Name) +} + +// Verify each ContentOption is applied to a GetReleaseContentRequest correctly. +func TestReleaseContent_VerifyOptions(t *testing.T) { + // Options testdata + var releaseName = "test" + var revision = int32(2) + + // Expected GetReleaseContentRequest message + exp := &tpb.GetReleaseContentRequest{ + Name: releaseName, + Version: revision, + } + + // BeforeCall option to intercept Helm client GetReleaseContentRequest + b4c := BeforeCall(func(_ context.Context, msg proto.Message) error { + switch act := msg.(type) { + case *tpb.GetReleaseContentRequest: + t.Logf("GetReleaseContentRequest: %#+v\n", act) + assert(t, exp, act) + default: + t.Fatalf("expected message of type GetReleaseContentRequest, got %T\n", act) + } + return errSkip + }) + + client := NewClient(b4c) + if _, err := client.ReleaseContent(releaseName, ContentReleaseVersion(revision)); err != errSkip { + t.Fatalf("did not expect error but got (%v)\n``", err) + } + + // ensure options for call are not saved to client + assert(t, "", client.opts.contentReq.Name) +} + +func assert(t *testing.T, expect, actual interface{}) { + if !reflect.DeepEqual(expect, actual) { + t.Fatalf("expected %#+v, actual %#+v\n", expect, actual) + } +} + +func loadChart(t *testing.T, name string) *cpb.Chart { + c, err := chartutil.Load(filepath.Join(chartsDir, name)) + if err != nil { + t.Fatalf("failed to load test chart (%q): %s\n", name, err) + } + return c +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome.go new file mode 100644 index 000000000..b5ec4909e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 helmpath + +import ( + "fmt" + "os" + "path/filepath" +) + +// Home describes the location of a CLI configuration. +// +// This helper builds paths relative to a Helm Home directory. +type Home string + +// String returns Home as a string. +// +// Implements fmt.Stringer. +func (h Home) String() string { + return os.ExpandEnv(string(h)) +} + +// Path returns Home with elements appended. +func (h Home) Path(elem ...string) string { + p := []string{h.String()} + p = append(p, elem...) + return filepath.Join(p...) +} + +// Repository returns the path to the local repository. +func (h Home) Repository() string { + return h.Path("repository") +} + +// RepositoryFile returns the path to the repositories.yaml file. +func (h Home) RepositoryFile() string { + return h.Path("repository", "repositories.yaml") +} + +// Cache returns the path to the local cache. +func (h Home) Cache() string { + return h.Path("repository", "cache") +} + +// CacheIndex returns the path to an index for the given named repository. +func (h Home) CacheIndex(name string) string { + target := fmt.Sprintf("%s-index.yaml", name) + return h.Path("repository", "cache", target) +} + +// Starters returns the path to the Helm starter packs. +func (h Home) Starters() string { + return h.Path("starters") +} + +// LocalRepository returns the location to the local repo. +// +// The local repo is the one used by 'helm serve' +// +// If additional path elements are passed, they are appended to the returned path. +func (h Home) LocalRepository(elem ...string) string { + p := []string{"repository", "local"} + p = append(p, elem...) + return h.Path(p...) +} + +// Plugins returns the path to the plugins directory. +func (h Home) Plugins() string { + return h.Path("plugins") +} + +// Archive returns the path to download chart archives. +func (h Home) Archive() string { + return h.Path("cache", "archive") +} + +// TLSCaCert returns the path to fetch the CA certificate. +func (h Home) TLSCaCert() string { + return h.Path("ca.pem") +} + +// TLSCert returns the path to fetch the client certificate. +func (h Home) TLSCert() string { + return h.Path("cert.pem") +} + +// TLSKey returns the path to fetch the client public key. +func (h Home) TLSKey() string { + return h.Path("key.pem") +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_unix_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_unix_test.go new file mode 100644 index 000000000..494d0f6b4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_unix_test.go @@ -0,0 +1,50 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// 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. + +// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris +// +build !windows + +package helmpath + +import ( + "runtime" + "testing" +) + +func TestHelmHome(t *testing.T) { + hh := Home("/r") + isEq := func(t *testing.T, a, b string) { + if a != b { + t.Error(runtime.GOOS) + t.Errorf("Expected %q, got %q", a, b) + } + } + + isEq(t, hh.String(), "/r") + isEq(t, hh.Repository(), "/r/repository") + isEq(t, hh.RepositoryFile(), "/r/repository/repositories.yaml") + isEq(t, hh.LocalRepository(), "/r/repository/local") + isEq(t, hh.Cache(), "/r/repository/cache") + isEq(t, hh.CacheIndex("t"), "/r/repository/cache/t-index.yaml") + isEq(t, hh.Starters(), "/r/starters") + isEq(t, hh.Archive(), "/r/cache/archive") + isEq(t, hh.TLSCaCert(), "/r/ca.pem") + isEq(t, hh.TLSCert(), "/r/cert.pem") + isEq(t, hh.TLSKey(), "/r/key.pem") +} + +func TestHelmHome_expand(t *testing.T) { + if Home("$HOME").String() == "$HOME" { + t.Error("expected variable expansion") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_windows_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_windows_test.go new file mode 100644 index 000000000..e416bfd58 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/helmpath/helmhome_windows_test.go @@ -0,0 +1,41 @@ +// Copyright 2016 The Kubernetes Authors All rights reserved. +// 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. + +// +build windows + +package helmpath + +import ( + "testing" +) + +func TestHelmHome(t *testing.T) { + hh := Home("r:\\") + isEq := func(t *testing.T, a, b string) { + if a != b { + t.Errorf("Expected %q, got %q", b, a) + } + } + + isEq(t, hh.String(), "r:\\") + isEq(t, hh.Repository(), "r:\\repository") + isEq(t, hh.RepositoryFile(), "r:\\repository\\repositories.yaml") + isEq(t, hh.LocalRepository(), "r:\\repository\\local") + isEq(t, hh.Cache(), "r:\\repository\\cache") + isEq(t, hh.CacheIndex("t"), "r:\\repository\\cache\\t-index.yaml") + isEq(t, hh.Starters(), "r:\\starters") + isEq(t, hh.Archive(), "r:\\cache\\archive") + isEq(t, hh.TLSCaCert(), "r:\\ca.pem") + isEq(t, hh.TLSCert(), "r:\\cert.pem") + isEq(t, hh.TLSKey(), "r:\\key.pem") +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/interface.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/interface.go new file mode 100644 index 000000000..10c04c710 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/interface.go @@ -0,0 +1,39 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm + +import ( + "k8s.io/helm/pkg/proto/hapi/chart" + rls "k8s.io/helm/pkg/proto/hapi/services" +) + +// Interface for helm client for mocking in tests +type Interface interface { + ListReleases(opts ...ReleaseListOption) (*rls.ListReleasesResponse, error) + InstallRelease(chStr, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) + InstallReleaseFromChart(chart *chart.Chart, namespace string, opts ...InstallOption) (*rls.InstallReleaseResponse, error) + DeleteRelease(rlsName string, opts ...DeleteOption) (*rls.UninstallReleaseResponse, error) + ReleaseStatus(rlsName string, opts ...StatusOption) (*rls.GetReleaseStatusResponse, error) + UpdateRelease(rlsName, chStr string, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) + UpdateReleaseFromChart(rlsName string, chart *chart.Chart, opts ...UpdateOption) (*rls.UpdateReleaseResponse, error) + RollbackRelease(rlsName string, opts ...RollbackOption) (*rls.RollbackReleaseResponse, error) + ReleaseContent(rlsName string, opts ...ContentOption) (*rls.GetReleaseContentResponse, error) + ReleaseHistory(rlsName string, opts ...HistoryOption) (*rls.GetHistoryResponse, error) + GetVersion(opts ...VersionOption) (*rls.GetVersionResponse, error) + RunReleaseTest(rlsName string, opts ...ReleaseTestOption) (<-chan *rls.TestReleaseResponse, <-chan error) + PingTiller() error +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/option.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/option.go new file mode 100644 index 000000000..3381e3f80 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/option.go @@ -0,0 +1,444 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 helm + +import ( + "crypto/tls" + "time" + + "github.com/golang/protobuf/proto" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" + + cpb "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + rls "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/version" +) + +// Option allows specifying various settings configurable by +// the helm client user for overriding the defaults used when +// issuing rpc's to the Tiller release server. +type Option func(*options) + +// options specify optional settings used by the helm client. +type options struct { + // value of helm home override + host string + // if set dry-run helm client calls + dryRun bool + // if set enable TLS on helm client calls + useTLS bool + // if set, re-use an existing name + reuseName bool + // if set, performs pod restart during upgrade/rollback + recreate bool + // if set, force resource update through delete/recreate if needed + force bool + // if set, skip running hooks + disableHooks bool + // name of release + releaseName string + // tls.Config to use for rpc if tls enabled + tlsConfig *tls.Config + // release list options are applied directly to the list releases request + listReq rls.ListReleasesRequest + // release install options are applied directly to the install release request + instReq rls.InstallReleaseRequest + // release update options are applied directly to the update release request + updateReq rls.UpdateReleaseRequest + // release uninstall options are applied directly to the uninstall release request + uninstallReq rls.UninstallReleaseRequest + // release get status options are applied directly to the get release status request + statusReq rls.GetReleaseStatusRequest + // release get content options are applied directly to the get release content request + contentReq rls.GetReleaseContentRequest + // release rollback options are applied directly to the rollback release request + rollbackReq rls.RollbackReleaseRequest + // before intercepts client calls before sending + before func(context.Context, proto.Message) error + // release history options are applied directly to the get release history request + histReq rls.GetHistoryRequest + // resetValues instructs Tiller to reset values to their defaults. + resetValues bool + // reuseValues instructs Tiller to reuse the values from the last release. + reuseValues bool + // release test options are applied directly to the test release history request + testReq rls.TestReleaseRequest + // connectTimeout specifies the time duration Helm will wait to establish a connection to tiller + connectTimeout time.Duration +} + +// Host specifies the host address of the Tiller release server, (default = ":44134"). +func Host(host string) Option { + return func(opts *options) { + opts.host = host + } +} + +// WithTLS specifies the tls configuration if the helm client is enabled to use TLS. +func WithTLS(cfg *tls.Config) Option { + return func(opts *options) { + opts.useTLS = true + opts.tlsConfig = cfg + } +} + +// BeforeCall returns an option that allows intercepting a helm client rpc +// before being sent OTA to tiller. The intercepting function should return +// an error to indicate that the call should not proceed or nil otherwise. +func BeforeCall(fn func(context.Context, proto.Message) error) Option { + return func(opts *options) { + opts.before = fn + } +} + +// ReleaseListOption allows specifying various settings +// configurable by the helm client user for overriding +// the defaults used when running the `helm list` command. +type ReleaseListOption func(*options) + +// ReleaseListOffset specifies the offset into a list of releases. +func ReleaseListOffset(offset string) ReleaseListOption { + return func(opts *options) { + opts.listReq.Offset = offset + } +} + +// ReleaseListFilter specifies a filter to apply a list of releases. +func ReleaseListFilter(filter string) ReleaseListOption { + return func(opts *options) { + opts.listReq.Filter = filter + } +} + +// ReleaseListLimit set an upper bound on the number of releases returned. +func ReleaseListLimit(limit int) ReleaseListOption { + return func(opts *options) { + opts.listReq.Limit = int64(limit) + } +} + +// ReleaseListOrder specifies how to order a list of releases. +func ReleaseListOrder(order int32) ReleaseListOption { + return func(opts *options) { + opts.listReq.SortOrder = rls.ListSort_SortOrder(order) + } +} + +// ReleaseListSort specifies how to sort a release list. +func ReleaseListSort(sort int32) ReleaseListOption { + return func(opts *options) { + opts.listReq.SortBy = rls.ListSort_SortBy(sort) + } +} + +// ReleaseListStatuses specifies which status codes should be returned. +func ReleaseListStatuses(statuses []release.Status_Code) ReleaseListOption { + return func(opts *options) { + if len(statuses) == 0 { + statuses = []release.Status_Code{release.Status_DEPLOYED} + } + opts.listReq.StatusCodes = statuses + } +} + +// ReleaseListNamespace specifies the namespace to list releases from +func ReleaseListNamespace(namespace string) ReleaseListOption { + return func(opts *options) { + opts.listReq.Namespace = namespace + } +} + +// InstallOption allows specifying various settings +// configurable by the helm client user for overriding +// the defaults used when running the `helm install` command. +type InstallOption func(*options) + +// ValueOverrides specifies a list of values to include when installing. +func ValueOverrides(raw []byte) InstallOption { + return func(opts *options) { + opts.instReq.Values = &cpb.Config{Raw: string(raw)} + } +} + +// ReleaseName specifies the name of the release when installing. +func ReleaseName(name string) InstallOption { + return func(opts *options) { + opts.instReq.Name = name + } +} + +// ConnectTimeout specifies the duration (in seconds) Helm will wait to establish a connection to tiller +func ConnectTimeout(timeout int64) Option { + return func(opts *options) { + opts.connectTimeout = time.Duration(timeout) * time.Second + } +} + +// InstallTimeout specifies the number of seconds before kubernetes calls timeout +func InstallTimeout(timeout int64) InstallOption { + return func(opts *options) { + opts.instReq.Timeout = timeout + } +} + +// UpgradeTimeout specifies the number of seconds before kubernetes calls timeout +func UpgradeTimeout(timeout int64) UpdateOption { + return func(opts *options) { + opts.updateReq.Timeout = timeout + } +} + +// DeleteTimeout specifies the number of seconds before kubernetes calls timeout +func DeleteTimeout(timeout int64) DeleteOption { + return func(opts *options) { + opts.uninstallReq.Timeout = timeout + } +} + +// ReleaseTestTimeout specifies the number of seconds before kubernetes calls timeout +func ReleaseTestTimeout(timeout int64) ReleaseTestOption { + return func(opts *options) { + opts.testReq.Timeout = timeout + } +} + +// ReleaseTestCleanup is a boolean value representing whether to cleanup test pods +func ReleaseTestCleanup(cleanup bool) ReleaseTestOption { + return func(opts *options) { + opts.testReq.Cleanup = cleanup + } +} + +// RollbackTimeout specifies the number of seconds before kubernetes calls timeout +func RollbackTimeout(timeout int64) RollbackOption { + return func(opts *options) { + opts.rollbackReq.Timeout = timeout + } +} + +// InstallWait specifies whether or not to wait for all resources to be ready +func InstallWait(wait bool) InstallOption { + return func(opts *options) { + opts.instReq.Wait = wait + } +} + +// UpgradeWait specifies whether or not to wait for all resources to be ready +func UpgradeWait(wait bool) UpdateOption { + return func(opts *options) { + opts.updateReq.Wait = wait + } +} + +// RollbackWait specifies whether or not to wait for all resources to be ready +func RollbackWait(wait bool) RollbackOption { + return func(opts *options) { + opts.rollbackReq.Wait = wait + } +} + +// UpdateValueOverrides specifies a list of values to include when upgrading +func UpdateValueOverrides(raw []byte) UpdateOption { + return func(opts *options) { + opts.updateReq.Values = &cpb.Config{Raw: string(raw)} + } +} + +// DeleteDisableHooks will disable hooks for a deletion operation. +func DeleteDisableHooks(disable bool) DeleteOption { + return func(opts *options) { + opts.disableHooks = disable + } +} + +// DeleteDryRun will (if true) execute a deletion as a dry run. +func DeleteDryRun(dry bool) DeleteOption { + return func(opts *options) { + opts.dryRun = dry + } +} + +// DeletePurge removes the release from the store and make its name free for later use. +func DeletePurge(purge bool) DeleteOption { + return func(opts *options) { + opts.uninstallReq.Purge = purge + } +} + +// InstallDryRun will (if true) execute an installation as a dry run. +func InstallDryRun(dry bool) InstallOption { + return func(opts *options) { + opts.dryRun = dry + } +} + +// InstallDisableHooks disables hooks during installation. +func InstallDisableHooks(disable bool) InstallOption { + return func(opts *options) { + opts.disableHooks = disable + } +} + +// InstallReuseName will (if true) instruct Tiller to re-use an existing name. +func InstallReuseName(reuse bool) InstallOption { + return func(opts *options) { + opts.reuseName = reuse + } +} + +// RollbackDisableHooks will disable hooks for a rollback operation +func RollbackDisableHooks(disable bool) RollbackOption { + return func(opts *options) { + opts.disableHooks = disable + } +} + +// RollbackDryRun will (if true) execute a rollback as a dry run. +func RollbackDryRun(dry bool) RollbackOption { + return func(opts *options) { + opts.dryRun = dry + } +} + +// RollbackRecreate will (if true) recreate pods after rollback. +func RollbackRecreate(recreate bool) RollbackOption { + return func(opts *options) { + opts.recreate = recreate + } +} + +// RollbackForce will (if true) force resource update through delete/recreate if needed +func RollbackForce(force bool) RollbackOption { + return func(opts *options) { + opts.force = force + } +} + +// RollbackVersion sets the version of the release to deploy. +func RollbackVersion(ver int32) RollbackOption { + return func(opts *options) { + opts.rollbackReq.Version = ver + } +} + +// UpgradeDisableHooks will disable hooks for an upgrade operation. +func UpgradeDisableHooks(disable bool) UpdateOption { + return func(opts *options) { + opts.disableHooks = disable + } +} + +// UpgradeDryRun will (if true) execute an upgrade as a dry run. +func UpgradeDryRun(dry bool) UpdateOption { + return func(opts *options) { + opts.dryRun = dry + } +} + +// ResetValues will (if true) trigger resetting the values to their original state. +func ResetValues(reset bool) UpdateOption { + return func(opts *options) { + opts.resetValues = reset + } +} + +// ReuseValues will cause Tiller to reuse the values from the last release. +// This is ignored if ResetValues is true. +func ReuseValues(reuse bool) UpdateOption { + return func(opts *options) { + opts.reuseValues = reuse + } +} + +// UpgradeRecreate will (if true) recreate pods after upgrade. +func UpgradeRecreate(recreate bool) UpdateOption { + return func(opts *options) { + opts.recreate = recreate + } +} + +// UpgradeForce will (if true) force resource update through delete/recreate if needed +func UpgradeForce(force bool) UpdateOption { + return func(opts *options) { + opts.force = force + } +} + +// ContentOption allows setting optional attributes when +// performing a GetReleaseContent tiller rpc. +type ContentOption func(*options) + +// ContentReleaseVersion will instruct Tiller to retrieve the content +// of a particular version of a release. +func ContentReleaseVersion(version int32) ContentOption { + return func(opts *options) { + opts.contentReq.Version = version + } +} + +// StatusOption allows setting optional attributes when +// performing a GetReleaseStatus tiller rpc. +type StatusOption func(*options) + +// StatusReleaseVersion will instruct Tiller to retrieve the status +// of a particular version of a release. +func StatusReleaseVersion(version int32) StatusOption { + return func(opts *options) { + opts.statusReq.Version = version + } +} + +// DeleteOption allows setting optional attributes when +// performing a UninstallRelease tiller rpc. +type DeleteOption func(*options) + +// VersionOption -- TODO +type VersionOption func(*options) + +// UpdateOption allows specifying various settings +// configurable by the helm client user for overriding +// the defaults used when running the `helm upgrade` command. +type UpdateOption func(*options) + +// RollbackOption allows specififying various settings configurable +// by the helm client user for overriding the defaults used when +// running the `helm rollback` command. +type RollbackOption func(*options) + +// HistoryOption allows configuring optional request data for +// issuing a GetHistory rpc. +type HistoryOption func(*options) + +// WithMaxHistory sets the max number of releases to return +// in a release history query. +func WithMaxHistory(max int32) HistoryOption { + return func(opts *options) { + opts.histReq.Max = max + } +} + +// NewContext creates a versioned context. +func NewContext() context.Context { + md := metadata.Pairs("x-helm-api-client", version.GetVersion()) + return metadata.NewOutgoingContext(context.TODO(), md) +} + +// ReleaseTestOption allows configuring optional request data for +// issuing a TestRelease rpc. +type ReleaseTestOption func(*options) diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/pod.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/pod.go new file mode 100644 index 000000000..7c2355204 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/pod.go @@ -0,0 +1,60 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 portforwarder + +import ( + "k8s.io/api/core/v1" +) + +// These functions are adapted from the "kubernetes" repository's file +// +// kubernetes/pkg/api/v1/pod/util.go +// +// where they rely upon the API types specific to that repository. Here we recast them to operate +// upon the type from the "client-go" repository instead. + +// isPodReady returns true if a pod is ready; false otherwise. +func isPodReady(pod *v1.Pod) bool { + return isPodReadyConditionTrue(pod.Status) +} + +// isPodReady retruns true if a pod is ready; false otherwise. +func isPodReadyConditionTrue(status v1.PodStatus) bool { + condition := getPodReadyCondition(status) + return condition != nil && condition.Status == v1.ConditionTrue +} + +// getPodReadyCondition extracts the pod ready condition from the given status and returns that. +// Returns nil if the condition is not present. +func getPodReadyCondition(status v1.PodStatus) *v1.PodCondition { + _, condition := getPodCondition(&status, v1.PodReady) + return condition +} + +// getPodCondition extracts the provided condition from the given status and returns that. +// Returns nil and -1 if the condition is not present, and the index of the located condition. +func getPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) { + if status == nil { + return -1, nil + } + for i := range status.Conditions { + if status.Conditions[i].Type == conditionType { + return i, &status.Conditions[i] + } + } + return -1, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder.go new file mode 100644 index 000000000..878610d5f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 portforwarder + +import ( + "fmt" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/kubernetes" + corev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + + "k8s.io/helm/pkg/kube" +) + +var ( + tillerPodLabels = labels.Set{"app": "helm", "name": "tiller"} +) + +// New creates a new and initialized tunnel. +func New(namespace string, client kubernetes.Interface, config *rest.Config) (*kube.Tunnel, error) { + podName, err := GetTillerPodName(client.CoreV1(), namespace) + if err != nil { + return nil, err + } + const tillerPort = 44134 + t := kube.NewTunnel(client.CoreV1().RESTClient(), config, namespace, podName, tillerPort) + return t, t.ForwardPort() +} + +// GetTillerPodName fetches the name of tiller pod running in the given namespace. +func GetTillerPodName(client corev1.PodsGetter, namespace string) (string, error) { + selector := tillerPodLabels.AsSelector() + pod, err := getFirstRunningPod(client, namespace, selector) + if err != nil { + return "", err + } + return pod.ObjectMeta.GetName(), nil +} + +func getFirstRunningPod(client corev1.PodsGetter, namespace string, selector labels.Selector) (*v1.Pod, error) { + options := metav1.ListOptions{LabelSelector: selector.String()} + pods, err := client.Pods(namespace).List(options) + if err != nil { + return nil, err + } + if len(pods.Items) < 1 { + return nil, fmt.Errorf("could not find tiller") + } + for _, p := range pods.Items { + if isPodReady(&p) { + return &p, nil + } + } + return nil, fmt.Errorf("could not find a ready tiller pod") +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder_test.go b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder_test.go new file mode 100644 index 000000000..e4c148991 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/helm/portforwarder/portforwarder_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 portforwarder + +import ( + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func mockTillerPod() v1.Pod { + return v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "orca", + Namespace: v1.NamespaceDefault, + Labels: tillerPodLabels, + }, + Status: v1.PodStatus{ + Phase: v1.PodRunning, + Conditions: []v1.PodCondition{ + { + Status: v1.ConditionTrue, + Type: v1.PodReady, + }, + }, + }, + } +} + +func mockTillerPodPending() v1.Pod { + p := mockTillerPod() + p.Name = "blue" + p.Status.Conditions[0].Status = v1.ConditionFalse + return p +} + +func TestGetFirstPod(t *testing.T) { + tests := []struct { + name string + pods []v1.Pod + expected string + err bool + }{ + { + name: "with a ready pod", + pods: []v1.Pod{mockTillerPod()}, + expected: "orca", + }, + { + name: "without a ready pod", + pods: []v1.Pod{mockTillerPodPending()}, + err: true, + }, + { + name: "without a pod", + pods: []v1.Pod{}, + err: true, + }, + } + + for _, tt := range tests { + client := fake.NewSimpleClientset(&v1.PodList{Items: tt.pods}) + name, err := GetTillerPodName(client.Core(), v1.NamespaceDefault) + if (err != nil) != tt.err { + t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err) + } + if name != tt.expected { + t.Errorf("%q. expected %q, got %q", tt.name, tt.expected, name) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/hooks/hooks.go b/src/vendor/github.com/kubernetes/helm/pkg/hooks/hooks.go new file mode 100644 index 000000000..80f838368 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/hooks/hooks.go @@ -0,0 +1,67 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 hooks + +import ( + "k8s.io/helm/pkg/proto/hapi/release" +) + +// HookAnno is the label name for a hook +const HookAnno = "helm.sh/hook" + +// HookWeightAnno is the label name for a hook weight +const HookWeightAnno = "helm.sh/hook-weight" + +// HookDeleteAnno is the label name for the delete policy for a hook +const HookDeleteAnno = "helm.sh/hook-delete-policy" + +// Types of hooks +const ( + PreInstall = "pre-install" + PostInstall = "post-install" + PreDelete = "pre-delete" + PostDelete = "post-delete" + PreUpgrade = "pre-upgrade" + PostUpgrade = "post-upgrade" + PreRollback = "pre-rollback" + PostRollback = "post-rollback" + ReleaseTestSuccess = "test-success" + ReleaseTestFailure = "test-failure" +) + +// Type of policy for deleting the hook +const ( + HookSucceeded = "hook-succeeded" + HookFailed = "hook-failed" + BeforeHookCreation = "before-hook-creation" +) + +// FilterTestHooks filters the list of hooks are returns only testing hooks. +func FilterTestHooks(hooks []*release.Hook) []*release.Hook { + testHooks := []*release.Hook{} + + for _, h := range hooks { + for _, e := range h.Events { + if e == release.Hook_RELEASE_TEST_SUCCESS || e == release.Hook_RELEASE_TEST_FAILURE { + testHooks = append(testHooks, h) + continue + } + } + } + + return testHooks +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/ignore/doc.go new file mode 100644 index 000000000..7281c33a9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/ignore/doc.go @@ -0,0 +1,67 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ignore provides tools for writing ignore files (a la .gitignore). + +This provides both an ignore parser and a file-aware processor. + +The format of ignore files closely follows, but does not exactly match, the +format for .gitignore files (https://git-scm.com/docs/gitignore). + +The formatting rules are as follows: + + - Parsing is line-by-line + - Empty lines are ignored + - Lines the begin with # (comments) will be ignored + - Leading and trailing spaces are always ignored + - Inline comments are NOT supported ('foo* # Any foo' does not contain a comment) + - There is no support for multi-line patterns + - Shell glob patterns are supported. See Go's "path/filepath".Match + - If a pattern begins with a leading !, the match will be negated. + - If a pattern begins with a leading /, only paths relatively rooted will match. + - If the pattern ends with a trailing /, only directories will match + - If a pattern contains no slashes, file basenames are tested (not paths) + - The pattern sequence "**", while legal in a glob, will cause an error here + (to indicate incompatibility with .gitignore). + +Example: + + # Match any file named foo.txt + foo.txt + + # Match any text file + *.txt + + # Match only directories named mydir + mydir/ + + # Match only text files in the top-level directory + /*.txt + + # Match only the file foo.txt in the top-level directory + /foo.txt + + # Match any file named ab.txt, ac.txt, or ad.txt + a[b-d].txt + +Notable differences from .gitignore: + - The '**' syntax is not supported. + - The globbing library is Go's 'filepath.Match', not fnmatch(3) + - Trailing spaces are always ignored (there is no supported escape sequence) + - The evaluation of escape sequences has not been tested for compatibility + - There is no support for '\!' as a special leading sequence. +*/ +package ignore // import "k8s.io/helm/pkg/ignore" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules.go b/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules.go new file mode 100644 index 000000000..76f45fc7a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules.go @@ -0,0 +1,221 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ignore + +import ( + "bufio" + "errors" + "io" + "log" + "os" + "path/filepath" + "strings" +) + +// HelmIgnore default name of an ignorefile. +const HelmIgnore = ".helmignore" + +// Rules is a collection of path matching rules. +// +// Parse() and ParseFile() will construct and populate new Rules. +// Empty() will create an immutable empty ruleset. +type Rules struct { + patterns []*pattern +} + +// Empty builds an empty ruleset. +func Empty() *Rules { + return &Rules{patterns: []*pattern{}} +} + +// AddDefaults adds default ignore patterns. +// +// Ignore all dotfiles in "templates/" +func (r *Rules) AddDefaults() { + r.parseRule(`templates/.?*`) +} + +// ParseFile parses a helmignore file and returns the *Rules. +func ParseFile(file string) (*Rules, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + return Parse(f) +} + +// Parse parses a rules file +func Parse(file io.Reader) (*Rules, error) { + r := &Rules{patterns: []*pattern{}} + + s := bufio.NewScanner(file) + for s.Scan() { + if err := r.parseRule(s.Text()); err != nil { + return r, err + } + } + return r, s.Err() +} + +// Len returns the number of patterns in this rule set. +func (r *Rules) Len() int { + return len(r.patterns) +} + +// Ignore evalutes the file at the given path, and returns true if it should be ignored. +// +// Ignore evaluates path against the rules in order. Evaluation stops when a match +// is found. Matching a negative rule will stop evaluation. +func (r *Rules) Ignore(path string, fi os.FileInfo) bool { + // Don't match on empty dirs. + if path == "" { + return false + } + + // Disallow ignoring the current working directory. + // See issue: + // 1776 (New York City) Hamilton: "Pardon me, are you Aaron Burr, sir?" + if path == "." || path == "./" { + return false + } + for _, p := range r.patterns { + if p.match == nil { + log.Printf("ignore: no matcher supplied for %q", p.raw) + return false + } + + // For negative rules, we need to capture and return non-matches, + // and continue for matches. + if p.negate { + if p.mustDir && !fi.IsDir() { + return true + } + if !p.match(path, fi) { + return true + } + continue + } + + // If the rule is looking for directories, and this is not a directory, + // skip it. + if p.mustDir && !fi.IsDir() { + continue + } + if p.match(path, fi) { + return true + } + } + return false +} + +// parseRule parses a rule string and creates a pattern, which is then stored in the Rules object. +func (r *Rules) parseRule(rule string) error { + rule = strings.TrimSpace(rule) + + // Ignore blank lines + if rule == "" { + return nil + } + // Comment + if strings.HasPrefix(rule, "#") { + return nil + } + + // Fail any rules that contain ** + if strings.Contains(rule, "**") { + return errors.New("double-star (**) syntax is not supported") + } + + // Fail any patterns that can't compile. A non-empty string must be + // given to Match() to avoid optimization that skips rule evaluation. + if _, err := filepath.Match(rule, "abc"); err != nil { + return err + } + + p := &pattern{raw: rule} + + // Negation is handled at a higher level, so strip the leading ! from the + // string. + if strings.HasPrefix(rule, "!") { + p.negate = true + rule = rule[1:] + } + + // Directory verification is handled by a higher level, so the trailing / + // is removed from the rule. That way, a directory named "foo" matches, + // even if the supplied string does not contain a literal slash character. + if strings.HasSuffix(rule, "/") { + p.mustDir = true + rule = strings.TrimSuffix(rule, "/") + } + + if strings.HasPrefix(rule, "/") { + // Require path matches the root path. + p.match = func(n string, fi os.FileInfo) bool { + rule = strings.TrimPrefix(rule, "/") + ok, err := filepath.Match(rule, n) + if err != nil { + log.Printf("Failed to compile %q: %s", rule, err) + return false + } + return ok + } + } else if strings.Contains(rule, "/") { + // require structural match. + p.match = func(n string, fi os.FileInfo) bool { + ok, err := filepath.Match(rule, n) + if err != nil { + log.Printf("Failed to compile %q: %s", rule, err) + return false + } + return ok + } + } else { + p.match = func(n string, fi os.FileInfo) bool { + // When there is no slash in the pattern, we evaluate ONLY the + // filename. + n = filepath.Base(n) + ok, err := filepath.Match(rule, n) + if err != nil { + log.Printf("Failed to compile %q: %s", rule, err) + return false + } + return ok + } + } + + r.patterns = append(r.patterns, p) + return nil +} + +// matcher is a function capable of computing a match. +// +// It returns true if the rule matches. +type matcher func(name string, fi os.FileInfo) bool + +// pattern describes a pattern to be matched in a rule set. +type pattern struct { + // raw is the unparsed string, with nothing stripped. + raw string + // match is the matcher function. + match matcher + // negate indicates that the rule's outcome should be negated. + negate bool + // mustDir indicates that the matched file must be a directory. + mustDir bool +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules_test.go b/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules_test.go new file mode 100644 index 000000000..17b8bf403 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/ignore/rules_test.go @@ -0,0 +1,155 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ignore + +import ( + "bytes" + "os" + "path/filepath" + "testing" +) + +var testdata = "./testdata" + +func TestParse(t *testing.T) { + rules := `#ignore + + #ignore +foo +bar/* +baz/bar/foo.txt + +one/more +` + r, err := parseString(rules) + if err != nil { + t.Fatalf("Error parsing rules: %s", err) + } + + if len(r.patterns) != 4 { + t.Errorf("Expected 4 rules, got %d", len(r.patterns)) + } + + expects := []string{"foo", "bar/*", "baz/bar/foo.txt", "one/more"} + for i, p := range r.patterns { + if p.raw != expects[i] { + t.Errorf("Expected %q, got %q", expects[i], p.raw) + } + if p.match == nil { + t.Errorf("Expected %s to have a matcher function.", p.raw) + } + } +} + +func TestParseFail(t *testing.T) { + shouldFail := []string{"foo/**/bar", "[z-"} + for _, fail := range shouldFail { + _, err := parseString(fail) + if err == nil { + t.Errorf("Rule %q should have failed", fail) + } + } +} + +func TestParseFile(t *testing.T) { + f := filepath.Join(testdata, HelmIgnore) + if _, err := os.Stat(f); err != nil { + t.Fatalf("Fixture %s missing: %s", f, err) + } + + r, err := ParseFile(f) + if err != nil { + t.Fatalf("Failed to parse rules file: %s", err) + } + + if len(r.patterns) != 3 { + t.Errorf("Expected 3 patterns, got %d", len(r.patterns)) + } +} + +func TestIgnore(t *testing.T) { + // Test table: Given pattern and name, Ignore should return expect. + tests := []struct { + pattern string + name string + expect bool + }{ + // Glob tests + {`helm.txt`, "helm.txt", true}, + {`helm.*`, "helm.txt", true}, + {`helm.*`, "rudder.txt", false}, + {`*.txt`, "tiller.txt", true}, + {`*.txt`, "cargo/a.txt", true}, + {`cargo/*.txt`, "cargo/a.txt", true}, + {`cargo/*.*`, "cargo/a.txt", true}, + {`cargo/*.txt`, "mast/a.txt", false}, + {`ru[c-e]?er.txt`, "rudder.txt", true}, + {`templates/.?*`, "templates/.dotfile", true}, + // "." should never get ignored. https://github.com/kubernetes/helm/issues/1776 + {`.*`, ".", false}, + {`.*`, "./", false}, + {`.*`, ".joonix", true}, + {`.*`, "helm.txt", false}, + {`.*`, "", false}, + + // Directory tests + {`cargo/`, "cargo", true}, + {`cargo/`, "cargo/", true}, + {`cargo/`, "mast/", false}, + {`helm.txt/`, "helm.txt", false}, + + // Negation tests + {`!helm.txt`, "helm.txt", false}, + {`!helm.txt`, "tiller.txt", true}, + {`!*.txt`, "cargo", true}, + {`!cargo/`, "mast/", true}, + + // Absolute path tests + {`/a.txt`, "a.txt", true}, + {`/a.txt`, "cargo/a.txt", false}, + {`/cargo/a.txt`, "cargo/a.txt", true}, + } + + for _, test := range tests { + r, err := parseString(test.pattern) + if err != nil { + t.Fatalf("Failed to parse: %s", err) + } + fi, err := os.Stat(filepath.Join(testdata, test.name)) + if err != nil { + t.Fatalf("Fixture missing: %s", err) + } + + if r.Ignore(test.name, fi) != test.expect { + t.Errorf("Expected %q to be %v for pattern %q", test.name, test.expect, test.pattern) + } + } +} + +func TestAddDefaults(t *testing.T) { + r := Rules{} + r.AddDefaults() + + if len(r.patterns) != 1 { + t.Errorf("Expected 1 default patterns, got %d", len(r.patterns)) + } +} + +func parseString(str string) (*Rules, error) { + b := bytes.NewBuffer([]byte(str)) + return Parse(b) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.helmignore b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.helmignore new file mode 100644 index 000000000..b2693bae7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.helmignore @@ -0,0 +1,3 @@ +mast/a.txt +.DS_Store +.git diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.joonix b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/.joonix new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/a.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/a.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/a.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/a.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/b.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/b.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/c.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/cargo/c.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/helm.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/helm.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/a.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/a.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/b.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/b.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/c.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/mast/c.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/rudder.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/rudder.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/templates/.dotfile b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/templates/.dotfile new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/tiller.txt b/src/vendor/github.com/kubernetes/helm/pkg/ignore/testdata/tiller.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/client.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/client.go new file mode 100644 index 000000000..5086e8359 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/client.go @@ -0,0 +1,744 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import ( + "bytes" + "encoding/json" + goerrors "errors" + "fmt" + "io" + "log" + "strings" + "time" + + jsonpatch "github.com/evanphx/json-patch" + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batch "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + extv1beta1 "k8s.io/api/extensions/v1beta1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/clientcmd" + batchinternal "k8s.io/kubernetes/pkg/apis/batch" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/validation" + "k8s.io/kubernetes/pkg/printers" +) + +const ( + // MissingGetHeader is added to Get's outout when a resource is not found. + MissingGetHeader = "==> MISSING\nKIND\t\tNAME\n" +) + +// ErrNoObjectsVisited indicates that during a visit operation, no matching objects were found. +var ErrNoObjectsVisited = goerrors.New("no objects visited") + +// Client represents a client capable of communicating with the Kubernetes API. +type Client struct { + cmdutil.Factory + // SchemaCacheDir is the path for loading cached schema. + SchemaCacheDir string + + Log func(string, ...interface{}) +} + +// New creates a new Client. +func New(config clientcmd.ClientConfig) *Client { + return &Client{ + Factory: cmdutil.NewFactory(config), + SchemaCacheDir: clientcmd.RecommendedSchemaFile, + Log: nopLogger, + } +} + +var nopLogger = func(_ string, _ ...interface{}) {} + +// ResourceActorFunc performs an action on a single resource. +type ResourceActorFunc func(*resource.Info) error + +// Create creates Kubernetes resources from an io.reader. +// +// Namespace will set the namespace. +func (c *Client) Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { + client, err := c.ClientSet() + if err != nil { + return err + } + if err := ensureNamespace(client, namespace); err != nil { + return err + } + c.Log("building resources from manifest") + infos, buildErr := c.BuildUnstructured(namespace, reader) + if buildErr != nil { + return buildErr + } + c.Log("creating %d resource(s)", len(infos)) + if err := perform(infos, createResource); err != nil { + return err + } + if shouldWait { + return c.waitForResources(time.Duration(timeout)*time.Second, infos) + } + return nil +} + +func (c *Client) newBuilder(namespace string, reader io.Reader) *resource.Result { + return c.NewBuilder(). + Internal(). + ContinueOnError(). + Schema(c.validator()). + NamespaceParam(namespace). + DefaultNamespace(). + Stream(reader, ""). + Flatten(). + Do() +} + +func (c *Client) validator() validation.Schema { + schema, err := c.Validator(true) + if err != nil { + c.Log("warning: failed to load schema: %s", err) + } + return schema +} + +// BuildUnstructured validates for Kubernetes objects and returns unstructured infos. +func (c *Client) BuildUnstructured(namespace string, reader io.Reader) (Result, error) { + var result Result + + result, err := c.NewBuilder(). + Unstructured(). + ContinueOnError(). + NamespaceParam(namespace). + DefaultNamespace(). + Stream(reader, ""). + Flatten(). + Do().Infos() + return result, scrubValidationError(err) +} + +// Build validates for Kubernetes objects and returns resource Infos from a io.Reader. +func (c *Client) Build(namespace string, reader io.Reader) (Result, error) { + var result Result + result, err := c.newBuilder(namespace, reader).Infos() + return result, scrubValidationError(err) +} + +// Get gets Kubernetes resources as pretty-printed string. +// +// Namespace will set the namespace. +func (c *Client) Get(namespace string, reader io.Reader) (string, error) { + // Since we don't know what order the objects come in, let's group them by the types, so + // that when we print them, they come out looking good (headers apply to subgroups, etc.). + objs := make(map[string][]runtime.Object) + infos, err := c.BuildUnstructured(namespace, reader) + if err != nil { + return "", err + } + + var objPods = make(map[string][]core.Pod) + + missing := []string{} + err = perform(infos, func(info *resource.Info) error { + c.Log("Doing get for %s: %q", info.Mapping.GroupVersionKind.Kind, info.Name) + if err := info.Get(); err != nil { + c.Log("WARNING: Failed Get for resource %q: %s", info.Name, err) + missing = append(missing, fmt.Sprintf("%v\t\t%s", info.Mapping.Resource, info.Name)) + return nil + } + + // Use APIVersion/Kind as grouping mechanism. I'm not sure if you can have multiple + // versions per cluster, but this certainly won't hurt anything, so let's be safe. + gvk := info.ResourceMapping().GroupVersionKind + vk := gvk.Version + "/" + gvk.Kind + objs[vk] = append(objs[vk], info.AsInternal()) + + //Get the relation pods + objPods, err = c.getSelectRelationPod(info, objPods) + if err != nil { + c.Log("Warning: get the relation pod is failed, err:%s", err.Error()) + } + + return nil + }) + if err != nil { + return "", err + } + + //here, we will add the objPods to the objs + for key, podItems := range objPods { + for i := range podItems { + objs[key+"(related)"] = append(objs[key+"(related)"], &podItems[i]) + } + } + + // Ok, now we have all the objects grouped by types (say, by v1/Pod, v1/Service, etc.), so + // spin through them and print them. Printer is cool since it prints the header only when + // an object type changes, so we can just rely on that. Problem is it doesn't seem to keep + // track of tab widths. + buf := new(bytes.Buffer) + p, err := cmdutil.PrinterForOptions(&printers.PrintOptions{}) + if err != nil { + return "", err + } + for t, ot := range objs { + if _, err = buf.WriteString("==> " + t + "\n"); err != nil { + return "", err + } + for _, o := range ot { + if err := p.PrintObj(o, buf); err != nil { + c.Log("failed to print object type %s, object: %q :\n %v", t, o, err) + return "", err + } + } + if _, err := buf.WriteString("\n"); err != nil { + return "", err + } + } + if len(missing) > 0 { + buf.WriteString(MissingGetHeader) + for _, s := range missing { + fmt.Fprintln(buf, s) + } + } + return buf.String(), nil +} + +// Update reads in the current configuration and a target configuration from io.reader +// and creates resources that don't already exists, updates resources that have been modified +// in the target configuration and deletes resources from the current configuration that are +// not present in the target configuration. +// +// Namespace will set the namespaces. +func (c *Client) Update(namespace string, originalReader, targetReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { + original, err := c.BuildUnstructured(namespace, originalReader) + if err != nil { + return fmt.Errorf("failed decoding reader into objects: %s", err) + } + + c.Log("building resources from updated manifest") + target, err := c.BuildUnstructured(namespace, targetReader) + if err != nil { + return fmt.Errorf("failed decoding reader into objects: %s", err) + } + + updateErrors := []string{} + + c.Log("checking %d resources for changes", len(target)) + err = target.Visit(func(info *resource.Info, err error) error { + if err != nil { + return err + } + + helper := resource.NewHelper(info.Client, info.Mapping) + if _, err := helper.Get(info.Namespace, info.Name, info.Export); err != nil { + if !errors.IsNotFound(err) { + return fmt.Errorf("Could not get information about the resource: %s", err) + } + + // Since the resource does not exist, create it. + if err := createResource(info); err != nil { + return fmt.Errorf("failed to create resource: %s", err) + } + + kind := info.Mapping.GroupVersionKind.Kind + c.Log("Created a new %s called %q\n", kind, info.Name) + return nil + } + + originalInfo := original.Get(info) + if originalInfo == nil { + kind := info.Mapping.GroupVersionKind.Kind + return fmt.Errorf("no %s with the name %q found", kind, info.Name) + } + + if err := updateResource(c, info, originalInfo.Object, force, recreate); err != nil { + c.Log("error updating the resource %q:\n\t %v", info.Name, err) + updateErrors = append(updateErrors, err.Error()) + } + + return nil + }) + + switch { + case err != nil: + return err + case len(updateErrors) != 0: + return fmt.Errorf(strings.Join(updateErrors, " && ")) + } + + for _, info := range original.Difference(target) { + c.Log("Deleting %q in %s...", info.Name, info.Namespace) + if err := deleteResource(c, info); err != nil { + c.Log("Failed to delete %q, err: %s", info.Name, err) + } + } + if shouldWait { + return c.waitForResources(time.Duration(timeout)*time.Second, target) + } + return nil +} + +// Delete deletes Kubernetes resources from an io.reader. +// +// Namespace will set the namespace. +func (c *Client) Delete(namespace string, reader io.Reader) error { + infos, err := c.BuildUnstructured(namespace, reader) + if err != nil { + return err + } + return perform(infos, func(info *resource.Info) error { + c.Log("Starting delete for %q %s", info.Name, info.Mapping.GroupVersionKind.Kind) + err := deleteResource(c, info) + return c.skipIfNotFound(err) + }) +} + +func (c *Client) skipIfNotFound(err error) error { + if errors.IsNotFound(err) { + c.Log("%v", err) + return nil + } + return err +} + +func (c *Client) watchTimeout(t time.Duration) ResourceActorFunc { + return func(info *resource.Info) error { + return c.watchUntilReady(t, info) + } +} + +// WatchUntilReady watches the resource given in the reader, and waits until it is ready. +// +// This function is mainly for hook implementations. It watches for a resource to +// hit a particular milestone. The milestone depends on the Kind. +// +// For most kinds, it checks to see if the resource is marked as Added or Modified +// by the Kubernetes event stream. For some kinds, it does more: +// +// - Jobs: A job is marked "Ready" when it has successfully completed. This is +// ascertained by watching the Status fields in a job's output. +// +// Handling for other kinds will be added as necessary. +func (c *Client) WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error { + infos, err := c.Build(namespace, reader) + if err != nil { + return err + } + // For jobs, there's also the option to do poll c.Jobs(namespace).Get(): + // https://github.com/adamreese/kubernetes/blob/master/test/e2e/job.go#L291-L300 + return perform(infos, c.watchTimeout(time.Duration(timeout)*time.Second)) +} + +func perform(infos Result, fn ResourceActorFunc) error { + if len(infos) == 0 { + return ErrNoObjectsVisited + } + + for _, info := range infos { + if err := fn(info); err != nil { + return err + } + } + return nil +} + +func createResource(info *resource.Info) error { + obj, err := resource.NewHelper(info.Client, info.Mapping).Create(info.Namespace, true, info.Object) + if err != nil { + return err + } + return info.Refresh(obj, true) +} + +func deleteResource(c *Client, info *resource.Info) error { + reaper, err := c.Reaper(info.Mapping) + if err != nil { + // If there is no reaper for this resources, delete it. + if kubectl.IsNoSuchReaperError(err) { + return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name) + } + return err + } + c.Log("Using reaper for deleting %q", info.Name) + return reaper.Stop(info.Namespace, info.Name, 0, nil) +} + +func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.PatchType, error) { + oldData, err := json.Marshal(current) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing current configuration: %s", err) + } + newData, err := json.Marshal(target.Object) + if err != nil { + return nil, types.StrategicMergePatchType, fmt.Errorf("serializing target configuration: %s", err) + } + + // While different objects need different merge types, the parent function + // that calls this does not try to create a patch when the data (first + // returned object) is nil. We can skip calculating the the merge type as + // the returned merge type is ignored. + if apiequality.Semantic.DeepEqual(oldData, newData) { + return nil, types.StrategicMergePatchType, nil + } + + // Get a versioned object + versionedObject, err := target.Versioned() + + // Unstructured objects, such as CRDs, may not have an not registered error + // returned from ConvertToVersion. Anything that's unstructured should + // use the jsonpatch.CreateMergePatch. Strategic Merge Patch is not supported + // on objects like CRDs. + _, isUnstructured := versionedObject.(runtime.Unstructured) + + switch { + case runtime.IsNotRegisteredError(err), isUnstructured: + // fall back to generic JSON merge patch + patch, err := jsonpatch.CreateMergePatch(oldData, newData) + return patch, types.MergePatchType, err + case err != nil: + return nil, types.StrategicMergePatchType, fmt.Errorf("failed to get versionedObject: %s", err) + default: + patch, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, versionedObject) + return patch, types.StrategicMergePatchType, err + } +} + +func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool) error { + patch, patchType, err := createPatch(target, currentObj) + if err != nil { + return fmt.Errorf("failed to create patch: %s", err) + } + if patch == nil { + c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) + // This needs to happen to make sure that tiller has the latest info from the API + // Otherwise there will be no labels and other functions that use labels will panic + if err := target.Get(); err != nil { + return fmt.Errorf("error trying to refresh resource information: %v", err) + } + } else { + // send patch to server + helper := resource.NewHelper(target.Client, target.Mapping) + + obj, err := helper.Patch(target.Namespace, target.Name, patchType, patch) + if err != nil { + kind := target.Mapping.GroupVersionKind.Kind + log.Printf("Cannot patch %s: %q (%v)", kind, target.Name, err) + + if force { + // Attempt to delete... + if err := deleteResource(c, target); err != nil { + return err + } + log.Printf("Deleted %s: %q", kind, target.Name) + + // ... and recreate + if err := createResource(target); err != nil { + return fmt.Errorf("Failed to recreate resource: %s", err) + } + log.Printf("Created a new %s called %q\n", kind, target.Name) + + // No need to refresh the target, as we recreated the resource based + // on it. In addition, it might not exist yet and a call to `Refresh` + // may fail. + } else { + log.Print("Use --force to force recreation of the resource") + return err + } + } else { + // When patch succeeds without needing to recreate, refresh target. + target.Refresh(obj, true) + } + } + + if !recreate { + return nil + } + + versioned := target.AsVersioned() + selector, ok := getSelectorFromObject(versioned) + if !ok { + return nil + } + + client, err := c.ClientSet() + if err != nil { + return err + } + + pods, err := client.Core().Pods(target.Namespace).List(metav1.ListOptions{ + FieldSelector: fields.Everything().String(), + LabelSelector: labels.Set(selector).AsSelector().String(), + }) + if err != nil { + return err + } + + // Restart pods + for _, pod := range pods.Items { + c.Log("Restarting pod: %v/%v", pod.Namespace, pod.Name) + + // Delete each pod for get them restarted with changed spec. + if err := client.Core().Pods(pod.Namespace).Delete(pod.Name, metav1.NewPreconditionDeleteOptions(string(pod.UID))); err != nil { + return err + } + } + return nil +} + +func getSelectorFromObject(obj runtime.Object) (map[string]string, bool) { + switch typed := obj.(type) { + + case *v1.ReplicationController: + return typed.Spec.Selector, true + + case *extv1beta1.ReplicaSet: + return typed.Spec.Selector.MatchLabels, true + case *appsv1.ReplicaSet: + return typed.Spec.Selector.MatchLabels, true + + case *extv1beta1.Deployment: + return typed.Spec.Selector.MatchLabels, true + case *appsv1beta1.Deployment: + return typed.Spec.Selector.MatchLabels, true + case *appsv1beta2.Deployment: + return typed.Spec.Selector.MatchLabels, true + case *appsv1.Deployment: + return typed.Spec.Selector.MatchLabels, true + + case *extv1beta1.DaemonSet: + return typed.Spec.Selector.MatchLabels, true + case *appsv1beta2.DaemonSet: + return typed.Spec.Selector.MatchLabels, true + case *appsv1.DaemonSet: + return typed.Spec.Selector.MatchLabels, true + + case *batch.Job: + return typed.Spec.Selector.MatchLabels, true + + case *appsv1beta1.StatefulSet: + return typed.Spec.Selector.MatchLabels, true + case *appsv1beta2.StatefulSet: + return typed.Spec.Selector.MatchLabels, true + case *appsv1.StatefulSet: + return typed.Spec.Selector.MatchLabels, true + + default: + return nil, false + } +} + +func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { + w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) + if err != nil { + return err + } + + kind := info.Mapping.GroupVersionKind.Kind + c.Log("Watching for changes to %s %s with timeout of %v", kind, info.Name, timeout) + + // What we watch for depends on the Kind. + // - For a Job, we watch for completion. + // - For all else, we watch until Ready. + // In the future, we might want to add some special logic for types + // like Ingress, Volume, etc. + + _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { + switch e.Type { + case watch.Added, watch.Modified: + // For things like a secret or a config map, this is the best indicator + // we get. We care mostly about jobs, where what we want to see is + // the status go into a good state. For other types, like ReplicaSet + // we don't really do anything to support these as hooks. + c.Log("Add/Modify event for %s: %v", info.Name, e.Type) + if kind == "Job" { + return c.waitForJob(e, info.Name) + } + return true, nil + case watch.Deleted: + c.Log("Deleted event for %s", info.Name) + return true, nil + case watch.Error: + // Handle error and return with an error. + c.Log("Error event for %s", info.Name) + return true, fmt.Errorf("Failed to deploy %s", info.Name) + default: + return false, nil + } + }) + return err +} + +// waitForJob is a helper that waits for a job to complete. +// +// This operates on an event returned from a watcher. +func (c *Client) waitForJob(e watch.Event, name string) (bool, error) { + o, ok := e.Object.(*batchinternal.Job) + if !ok { + return true, fmt.Errorf("Expected %s to be a *batch.Job, got %T", name, e.Object) + } + + for _, c := range o.Status.Conditions { + if c.Type == batchinternal.JobComplete && c.Status == core.ConditionTrue { + return true, nil + } else if c.Type == batchinternal.JobFailed && c.Status == core.ConditionTrue { + return true, fmt.Errorf("Job failed: %s", c.Reason) + } + } + + c.Log("%s: Jobs active: %d, jobs failed: %d, jobs succeeded: %d", name, o.Status.Active, o.Status.Failed, o.Status.Succeeded) + return false, nil +} + +// scrubValidationError removes kubectl info from the message. +func scrubValidationError(err error) error { + if err == nil { + return nil + } + const stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false" + + if strings.Contains(err.Error(), stopValidateMessage) { + return goerrors.New(strings.Replace(err.Error(), "; "+stopValidateMessage, "", -1)) + } + return err +} + +// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase +// and returns said phase (PodSucceeded or PodFailed qualify). +func (c *Client) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { + infos, err := c.Build(namespace, reader) + if err != nil { + return core.PodUnknown, err + } + info := infos[0] + + kind := info.Mapping.GroupVersionKind.Kind + if kind != "Pod" { + return core.PodUnknown, fmt.Errorf("%s is not a Pod", info.Name) + } + + if err := c.watchPodUntilComplete(timeout, info); err != nil { + return core.PodUnknown, err + } + + if err := info.Get(); err != nil { + return core.PodUnknown, err + } + status := info.Object.(*core.Pod).Status.Phase + + return status, nil +} + +func (c *Client) watchPodUntilComplete(timeout time.Duration, info *resource.Info) error { + w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion) + if err != nil { + return err + } + + c.Log("Watching pod %s for completion with timeout of %v", info.Name, timeout) + _, err = watch.Until(timeout, w, func(e watch.Event) (bool, error) { + return isPodComplete(e) + }) + + return err +} + +func isPodComplete(event watch.Event) (bool, error) { + o, ok := event.Object.(*core.Pod) + if !ok { + return true, fmt.Errorf("expected a *core.Pod, got %T", event.Object) + } + if event.Type == watch.Deleted { + return false, fmt.Errorf("pod not found") + } + switch o.Status.Phase { + case core.PodFailed, core.PodSucceeded: + return true, nil + } + return false, nil +} + +//get a kubernetes resources' relation pods +// kubernetes resource used select labels to relate pods +func (c *Client) getSelectRelationPod(info *resource.Info, objPods map[string][]core.Pod) (map[string][]core.Pod, error) { + if info == nil { + return objPods, nil + } + + c.Log("get relation pod of object: %s/%s/%s", info.Namespace, info.Mapping.GroupVersionKind.Kind, info.Name) + + versioned, err := info.Versioned() + switch { + case runtime.IsNotRegisteredError(err): + return objPods, nil + case err != nil: + return objPods, err + } + + selector, ok := getSelectorFromObject(versioned) + if !ok { + return objPods, nil + } + + client, _ := c.ClientSet() + + pods, err := client.Core().Pods(info.Namespace).List(metav1.ListOptions{ + FieldSelector: fields.Everything().String(), + LabelSelector: labels.Set(selector).AsSelector().String(), + }) + if err != nil { + return objPods, err + } + + for _, pod := range pods.Items { + if pod.APIVersion == "" { + pod.APIVersion = "v1" + } + + if pod.Kind == "" { + pod.Kind = "Pod" + } + vk := pod.GroupVersionKind().Version + "/" + pod.GroupVersionKind().Kind + + if !isFoundPod(objPods[vk], pod) { + objPods[vk] = append(objPods[vk], pod) + } + } + return objPods, nil +} + +func isFoundPod(podItem []core.Pod, pod core.Pod) bool { + for _, value := range podItem { + if (value.Namespace == pod.Namespace) && (value.Name == pod.Name) { + return true + } + } + return false +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/client_test.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/client_test.go new file mode 100644 index 000000000..47049810a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/client_test.go @@ -0,0 +1,546 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "bytes" + "io" + "io/ioutil" + "net/http" + "strings" + "testing" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest/fake" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl" + cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/kubectl/scheme" +) + +var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer + +func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) +} + +func newPod(name string) core.Pod { + return newPodWithStatus(name, core.PodStatus{}, "") +} + +func newPodWithStatus(name string, status core.PodStatus, namespace string) core.Pod { + ns := core.NamespaceDefault + if namespace != "" { + ns = namespace + } + return core.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + SelfLink: "/api/v1/namespaces/default/pods/" + name, + }, + Spec: core.PodSpec{ + Containers: []core.Container{{ + Name: "app:v4", + Image: "abc/app:v4", + Ports: []core.ContainerPort{{Name: "http", ContainerPort: 80}}, + }}, + }, + Status: status, + } +} + +func newPodList(names ...string) core.PodList { + var list core.PodList + for _, name := range names { + list.Items = append(list.Items, newPod(name)) + } + return list +} + +func notFoundBody() *metav1.Status { + return &metav1.Status{ + Code: http.StatusNotFound, + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonNotFound, + Message: " \"\" not found", + Details: &metav1.StatusDetails{}, + } +} + +func newResponse(code int, obj runtime.Object) (*http.Response, error) { + header := http.Header{} + header.Set("Content-Type", runtime.ContentTypeJSON) + body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj)))) + return &http.Response{StatusCode: code, Header: header, Body: body}, nil +} + +type fakeReaper struct { + name string +} + +func (r *fakeReaper) Stop(namespace, name string, timeout time.Duration, gracePeriod *metav1.DeleteOptions) error { + r.name = name + return nil +} + +type fakeReaperFactory struct { + cmdutil.Factory + reaper kubectl.Reaper +} + +func (f *fakeReaperFactory) Reaper(mapping *meta.RESTMapping) (kubectl.Reaper, error) { + return f.reaper, nil +} + +type testClient struct { + *Client + *cmdtesting.TestFactory +} + +func newTestClient() *testClient { + tf := cmdtesting.NewTestFactory() + c := &Client{ + Factory: tf, + Log: nopLogger, + } + return &testClient{ + Client: c, + TestFactory: tf, + } +} + +func TestUpdate(t *testing.T) { + listA := newPodList("starfish", "otter", "squid") + listB := newPodList("starfish", "otter", "dolphin") + listC := newPodList("starfish", "otter", "dolphin") + listB.Items[0].Spec.Containers[0].Ports = []core.ContainerPort{{Name: "https", ContainerPort: 443}} + listC.Items[0].Spec.Containers[0].Ports = []core.ContainerPort{{Name: "https", ContainerPort: 443}} + + var actions []string + + tf := cmdtesting.NewTestFactory() + defer tf.Cleanup() + tf.UnstructuredClient = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + actions = append(actions, p+":"+m) + t.Logf("got request %s %s", p, m) + switch { + case p == "/namespaces/default/pods/starfish" && m == "GET": + return newResponse(200, &listA.Items[0]) + case p == "/namespaces/default/pods/otter" && m == "GET": + return newResponse(200, &listA.Items[1]) + case p == "/namespaces/default/pods/dolphin" && m == "GET": + return newResponse(404, notFoundBody()) + case p == "/namespaces/default/pods/starfish" && m == "PATCH": + data, err := ioutil.ReadAll(req.Body) + if err != nil { + t.Fatalf("could not dump request: %s", err) + } + req.Body.Close() + expected := `{"spec":{"$setElementOrder/containers":[{"name":"app:v4"}],"containers":[{"$setElementOrder/ports":[{"containerPort":443}],"name":"app:v4","ports":[{"containerPort":443,"name":"https"},{"$patch":"delete","containerPort":80}]}]}}` + if string(data) != expected { + t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) + } + return newResponse(200, &listB.Items[0]) + case p == "/namespaces/default/pods" && m == "POST": + return newResponse(200, &listB.Items[1]) + case p == "/namespaces/default/pods/squid" && m == "DELETE": + return newResponse(200, &listB.Items[1]) + default: + t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) + return nil, nil + } + }), + } + + c := newTestClient() + reaper := &fakeReaper{} + rf := &fakeReaperFactory{Factory: tf, reaper: reaper} + c.Client.Factory = rf + codec := legacyscheme.Codecs.LegacyCodec(scheme.Versions...) + if err := c.Update(core.NamespaceDefault, objBody(codec, &listA), objBody(codec, &listB), false, false, 0, false); err != nil { + t.Fatal(err) + } + // TODO: Find a way to test methods that use Client Set + // Test with a wait + // if err := c.Update("test", objBody(codec, &listB), objBody(codec, &listC), false, 300, true); err != nil { + // t.Fatal(err) + // } + // Test with a wait should fail + // TODO: A way to make this not based off of an extremely short timeout? + // if err := c.Update("test", objBody(codec, &listC), objBody(codec, &listA), false, 2, true); err != nil { + // t.Fatal(err) + // } + expectedActions := []string{ + "/namespaces/default/pods/starfish:GET", + "/namespaces/default/pods/starfish:PATCH", + "/namespaces/default/pods/otter:GET", + "/namespaces/default/pods/otter:GET", + "/namespaces/default/pods/dolphin:GET", + "/namespaces/default/pods:POST", + } + if len(expectedActions) != len(actions) { + t.Errorf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) + return + } + for k, v := range expectedActions { + if actions[k] != v { + t.Errorf("expected %s request got %s", v, actions[k]) + } + } + + if reaper.name != "squid" { + t.Errorf("unexpected reaper: %#v", reaper) + } + +} + +func TestBuild(t *testing.T) { + tests := []struct { + name string + namespace string + reader io.Reader + count int + err bool + }{ + { + name: "Valid input", + namespace: "test", + reader: strings.NewReader(guestbookManifest), + count: 6, + }, { + name: "Invalid schema", + namespace: "test", + reader: strings.NewReader(testInvalidServiceManifest), + err: true, + }, + } + + c := newTestClient() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c.Cleanup() + + // Test for an invalid manifest + infos, err := c.Build(tt.namespace, tt.reader) + if err != nil && !tt.err { + t.Errorf("Got error message when no error should have occurred: %v", err) + } else if err != nil && strings.Contains(err.Error(), "--validate=false") { + t.Error("error message was not scrubbed") + } + + if len(infos) != tt.count { + t.Errorf("expected %d result objects, got %d", tt.count, len(infos)) + } + }) + } +} + +func TestGet(t *testing.T) { + list := newPodList("starfish", "otter") + c := newTestClient() + defer c.Cleanup() + c.TestFactory.UnstructuredClient = &fake.RESTClient{ + GroupVersion: schema.GroupVersion{Version: "v1"}, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + t.Logf("got request %s %s", p, m) + switch { + case p == "/namespaces/default/pods/starfish" && m == "GET": + return newResponse(404, notFoundBody()) + case p == "/namespaces/default/pods/otter" && m == "GET": + return newResponse(200, &list.Items[1]) + default: + t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) + return nil, nil + } + }), + } + + // Test Success + data := strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: otter") + o, err := c.Get("default", data) + if err != nil { + t.Errorf("Expected missing results, got %q", err) + } + if !strings.Contains(o, "==> v1/Pod") && !strings.Contains(o, "otter") { + t.Errorf("Expected v1/Pod otter, got %s", o) + } + + // Test failure + data = strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: starfish") + o, err = c.Get("default", data) + if err != nil { + t.Errorf("Expected missing results, got %q", err) + } + if !strings.Contains(o, "MISSING") && !strings.Contains(o, "pods\t\tstarfish") { + t.Errorf("Expected missing starfish, got %s", o) + } +} + +func TestPerform(t *testing.T) { + tests := []struct { + name string + namespace string + reader io.Reader + count int + err bool + errMessage string + }{ + { + name: "Valid input", + namespace: "test", + reader: strings.NewReader(guestbookManifest), + count: 6, + }, { + name: "Empty manifests", + namespace: "test", + reader: strings.NewReader(""), + err: true, + errMessage: "no objects visited", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results := []*resource.Info{} + + fn := func(info *resource.Info) error { + results = append(results, info) + + if info.Namespace != tt.namespace { + t.Errorf("expected namespace to be '%s', got %s", tt.namespace, info.Namespace) + } + return nil + } + + c := newTestClient() + defer c.Cleanup() + infos, err := c.Build(tt.namespace, tt.reader) + if err != nil && err.Error() != tt.errMessage { + t.Errorf("Error while building manifests: %v", err) + } + + err = perform(infos, fn) + if (err != nil) != tt.err { + t.Errorf("expected error: %v, got %v", tt.err, err) + } + if err != nil && err.Error() != tt.errMessage { + t.Errorf("expected error message: %v, got %v", tt.errMessage, err) + } + + if len(results) != tt.count { + t.Errorf("expected %d result objects, got %d", tt.count, len(results)) + } + }) + } +} + +func TestReal(t *testing.T) { + t.Skip("This is a live test, comment this line to run") + c := New(nil) + if err := c.Create("test", strings.NewReader(guestbookManifest), 300, false); err != nil { + t.Fatal(err) + } + + testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest + c = New(nil) + if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest), 300, false); err != nil { + t.Fatal(err) + } + + if err := c.Delete("test-delete", strings.NewReader(testEndpointManifest)); err != nil { + t.Fatal(err) + } + + // ensures that delete does not fail if a resource is not found + if err := c.Delete("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil { + t.Fatal(err) + } +} + +const testServiceManifest = ` +kind: Service +apiVersion: v1 +metadata: + name: my-service +spec: + selector: + app: myapp + ports: + - port: 80 + protocol: TCP + targetPort: 9376 +` + +const testInvalidServiceManifest = ` +kind: Service +apiVersion: v1 +spec: + ports: + - port: "80" +` + +const testEndpointManifest = ` +kind: Endpoints +apiVersion: v1 +metadata: + name: my-service +subsets: + - addresses: + - ip: "1.2.3.4" + ports: + - port: 9376 +` + +const guestbookManifest = ` +apiVersion: v1 +kind: Service +metadata: + name: redis-master + labels: + app: redis + tier: backend + role: master +spec: + ports: + - port: 6379 + targetPort: 6379 + selector: + app: redis + tier: backend + role: master +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: redis-master +spec: + replicas: 1 + template: + metadata: + labels: + app: redis + role: master + tier: backend + spec: + containers: + - name: master + image: k8s.gcr.io/redis:e2e # or just image: redis + resources: + requests: + cpu: 100m + memory: 100Mi + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: redis-slave + labels: + app: redis + tier: backend + role: slave +spec: + ports: + # the port that this service should serve on + - port: 6379 + selector: + app: redis + tier: backend + role: slave +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: redis-slave +spec: + replicas: 2 + template: + metadata: + labels: + app: redis + role: slave + tier: backend + spec: + containers: + - name: slave + image: gcr.io/google_samples/gb-redisslave:v1 + resources: + requests: + cpu: 100m + memory: 100Mi + env: + - name: GET_HOSTS_FROM + value: dns + ports: + - containerPort: 6379 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend + labels: + app: guestbook + tier: frontend +spec: + ports: + - port: 80 + selector: + app: guestbook + tier: frontend +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: frontend +spec: + replicas: 3 + template: + metadata: + labels: + app: guestbook + tier: frontend + spec: + containers: + - name: php-redis + image: gcr.io/google-samples/gb-frontend:v4 + resources: + requests: + cpu: 100m + memory: 100Mi + env: + - name: GET_HOSTS_FROM + value: dns + ports: + - containerPort: 80 +` diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/config.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/config.go new file mode 100644 index 000000000..b6560486e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/config.go @@ -0,0 +1,32 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import "k8s.io/client-go/tools/clientcmd" + +// GetConfig returns a Kubernetes client config for a given context. +func GetConfig(context string) clientcmd.ClientConfig { + rules := clientcmd.NewDefaultClientConfigLoadingRules() + rules.DefaultClientConfig = &clientcmd.DefaultClientConfig + + overrides := &clientcmd.ConfigOverrides{ClusterDefaults: clientcmd.ClusterDefaults} + + if context != "" { + overrides.CurrentContext = context + } + return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(rules, overrides) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/log.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/log.go new file mode 100644 index 000000000..fbe51823a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/log.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "flag" + "fmt" + "os" +) + +func init() { + if level := os.Getenv("KUBE_LOG_LEVEL"); level != "" { + flag.Set("vmodule", fmt.Sprintf("loader=%s,round_trippers=%s,request=%s", level, level, level)) + flag.Set("logtostderr", "true") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace.go new file mode 100644 index 000000000..9d2793d87 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace.go @@ -0,0 +1,46 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import ( + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" +) + +func createNamespace(client internalclientset.Interface, namespace string) error { + ns := &core.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + }, + } + _, err := client.Core().Namespaces().Create(ns) + return err +} + +func getNamespace(client internalclientset.Interface, namespace string) (*core.Namespace, error) { + return client.Core().Namespaces().Get(namespace, metav1.GetOptions{}) +} + +func ensureNamespace(client internalclientset.Interface, namespace string) error { + _, err := getNamespace(client, namespace) + if err != nil && errors.IsNotFound(err) { + return createNamespace(client, namespace) + } + return err +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace_test.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace_test.go new file mode 100644 index 000000000..eb96557d0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/namespace_test.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" +) + +func TestEnsureNamespace(t *testing.T) { + client := fake.NewSimpleClientset() + if err := ensureNamespace(client, "foo"); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if err := ensureNamespace(client, "foo"); err != nil { + t.Fatalf("unexpected error: %s", err) + } + if _, err := client.Core().Namespaces().Get("foo", metav1.GetOptions{}); err != nil { + t.Fatalf("unexpected error: %s", err) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/result.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/result.go new file mode 100644 index 000000000..87c7e6ac1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/result.go @@ -0,0 +1,87 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import "k8s.io/kubernetes/pkg/kubectl/resource" + +// Result provides convenience methods for comparing collections of Infos. +type Result []*resource.Info + +// Append adds an Info to the Result. +func (r *Result) Append(val *resource.Info) { + *r = append(*r, val) +} + +// Visit implements resource.Visitor. +func (r Result) 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 Result) Filter(fn func(*resource.Info) bool) Result { + var result Result + 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 Result) 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 Result) 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 Result) Difference(rs Result) Result { + 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 Result) Intersect(rs Result) Result { + return r.Filter(func(info *resource.Info) bool { + return rs.Contains(info) + }) +} + +// isMatchingInfo returns true if infos match on Name and GroupVersionKind. +func isMatchingInfo(a, b *resource.Info) bool { + return a.Name == b.Name && a.Mapping.GroupVersionKind.Kind == b.Mapping.GroupVersionKind.Kind +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/result_test.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/result_test.go new file mode 100644 index 000000000..962e90426 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/result_test.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import ( + "testing" + + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/kubernetes/pkg/api/testapi" + "k8s.io/kubernetes/pkg/kubectl/resource" +) + +func TestResult(t *testing.T) { + mapping, err := testapi.Default.RESTMapper().RESTMapping(schema.GroupKind{Kind: "Pod"}) + if err != nil { + t.Fatal(err) + } + + info := func(name string) *resource.Info { + return &resource.Info{Name: name, Mapping: mapping} + } + + var r1, r2 Result + r1 = []*resource.Info{info("foo"), info("bar")} + r2 = []*resource.Info{info("bar")} + + 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/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel.go new file mode 100644 index 000000000..08280f25d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "strconv" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" +) + +// Tunnel describes a ssh-like tunnel to a kubernetes pod +type Tunnel struct { + Local int + Remote int + Namespace string + PodName string + Out io.Writer + stopChan chan struct{} + readyChan chan struct{} + config *rest.Config + client rest.Interface +} + +// NewTunnel creates a new tunnel +func NewTunnel(client rest.Interface, config *rest.Config, namespace, podName string, remote int) *Tunnel { + return &Tunnel{ + config: config, + client: client, + Namespace: namespace, + PodName: podName, + Remote: remote, + stopChan: make(chan struct{}, 1), + readyChan: make(chan struct{}, 1), + Out: ioutil.Discard, + } +} + +// Close disconnects a tunnel connection +func (t *Tunnel) Close() { + close(t.stopChan) +} + +// ForwardPort opens a tunnel to a kubernetes pod +func (t *Tunnel) ForwardPort() error { + // Build a url to the portforward endpoint + // example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward + u := t.client.Post(). + Resource("pods"). + Namespace(t.Namespace). + Name(t.PodName). + SubResource("portforward").URL() + + transport, upgrader, err := spdy.RoundTripperFor(t.config) + if err != nil { + return err + } + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", u) + + local, err := getAvailablePort() + if err != nil { + return fmt.Errorf("could not find an available port: %s", err) + } + t.Local = local + + ports := []string{fmt.Sprintf("%d:%d", t.Local, t.Remote)} + + pf, err := portforward.New(dialer, ports, t.stopChan, t.readyChan, t.Out, t.Out) + if err != nil { + return err + } + + errChan := make(chan error) + go func() { + errChan <- pf.ForwardPorts() + }() + + select { + case err = <-errChan: + return fmt.Errorf("forwarding ports: %v", err) + case <-pf.Ready: + return nil + } +} + +func getAvailablePort() (int, error) { + l, err := net.Listen("tcp", ":0") + if err != nil { + return 0, err + } + defer l.Close() + + _, p, err := net.SplitHostPort(l.Addr().String()) + if err != nil { + return 0, err + } + port, err := strconv.Atoi(p) + if err != nil { + return 0, err + } + return port, err +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel_test.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel_test.go new file mode 100644 index 000000000..264200ddf --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/tunnel_test.go @@ -0,0 +1,31 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "testing" +) + +func TestAvailablePort(t *testing.T) { + port, err := getAvailablePort() + if err != nil { + t.Fatal(err) + } + if port < 1 { + t.Fatalf("generated port should be > 1, got %d", port) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/kube/wait.go b/src/vendor/github.com/kubernetes/helm/pkg/kube/wait.go new file mode 100644 index 000000000..88f3c7d34 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/kube/wait.go @@ -0,0 +1,266 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/kube" + +import ( + "time" + + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" + podutil "k8s.io/kubernetes/pkg/api/v1/pod" + "k8s.io/kubernetes/pkg/apis/core/v1/helper" + deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util" +) + +// deployment holds associated replicaSets for a deployment +type deployment struct { + replicaSets *extensions.ReplicaSet + deployment *extensions.Deployment +} + +// waitForResources polls to get the current status of all pods, PVCs, and Services +// until all are ready or a timeout is reached +func (c *Client) waitForResources(timeout time.Duration, created Result) error { + c.Log("beginning wait for %d resources with timeout of %v", len(created), timeout) + + kcs, err := c.KubernetesClientSet() + if err != nil { + return err + } + return wait.Poll(2*time.Second, timeout, func() (bool, error) { + pods := []v1.Pod{} + services := []v1.Service{} + pvc := []v1.PersistentVolumeClaim{} + deployments := []deployment{} + for _, v := range created { + obj, err := v.Versioned() + if err != nil && !runtime.IsNotRegisteredError(err) { + return false, err + } + switch value := obj.(type) { + case *v1.ReplicationController: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *v1.Pod: + pod, err := kcs.CoreV1().Pods(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + pods = append(pods, *pod) + case *appsv1.Deployment: + currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1()) + if err != nil || newReplicaSet == nil { + return false, err + } + newDeployment := deployment{ + newReplicaSet, + currentDeployment, + } + deployments = append(deployments, newDeployment) + case *appsv1beta1.Deployment: + currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1()) + if err != nil || newReplicaSet == nil { + return false, err + } + newDeployment := deployment{ + newReplicaSet, + currentDeployment, + } + deployments = append(deployments, newDeployment) + case *appsv1beta2.Deployment: + currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1()) + if err != nil || newReplicaSet == nil { + return false, err + } + newDeployment := deployment{ + newReplicaSet, + currentDeployment, + } + deployments = append(deployments, newDeployment) + case *extensions.Deployment: + currentDeployment, err := kcs.ExtensionsV1beta1().Deployments(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + // Find RS associated with deployment + newReplicaSet, err := deploymentutil.GetNewReplicaSet(currentDeployment, kcs.ExtensionsV1beta1()) + if err != nil || newReplicaSet == nil { + return false, err + } + newDeployment := deployment{ + newReplicaSet, + currentDeployment, + } + deployments = append(deployments, newDeployment) + case *extensions.DaemonSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1.DaemonSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1beta2.DaemonSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1.StatefulSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1beta1.StatefulSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1beta2.StatefulSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *extensions.ReplicaSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1beta2.ReplicaSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *appsv1.ReplicaSet: + list, err := getPods(kcs, value.Namespace, value.Spec.Selector.MatchLabels) + if err != nil { + return false, err + } + pods = append(pods, list...) + case *v1.PersistentVolumeClaim: + claim, err := kcs.CoreV1().PersistentVolumeClaims(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + pvc = append(pvc, *claim) + case *v1.Service: + svc, err := kcs.CoreV1().Services(value.Namespace).Get(value.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + services = append(services, *svc) + } + } + isReady := c.podsReady(pods) && c.servicesReady(services) && c.volumesReady(pvc) && c.deploymentsReady(deployments) + return isReady, nil + }) +} + +func (c *Client) podsReady(pods []v1.Pod) bool { + for _, pod := range pods { + if !podutil.IsPodReady(&pod) { + c.Log("Pod is not ready: %s/%s", pod.GetNamespace(), pod.GetName()) + return false + } + } + return true +} + +func (c *Client) servicesReady(svc []v1.Service) bool { + for _, s := range svc { + // 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 == v1.ServiceTypeExternalName { + continue + } + + // Make sure the service is not explicitly set to "None" before checking the IP + if s.Spec.ClusterIP != v1.ClusterIPNone && !helper.IsServiceIPSet(&s) { + c.Log("Service is not ready: %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 == v1.ServiceTypeLoadBalancer && s.Status.LoadBalancer.Ingress == nil { + c.Log("Service is not ready: %s/%s", s.GetNamespace(), s.GetName()) + return false + } + } + return true +} + +func (c *Client) volumesReady(vols []v1.PersistentVolumeClaim) bool { + for _, v := range vols { + if v.Status.Phase != v1.ClaimBound { + c.Log("PersistentVolumeClaim is not ready: %s/%s", v.GetNamespace(), v.GetName()) + return false + } + } + return true +} + +func (c *Client) deploymentsReady(deployments []deployment) bool { + for _, v := range deployments { + if !(v.replicaSets.Status.ReadyReplicas >= *v.deployment.Spec.Replicas-deploymentutil.MaxUnavailable(*v.deployment)) { + c.Log("Deployment is not ready: %s/%s", v.deployment.GetNamespace(), v.deployment.GetName()) + return false + } + } + return true +} + +func getPods(client kubernetes.Interface, namespace string, selector map[string]string) ([]v1.Pod, error) { + list, err := client.CoreV1().Pods(namespace).List(metav1.ListOptions{ + FieldSelector: fields.Everything().String(), + LabelSelector: labels.Set(selector).AsSelector().String(), + }) + return list.Items, err +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/lint.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/lint.go new file mode 100644 index 000000000..256eab906 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/lint.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/lint" + +import ( + "path/filepath" + + "k8s.io/helm/pkg/lint/rules" + "k8s.io/helm/pkg/lint/support" +) + +// All runs all of the available linters on the given base directory. +func All(basedir string, values []byte, 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.Values(&linter) + rules.Templates(&linter, values, namespace, strict) + return linter +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/lint_test.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/lint_test.go new file mode 100644 index 000000000..d84faa10b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/lint_test.go @@ -0,0 +1,98 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/helm/pkg/lint/support" + + "testing" +) + +var values = []byte{} + +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" + +func TestBadChart(t *testing.T) { + m := All(badChartDir, values, namespace, strict).Messages + if len(m) != 5 { + 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 one ERROR messages, check for them + var i, w, e, e2, e3 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 is less than or equal to 0") { + e = true + } + if strings.Contains(msg.Err.Error(), "name is required") { + e2 = true + } + if strings.Contains(msg.Err.Error(), "directory name (badchartfile) and chart name () must be the same") { + e3 = true + } + } + } + if !e || !e2 || !e3 || !w || !i { + 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(), "cannot unmarshal") { + 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.Errorf("All failed but shouldn't have: %#v", m) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile.go new file mode 100644 index 000000000..0dab0d250 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile.go @@ -0,0 +1,172 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/lint/rules" + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver" + + "github.com/asaskevich/govalidator" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/lint/support" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +// 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 parseable ChartFile + if !validChartFile { + return + } + + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartName(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartNameDirMatch(linter.ChartDir, chartFile)) + + // Chart metadata + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartVersion(chartFile)) + linter.RunLinterRule(support.ErrorSev, chartFileName, validateChartEngine(chartFile)) + 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)) +} + +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 fmt.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 validateChartNameDirMatch(chartDir string, cf *chart.Metadata) error { + if cf.Name != filepath.Base(chartDir) { + return fmt.Errorf("directory name (%s) and chart name (%s) must be the same", filepath.Base(chartDir), cf.Name) + } + 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 fmt.Errorf("version '%s' is not a valid SemVer", cf.Version) + } + + c, err := semver.NewConstraint("> 0") + if err != nil { + return err + } + valid, msg := c.Validate(version) + + if !valid && len(msg) > 0 { + return fmt.Errorf("version %v", msg[0]) + } + + return nil +} + +func validateChartEngine(cf *chart.Metadata) error { + if cf.Engine == "" { + return nil + } + + keys := make([]string, 0, len(chart.Metadata_Engine_value)) + for engine := range chart.Metadata_Engine_value { + str := strings.ToLower(engine) + + if str == "unknown" { + continue + } + + if str == cf.Engine { + return nil + } + + keys = append(keys, str) + } + + return fmt.Errorf("engine '%v' not valid. Valid options are %v", cf.Engine, keys) +} + +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 fmt.Errorf("invalid email '%s' for maintainer '%s'", maintainer.Email, maintainer.Name) + } else if maintainer.Url != "" && !govalidator.IsURL(maintainer.Url) { + return fmt.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 fmt.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 fmt.Errorf("invalid icon URL '%s'", cf.Icon) + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile_test.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile_test.go new file mode 100644 index 000000000..99dc4de0f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/chartfile_test.go @@ -0,0 +1,249 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/lint/support" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const ( + badChartDir = "testdata/badchartfile" + goodChartDir = "testdata/goodone" +) + +var ( + badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") + goodChartFilePath = filepath.Join(goodChartDir, "Chart.yaml") + nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") +) + +var badChart, chatLoadRrr = chartutil.LoadChartfile(badChartFilePath) +var goodChart, _ = chartutil.LoadChartfile(goodChartFilePath) + +// 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 TestValidateChartNameDirMatch(t *testing.T) { + err := validateChartNameDirMatch(goodChartDir, goodChart) + if err != nil { + t.Errorf("validateChartNameDirMatch to return no error, gor a linter error") + } + // It has not name + err = validateChartNameDirMatch(badChartDir, badChart) + if err == nil { + t.Errorf("validatechartnamedirmatch to return a linter error, got no error") + } + + // Wrong path + err = validateChartNameDirMatch(badChartDir, goodChart) + if err == nil { + t.Errorf("validatechartnamedirmatch to return a linter error, got no error") + } +} + +func TestValidateChartVersion(t *testing.T) { + var failTest = []struct { + Version string + ErrorMsg string + }{ + {"", "version is required"}, + {"0", "0 is less than or equal to 0"}, + {"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 TestValidateChartEngine(t *testing.T) { + var successTest = []string{"", "gotpl"} + + for _, engine := range successTest { + badChart.Engine = engine + err := validateChartEngine(badChart) + if err != nil { + t.Errorf("validateChartEngine(%s) to return no error, got a linter error %s", engine, err.Error()) + } + } + + badChart.Engine = "foobar" + err := validateChartEngine(badChart) + if err == nil || !strings.Contains(err.Error(), "not valid. Valid options are [gotpl") { + t.Errorf("validateChartEngine(%s) to return an error, got no error", badChart.Engine) + } +} + +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) { + linter := support.Linter{ChartDir: badChartDir} + Chartfile(&linter) + msgs := linter.Messages + + if len(msgs) != 4 { + t.Errorf("Expected 3 errors, got %d", len(msgs)) + } + + 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(), "directory name (badchartfile) and chart name () must be the same") { + t.Errorf("Unexpected message 1: %s", msgs[1].Err) + } + + if !strings.Contains(msgs[2].Err.Error(), "version 0.0.0 is less than or equal to 0") { + 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) + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template.go new file mode 100644 index 000000000..a8b6a6757 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template.go @@ -0,0 +1,165 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/lint/support" + cpb "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/timeconv" + tversion "k8s.io/helm/pkg/version" +) + +// Templates lints the templates in the Linter. +func Templates(linter *support.Linter, values []byte, namespace string, strict bool) { + path := "templates/" + templatesPath := filepath.Join(linter.ChartDir, path) + + templatesDirExist := linter.RunLinterRule(support.WarningSev, path, validateTemplatesDir(templatesPath)) + + // Templates directory is optional for now + if !templatesDirExist { + return + } + + // Load chart and parse templates, based on tiller/release_server + chart, err := chartutil.Load(linter.ChartDir) + + chartLoaded := linter.RunLinterRule(support.ErrorSev, path, err) + + if !chartLoaded { + return + } + + options := chartutil.ReleaseOptions{Name: "testRelease", Time: timeconv.Now(), Namespace: namespace} + caps := &chartutil.Capabilities{ + APIVersions: chartutil.DefaultVersionSet, + KubeVersion: chartutil.DefaultKubeVersion, + TillerVersion: tversion.GetVersionProto(), + } + cvals, err := chartutil.CoalesceValues(chart, &cpb.Config{Raw: string(values)}) + if err != nil { + return + } + // convert our values back into config + yvals, err := cvals.YAML() + if err != nil { + return + } + cc := &cpb.Config{Raw: yvals} + valuesToRender, err := chartutil.ToRenderValuesCaps(chart, cc, options, caps) + if err != nil { + // FIXME: This seems to generate a duplicate, but I can't find where the first + // error is coming from. + //linter.RunLinterRule(support.ErrorSev, err) + return + } + e := engine.New() + if strict { + e.Strict = true + } + renderedContentMap, err := e.Render(chart, valuesToRender) + + renderOk := linter.RunLinterRule(support.ErrorSev, path, 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, _ := template.Name, template.Data + path = fileName + + linter.RunLinterRule(support.ErrorSev, path, validateAllowedExtension(fileName)) + + // 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/kubernetes/helm/issues/1463 + // Check that all the templates have a matching value + //linter.RunLinterRule(support.WarningSev, path, validateNoMissingValues(templatesPath, valuesToRender, preExecutedTemplate)) + + // NOTE: disabled for now, Refs https://github.com/kubernetes/helm/issues/1037 + // linter.RunLinterRule(support.WarningSev, path, validateQuotes(string(preExecutedTemplate))) + + renderedContent := renderedContentMap[filepath.Join(chart.GetMetadata().Name, fileName)] + var yamlStruct K8sYamlStruct + // Even though K8sYamlStruct only defines Metadata namespace, an error in any other + // key will be raised as well + err := yaml.Unmarshal([]byte(renderedContent), &yamlStruct) + + validYaml := linter.RunLinterRule(support.ErrorSev, path, validateYamlContent(err)) + + if !validYaml { + continue + } + } +} + +// Validation functions +func validateTemplatesDir(templatesPath string) error { + if fi, err := os.Stat(templatesPath); err != nil { + return errors.New("directory not found") + } else if err == nil && !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 fmt.Errorf("file extension '%s' not valid. Valid extensions are .yaml, .yml, .tpl, or .txt", ext) +} + +func validateYamlContent(err error) error { + if err != nil { + return fmt.Errorf("unable to parse YAML\n\t%s", err) + } + return nil +} + +// K8sYamlStruct stubs a Kubernetes YAML file. +// Need to access for now to Namespace only +type K8sYamlStruct struct { + Metadata struct { + Namespace string + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template_test.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template_test.go new file mode 100644 index 000000000..cb1be94a2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/template_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/helm/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 = []byte("nameOverride: ''\nhttpPort: 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) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/Chart.yaml new file mode 100644 index 000000000..c108fa5e5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/Chart.yaml @@ -0,0 +1,4 @@ +name: albatross +description: testing chart +version: 199.44.12345-Alpha.1+cafe009 +icon: http://riverrun.io diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl new file mode 100644 index 000000000..24f76db73 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/_helpers.tpl @@ -0,0 +1,16 @@ +{{/* 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/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/fail.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/fail.yaml new file mode 100644 index 000000000..a11e0e90e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/fail.yaml @@ -0,0 +1 @@ +{{ deliberateSyntaxError }} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/svc.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/svc.yaml new file mode 100644 index 000000000..167148112 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/templates/svc.yaml @@ -0,0 +1,20 @@ +# 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: + heritage: {{ .Release.Service | quote }} + release: {{ .Release.Name | quote }} + chart: "{{.Chart.Name}}-{{.Chart.Version}}" + kubeVersion: {{ .Capabilities.KubeVersion.Major }} + tillerVersion: {{ .Capabilities.TillerVersion }} +spec: + ports: + - port: {{default 80 .Values.httpPort | quote}} + targetPort: 80 + protocol: TCP + name: http + selector: + app: {{template "fullname" .}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/values.yaml new file mode 100644 index 000000000..74cc6a0dc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/albatross/values.yaml @@ -0,0 +1 @@ +name: "mariner" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/Chart.yaml new file mode 100644 index 000000000..dbb4a1501 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/Chart.yaml @@ -0,0 +1,3 @@ +description: A Helm chart for Kubernetes +version: 0.0.0 +home: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/values.yaml new file mode 100644 index 000000000..9f367033b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badchartfile/values.yaml @@ -0,0 +1 @@ +# Default values for badchartfile. diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml new file mode 100644 index 000000000..bed845249 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/Chart.yaml @@ -0,0 +1,5 @@ +name: badvaluesfile +description: A Helm chart for Kubernetes +version: 0.0.1 +home: "" +icon: http://riverrun.io diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml new file mode 100644 index 000000000..6c2ceb8db --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/templates/badvaluesfile.yaml @@ -0,0 +1,2 @@ +metadata: + name: {{.name | default "foo" | title}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/values.yaml new file mode 100644 index 000000000..b5a10271c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/badvaluesfile/values.yaml @@ -0,0 +1,2 @@ +# Invalid value for badvaluesfile for testing lint fails with invalid yaml format +name= "value" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/Chart.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/Chart.yaml new file mode 100644 index 000000000..de05463ca --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/Chart.yaml @@ -0,0 +1,4 @@ +name: goodone +description: good testing chart +version: 199.44.12345-Alpha.1+cafe009 +icon: http://riverrun.io diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/templates/goodone.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/templates/goodone.yaml new file mode 100644 index 000000000..0e77f46f2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/templates/goodone.yaml @@ -0,0 +1,2 @@ +metadata: + name: {{.Values.name | default "foo" | title}} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/values.yaml b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/values.yaml new file mode 100644 index 000000000..fe9abd983 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/testdata/goodone/values.yaml @@ -0,0 +1 @@ +name: "goodone here" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/values.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/values.go new file mode 100644 index 000000000..9b97598f0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/rules/values.go @@ -0,0 +1,55 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/lint/support" +) + +// Values lints a chart's values.yaml file. +func Values(linter *support.Linter) { + file := "values.yaml" + vf := filepath.Join(linter.ChartDir, file) + fileExists := linter.RunLinterRule(support.InfoSev, file, validateValuesFileExistence(linter, vf)) + + if !fileExists { + return + } + + linter.RunLinterRule(support.ErrorSev, file, validateValuesFile(linter, vf)) +} + +func validateValuesFileExistence(linter *support.Linter, valuesPath string) error { + _, err := os.Stat(valuesPath) + if err != nil { + return fmt.Errorf("file does not exist") + } + return nil +} + +func validateValuesFile(linter *support.Linter, valuesPath string) error { + _, err := chartutil.ReadValuesFile(valuesPath) + if err != nil { + return fmt.Errorf("unable to parse YAML\n\t%s", err) + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/support/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/doc.go new file mode 100644 index 000000000..4cf7272e4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/lint/support" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message.go new file mode 100644 index 000000000..6a878031a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message.go @@ -0,0 +1,76 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 indicatest 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/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message_test.go b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message_test.go new file mode 100644 index 000000000..4a9c33c34 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/lint/support/message_test.go @@ -0,0 +1,79 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "errors" + "testing" +) + +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/src/vendor/github.com/kubernetes/helm/pkg/plugin/cache/cache.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/cache/cache.go new file mode 100644 index 000000000..a1d3224c8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/cache/cache.go @@ -0,0 +1,74 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/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 + var err error + var strip bool + 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{ + Scheme: "ssh", + User: url.User(m[1]), + Host: m[2], + Path: "/" + m[3], + } + strip = true + } else { + u, err = url.Parse(repo) + if err != nil { + return "", err + } + } + + if strip { + u.Scheme = "" + } + + var key string + if u.Scheme != "" { + key = u.Scheme + "-" + } + if u.User != nil && u.User.Username() != "" { + key = key + u.User.Username() + "-" + } + key = key + u.Host + if u.Path != "" { + key = key + strings.Replace(u.Path, "/", "-", -1) + } + + key = strings.Replace(key, ":", "-", -1) + + return key, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/hooks.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/hooks.go new file mode 100644 index 000000000..b5ca032ac --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/hooks.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/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 + +// Get returns a hook for an event. +func (hooks Hooks) Get(event string) string { + h, _ := hooks[event] + return h +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/base.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/base.go new file mode 100644 index 000000000..0664dae76 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/base.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "os" + "path/filepath" + + "k8s.io/helm/pkg/helm/helmpath" +) + +type base struct { + // Source is the reference to a plugin + Source string + // HelmHome is the $HELM_HOME directory + HelmHome helmpath.Home +} + +func newBase(source string, home helmpath.Home) base { + return base{source, home} +} + +// link creates a symlink from the plugin source to $HELM_HOME. +func (b *base) link(from string) error { + debug("symlinking %s to %s", from, b.Path()) + return os.Symlink(from, b.Path()) +} + +// Path is where the plugin will be symlinked to. +func (b *base) Path() string { + if b.Source == "" { + return "" + } + return filepath.Join(b.HelmHome.Plugins(), filepath.Base(b.Source)) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/doc.go new file mode 100644 index 000000000..a2a66f3e1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer.go new file mode 100644 index 000000000..91d497651 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer.go @@ -0,0 +1,208 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin/cache" + "os" + "path/filepath" + "regexp" + "strings" +) + +// 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{}, +} + +// 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, fmt.Errorf("no extractor implemented yet for %s", source) +} + +// NewHTTPInstaller creates a new HttpInstaller. +func NewHTTPInstaller(source string, home helmpath.Home) (*HTTPInstaller, error) { + + key, err := cache.Key(source) + if err != nil { + return nil, err + } + + extractor, err := NewExtractor(source) + if err != nil { + return nil, err + } + + getConstructor, err := getter.ByScheme("http", environment.EnvSettings{}) + if err != nil { + return nil, err + } + + get, err := getConstructor.New(source, "", "", "") + if err != nil { + return nil, err + } + + i := &HTTPInstaller{ + CacheDir: home.Path("cache", "plugins", key), + PluginName: stripPluginName(filepath.Base(source)), + base: newBase(source, home), + 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 creates a symlink to the plugin directory in $HELM_HOME. +// +// Implements Installer. +func (i *HTTPInstaller) Install() error { + + pluginData, err := i.getter.Get(i.Source) + if err != nil { + return err + } + + err = i.extractor.Extract(pluginData, i.CacheDir) + if err != nil { + return err + } + + if !isPlugin(i.CacheDir) { + return ErrMissingMetadata + } + + src, err := filepath.Abs(i.CacheDir) + if err != nil { + return err + } + + return i.link(src) +} + +// Update updates a local repository +// Not implemented for now since tarball most likely will be packaged by version +func (i *HTTPInstaller) Update() error { + return fmt.Errorf("method Update() not implemented for HttpInstaller") +} + +// Override link because we want to use HttpInstaller.Path() not base.Path() +func (i *HTTPInstaller) link(from string) error { + debug("symlinking %s to %s", from, i.Path()) + return os.Symlink(from, i.Path()) +} + +// 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 filepath.Join(i.base.HelmHome.Plugins(), i.PluginName) +} + +// 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 + } + + tarReader := tar.NewReader(uncompressedStream) + + os.MkdirAll(targetDir, 0755) + + for true { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + return err + } + + path := filepath.Join(targetDir, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.Mkdir(path, 0755); err != nil { + return err + } + case tar.TypeReg: + outFile, err := os.Create(path) + if err != nil { + return err + } + if _, err := io.Copy(outFile, tarReader); err != nil { + outFile.Close() + return err + } + outFile.Close() + default: + return fmt.Errorf("unknown type: %b in %s", header.Typeflag, header.Name) + } + } + + return nil + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer_test.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer_test.go new file mode 100644 index 000000000..ca1a71e3e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/http_installer_test.go @@ -0,0 +1,189 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "bytes" + "encoding/base64" + "fmt" + "io/ioutil" + "k8s.io/helm/pkg/helm/helmpath" + "os" + "testing" +) + +var _ Installer = new(HTTPInstaller) + +// Fake http client +type TestHTTPGetter struct { + MockResponse *bytes.Buffer + MockError error +} + +func (t *TestHTTPGetter) Get(href string) (*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 TestHTTPInstaller(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.1", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("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.Error(err) + } + if i.Path() != home.Path("plugins", "fake-plugin") { + t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + } + + // Install again to test plugin exists error + if err := Install(i); err == nil { + t.Error("expected error for plugin exists, got none") + } else if err.Error() != "plugin already exists" { + t.Errorf("expected error for plugin exists, got (%v)", err) + } + +} + +func TestHTTPInstallerNonExistentVersion(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.2.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.2", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("expected a HTTPInstaller") + } + + // inject fake http client responding with error + httpInstaller.getter = &TestHTTPGetter{ + MockError: fmt.Errorf("failed to download plugin for some reason"), + } + + // attempt to install the plugin + if err := Install(i); err == nil { + t.Error("expected error from http client") + } + +} + +func TestHTTPInstallerUpdate(t *testing.T) { + source := "https://repo.localdomain/plugins/fake-plugin-0.0.1.tar.gz" + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + i, err := NewForSource(source, "0.0.1", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + // ensure a HTTPInstaller was returned + httpInstaller, ok := i.(*HTTPInstaller) + if !ok { + t.Error("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.Error(err) + } + if i.Path() != home.Path("plugins", "fake-plugin") { + t.Errorf("expected path '$HELM_HOME/plugins/fake-plugin', got %q", i.Path()) + } + + // Update plugin, should fail because it is not implemented + if err := Update(i); err == nil { + t.Error("update method not implemented for http installer") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/installer.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/installer.go new file mode 100644 index 000000000..02aee9f46 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/installer.go @@ -0,0 +1,116 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "errors" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "k8s.io/helm/pkg/helm/helmpath" +) + +// 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 to $HELM_HOME. + Install() error + // Path is the directory of the installed plugin. + Path() string + // Update updates a plugin to $HELM_HOME. + Update() error +} + +// Install installs a plugin to $HELM_HOME. +func Install(i Installer) error { + if _, pathErr := os.Stat(path.Dir(i.Path())); os.IsNotExist(pathErr) { + return errors.New(`plugin home "$HELM_HOME/plugins" does not exist`) + } + + if _, pathErr := os.Stat(i.Path()); !os.IsNotExist(pathErr) { + return errors.New("plugin already exists") + } + + return i.Install() +} + +// Update updates a plugin in $HELM_HOME. +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, home helmpath.Home) (Installer, error) { + // Check if source is a local directory + if isLocalReference(source) { + return NewLocalInstaller(source, home) + } else if isRemoteHTTPArchive(source) { + return NewHTTPInstaller(source, home) + } + return NewVCSInstaller(source, version, home) +} + +// FindSource determines the correct Installer for the given source. +func FindSource(location string, home helmpath.Home) (Installer, error) { + installer, err := existingVCSRepo(location, home) + 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 +func isRemoteHTTPArchive(source string) bool { + if strings.HasPrefix(source, "http://") || strings.HasPrefix(source, "https://") { + for suffix := range Extractors { + if strings.HasSuffix(source, 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.yaml")) + return err == nil +} + +func debug(format string, args ...interface{}) { + if Debug { + format = fmt.Sprintf("[debug] %s\n", format) + fmt.Printf(format, args...) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer.go new file mode 100644 index 000000000..3cf6bb422 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer.go @@ -0,0 +1,56 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "fmt" + "path/filepath" + + "k8s.io/helm/pkg/helm/helmpath" +) + +// LocalInstaller installs plugins from the filesystem. +type LocalInstaller struct { + base +} + +// NewLocalInstaller creates a new LocalInstaller. +func NewLocalInstaller(source string, home helmpath.Home) (*LocalInstaller, error) { + src, err := filepath.Abs(source) + if err != nil { + return nil, fmt.Errorf("unable to get absolute path to plugin: %v", err) + } + i := &LocalInstaller{ + base: newBase(src, home), + } + return i, nil +} + +// Install creates a symlink to the plugin directory in $HELM_HOME. +// +// Implements Installer. +func (i *LocalInstaller) Install() error { + if !isPlugin(i.Source) { + return ErrMissingMetadata + } + return i.link(i.Source) +} + +// Update updates a local repository +func (i *LocalInstaller) Update() error { + debug("local repository is auto-updated") + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer_test.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer_test.go new file mode 100644 index 000000000..6a7c957d6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/local_installer_test.go @@ -0,0 +1,64 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" +) + +var _ Installer = new(LocalInstaller) + +func TestLocalInstaller(t *testing.T) { + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + // Make a temp dir + tdir, err := ioutil.TempDir("", "helm-installer-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tdir) + if err := ioutil.WriteFile(filepath.Join(tdir, "plugin.yaml"), []byte{}, 0644); err != nil { + t.Fatal(err) + } + + source := "../testdata/plugdir/echo" + i, err := NewForSource(source, "", home) + if err != nil { + t.Errorf("unexpected error: %s", err) + } + + if err := Install(i); err != nil { + t.Error(err) + } + + if i.Path() != home.Path("plugins", "echo") { + t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer.go new file mode 100644 index 000000000..0a373a971 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer.go @@ -0,0 +1,175 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "errors" + "fmt" + "os" + "sort" + + "github.com/Masterminds/semver" + "github.com/Masterminds/vcs" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/plugin/cache" +) + +// VCSInstaller installs plugins from remote a repository. +type VCSInstaller struct { + Repo vcs.Repo + Version string + base +} + +func existingVCSRepo(location string, home helmpath.Home) (Installer, error) { + repo, err := vcs.NewRepo("", location) + if err != nil { + return nil, err + } + i := &VCSInstaller{ + Repo: repo, + base: newBase(repo.Remote(), home), + } + return i, err +} + +// NewVCSInstaller creates a new VCSInstaller. +func NewVCSInstaller(source, version string, home helmpath.Home) (*VCSInstaller, error) { + key, err := cache.Key(source) + if err != nil { + return nil, err + } + cachedpath := home.Path("cache", "plugins", key) + repo, err := vcs.NewRepo(source, cachedpath) + if err != nil { + return nil, err + } + i := &VCSInstaller{ + Repo: repo, + Version: version, + base: newBase(source, home), + } + return i, err +} + +// Install clones a remote repository and creates a symlink to the plugin directory in HELM_HOME. +// +// 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 + } + + return i.link(i.Repo.LocalPath()) +} + +// 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 constrint passes get the original reference + ver := v.Original() + debug("setting to %s", ver) + return ver, nil + } + } + + return "", fmt.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/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer_test.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer_test.go new file mode 100644 index 000000000..453899543 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/installer/vcs_installer_test.go @@ -0,0 +1,203 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin/installer" + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/helm/helmpath" + + "github.com/Masterminds/vcs" +) + +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) { + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + source := "https://github.com/adamreese/helm-env" + testRepoPath, _ := filepath.Abs("../testdata/plugdir/echo") + repo := &testRepo{ + local: testRepoPath, + tags: []string{"0.1.0", "0.1.1"}, + } + + i, err := NewForSource(source, "~0.1.0", home) + 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.Errorf("expected version '0.1.1', got %q", repo.current) + } + if i.Path() != home.Path("plugins", "helm-env") { + t.Errorf("expected path '$HELM_HOME/plugins/helm-env', got %q", i.Path()) + } + + // Install again to test plugin exists error + if err := Install(i); err == nil { + t.Error("expected error for plugin exists, got none") + } else if err.Error() != "plugin already exists" { + t.Errorf("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(), home); err == nil { + t.Error("expected error for inability to find plugin source, got none") + } else if err.Error() != "cannot get information about plugin source" { + t.Errorf("expected error for inability to find plugin source, got (%v)", err) + } +} + +func TestVCSInstallerNonExistentVersion(t *testing.T) { + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + source := "https://github.com/adamreese/helm-env" + version := "0.2.0" + + i, err := NewForSource(source, version, home) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // ensure a VCSInstaller was returned + _, ok := i.(*VCSInstaller) + if !ok { + t.Fatal("expected a VCSInstaller") + } + + if err := Install(i); err == nil { + t.Error("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.Errorf("expected error for version does not exists, got (%v)", err) + } +} +func TestVCSInstallerUpdate(t *testing.T) { + + hh, err := ioutil.TempDir("", "helm-home-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(hh) + + home := helmpath.Home(hh) + if err := os.MkdirAll(home.Plugins(), 0755); err != nil { + t.Fatalf("Could not create %s: %s", home.Plugins(), err) + } + + source := "https://github.com/adamreese/helm-env" + + i, err := NewForSource(source, "", home) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + // ensure a VCSInstaller was returned + _, ok := i.(*VCSInstaller) + if !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(), home) + if err != nil { + t.Fatal(err) + } + + repoRemote := pluginInfo.(*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 + os.Remove(filepath.Join(i.Path(), "plugin.yaml")) + // Testing update for error + if err := Update(i); err == nil { + t.Error("expected error for plugin modified, got none") + } else if err.Error() != "plugin repo was modified" { + t.Errorf("expected error for plugin modified, got (%v)", err) + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin.go new file mode 100644 index 000000000..b3458c2d8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin.go @@ -0,0 +1,200 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin" + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + helm_env "k8s.io/helm/pkg/helm/environment" + + "github.com/ghodss/yaml" +) + +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"` +} + +// 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. + 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"` + + // UseTunnel indicates that this command needs a tunnel. + // Setting this will cause a number of side effects, such as the + // automatic setting of HELM_HOST. + UseTunnel bool `json:"useTunnel"` + + // 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"` +} + +// 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 +} + +// PrepareCommand takes a Plugin.Command and prepares it for execution. +// +// 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) { + parts := strings.Split(os.ExpandEnv(p.Metadata.Command), " ") + main := parts[0] + baseArgs := []string{} + if len(parts) > 1 { + baseArgs = parts[1:] + } + if !p.Metadata.IgnoreFlags { + baseArgs = append(baseArgs, extraArgs...) + } + return main, baseArgs +} + +// LoadDir loads a plugin from the given directory. +func LoadDir(dirname string) (*Plugin, error) { + data, err := ioutil.ReadFile(filepath.Join(dirname, pluginFileName)) + if err != nil { + return nil, err + } + + plug := &Plugin{Dir: dirname} + if err := yaml.Unmarshal(data, &plug.Metadata); err != nil { + return nil, err + } + return plug, nil +} + +// 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, err + } + + 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, nil +} + +// 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 helm_env.EnvSettings, + shortName, base string) { + for key, val := range map[string]string{ + "HELM_PLUGIN_NAME": shortName, + "HELM_PLUGIN_DIR": base, + "HELM_BIN": os.Args[0], + + // Set vars that may not have been set, and save client the + // trouble of re-parsing. + "HELM_PLUGIN": settings.PluginDirs(), + "HELM_HOME": settings.Home.String(), + + // Set vars that convey common information. + "HELM_PATH_REPOSITORY": settings.Home.Repository(), + "HELM_PATH_REPOSITORY_FILE": settings.Home.RepositoryFile(), + "HELM_PATH_CACHE": settings.Home.Cache(), + "HELM_PATH_LOCAL_REPOSITORY": settings.Home.LocalRepository(), + "HELM_PATH_STARTER": settings.Home.Starters(), + + "TILLER_HOST": settings.TillerHost, + "TILLER_NAMESPACE": settings.TillerNamespace, + } { + os.Setenv(key, val) + } + + if settings.Debug { + os.Setenv("HELM_DEBUG", "1") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin_test.go b/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin_test.go new file mode 100644 index 000000000..5ddbf15f3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/plugin_test.go @@ -0,0 +1,153 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/plugin" + +import ( + "reflect" + "testing" +) + +func TestPrepareCommand(t *testing.T) { + p := &Plugin{ + Dir: "/tmp", // Unused + Metadata: &Metadata{ + Name: "test", + Command: "echo -n foo", + }, + } + argv := []string{"--debug", "--foo", "bar"} + + cmd, args := p.PrepareCommand(argv) + if cmd != "echo" { + t.Errorf("Expected echo, got %q", cmd) + } + + if l := len(args); l != 5 { + t.Errorf("expected 5 args, got %d", l) + } + + expect := []string{"-n", "foo", "--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 = p.PrepareCommand(argv) + if cmd != "echo" { + t.Errorf("Expected echo, got %q", cmd) + } + if l := len(args); l != 2 { + t.Errorf("expected 2 args, got %d", l) + } + expect = []string{"-n", "foo"} + for i := 0; i < len(args); i++ { + if expect[i] != args[i] { + t.Errorf("Expected arg=%q, got %q", expect[i], args[i]) + } + } +} + +func TestLoadDir(t *testing.T) { + dirname := "testdata/plugdir/hello" + plug, err := LoadDir(dirname) + if err != nil { + t.Fatalf("error loading Hello plugin: %s", err) + } + + if plug.Dir != dirname { + t.Errorf("Expected dir %q, got %q", dirname, plug.Dir) + } + + expect := &Metadata{ + Name: "hello", + Version: "0.1.0", + Usage: "usage", + Description: "description", + Command: "$HELM_PLUGIN_SELF/hello.sh", + UseTunnel: true, + IgnoreFlags: true, + Hooks: map[string]string{ + Install: "echo installing...", + }, + } + + if !reflect.DeepEqual(expect, plug.Metadata) { + t.Errorf("Expected plugin metadata %v, got %v", expect, plug.Metadata) + } +} + +func TestDownloader(t *testing.T) { + dirname := "testdata/plugdir/downloader" + plug, err := LoadDir(dirname) + if err != nil { + t.Fatalf("error loading Hello plugin: %s", err) + } + + if plug.Dir != dirname { + t.Errorf("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.Errorf("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" + 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) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/downloader/plugin.yaml b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/downloader/plugin.yaml new file mode 100644 index 000000000..c0b90379b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/downloader/plugin.yaml @@ -0,0 +1,11 @@ +name: "downloader" +version: "1.2.3" +usage: "usage" +description: |- + download something +command: "echo Hello" +downloaders: + - protocols: + - "myprotocol" + - "myprotocols" + command: "echo Download" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/echo/plugin.yaml b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/echo/plugin.yaml new file mode 100644 index 000000000..8baa35b6d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/echo/plugin.yaml @@ -0,0 +1,8 @@ +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/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/hello.sh b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/hello.sh new file mode 100755 index 000000000..db7c0f54d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/hello.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "Hello from a Helm plugin" + +echo "PARAMS" +echo $* + +echo "ENVIRONMENT" +echo $TILLER_HOST +echo $HELM_HOME + +$HELM_BIN --host $TILLER_HOST ls --all + diff --git a/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/plugin.yaml b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/plugin.yaml new file mode 100644 index 000000000..cdb27b291 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/plugin/testdata/plugdir/hello/plugin.yaml @@ -0,0 +1,11 @@ +name: "hello" +version: "0.1.0" +usage: "usage" +description: |- + description +command: "$HELM_PLUGIN_SELF/hello.sh" +useTunnel: true +ignoreFlags: true +install: "echo installing..." +hooks: + install: "echo installing..." diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/chart.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/chart.pb.go new file mode 100644 index 000000000..a884ed552 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/chart.pb.go @@ -0,0 +1,119 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/chart/chart.proto + +/* +Package chart is a generated protocol buffer package. + +It is generated from these files: + hapi/chart/chart.proto + hapi/chart/config.proto + hapi/chart/metadata.proto + hapi/chart/template.proto + +It has these top-level messages: + Chart + Config + Value + Maintainer + Metadata + Template +*/ +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/any" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// 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 { + // Contents of the Chartfile. + Metadata *Metadata `protobuf:"bytes,1,opt,name=metadata" json:"metadata,omitempty"` + // Templates for this chart. + Templates []*Template `protobuf:"bytes,2,rep,name=templates" json:"templates,omitempty"` + // Charts that this chart depends on. + Dependencies []*Chart `protobuf:"bytes,3,rep,name=dependencies" json:"dependencies,omitempty"` + // Default config for this template. + Values *Config `protobuf:"bytes,4,opt,name=values" json:"values,omitempty"` + // Miscellaneous files in a chart archive, + // e.g. README, LICENSE, etc. + Files []*google_protobuf.Any `protobuf:"bytes,5,rep,name=files" json:"files,omitempty"` +} + +func (m *Chart) Reset() { *m = Chart{} } +func (m *Chart) String() string { return proto.CompactTextString(m) } +func (*Chart) ProtoMessage() {} +func (*Chart) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Chart) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +func (m *Chart) GetTemplates() []*Template { + if m != nil { + return m.Templates + } + return nil +} + +func (m *Chart) GetDependencies() []*Chart { + if m != nil { + return m.Dependencies + } + return nil +} + +func (m *Chart) GetValues() *Config { + if m != nil { + return m.Values + } + return nil +} + +func (m *Chart) GetFiles() []*google_protobuf.Any { + if m != nil { + return m.Files + } + return nil +} + +func init() { + proto.RegisterType((*Chart)(nil), "hapi.chart.Chart") +} + +func init() { proto.RegisterFile("hapi/chart/chart.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 242 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0x15, 0x4a, 0x0a, 0x1c, 0x2c, 0x58, 0x08, 0x4c, 0xa7, 0x8a, 0x09, 0x75, 0x70, 0x50, + 0x11, 0x0f, 0x00, 0xcc, 0x2c, 0x16, 0x13, 0xdb, 0xb5, 0xb9, 0xa4, 0x91, 0x52, 0x3b, 0xaa, 0x5d, + 0xa4, 0xbe, 0x3b, 0x03, 0xea, 0xd9, 0xa6, 0x09, 0xea, 0x12, 0x29, 0xf7, 0x7d, 0xff, 0xe5, 0xbf, + 0xc0, 0xed, 0x0a, 0xbb, 0xa6, 0x58, 0xae, 0x70, 0xe3, 0xc3, 0x53, 0x75, 0x1b, 0xeb, 0xad, 0x80, + 0xfd, 0x5c, 0xf1, 0x64, 0x72, 0xd7, 0x77, 0xac, 0xa9, 0x9a, 0x3a, 0x48, 0x93, 0xfb, 0x1e, 0x58, + 0x93, 0xc7, 0x12, 0x3d, 0x1e, 0x41, 0x9e, 0xd6, 0x5d, 0x8b, 0x9e, 0x12, 0xaa, 0xad, 0xad, 0x5b, + 0x2a, 0xf8, 0x6d, 0xb1, 0xad, 0x0a, 0x34, 0xbb, 0x80, 0x1e, 0x7e, 0x32, 0xc8, 0xdf, 0xf7, 0x19, + 0xf1, 0x04, 0xe7, 0x69, 0xa3, 0xcc, 0xa6, 0xd9, 0xe3, 0xe5, 0xfc, 0x46, 0x1d, 0x2a, 0xa9, 0x8f, + 0xc8, 0xf4, 0x9f, 0x25, 0xe6, 0x70, 0x91, 0x3e, 0xe4, 0xe4, 0xc9, 0x74, 0xf4, 0x3f, 0xf2, 0x19, + 0xa1, 0x3e, 0x68, 0xe2, 0x05, 0xae, 0x4a, 0xea, 0xc8, 0x94, 0x64, 0x96, 0x0d, 0x39, 0x39, 0xe2, + 0xd8, 0x75, 0x3f, 0xc6, 0x75, 0xf4, 0x40, 0x13, 0x33, 0x18, 0x7f, 0x63, 0xbb, 0x25, 0x27, 0x4f, + 0xb9, 0x9a, 0x18, 0x04, 0xf8, 0x0f, 0xe9, 0x68, 0x88, 0x19, 0xe4, 0x55, 0xd3, 0x92, 0x93, 0x79, + 0xac, 0x14, 0xae, 0x57, 0xe9, 0x7a, 0xf5, 0x6a, 0x76, 0x3a, 0x28, 0x6f, 0x67, 0x5f, 0x39, 0xef, + 0x58, 0x8c, 0x99, 0x3e, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0xe9, 0x70, 0x34, 0x75, 0x9e, 0x01, + 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/config.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/config.pb.go new file mode 100644 index 000000000..30c652700 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/config.pb.go @@ -0,0 +1,78 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/chart/config.proto + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Config supplies values to the parametrizable templates of a chart. +type Config struct { + Raw string `protobuf:"bytes,1,opt,name=raw" json:"raw,omitempty"` + Values map[string]*Value `protobuf:"bytes,2,rep,name=values" json:"values,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (m *Config) Reset() { *m = Config{} } +func (m *Config) String() string { return proto.CompactTextString(m) } +func (*Config) ProtoMessage() {} +func (*Config) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +func (m *Config) GetRaw() string { + if m != nil { + return m.Raw + } + return "" +} + +func (m *Config) GetValues() map[string]*Value { + if m != nil { + return m.Values + } + return nil +} + +// Value describes a configuration value as a string. +type Value struct { + Value string `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"` +} + +func (m *Value) Reset() { *m = Value{} } +func (m *Value) String() string { return proto.CompactTextString(m) } +func (*Value) ProtoMessage() {} +func (*Value) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{1} } + +func (m *Value) GetValue() string { + if m != nil { + return m.Value + } + return "" +} + +func init() { + proto.RegisterType((*Config)(nil), "hapi.chart.Config") + proto.RegisterType((*Value)(nil), "hapi.chart.Value") +} + +func init() { proto.RegisterFile("hapi/chart/config.proto", fileDescriptor1) } + +var fileDescriptor1 = []byte{ + // 182 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xcf, 0x48, 0x2c, 0xc8, + 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x4f, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0xd7, 0x2b, 0x28, + 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe8, 0x81, 0x25, 0x94, 0x16, 0x30, 0x72, 0xb1, 0x39, + 0x83, 0x25, 0x85, 0x04, 0xb8, 0x98, 0x8b, 0x12, 0xcb, 0x25, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, + 0x40, 0x4c, 0x21, 0x33, 0x2e, 0xb6, 0xb2, 0xc4, 0x9c, 0xd2, 0xd4, 0x62, 0x09, 0x26, 0x05, 0x66, + 0x0d, 0x6e, 0x23, 0x39, 0x3d, 0x84, 0x4e, 0x3d, 0x88, 0x2e, 0xbd, 0x30, 0xb0, 0x02, 0xd7, 0xbc, + 0x92, 0xa2, 0xca, 0x20, 0xa8, 0x6a, 0x29, 0x1f, 0x2e, 0x6e, 0x24, 0x61, 0x90, 0xc1, 0xd9, 0xa9, + 0x95, 0x30, 0x83, 0xb3, 0x53, 0x2b, 0x85, 0xd4, 0xb9, 0x58, 0xc1, 0x4a, 0x25, 0x98, 0x14, 0x18, + 0x35, 0xb8, 0x8d, 0x04, 0x91, 0xcd, 0x05, 0xeb, 0x0c, 0x82, 0xc8, 0x5b, 0x31, 0x59, 0x30, 0x2a, + 0xc9, 0x72, 0xb1, 0x82, 0xc5, 0x84, 0x44, 0x60, 0xba, 0x20, 0x26, 0x41, 0x38, 0x4e, 0xec, 0x51, + 0xac, 0x60, 0x8d, 0x49, 0x6c, 0x60, 0xdf, 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x12, + 0x60, 0xda, 0xf8, 0x00, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/metadata.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/metadata.pb.go new file mode 100644 index 000000000..9daeaa9e5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/metadata.pb.go @@ -0,0 +1,276 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/chart/metadata.proto + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Metadata_Engine int32 + +const ( + Metadata_UNKNOWN Metadata_Engine = 0 + Metadata_GOTPL Metadata_Engine = 1 +) + +var Metadata_Engine_name = map[int32]string{ + 0: "UNKNOWN", + 1: "GOTPL", +} +var Metadata_Engine_value = map[string]int32{ + "UNKNOWN": 0, + "GOTPL": 1, +} + +func (x Metadata_Engine) String() string { + return proto.EnumName(Metadata_Engine_name, int32(x)) +} +func (Metadata_Engine) EnumDescriptor() ([]byte, []int) { return fileDescriptor2, []int{1, 0} } + +// Maintainer describes a Chart maintainer. +type Maintainer struct { + // Name is a user name or organization name + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Email is an optional email address to contact the named maintainer + Email string `protobuf:"bytes,2,opt,name=email" json:"email,omitempty"` + // Url is an optional URL to an address for the named maintainer + Url string `protobuf:"bytes,3,opt,name=url" json:"url,omitempty"` +} + +func (m *Maintainer) Reset() { *m = Maintainer{} } +func (m *Maintainer) String() string { return proto.CompactTextString(m) } +func (*Maintainer) ProtoMessage() {} +func (*Maintainer) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } + +func (m *Maintainer) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Maintainer) GetEmail() string { + if m != nil { + return m.Email + } + return "" +} + +func (m *Maintainer) GetUrl() string { + if m != nil { + return m.Url + } + return "" +} + +// Metadata for a Chart file. This models the structure of a Chart.yaml file. +// +// Spec: https://k8s.io/helm/blob/master/docs/design/chart_format.md#the-chart-file +type Metadata struct { + // The name of the chart + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // The URL to a relevant project page, git repo, or contact person + Home string `protobuf:"bytes,2,opt,name=home" json:"home,omitempty"` + // Source is the URL to the source code of this chart + Sources []string `protobuf:"bytes,3,rep,name=sources" json:"sources,omitempty"` + // A SemVer 2 conformant version string of the chart + Version string `protobuf:"bytes,4,opt,name=version" json:"version,omitempty"` + // A one-sentence description of the chart + Description string `protobuf:"bytes,5,opt,name=description" json:"description,omitempty"` + // A list of string keywords + Keywords []string `protobuf:"bytes,6,rep,name=keywords" json:"keywords,omitempty"` + // A list of name and URL/email address combinations for the maintainer(s) + Maintainers []*Maintainer `protobuf:"bytes,7,rep,name=maintainers" json:"maintainers,omitempty"` + // The name of the template engine to use. Defaults to 'gotpl'. + Engine string `protobuf:"bytes,8,opt,name=engine" json:"engine,omitempty"` + // The URL to an icon file. + Icon string `protobuf:"bytes,9,opt,name=icon" json:"icon,omitempty"` + // The API Version of this chart. + ApiVersion string `protobuf:"bytes,10,opt,name=apiVersion" json:"apiVersion,omitempty"` + // The condition to check to enable chart + Condition string `protobuf:"bytes,11,opt,name=condition" json:"condition,omitempty"` + // The tags to check to enable chart + Tags string `protobuf:"bytes,12,opt,name=tags" json:"tags,omitempty"` + // The version of the application enclosed inside of this chart. + AppVersion string `protobuf:"bytes,13,opt,name=appVersion" json:"appVersion,omitempty"` + // Whether or not this chart is deprecated + Deprecated bool `protobuf:"varint,14,opt,name=deprecated" json:"deprecated,omitempty"` + // TillerVersion is a SemVer constraints on what version of Tiller is required. + // See SemVer ranges here: https://github.com/Masterminds/semver#basic-comparisons + TillerVersion string `protobuf:"bytes,15,opt,name=tillerVersion" json:"tillerVersion,omitempty"` + // Annotations are additional mappings uninterpreted by Tiller, + // made available for inspection by other applications. + Annotations map[string]string `protobuf:"bytes,16,rep,name=annotations" json:"annotations,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + // KubeVersion is a SemVer constraint specifying the version of Kubernetes required. + KubeVersion string `protobuf:"bytes,17,opt,name=kubeVersion" json:"kubeVersion,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} } + +func (m *Metadata) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Metadata) GetHome() string { + if m != nil { + return m.Home + } + return "" +} + +func (m *Metadata) GetSources() []string { + if m != nil { + return m.Sources + } + return nil +} + +func (m *Metadata) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +func (m *Metadata) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *Metadata) GetKeywords() []string { + if m != nil { + return m.Keywords + } + return nil +} + +func (m *Metadata) GetMaintainers() []*Maintainer { + if m != nil { + return m.Maintainers + } + return nil +} + +func (m *Metadata) GetEngine() string { + if m != nil { + return m.Engine + } + return "" +} + +func (m *Metadata) GetIcon() string { + if m != nil { + return m.Icon + } + return "" +} + +func (m *Metadata) GetApiVersion() string { + if m != nil { + return m.ApiVersion + } + return "" +} + +func (m *Metadata) GetCondition() string { + if m != nil { + return m.Condition + } + return "" +} + +func (m *Metadata) GetTags() string { + if m != nil { + return m.Tags + } + return "" +} + +func (m *Metadata) GetAppVersion() string { + if m != nil { + return m.AppVersion + } + return "" +} + +func (m *Metadata) GetDeprecated() bool { + if m != nil { + return m.Deprecated + } + return false +} + +func (m *Metadata) GetTillerVersion() string { + if m != nil { + return m.TillerVersion + } + return "" +} + +func (m *Metadata) GetAnnotations() map[string]string { + if m != nil { + return m.Annotations + } + return nil +} + +func (m *Metadata) GetKubeVersion() string { + if m != nil { + return m.KubeVersion + } + return "" +} + +func init() { + proto.RegisterType((*Maintainer)(nil), "hapi.chart.Maintainer") + proto.RegisterType((*Metadata)(nil), "hapi.chart.Metadata") + proto.RegisterEnum("hapi.chart.Metadata_Engine", Metadata_Engine_name, Metadata_Engine_value) +} + +func init() { proto.RegisterFile("hapi/chart/metadata.proto", fileDescriptor2) } + +var fileDescriptor2 = []byte{ + // 435 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x52, 0x5d, 0x6b, 0xd4, 0x40, + 0x14, 0x35, 0xcd, 0x66, 0x77, 0x73, 0x63, 0x35, 0x0e, 0x52, 0xc6, 0x22, 0x12, 0x16, 0x85, 0x7d, + 0xda, 0x82, 0xbe, 0x14, 0x1f, 0x04, 0x85, 0x52, 0x41, 0xbb, 0x95, 0xe0, 0x07, 0xf8, 0x36, 0x4d, + 0x2e, 0xdd, 0x61, 0x93, 0x99, 0x30, 0x99, 0xad, 0xec, 0xaf, 0xf0, 0x2f, 0xcb, 0xdc, 0x64, 0x9a, + 0xac, 0xf4, 0xed, 0x9e, 0x73, 0x66, 0xce, 0xcc, 0xbd, 0xf7, 0xc0, 0x8b, 0x8d, 0x68, 0xe4, 0x59, + 0xb1, 0x11, 0xc6, 0x9e, 0xd5, 0x68, 0x45, 0x29, 0xac, 0x58, 0x35, 0x46, 0x5b, 0xcd, 0xc0, 0x49, + 0x2b, 0x92, 0x16, 0x9f, 0x01, 0xae, 0x84, 0x54, 0x56, 0x48, 0x85, 0x86, 0x31, 0x98, 0x28, 0x51, + 0x23, 0x0f, 0xb2, 0x60, 0x19, 0xe7, 0x54, 0xb3, 0xe7, 0x10, 0x61, 0x2d, 0x64, 0xc5, 0x8f, 0x88, + 0xec, 0x00, 0x4b, 0x21, 0xdc, 0x99, 0x8a, 0x87, 0xc4, 0xb9, 0x72, 0xf1, 0x37, 0x82, 0xf9, 0x55, + 0xff, 0xd0, 0x83, 0x46, 0x0c, 0x26, 0x1b, 0x5d, 0x63, 0xef, 0x43, 0x35, 0xe3, 0x30, 0x6b, 0xf5, + 0xce, 0x14, 0xd8, 0xf2, 0x30, 0x0b, 0x97, 0x71, 0xee, 0xa1, 0x53, 0xee, 0xd0, 0xb4, 0x52, 0x2b, + 0x3e, 0xa1, 0x0b, 0x1e, 0xb2, 0x0c, 0x92, 0x12, 0xdb, 0xc2, 0xc8, 0xc6, 0x3a, 0x35, 0x22, 0x75, + 0x4c, 0xb1, 0x53, 0x98, 0x6f, 0x71, 0xff, 0x47, 0x9b, 0xb2, 0xe5, 0x53, 0xb2, 0xbd, 0xc7, 0xec, + 0x1c, 0x92, 0xfa, 0xbe, 0xe1, 0x96, 0xcf, 0xb2, 0x70, 0x99, 0xbc, 0x3d, 0x59, 0x0d, 0x23, 0x59, + 0x0d, 0xf3, 0xc8, 0xc7, 0x47, 0xd9, 0x09, 0x4c, 0x51, 0xdd, 0x4a, 0x85, 0x7c, 0x4e, 0x4f, 0xf6, + 0xc8, 0xf5, 0x25, 0x0b, 0xad, 0x78, 0xdc, 0xf5, 0xe5, 0x6a, 0xf6, 0x0a, 0x40, 0x34, 0xf2, 0x67, + 0xdf, 0x00, 0x90, 0x32, 0x62, 0xd8, 0x4b, 0x88, 0x0b, 0xad, 0x4a, 0x49, 0x1d, 0x24, 0x24, 0x0f, + 0x84, 0x73, 0xb4, 0xe2, 0xb6, 0xe5, 0x8f, 0x3b, 0x47, 0x57, 0x77, 0x8e, 0x8d, 0x77, 0x3c, 0xf6, + 0x8e, 0x9e, 0x71, 0x7a, 0x89, 0x8d, 0xc1, 0x42, 0x58, 0x2c, 0xf9, 0x93, 0x2c, 0x58, 0xce, 0xf3, + 0x11, 0xc3, 0x5e, 0xc3, 0xb1, 0x95, 0x55, 0x85, 0xc6, 0x5b, 0x3c, 0x25, 0x8b, 0x43, 0x92, 0x5d, + 0x42, 0x22, 0x94, 0xd2, 0x56, 0xb8, 0x7f, 0xb4, 0x3c, 0xa5, 0xe9, 0xbc, 0x39, 0x98, 0x8e, 0xcf, + 0xd2, 0xc7, 0xe1, 0xdc, 0x85, 0xb2, 0x66, 0x9f, 0x8f, 0x6f, 0xba, 0x25, 0x6d, 0x77, 0x37, 0xe8, + 0x1f, 0x7b, 0xd6, 0x2d, 0x69, 0x44, 0x9d, 0x7e, 0x80, 0xf4, 0x7f, 0x0b, 0x97, 0xaa, 0x2d, 0xee, + 0xfb, 0xd4, 0xb8, 0xd2, 0xa5, 0xef, 0x4e, 0x54, 0x3b, 0x9f, 0x9a, 0x0e, 0xbc, 0x3f, 0x3a, 0x0f, + 0x16, 0x19, 0x4c, 0x2f, 0xba, 0x05, 0x24, 0x30, 0xfb, 0xb1, 0xfe, 0xb2, 0xbe, 0xfe, 0xb5, 0x4e, + 0x1f, 0xb1, 0x18, 0xa2, 0xcb, 0xeb, 0xef, 0xdf, 0xbe, 0xa6, 0xc1, 0xa7, 0xd9, 0xef, 0x88, 0xfe, + 0x7c, 0x33, 0xa5, 0xdc, 0xbf, 0xfb, 0x17, 0x00, 0x00, 0xff, 0xff, 0x36, 0xf9, 0x0d, 0xa6, 0x14, + 0x03, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/template.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/template.pb.go new file mode 100644 index 000000000..439aec5a8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/chart/template.pb.go @@ -0,0 +1,60 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/chart/template.proto + +package chart + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Template represents a template as a name/value pair. +// +// By convention, name is a relative path within the scope of the chart's +// base directory. +type Template struct { + // Name is the path-like name of the template. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Data is the template as byte data. + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (m *Template) Reset() { *m = Template{} } +func (m *Template) String() string { return proto.CompactTextString(m) } +func (*Template) ProtoMessage() {} +func (*Template) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} } + +func (m *Template) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Template) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func init() { + proto.RegisterType((*Template)(nil), "hapi.chart.Template") +} + +func init() { proto.RegisterFile("hapi/chart/template.proto", fileDescriptor3) } + +var fileDescriptor3 = []byte{ + // 107 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcc, 0x48, 0x2c, 0xc8, + 0xd4, 0x4f, 0xce, 0x48, 0x2c, 0x2a, 0xd1, 0x2f, 0x49, 0xcd, 0x2d, 0xc8, 0x49, 0x2c, 0x49, 0xd5, + 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x02, 0x49, 0xe9, 0x81, 0xa5, 0x94, 0x8c, 0xb8, 0x38, + 0x42, 0xa0, 0xb2, 0x42, 0x42, 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, + 0x9c, 0x41, 0x60, 0x36, 0x48, 0x2c, 0x25, 0xb1, 0x24, 0x51, 0x82, 0x49, 0x81, 0x51, 0x83, 0x27, + 0x08, 0xcc, 0x76, 0x62, 0x8f, 0x62, 0x05, 0x6b, 0x4e, 0x62, 0x03, 0x9b, 0x67, 0x0c, 0x08, 0x00, + 0x00, 0xff, 0xff, 0x53, 0xee, 0x0e, 0x67, 0x6c, 0x00, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/hook.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/hook.pb.go new file mode 100644 index 000000000..00fa5c188 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/hook.pb.go @@ -0,0 +1,231 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/hook.proto + +/* +Package release is a generated protocol buffer package. + +It is generated from these files: + hapi/release/hook.proto + hapi/release/info.proto + hapi/release/release.proto + hapi/release/status.proto + hapi/release/test_run.proto + hapi/release/test_suite.proto + +It has these top-level messages: + Hook + Info + Release + Status + TestRun + TestSuite +*/ +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Hook_Event int32 + +const ( + Hook_UNKNOWN Hook_Event = 0 + Hook_PRE_INSTALL Hook_Event = 1 + Hook_POST_INSTALL Hook_Event = 2 + Hook_PRE_DELETE Hook_Event = 3 + Hook_POST_DELETE Hook_Event = 4 + Hook_PRE_UPGRADE Hook_Event = 5 + Hook_POST_UPGRADE Hook_Event = 6 + Hook_PRE_ROLLBACK Hook_Event = 7 + Hook_POST_ROLLBACK Hook_Event = 8 + Hook_RELEASE_TEST_SUCCESS Hook_Event = 9 + Hook_RELEASE_TEST_FAILURE Hook_Event = 10 +) + +var Hook_Event_name = map[int32]string{ + 0: "UNKNOWN", + 1: "PRE_INSTALL", + 2: "POST_INSTALL", + 3: "PRE_DELETE", + 4: "POST_DELETE", + 5: "PRE_UPGRADE", + 6: "POST_UPGRADE", + 7: "PRE_ROLLBACK", + 8: "POST_ROLLBACK", + 9: "RELEASE_TEST_SUCCESS", + 10: "RELEASE_TEST_FAILURE", +} +var Hook_Event_value = map[string]int32{ + "UNKNOWN": 0, + "PRE_INSTALL": 1, + "POST_INSTALL": 2, + "PRE_DELETE": 3, + "POST_DELETE": 4, + "PRE_UPGRADE": 5, + "POST_UPGRADE": 6, + "PRE_ROLLBACK": 7, + "POST_ROLLBACK": 8, + "RELEASE_TEST_SUCCESS": 9, + "RELEASE_TEST_FAILURE": 10, +} + +func (x Hook_Event) String() string { + return proto.EnumName(Hook_Event_name, int32(x)) +} +func (Hook_Event) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } + +type Hook_DeletePolicy int32 + +const ( + Hook_SUCCEEDED Hook_DeletePolicy = 0 + Hook_FAILED Hook_DeletePolicy = 1 + Hook_BEFORE_HOOK_CREATION Hook_DeletePolicy = 2 +) + +var Hook_DeletePolicy_name = map[int32]string{ + 0: "SUCCEEDED", + 1: "FAILED", + 2: "BEFORE_HOOK_CREATION", +} +var Hook_DeletePolicy_value = map[string]int32{ + "SUCCEEDED": 0, + "FAILED": 1, + "BEFORE_HOOK_CREATION": 2, +} + +func (x Hook_DeletePolicy) String() string { + return proto.EnumName(Hook_DeletePolicy_name, int32(x)) +} +func (Hook_DeletePolicy) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 1} } + +// Hook defines a hook object. +type Hook struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Kind is the Kubernetes kind. + Kind string `protobuf:"bytes,2,opt,name=kind" json:"kind,omitempty"` + // Path is the chart-relative path to the template. + Path string `protobuf:"bytes,3,opt,name=path" json:"path,omitempty"` + // Manifest is the manifest contents. + Manifest string `protobuf:"bytes,4,opt,name=manifest" json:"manifest,omitempty"` + // Events are the events that this hook fires on. + Events []Hook_Event `protobuf:"varint,5,rep,packed,name=events,enum=hapi.release.Hook_Event" json:"events,omitempty"` + // LastRun indicates the date/time this was last run. + LastRun *google_protobuf.Timestamp `protobuf:"bytes,6,opt,name=last_run,json=lastRun" json:"last_run,omitempty"` + // Weight indicates the sort order for execution among similar Hook type + Weight int32 `protobuf:"varint,7,opt,name=weight" json:"weight,omitempty"` + // DeletePolicies are the policies that indicate when to delete the hook + DeletePolicies []Hook_DeletePolicy `protobuf:"varint,8,rep,packed,name=delete_policies,json=deletePolicies,enum=hapi.release.Hook_DeletePolicy" json:"delete_policies,omitempty"` +} + +func (m *Hook) Reset() { *m = Hook{} } +func (m *Hook) String() string { return proto.CompactTextString(m) } +func (*Hook) ProtoMessage() {} +func (*Hook) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Hook) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Hook) GetKind() string { + if m != nil { + return m.Kind + } + return "" +} + +func (m *Hook) GetPath() string { + if m != nil { + return m.Path + } + return "" +} + +func (m *Hook) GetManifest() string { + if m != nil { + return m.Manifest + } + return "" +} + +func (m *Hook) GetEvents() []Hook_Event { + if m != nil { + return m.Events + } + return nil +} + +func (m *Hook) GetLastRun() *google_protobuf.Timestamp { + if m != nil { + return m.LastRun + } + return nil +} + +func (m *Hook) GetWeight() int32 { + if m != nil { + return m.Weight + } + return 0 +} + +func (m *Hook) GetDeletePolicies() []Hook_DeletePolicy { + if m != nil { + return m.DeletePolicies + } + return nil +} + +func init() { + proto.RegisterType((*Hook)(nil), "hapi.release.Hook") + proto.RegisterEnum("hapi.release.Hook_Event", Hook_Event_name, Hook_Event_value) + proto.RegisterEnum("hapi.release.Hook_DeletePolicy", Hook_DeletePolicy_name, Hook_DeletePolicy_value) +} + +func init() { proto.RegisterFile("hapi/release/hook.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 445 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0x51, 0x8f, 0x9a, 0x40, + 0x10, 0x80, 0x8f, 0x13, 0x41, 0x47, 0xcf, 0xdb, 0x6e, 0x9a, 0x76, 0xe3, 0xcb, 0x19, 0x9f, 0x7c, + 0xc2, 0xe6, 0x9a, 0xfe, 0x00, 0x84, 0xb9, 0x6a, 0x24, 0x60, 0x16, 0x4c, 0x93, 0xbe, 0x10, 0xae, + 0xee, 0x29, 0x11, 0x81, 0x08, 0xb6, 0xe9, 0x0f, 0xec, 0x3f, 0xe8, 0x0f, 0x6a, 0x76, 0x45, 0x7b, + 0x49, 0xfb, 0x36, 0xf3, 0xcd, 0x37, 0xc3, 0x0c, 0x0b, 0xef, 0x77, 0x49, 0x99, 0x4e, 0x8f, 0x22, + 0x13, 0x49, 0x25, 0xa6, 0xbb, 0xa2, 0xd8, 0x5b, 0xe5, 0xb1, 0xa8, 0x0b, 0xda, 0x97, 0x05, 0xab, + 0x29, 0x0c, 0x1f, 0xb6, 0x45, 0xb1, 0xcd, 0xc4, 0x54, 0xd5, 0x9e, 0x4f, 0x2f, 0xd3, 0x3a, 0x3d, + 0x88, 0xaa, 0x4e, 0x0e, 0xe5, 0x59, 0x1f, 0xff, 0xd2, 0x41, 0x9f, 0x17, 0xc5, 0x9e, 0x52, 0xd0, + 0xf3, 0xe4, 0x20, 0x98, 0x36, 0xd2, 0x26, 0x5d, 0xae, 0x62, 0xc9, 0xf6, 0x69, 0xbe, 0x61, 0xb7, + 0x67, 0x26, 0x63, 0xc9, 0xca, 0xa4, 0xde, 0xb1, 0xd6, 0x99, 0xc9, 0x98, 0x0e, 0xa1, 0x73, 0x48, + 0xf2, 0xf4, 0x45, 0x54, 0x35, 0xd3, 0x15, 0xbf, 0xe6, 0xf4, 0x03, 0x18, 0xe2, 0xbb, 0xc8, 0xeb, + 0x8a, 0xb5, 0x47, 0xad, 0xc9, 0xe0, 0x91, 0x59, 0xaf, 0x17, 0xb4, 0xe4, 0xb7, 0x2d, 0x94, 0x02, + 0x6f, 0x3c, 0xfa, 0x09, 0x3a, 0x59, 0x52, 0xd5, 0xf1, 0xf1, 0x94, 0x33, 0x63, 0xa4, 0x4d, 0x7a, + 0x8f, 0x43, 0xeb, 0x7c, 0x86, 0x75, 0x39, 0xc3, 0x8a, 0x2e, 0x67, 0x70, 0x53, 0xba, 0xfc, 0x94, + 0xd3, 0x77, 0x60, 0xfc, 0x10, 0xe9, 0x76, 0x57, 0x33, 0x73, 0xa4, 0x4d, 0xda, 0xbc, 0xc9, 0xe8, + 0x1c, 0xee, 0x37, 0x22, 0x13, 0xb5, 0x88, 0xcb, 0x22, 0x4b, 0xbf, 0xa5, 0xa2, 0x62, 0x1d, 0xb5, + 0xc9, 0xc3, 0x7f, 0x36, 0x71, 0x95, 0xb9, 0x92, 0xe2, 0x4f, 0x3e, 0xd8, 0xfc, 0xcd, 0x52, 0x51, + 0x8d, 0x7f, 0x6b, 0xd0, 0x56, 0xab, 0xd2, 0x1e, 0x98, 0x6b, 0x7f, 0xe9, 0x07, 0x5f, 0x7c, 0x72, + 0x43, 0xef, 0xa1, 0xb7, 0xe2, 0x18, 0x2f, 0xfc, 0x30, 0xb2, 0x3d, 0x8f, 0x68, 0x94, 0x40, 0x7f, + 0x15, 0x84, 0xd1, 0x95, 0xdc, 0xd2, 0x01, 0x80, 0x54, 0x5c, 0xf4, 0x30, 0x42, 0xd2, 0x52, 0x2d, + 0xd2, 0x68, 0x80, 0x7e, 0x99, 0xb1, 0x5e, 0x7d, 0xe6, 0xb6, 0x8b, 0xa4, 0x7d, 0x9d, 0x71, 0x21, + 0x86, 0x22, 0x1c, 0x63, 0x1e, 0x78, 0xde, 0xcc, 0x76, 0x96, 0xc4, 0xa4, 0x6f, 0xe0, 0x4e, 0x39, + 0x57, 0xd4, 0xa1, 0x0c, 0xde, 0x72, 0xf4, 0xd0, 0x0e, 0x31, 0x8e, 0x30, 0x8c, 0xe2, 0x70, 0xed, + 0x38, 0x18, 0x86, 0xa4, 0xfb, 0x4f, 0xe5, 0xc9, 0x5e, 0x78, 0x6b, 0x8e, 0x04, 0xc6, 0x0e, 0xf4, + 0x5f, 0x9f, 0x4d, 0xef, 0xa0, 0xab, 0xda, 0xd0, 0x45, 0x97, 0xdc, 0x50, 0x00, 0x43, 0xba, 0xe8, + 0x12, 0x4d, 0x0e, 0x99, 0xe1, 0x53, 0xc0, 0x31, 0x9e, 0x07, 0xc1, 0x32, 0x76, 0x38, 0xda, 0xd1, + 0x22, 0xf0, 0xc9, 0xed, 0xac, 0xfb, 0xd5, 0x6c, 0x7e, 0xe4, 0xb3, 0xa1, 0x5e, 0xe9, 0xe3, 0x9f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x13, 0x64, 0x75, 0x6c, 0xa3, 0x02, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/info.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/info.pb.go new file mode 100644 index 000000000..7a7ccdd74 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/info.pb.go @@ -0,0 +1,90 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/info.proto + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// Info describes release information. +type Info struct { + Status *Status `protobuf:"bytes,1,opt,name=status" json:"status,omitempty"` + FirstDeployed *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=first_deployed,json=firstDeployed" json:"first_deployed,omitempty"` + LastDeployed *google_protobuf.Timestamp `protobuf:"bytes,3,opt,name=last_deployed,json=lastDeployed" json:"last_deployed,omitempty"` + // Deleted tracks when this object was deleted. + Deleted *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=deleted" json:"deleted,omitempty"` + // Description is human-friendly "log entry" about this release. + Description string `protobuf:"bytes,5,opt,name=Description" json:"Description,omitempty"` +} + +func (m *Info) Reset() { *m = Info{} } +func (m *Info) String() string { return proto.CompactTextString(m) } +func (*Info) ProtoMessage() {} +func (*Info) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} } + +func (m *Info) GetStatus() *Status { + if m != nil { + return m.Status + } + return nil +} + +func (m *Info) GetFirstDeployed() *google_protobuf.Timestamp { + if m != nil { + return m.FirstDeployed + } + return nil +} + +func (m *Info) GetLastDeployed() *google_protobuf.Timestamp { + if m != nil { + return m.LastDeployed + } + return nil +} + +func (m *Info) GetDeleted() *google_protobuf.Timestamp { + if m != nil { + return m.Deleted + } + return nil +} + +func (m *Info) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func init() { + proto.RegisterType((*Info)(nil), "hapi.release.Info") +} + +func init() { proto.RegisterFile("hapi/release/info.proto", fileDescriptor1) } + +var fileDescriptor1 = []byte{ + // 235 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0x31, 0x4f, 0xc3, 0x30, + 0x10, 0x85, 0x95, 0x52, 0x5a, 0xd5, 0x6d, 0x19, 0x2c, 0x24, 0x42, 0x16, 0x22, 0xa6, 0x0e, 0xc8, + 0x91, 0x80, 0x1d, 0x81, 0xba, 0xb0, 0x06, 0x26, 0x16, 0xe4, 0xe2, 0x73, 0xb1, 0xe4, 0xe6, 0x2c, + 0xfb, 0x3a, 0xf0, 0x2f, 0xf8, 0xc9, 0xa8, 0xb6, 0x83, 0xd2, 0xa9, 0xab, 0xbf, 0xf7, 0x3e, 0xbf, + 0x63, 0x57, 0xdf, 0xd2, 0x99, 0xc6, 0x83, 0x05, 0x19, 0xa0, 0x31, 0x9d, 0x46, 0xe1, 0x3c, 0x12, + 0xf2, 0xc5, 0x01, 0x88, 0x0c, 0xaa, 0x9b, 0x2d, 0xe2, 0xd6, 0x42, 0x13, 0xd9, 0x66, 0xaf, 0x1b, + 0x32, 0x3b, 0x08, 0x24, 0x77, 0x2e, 0xc5, 0xab, 0xeb, 0x23, 0x4f, 0x20, 0x49, 0xfb, 0x90, 0xd0, + 0xed, 0xef, 0x88, 0x8d, 0x5f, 0x3b, 0x8d, 0xfc, 0x8e, 0x4d, 0x12, 0x28, 0x8b, 0xba, 0x58, 0xcd, + 0xef, 0x2f, 0xc5, 0xf0, 0x0f, 0xf1, 0x16, 0x59, 0x9b, 0x33, 0xfc, 0x99, 0x5d, 0x68, 0xe3, 0x03, + 0x7d, 0x2a, 0x70, 0x16, 0x7f, 0x40, 0x95, 0xa3, 0xd8, 0xaa, 0x44, 0xda, 0x22, 0xfa, 0x2d, 0xe2, + 0xbd, 0xdf, 0xd2, 0x2e, 0x63, 0x63, 0x9d, 0x0b, 0xfc, 0x89, 0x2d, 0xad, 0x1c, 0x1a, 0xce, 0x4e, + 0x1a, 0x16, 0x87, 0xc2, 0xbf, 0xe0, 0x91, 0x4d, 0x15, 0x58, 0x20, 0x50, 0xe5, 0xf8, 0x64, 0xb5, + 0x8f, 0xf2, 0x9a, 0xcd, 0xd7, 0x10, 0xbe, 0xbc, 0x71, 0x64, 0xb0, 0x2b, 0xcf, 0xeb, 0x62, 0x35, + 0x6b, 0x87, 0x4f, 0x2f, 0xb3, 0x8f, 0x69, 0xbe, 0x7a, 0x33, 0x89, 0xa6, 0x87, 0xbf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x1a, 0x52, 0x8f, 0x9c, 0x89, 0x01, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/release.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/release.pb.go new file mode 100644 index 000000000..511b543d7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/release.pb.go @@ -0,0 +1,124 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/release.proto + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" +import hapi_chart3 "k8s.io/helm/pkg/proto/hapi/chart" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// 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 `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Info provides information about a release + Info *Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + // Chart is the chart that was released. + Chart *hapi_chart3.Chart `protobuf:"bytes,3,opt,name=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 *hapi_chart.Config `protobuf:"bytes,4,opt,name=config" json:"config,omitempty"` + // Manifest is the string representation of the rendered template. + Manifest string `protobuf:"bytes,5,opt,name=manifest" json:"manifest,omitempty"` + // Hooks are all of the hooks declared for this release. + Hooks []*Hook `protobuf:"bytes,6,rep,name=hooks" json:"hooks,omitempty"` + // Version is an int32 which represents the version of the release. + Version int32 `protobuf:"varint,7,opt,name=version" json:"version,omitempty"` + // Namespace is the kubernetes namespace of the release. + Namespace string `protobuf:"bytes,8,opt,name=namespace" json:"namespace,omitempty"` +} + +func (m *Release) Reset() { *m = Release{} } +func (m *Release) String() string { return proto.CompactTextString(m) } +func (*Release) ProtoMessage() {} +func (*Release) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} } + +func (m *Release) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *Release) GetInfo() *Info { + if m != nil { + return m.Info + } + return nil +} + +func (m *Release) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *Release) GetConfig() *hapi_chart.Config { + if m != nil { + return m.Config + } + return nil +} + +func (m *Release) GetManifest() string { + if m != nil { + return m.Manifest + } + return "" +} + +func (m *Release) GetHooks() []*Hook { + if m != nil { + return m.Hooks + } + return nil +} + +func (m *Release) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *Release) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +func init() { + proto.RegisterType((*Release)(nil), "hapi.release.Release") +} + +func init() { proto.RegisterFile("hapi/release/release.proto", fileDescriptor2) } + +var fileDescriptor2 = []byte{ + // 256 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0xbf, 0x4e, 0xc3, 0x40, + 0x0c, 0xc6, 0x95, 0x36, 0x7f, 0x1a, 0xc3, 0x82, 0x07, 0xb0, 0x22, 0x86, 0x88, 0x01, 0x22, 0x86, + 0x54, 0x82, 0x37, 0x80, 0x05, 0xd6, 0x1b, 0xd9, 0x8e, 0xe8, 0x42, 0x4e, 0xa5, 0xe7, 0x28, 0x17, + 0xf1, 0x2c, 0x3c, 0x2e, 0xba, 0x3f, 0x85, 0x94, 0x2e, 0x4e, 0xec, 0xdf, 0xa7, 0xcf, 0xdf, 0x19, + 0xaa, 0x41, 0x8e, 0x7a, 0x3b, 0xa9, 0x4f, 0x25, 0xad, 0x3a, 0x7c, 0xdb, 0x71, 0xe2, 0x99, 0xf1, + 0xdc, 0xb1, 0x36, 0xce, 0xaa, 0xab, 0x23, 0xe5, 0xc0, 0xbc, 0x0b, 0xb2, 0x7f, 0x40, 0x9b, 0x9e, + 0x8f, 0x40, 0x37, 0xc8, 0x69, 0xde, 0x76, 0x6c, 0x7a, 0xfd, 0x11, 0xc1, 0xe5, 0x12, 0xb8, 0x1a, + 0xe6, 0x37, 0xdf, 0x2b, 0x28, 0x44, 0xf0, 0x41, 0x84, 0xd4, 0xc8, 0xbd, 0xa2, 0xa4, 0x4e, 0x9a, + 0x52, 0xf8, 0x7f, 0xbc, 0x85, 0xd4, 0xd9, 0xd3, 0xaa, 0x4e, 0x9a, 0xb3, 0x07, 0x6c, 0x97, 0xf9, + 0xda, 0x57, 0xd3, 0xb3, 0xf0, 0x1c, 0xef, 0x20, 0xf3, 0xb6, 0xb4, 0xf6, 0xc2, 0x8b, 0x20, 0x0c, + 0x9b, 0x9e, 0x5d, 0x15, 0x81, 0xe3, 0x3d, 0xe4, 0x21, 0x18, 0xa5, 0x4b, 0xcb, 0xa8, 0xf4, 0x44, + 0x44, 0x05, 0x56, 0xb0, 0xd9, 0x4b, 0xa3, 0x7b, 0x65, 0x67, 0xca, 0x7c, 0xa8, 0xdf, 0x1e, 0x1b, + 0xc8, 0xdc, 0x41, 0x2c, 0xe5, 0xf5, 0xfa, 0x34, 0xd9, 0x0b, 0xf3, 0x4e, 0x04, 0x01, 0x12, 0x14, + 0x5f, 0x6a, 0xb2, 0x9a, 0x0d, 0x15, 0x75, 0xd2, 0x64, 0xe2, 0xd0, 0xe2, 0x35, 0x94, 0xee, 0x91, + 0x76, 0x94, 0x9d, 0xa2, 0x8d, 0x5f, 0xf0, 0x37, 0x78, 0x2a, 0xdf, 0x8a, 0x68, 0xf7, 0x9e, 0xfb, + 0x63, 0x3d, 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x8f, 0xec, 0x97, 0xbb, 0x01, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/status.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/status.pb.go new file mode 100644 index 000000000..284892642 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/status.pb.go @@ -0,0 +1,141 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/status.proto + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/golang/protobuf/ptypes/any" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Status_Code int32 + +const ( + // Status_UNKNOWN indicates that a release is in an uncertain state. + Status_UNKNOWN Status_Code = 0 + // Status_DEPLOYED indicates that the release has been pushed to Kubernetes. + Status_DEPLOYED Status_Code = 1 + // Status_DELETED indicates that a release has been deleted from Kubermetes. + Status_DELETED Status_Code = 2 + // Status_SUPERSEDED indicates that this release object is outdated and a newer one exists. + Status_SUPERSEDED Status_Code = 3 + // Status_FAILED indicates that the release was not successfully deployed. + Status_FAILED Status_Code = 4 + // Status_DELETING indicates that a delete operation is underway. + Status_DELETING Status_Code = 5 + // Status_PENDING_INSTALL indicates that an install operation is underway. + Status_PENDING_INSTALL Status_Code = 6 + // Status_PENDING_UPGRADE indicates that an upgrade operation is underway. + Status_PENDING_UPGRADE Status_Code = 7 + // Status_PENDING_ROLLBACK indicates that an rollback operation is underway. + Status_PENDING_ROLLBACK Status_Code = 8 +) + +var Status_Code_name = map[int32]string{ + 0: "UNKNOWN", + 1: "DEPLOYED", + 2: "DELETED", + 3: "SUPERSEDED", + 4: "FAILED", + 5: "DELETING", + 6: "PENDING_INSTALL", + 7: "PENDING_UPGRADE", + 8: "PENDING_ROLLBACK", +} +var Status_Code_value = map[string]int32{ + "UNKNOWN": 0, + "DEPLOYED": 1, + "DELETED": 2, + "SUPERSEDED": 3, + "FAILED": 4, + "DELETING": 5, + "PENDING_INSTALL": 6, + "PENDING_UPGRADE": 7, + "PENDING_ROLLBACK": 8, +} + +func (x Status_Code) String() string { + return proto.EnumName(Status_Code_name, int32(x)) +} +func (Status_Code) EnumDescriptor() ([]byte, []int) { return fileDescriptor3, []int{0, 0} } + +// Status defines the status of a release. +type Status struct { + Code Status_Code `protobuf:"varint,1,opt,name=code,enum=hapi.release.Status_Code" json:"code,omitempty"` + // Cluster resources as kubectl would print them. + Resources string `protobuf:"bytes,3,opt,name=resources" json:"resources,omitempty"` + // Contains the rendered templates/NOTES.txt if available + Notes string `protobuf:"bytes,4,opt,name=notes" json:"notes,omitempty"` + // LastTestSuiteRun provides results on the last test run on a release + LastTestSuiteRun *TestSuite `protobuf:"bytes,5,opt,name=last_test_suite_run,json=lastTestSuiteRun" json:"last_test_suite_run,omitempty"` +} + +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} } + +func (m *Status) GetCode() Status_Code { + if m != nil { + return m.Code + } + return Status_UNKNOWN +} + +func (m *Status) GetResources() string { + if m != nil { + return m.Resources + } + return "" +} + +func (m *Status) GetNotes() string { + if m != nil { + return m.Notes + } + return "" +} + +func (m *Status) GetLastTestSuiteRun() *TestSuite { + if m != nil { + return m.LastTestSuiteRun + } + return nil +} + +func init() { + proto.RegisterType((*Status)(nil), "hapi.release.Status") + proto.RegisterEnum("hapi.release.Status_Code", Status_Code_name, Status_Code_value) +} + +func init() { proto.RegisterFile("hapi/release/status.proto", fileDescriptor3) } + +var fileDescriptor3 = []byte{ + // 333 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x90, 0xd1, 0x6e, 0xa2, 0x40, + 0x14, 0x86, 0x17, 0x45, 0xd4, 0xa3, 0x71, 0x27, 0xa3, 0xc9, 0xa2, 0xd9, 0x4d, 0x8c, 0x57, 0xde, + 0x2c, 0x24, 0xf6, 0x09, 0xd0, 0x19, 0x0d, 0x71, 0x82, 0x04, 0x30, 0x4d, 0x7b, 0x43, 0x50, 0xa7, + 0xd6, 0xc4, 0x30, 0x86, 0x19, 0x2e, 0xfa, 0x26, 0x7d, 0xaa, 0x3e, 0x53, 0x03, 0xd8, 0xa8, 0x97, + 0xff, 0xff, 0x7d, 0x87, 0x73, 0x18, 0x18, 0xbe, 0x27, 0x97, 0x93, 0x9d, 0xf1, 0x33, 0x4f, 0x24, + 0xb7, 0xa5, 0x4a, 0x54, 0x2e, 0xad, 0x4b, 0x26, 0x94, 0xc0, 0xdd, 0x02, 0x59, 0x57, 0x34, 0xfa, + 0xf7, 0x20, 0x2a, 0x2e, 0x55, 0x2c, 0xf3, 0x93, 0xe2, 0x95, 0x3c, 0x1a, 0x1e, 0x85, 0x38, 0x9e, + 0xb9, 0x5d, 0xa6, 0x5d, 0xfe, 0x66, 0x27, 0xe9, 0x47, 0x85, 0x26, 0x5f, 0x35, 0x30, 0xc2, 0xf2, + 0xc3, 0xf8, 0x3f, 0xe8, 0x7b, 0x71, 0xe0, 0xa6, 0x36, 0xd6, 0xa6, 0xbd, 0xd9, 0xd0, 0xba, 0xdf, + 0x60, 0x55, 0x8e, 0xb5, 0x10, 0x07, 0x1e, 0x94, 0x1a, 0xfe, 0x0b, 0xed, 0x8c, 0x4b, 0x91, 0x67, + 0x7b, 0x2e, 0xcd, 0xfa, 0x58, 0x9b, 0xb6, 0x83, 0x5b, 0x81, 0x07, 0xd0, 0x48, 0x85, 0xe2, 0xd2, + 0xd4, 0x4b, 0x52, 0x05, 0xbc, 0x84, 0xfe, 0x39, 0x91, 0x2a, 0xbe, 0x5d, 0x18, 0x67, 0x79, 0x6a, + 0x36, 0xc6, 0xda, 0xb4, 0x33, 0xfb, 0xf3, 0xb8, 0x31, 0xe2, 0x52, 0x85, 0x85, 0x12, 0xa0, 0x62, + 0xe6, 0x16, 0xf3, 0x74, 0xf2, 0xa9, 0x81, 0x5e, 0x9c, 0x82, 0x3b, 0xd0, 0xdc, 0x7a, 0x6b, 0x6f, + 0xf3, 0xec, 0xa1, 0x5f, 0xb8, 0x0b, 0x2d, 0x42, 0x7d, 0xb6, 0x79, 0xa1, 0x04, 0x69, 0x05, 0x22, + 0x94, 0xd1, 0x88, 0x12, 0x54, 0xc3, 0x3d, 0x80, 0x70, 0xeb, 0xd3, 0x20, 0xa4, 0x84, 0x12, 0x54, + 0xc7, 0x00, 0xc6, 0xd2, 0x71, 0x19, 0x25, 0x48, 0xaf, 0xc6, 0x18, 0x8d, 0x5c, 0x6f, 0x85, 0x1a, + 0xb8, 0x0f, 0xbf, 0x7d, 0xea, 0x11, 0xd7, 0x5b, 0xc5, 0xae, 0x17, 0x46, 0x0e, 0x63, 0xc8, 0xb8, + 0x2f, 0xb7, 0xfe, 0x2a, 0x70, 0x08, 0x45, 0x4d, 0x3c, 0x00, 0xf4, 0x53, 0x06, 0x1b, 0xc6, 0xe6, + 0xce, 0x62, 0x8d, 0x5a, 0xf3, 0xf6, 0x6b, 0xf3, 0xfa, 0x07, 0x3b, 0xa3, 0x7c, 0xe2, 0xa7, 0xef, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x09, 0x48, 0x18, 0xba, 0xc7, 0x01, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_run.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_run.pb.go new file mode 100644 index 000000000..4d39d17c2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_run.pb.go @@ -0,0 +1,118 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/test_run.proto + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type TestRun_Status int32 + +const ( + TestRun_UNKNOWN TestRun_Status = 0 + TestRun_SUCCESS TestRun_Status = 1 + TestRun_FAILURE TestRun_Status = 2 + TestRun_RUNNING TestRun_Status = 3 +) + +var TestRun_Status_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SUCCESS", + 2: "FAILURE", + 3: "RUNNING", +} +var TestRun_Status_value = map[string]int32{ + "UNKNOWN": 0, + "SUCCESS": 1, + "FAILURE": 2, + "RUNNING": 3, +} + +func (x TestRun_Status) String() string { + return proto.EnumName(TestRun_Status_name, int32(x)) +} +func (TestRun_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor4, []int{0, 0} } + +type TestRun struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Status TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,omitempty"` + Info string `protobuf:"bytes,3,opt,name=info" json:"info,omitempty"` + StartedAt *google_protobuf.Timestamp `protobuf:"bytes,4,opt,name=started_at,json=startedAt" json:"started_at,omitempty"` + CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,5,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"` +} + +func (m *TestRun) Reset() { *m = TestRun{} } +func (m *TestRun) String() string { return proto.CompactTextString(m) } +func (*TestRun) ProtoMessage() {} +func (*TestRun) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} } + +func (m *TestRun) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *TestRun) GetStatus() TestRun_Status { + if m != nil { + return m.Status + } + return TestRun_UNKNOWN +} + +func (m *TestRun) GetInfo() string { + if m != nil { + return m.Info + } + return "" +} + +func (m *TestRun) GetStartedAt() *google_protobuf.Timestamp { + if m != nil { + return m.StartedAt + } + return nil +} + +func (m *TestRun) GetCompletedAt() *google_protobuf.Timestamp { + if m != nil { + return m.CompletedAt + } + return nil +} + +func init() { + proto.RegisterType((*TestRun)(nil), "hapi.release.TestRun") + proto.RegisterEnum("hapi.release.TestRun_Status", TestRun_Status_name, TestRun_Status_value) +} + +func init() { proto.RegisterFile("hapi/release/test_run.proto", fileDescriptor4) } + +var fileDescriptor4 = []byte{ + // 274 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4b, 0xfb, 0x30, + 0x1c, 0xc5, 0x7f, 0xe9, 0xf6, 0x6b, 0x69, 0x3a, 0xa4, 0xe4, 0x54, 0xa6, 0x60, 0xd9, 0xa9, 0xa7, + 0x14, 0xa6, 0x17, 0x41, 0x0f, 0x75, 0x4c, 0x19, 0x4a, 0x84, 0x74, 0x45, 0xf0, 0x32, 0x32, 0xcd, + 0x66, 0xa1, 0x6d, 0x4a, 0xf3, 0xed, 0xdf, 0xe3, 0xbf, 0x2a, 0x69, 0x33, 0xf1, 0xe6, 0xed, 0xfb, + 0x78, 0x9f, 0xf7, 0xf2, 0x82, 0xcf, 0x3f, 0x45, 0x5b, 0xa6, 0x9d, 0xac, 0xa4, 0xd0, 0x32, 0x05, + 0xa9, 0x61, 0xd7, 0xf5, 0x0d, 0x6d, 0x3b, 0x05, 0x8a, 0xcc, 0x8c, 0x49, 0xad, 0x39, 0xbf, 0x3c, + 0x2a, 0x75, 0xac, 0x64, 0x3a, 0x78, 0xfb, 0xfe, 0x90, 0x42, 0x59, 0x4b, 0x0d, 0xa2, 0x6e, 0x47, + 0x7c, 0xf1, 0xe5, 0x60, 0x6f, 0x2b, 0x35, 0xf0, 0xbe, 0x21, 0x04, 0x4f, 0x1b, 0x51, 0xcb, 0x08, + 0xc5, 0x28, 0xf1, 0xf9, 0x70, 0x93, 0x6b, 0xec, 0x6a, 0x10, 0xd0, 0xeb, 0xc8, 0x89, 0x51, 0x72, + 0xb6, 0xbc, 0xa0, 0xbf, 0xfb, 0xa9, 0x8d, 0xd2, 0x7c, 0x60, 0xb8, 0x65, 0x4d, 0x53, 0xd9, 0x1c, + 0x54, 0x34, 0x19, 0x9b, 0xcc, 0x4d, 0x6e, 0x30, 0xd6, 0x20, 0x3a, 0x90, 0x1f, 0x3b, 0x01, 0xd1, + 0x34, 0x46, 0x49, 0xb0, 0x9c, 0xd3, 0x71, 0x1f, 0x3d, 0xed, 0xa3, 0xdb, 0xd3, 0x3e, 0xee, 0x5b, + 0x3a, 0x03, 0x72, 0x87, 0x67, 0xef, 0xaa, 0x6e, 0x2b, 0x69, 0xc3, 0xff, 0xff, 0x0c, 0x07, 0x3f, + 0x7c, 0x06, 0x8b, 0x5b, 0xec, 0x8e, 0xfb, 0x48, 0x80, 0xbd, 0x82, 0x3d, 0xb1, 0x97, 0x57, 0x16, + 0xfe, 0x33, 0x22, 0x2f, 0x56, 0xab, 0x75, 0x9e, 0x87, 0xc8, 0x88, 0x87, 0x6c, 0xf3, 0x5c, 0xf0, + 0x75, 0xe8, 0x18, 0xc1, 0x0b, 0xc6, 0x36, 0xec, 0x31, 0x9c, 0xdc, 0xfb, 0x6f, 0x9e, 0xfd, 0xed, + 0xde, 0x1d, 0x5e, 0xba, 0xfa, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x31, 0x86, 0x46, 0xdb, 0x81, 0x01, + 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_suite.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_suite.pb.go new file mode 100644 index 000000000..b7fa26147 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/release/test_suite.pb.go @@ -0,0 +1,73 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/release/test_suite.proto + +package release + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/timestamp" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// TestSuite comprises of the last run of the pre-defined test suite of a release version +type TestSuite struct { + // StartedAt indicates the date/time this test suite was kicked off + StartedAt *google_protobuf.Timestamp `protobuf:"bytes,1,opt,name=started_at,json=startedAt" json:"started_at,omitempty"` + // CompletedAt indicates the date/time this test suite was completed + CompletedAt *google_protobuf.Timestamp `protobuf:"bytes,2,opt,name=completed_at,json=completedAt" json:"completed_at,omitempty"` + // Results are the results of each segment of the test + Results []*TestRun `protobuf:"bytes,3,rep,name=results" json:"results,omitempty"` +} + +func (m *TestSuite) Reset() { *m = TestSuite{} } +func (m *TestSuite) String() string { return proto.CompactTextString(m) } +func (*TestSuite) ProtoMessage() {} +func (*TestSuite) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} } + +func (m *TestSuite) GetStartedAt() *google_protobuf.Timestamp { + if m != nil { + return m.StartedAt + } + return nil +} + +func (m *TestSuite) GetCompletedAt() *google_protobuf.Timestamp { + if m != nil { + return m.CompletedAt + } + return nil +} + +func (m *TestSuite) GetResults() []*TestRun { + if m != nil { + return m.Results + } + return nil +} + +func init() { + proto.RegisterType((*TestSuite)(nil), "hapi.release.TestSuite") +} + +func init() { proto.RegisterFile("hapi/release/test_suite.proto", fileDescriptor5) } + +var fileDescriptor5 = []byte{ + // 207 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x8f, 0xc1, 0x4a, 0x86, 0x40, + 0x14, 0x85, 0x31, 0x21, 0x71, 0x74, 0x35, 0x10, 0x88, 0x11, 0x49, 0x2b, 0x57, 0x33, 0x60, 0xab, + 0x16, 0x2d, 0xec, 0x11, 0xcc, 0x55, 0x1b, 0x19, 0xeb, 0x66, 0xc2, 0xe8, 0x0c, 0x73, 0xef, 0xbc, + 0x5a, 0xcf, 0x17, 0xea, 0x18, 0x41, 0x8b, 0x7f, 0xfd, 0x7d, 0xe7, 0x9c, 0x7b, 0xd9, 0xdd, 0x97, + 0xb2, 0xb3, 0x74, 0xa0, 0x41, 0x21, 0x48, 0x02, 0xa4, 0x01, 0xfd, 0x4c, 0x20, 0xac, 0x33, 0x64, + 0x78, 0xbe, 0x61, 0x11, 0x70, 0x79, 0x3f, 0x19, 0x33, 0x69, 0x90, 0x3b, 0x1b, 0xfd, 0xa7, 0xa4, + 0x79, 0x01, 0x24, 0xb5, 0xd8, 0x43, 0x2f, 0x6f, 0xff, 0xb7, 0x39, 0xbf, 0x1e, 0xf0, 0xe1, 0x3b, + 0x62, 0x69, 0x0f, 0x48, 0xaf, 0x5b, 0x3f, 0x7f, 0x62, 0x0c, 0x49, 0x39, 0x82, 0x8f, 0x41, 0x51, + 0x11, 0x55, 0x51, 0x9d, 0x35, 0xa5, 0x38, 0x06, 0xc4, 0x39, 0x20, 0xfa, 0x73, 0xa0, 0x4b, 0x83, + 0xdd, 0x12, 0x7f, 0x66, 0xf9, 0xbb, 0x59, 0xac, 0x86, 0x10, 0xbe, 0xba, 0x18, 0xce, 0x7e, 0xfd, + 0x96, 0xb8, 0x64, 0x89, 0x03, 0xf4, 0x9a, 0xb0, 0x88, 0xab, 0xb8, 0xce, 0x9a, 0x1b, 0xf1, 0xf7, + 0x4b, 0xb1, 0xdd, 0xd8, 0xf9, 0xb5, 0x3b, 0xad, 0x97, 0xf4, 0x2d, 0x09, 0x6c, 0xbc, 0xde, 0xcb, + 0x1f, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x59, 0x65, 0x4f, 0x37, 0x01, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/rudder/rudder.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/rudder/rudder.pb.go new file mode 100644 index 000000000..6e26d71eb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/rudder/rudder.pb.go @@ -0,0 +1,722 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/rudder/rudder.proto + +/* +Package rudder is a generated protocol buffer package. + +It is generated from these files: + hapi/rudder/rudder.proto + +It has these top-level messages: + Result + VersionReleaseRequest + VersionReleaseResponse + InstallReleaseRequest + InstallReleaseResponse + DeleteReleaseRequest + DeleteReleaseResponse + UpgradeReleaseRequest + UpgradeReleaseResponse + RollbackReleaseRequest + RollbackReleaseResponse + ReleaseStatusRequest + ReleaseStatusResponse +*/ +package rudder + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import hapi_release3 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Result_Status int32 + +const ( + // No status set + Result_UNKNOWN Result_Status = 0 + // Operation was successful + Result_SUCCESS Result_Status = 1 + // Operation had no results (e.g. upgrade identical, rollback to same, delete non-existent) + Result_UNCHANGED Result_Status = 2 + // Operation failed + Result_ERROR Result_Status = 3 +) + +var Result_Status_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SUCCESS", + 2: "UNCHANGED", + 3: "ERROR", +} +var Result_Status_value = map[string]int32{ + "UNKNOWN": 0, + "SUCCESS": 1, + "UNCHANGED": 2, + "ERROR": 3, +} + +func (x Result_Status) String() string { + return proto.EnumName(Result_Status_name, int32(x)) +} +func (Result_Status) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} } + +type Result struct { + Info string `protobuf:"bytes,1,opt,name=info" json:"info,omitempty"` + Log []string `protobuf:"bytes,2,rep,name=log" json:"log,omitempty"` +} + +func (m *Result) Reset() { *m = Result{} } +func (m *Result) String() string { return proto.CompactTextString(m) } +func (*Result) ProtoMessage() {} +func (*Result) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Result) GetInfo() string { + if m != nil { + return m.Info + } + return "" +} + +func (m *Result) GetLog() []string { + if m != nil { + return m.Log + } + return nil +} + +type VersionReleaseRequest struct { +} + +func (m *VersionReleaseRequest) Reset() { *m = VersionReleaseRequest{} } +func (m *VersionReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*VersionReleaseRequest) ProtoMessage() {} +func (*VersionReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +type VersionReleaseResponse struct { + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version" json:"version,omitempty"` +} + +func (m *VersionReleaseResponse) Reset() { *m = VersionReleaseResponse{} } +func (m *VersionReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*VersionReleaseResponse) ProtoMessage() {} +func (*VersionReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *VersionReleaseResponse) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *VersionReleaseResponse) GetVersion() string { + if m != nil { + return m.Version + } + return "" +} + +type InstallReleaseRequest struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } +func (m *InstallReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseRequest) ProtoMessage() {} +func (*InstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *InstallReleaseRequest) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +type InstallReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` +} + +func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } +func (m *InstallReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseResponse) ProtoMessage() {} +func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *InstallReleaseResponse) GetResult() *Result { + if m != nil { + return m.Result + } + return nil +} + +type DeleteReleaseRequest struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *DeleteReleaseRequest) Reset() { *m = DeleteReleaseRequest{} } +func (m *DeleteReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*DeleteReleaseRequest) ProtoMessage() {} +func (*DeleteReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *DeleteReleaseRequest) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +type DeleteReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` +} + +func (m *DeleteReleaseResponse) Reset() { *m = DeleteReleaseResponse{} } +func (m *DeleteReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*DeleteReleaseResponse) ProtoMessage() {} +func (*DeleteReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +func (m *DeleteReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *DeleteReleaseResponse) GetResult() *Result { + if m != nil { + return m.Result + } + return nil +} + +type UpgradeReleaseRequest struct { + Current *hapi_release5.Release `protobuf:"bytes,1,opt,name=current" json:"current,omitempty"` + Target *hapi_release5.Release `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` + Timeout int64 `protobuf:"varint,3,opt,name=Timeout" json:"Timeout,omitempty"` + Wait bool `protobuf:"varint,4,opt,name=Wait" json:"Wait,omitempty"` + Recreate bool `protobuf:"varint,5,opt,name=Recreate" json:"Recreate,omitempty"` + Force bool `protobuf:"varint,6,opt,name=Force" json:"Force,omitempty"` +} + +func (m *UpgradeReleaseRequest) Reset() { *m = UpgradeReleaseRequest{} } +func (m *UpgradeReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*UpgradeReleaseRequest) ProtoMessage() {} +func (*UpgradeReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +func (m *UpgradeReleaseRequest) GetCurrent() *hapi_release5.Release { + if m != nil { + return m.Current + } + return nil +} + +func (m *UpgradeReleaseRequest) GetTarget() *hapi_release5.Release { + if m != nil { + return m.Target + } + return nil +} + +func (m *UpgradeReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *UpgradeReleaseRequest) GetWait() bool { + if m != nil { + return m.Wait + } + return false +} + +func (m *UpgradeReleaseRequest) GetRecreate() bool { + if m != nil { + return m.Recreate + } + return false +} + +func (m *UpgradeReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + +type UpgradeReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` +} + +func (m *UpgradeReleaseResponse) Reset() { *m = UpgradeReleaseResponse{} } +func (m *UpgradeReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*UpgradeReleaseResponse) ProtoMessage() {} +func (*UpgradeReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +func (m *UpgradeReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *UpgradeReleaseResponse) GetResult() *Result { + if m != nil { + return m.Result + } + return nil +} + +type RollbackReleaseRequest struct { + Current *hapi_release5.Release `protobuf:"bytes,1,opt,name=current" json:"current,omitempty"` + Target *hapi_release5.Release `protobuf:"bytes,2,opt,name=target" json:"target,omitempty"` + Timeout int64 `protobuf:"varint,3,opt,name=Timeout" json:"Timeout,omitempty"` + Wait bool `protobuf:"varint,4,opt,name=Wait" json:"Wait,omitempty"` + Recreate bool `protobuf:"varint,5,opt,name=Recreate" json:"Recreate,omitempty"` + Force bool `protobuf:"varint,6,opt,name=Force" json:"Force,omitempty"` +} + +func (m *RollbackReleaseRequest) Reset() { *m = RollbackReleaseRequest{} } +func (m *RollbackReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*RollbackReleaseRequest) ProtoMessage() {} +func (*RollbackReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *RollbackReleaseRequest) GetCurrent() *hapi_release5.Release { + if m != nil { + return m.Current + } + return nil +} + +func (m *RollbackReleaseRequest) GetTarget() *hapi_release5.Release { + if m != nil { + return m.Target + } + return nil +} + +func (m *RollbackReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *RollbackReleaseRequest) GetWait() bool { + if m != nil { + return m.Wait + } + return false +} + +func (m *RollbackReleaseRequest) GetRecreate() bool { + if m != nil { + return m.Recreate + } + return false +} + +func (m *RollbackReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + +type RollbackReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Result *Result `protobuf:"bytes,2,opt,name=result" json:"result,omitempty"` +} + +func (m *RollbackReleaseResponse) Reset() { *m = RollbackReleaseResponse{} } +func (m *RollbackReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*RollbackReleaseResponse) ProtoMessage() {} +func (*RollbackReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +func (m *RollbackReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *RollbackReleaseResponse) GetResult() *Result { + if m != nil { + return m.Result + } + return nil +} + +type ReleaseStatusRequest struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *ReleaseStatusRequest) Reset() { *m = ReleaseStatusRequest{} } +func (m *ReleaseStatusRequest) String() string { return proto.CompactTextString(m) } +func (*ReleaseStatusRequest) ProtoMessage() {} +func (*ReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } + +func (m *ReleaseStatusRequest) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +type ReleaseStatusResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + Info *hapi_release3.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` +} + +func (m *ReleaseStatusResponse) Reset() { *m = ReleaseStatusResponse{} } +func (m *ReleaseStatusResponse) String() string { return proto.CompactTextString(m) } +func (*ReleaseStatusResponse) ProtoMessage() {} +func (*ReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } + +func (m *ReleaseStatusResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *ReleaseStatusResponse) GetInfo() *hapi_release3.Info { + if m != nil { + return m.Info + } + return nil +} + +func init() { + proto.RegisterType((*Result)(nil), "hapi.services.rudder.Result") + proto.RegisterType((*VersionReleaseRequest)(nil), "hapi.services.rudder.VersionReleaseRequest") + proto.RegisterType((*VersionReleaseResponse)(nil), "hapi.services.rudder.VersionReleaseResponse") + proto.RegisterType((*InstallReleaseRequest)(nil), "hapi.services.rudder.InstallReleaseRequest") + proto.RegisterType((*InstallReleaseResponse)(nil), "hapi.services.rudder.InstallReleaseResponse") + proto.RegisterType((*DeleteReleaseRequest)(nil), "hapi.services.rudder.DeleteReleaseRequest") + proto.RegisterType((*DeleteReleaseResponse)(nil), "hapi.services.rudder.DeleteReleaseResponse") + proto.RegisterType((*UpgradeReleaseRequest)(nil), "hapi.services.rudder.UpgradeReleaseRequest") + proto.RegisterType((*UpgradeReleaseResponse)(nil), "hapi.services.rudder.UpgradeReleaseResponse") + proto.RegisterType((*RollbackReleaseRequest)(nil), "hapi.services.rudder.RollbackReleaseRequest") + proto.RegisterType((*RollbackReleaseResponse)(nil), "hapi.services.rudder.RollbackReleaseResponse") + proto.RegisterType((*ReleaseStatusRequest)(nil), "hapi.services.rudder.ReleaseStatusRequest") + proto.RegisterType((*ReleaseStatusResponse)(nil), "hapi.services.rudder.ReleaseStatusResponse") + proto.RegisterEnum("hapi.services.rudder.Result_Status", Result_Status_name, Result_Status_value) +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for ReleaseModuleService service + +type ReleaseModuleServiceClient interface { + Version(ctx context.Context, in *VersionReleaseRequest, opts ...grpc.CallOption) (*VersionReleaseResponse, error) + // InstallRelease requests installation of a chart as a new release. + InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) + // DeleteRelease requests deletion of a named release. + DeleteRelease(ctx context.Context, in *DeleteReleaseRequest, opts ...grpc.CallOption) (*DeleteReleaseResponse, error) + // RollbackRelease rolls back a release to a previous version. + RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error) + // UpgradeRelease updates release content. + UpgradeRelease(ctx context.Context, in *UpgradeReleaseRequest, opts ...grpc.CallOption) (*UpgradeReleaseResponse, error) + // ReleaseStatus retrieves release status. + ReleaseStatus(ctx context.Context, in *ReleaseStatusRequest, opts ...grpc.CallOption) (*ReleaseStatusResponse, error) +} + +type releaseModuleServiceClient struct { + cc *grpc.ClientConn +} + +func NewReleaseModuleServiceClient(cc *grpc.ClientConn) ReleaseModuleServiceClient { + return &releaseModuleServiceClient{cc} +} + +func (c *releaseModuleServiceClient) Version(ctx context.Context, in *VersionReleaseRequest, opts ...grpc.CallOption) (*VersionReleaseResponse, error) { + out := new(VersionReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/Version", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseModuleServiceClient) InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) { + out := new(InstallReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/InstallRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseModuleServiceClient) DeleteRelease(ctx context.Context, in *DeleteReleaseRequest, opts ...grpc.CallOption) (*DeleteReleaseResponse, error) { + out := new(DeleteReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/DeleteRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseModuleServiceClient) RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error) { + out := new(RollbackReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/RollbackRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseModuleServiceClient) UpgradeRelease(ctx context.Context, in *UpgradeReleaseRequest, opts ...grpc.CallOption) (*UpgradeReleaseResponse, error) { + out := new(UpgradeReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/UpgradeRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseModuleServiceClient) ReleaseStatus(ctx context.Context, in *ReleaseStatusRequest, opts ...grpc.CallOption) (*ReleaseStatusResponse, error) { + out := new(ReleaseStatusResponse) + err := grpc.Invoke(ctx, "/hapi.services.rudder.ReleaseModuleService/ReleaseStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ReleaseModuleService service + +type ReleaseModuleServiceServer interface { + Version(context.Context, *VersionReleaseRequest) (*VersionReleaseResponse, error) + // InstallRelease requests installation of a chart as a new release. + InstallRelease(context.Context, *InstallReleaseRequest) (*InstallReleaseResponse, error) + // DeleteRelease requests deletion of a named release. + DeleteRelease(context.Context, *DeleteReleaseRequest) (*DeleteReleaseResponse, error) + // RollbackRelease rolls back a release to a previous version. + RollbackRelease(context.Context, *RollbackReleaseRequest) (*RollbackReleaseResponse, error) + // UpgradeRelease updates release content. + UpgradeRelease(context.Context, *UpgradeReleaseRequest) (*UpgradeReleaseResponse, error) + // ReleaseStatus retrieves release status. + ReleaseStatus(context.Context, *ReleaseStatusRequest) (*ReleaseStatusResponse, error) +} + +func RegisterReleaseModuleServiceServer(s *grpc.Server, srv ReleaseModuleServiceServer) { + s.RegisterService(&_ReleaseModuleService_serviceDesc, srv) +} + +func _ReleaseModuleService_Version_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(VersionReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).Version(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/Version", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).Version(ctx, req.(*VersionReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseModuleService_InstallRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InstallReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).InstallRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/InstallRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).InstallRelease(ctx, req.(*InstallReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseModuleService_DeleteRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).DeleteRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/DeleteRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).DeleteRelease(ctx, req.(*DeleteReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseModuleService_RollbackRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RollbackReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).RollbackRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/RollbackRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).RollbackRelease(ctx, req.(*RollbackReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseModuleService_UpgradeRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpgradeReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).UpgradeRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/UpgradeRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).UpgradeRelease(ctx, req.(*UpgradeReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseModuleService_ReleaseStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReleaseStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseModuleServiceServer).ReleaseStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.rudder.ReleaseModuleService/ReleaseStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseModuleServiceServer).ReleaseStatus(ctx, req.(*ReleaseStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ReleaseModuleService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hapi.services.rudder.ReleaseModuleService", + HandlerType: (*ReleaseModuleServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Version", + Handler: _ReleaseModuleService_Version_Handler, + }, + { + MethodName: "InstallRelease", + Handler: _ReleaseModuleService_InstallRelease_Handler, + }, + { + MethodName: "DeleteRelease", + Handler: _ReleaseModuleService_DeleteRelease_Handler, + }, + { + MethodName: "RollbackRelease", + Handler: _ReleaseModuleService_RollbackRelease_Handler, + }, + { + MethodName: "UpgradeRelease", + Handler: _ReleaseModuleService_UpgradeRelease_Handler, + }, + { + MethodName: "ReleaseStatus", + Handler: _ReleaseModuleService_ReleaseStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "hapi/rudder/rudder.proto", +} + +func init() { proto.RegisterFile("hapi/rudder/rudder.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 597 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x56, 0x5f, 0x8f, 0xd2, 0x4e, + 0x14, 0xa5, 0xb0, 0x14, 0xb8, 0x64, 0x7f, 0x3f, 0x32, 0xa1, 0xd0, 0x34, 0x3e, 0x90, 0x3e, 0x18, + 0xe2, 0xba, 0x25, 0x41, 0x1f, 0x7d, 0x51, 0x96, 0xfd, 0x13, 0x23, 0x9b, 0x0c, 0xe2, 0x26, 0xbe, + 0x75, 0xe1, 0x82, 0xd5, 0xd2, 0xd6, 0xe9, 0x74, 0x1f, 0xd5, 0x4f, 0xe3, 0x57, 0xd2, 0x8f, 0x63, + 0xda, 0x69, 0x89, 0xad, 0xd3, 0x88, 0x6b, 0xc2, 0x83, 0x4f, 0x9d, 0xe9, 0x3d, 0xdc, 0x39, 0xe7, + 0xf4, 0xce, 0x09, 0xa0, 0xbf, 0xb3, 0x03, 0x67, 0xc4, 0xa2, 0xd5, 0x0a, 0x59, 0xfa, 0xb0, 0x02, + 0xe6, 0x73, 0x9f, 0x74, 0xe3, 0x8a, 0x15, 0x22, 0xbb, 0x73, 0x96, 0x18, 0x5a, 0xa2, 0x66, 0xf4, + 0x05, 0x1e, 0x5d, 0xb4, 0x43, 0x1c, 0x39, 0xde, 0xda, 0x17, 0x70, 0xc3, 0xc8, 0x15, 0xd2, 0xa7, + 0xa8, 0x99, 0x2e, 0xa8, 0x14, 0xc3, 0xc8, 0xe5, 0x84, 0xc0, 0x51, 0xfc, 0x1b, 0x5d, 0x19, 0x28, + 0xc3, 0x16, 0x4d, 0xd6, 0xa4, 0x03, 0x35, 0xd7, 0xdf, 0xe8, 0xd5, 0x41, 0x6d, 0xd8, 0xa2, 0xf1, + 0xd2, 0x7c, 0x06, 0xea, 0x9c, 0xdb, 0x3c, 0x0a, 0x49, 0x1b, 0x1a, 0x8b, 0xd9, 0xcb, 0xd9, 0xf5, + 0xcd, 0xac, 0x53, 0x89, 0x37, 0xf3, 0xc5, 0x64, 0x32, 0x9d, 0xcf, 0x3b, 0x0a, 0x39, 0x86, 0xd6, + 0x62, 0x36, 0xb9, 0x7c, 0x3e, 0xbb, 0x98, 0x9e, 0x75, 0xaa, 0xa4, 0x05, 0xf5, 0x29, 0xa5, 0xd7, + 0xb4, 0x53, 0x33, 0xfb, 0xa0, 0xbd, 0x41, 0x16, 0x3a, 0xbe, 0x47, 0x05, 0x0b, 0x8a, 0x1f, 0x23, + 0x0c, 0xb9, 0x79, 0x0e, 0xbd, 0x62, 0x21, 0x0c, 0x7c, 0x2f, 0xc4, 0x98, 0x96, 0x67, 0x6f, 0x31, + 0xa3, 0x15, 0xaf, 0x89, 0x0e, 0x8d, 0x3b, 0x81, 0xd6, 0xab, 0xc9, 0xeb, 0x6c, 0x6b, 0x5e, 0x82, + 0x76, 0xe5, 0x85, 0xdc, 0x76, 0xdd, 0xfc, 0x01, 0x64, 0x04, 0x8d, 0x54, 0x78, 0xd2, 0xa9, 0x3d, + 0xd6, 0xac, 0xc4, 0xc4, 0xcc, 0x8d, 0x0c, 0x9e, 0xa1, 0xcc, 0xcf, 0xd0, 0x2b, 0x76, 0x4a, 0x19, + 0xfd, 0x69, 0x2b, 0xf2, 0x14, 0x54, 0x96, 0x78, 0x9c, 0xb0, 0x6d, 0x8f, 0x1f, 0x58, 0xb2, 0xef, + 0x67, 0x89, 0xef, 0x40, 0x53, 0xac, 0x79, 0x01, 0xdd, 0x33, 0x74, 0x91, 0xe3, 0xdf, 0x2a, 0xf9, + 0x04, 0x5a, 0xa1, 0xd1, 0x61, 0x85, 0x7c, 0x53, 0x40, 0x5b, 0x04, 0x1b, 0x66, 0xaf, 0x24, 0x52, + 0x96, 0x11, 0x63, 0xe8, 0xf1, 0xdf, 0x10, 0x48, 0x51, 0xe4, 0x14, 0x54, 0x6e, 0xb3, 0x0d, 0x66, + 0x04, 0x4a, 0xf0, 0x29, 0x28, 0x9e, 0x93, 0xd7, 0xce, 0x16, 0xfd, 0x88, 0xeb, 0xb5, 0x81, 0x32, + 0xac, 0xd1, 0x6c, 0x1b, 0x4f, 0xd5, 0x8d, 0xed, 0x70, 0xfd, 0x68, 0xa0, 0x0c, 0x9b, 0x34, 0x59, + 0x13, 0x03, 0x9a, 0x14, 0x97, 0x0c, 0x6d, 0x8e, 0x7a, 0x3d, 0x79, 0xbf, 0xdb, 0x93, 0x2e, 0xd4, + 0xcf, 0x7d, 0xb6, 0x44, 0x5d, 0x4d, 0x0a, 0x62, 0x13, 0xcf, 0x48, 0x51, 0xd8, 0x61, 0xad, 0xfd, + 0xae, 0x40, 0x8f, 0xfa, 0xae, 0x7b, 0x6b, 0x2f, 0x3f, 0xfc, 0x63, 0xde, 0x7e, 0x51, 0xa0, 0xff, + 0x8b, 0xb4, 0x83, 0xdf, 0xc0, 0xb4, 0x93, 0x88, 0xbc, 0x7b, 0xdf, 0xc0, 0x00, 0xb4, 0x42, 0xa3, + 0xfb, 0x0a, 0x79, 0x98, 0x86, 0xb4, 0x90, 0x41, 0xf2, 0xe8, 0x2b, 0x6f, 0xed, 0x8b, 0xe0, 0x1e, + 0x7f, 0xad, 0xef, 0xb8, 0xbf, 0xf2, 0x57, 0x91, 0x8b, 0x73, 0x21, 0x95, 0xac, 0xa1, 0x91, 0x06, + 0x2d, 0x39, 0x91, 0x9b, 0x20, 0x0d, 0x68, 0xe3, 0xf1, 0x7e, 0x60, 0xa1, 0xcb, 0xac, 0x90, 0x2d, + 0xfc, 0x97, 0x8f, 0xcf, 0xb2, 0xe3, 0xa4, 0x71, 0x5d, 0x76, 0x9c, 0x3c, 0x91, 0xcd, 0x0a, 0x79, + 0x0f, 0xc7, 0xb9, 0x8c, 0x23, 0x8f, 0xe4, 0x0d, 0x64, 0x89, 0x6a, 0x9c, 0xec, 0x85, 0xdd, 0x9d, + 0x15, 0xc0, 0xff, 0x85, 0xc1, 0x24, 0x25, 0x74, 0xe5, 0x57, 0xd3, 0x38, 0xdd, 0x13, 0xfd, 0xb3, + 0x99, 0xf9, 0x9c, 0x29, 0x33, 0x53, 0x1a, 0xb3, 0x65, 0x66, 0xca, 0xa3, 0x4b, 0x98, 0x99, 0x1b, + 0xd7, 0x32, 0x33, 0x65, 0x97, 0xa3, 0xcc, 0x4c, 0xe9, 0xfc, 0x9b, 0x95, 0x17, 0xcd, 0xb7, 0xaa, + 0x40, 0xdc, 0xaa, 0xc9, 0x1f, 0x92, 0x27, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xb6, 0xa5, 0x37, + 0x75, 0xf7, 0x08, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/services/tiller.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/services/tiller.pb.go new file mode 100644 index 000000000..37535aac7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/services/tiller.pb.go @@ -0,0 +1,1449 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/services/tiller.proto + +/* +Package services is a generated protocol buffer package. + +It is generated from these files: + hapi/services/tiller.proto + +It has these top-level messages: + ListReleasesRequest + ListSort + ListReleasesResponse + GetReleaseStatusRequest + GetReleaseStatusResponse + GetReleaseContentRequest + GetReleaseContentResponse + UpdateReleaseRequest + UpdateReleaseResponse + RollbackReleaseRequest + RollbackReleaseResponse + InstallReleaseRequest + InstallReleaseResponse + UninstallReleaseRequest + UninstallReleaseResponse + GetVersionRequest + GetVersionResponse + GetHistoryRequest + GetHistoryResponse + TestReleaseRequest + TestReleaseResponse +*/ +package services + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import hapi_chart3 "k8s.io/helm/pkg/proto/hapi/chart" +import hapi_chart "k8s.io/helm/pkg/proto/hapi/chart" +import hapi_release5 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_release4 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_release1 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_release3 "k8s.io/helm/pkg/proto/hapi/release" +import hapi_version "k8s.io/helm/pkg/proto/hapi/version" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +// SortBy defines sort operations. +type ListSort_SortBy int32 + +const ( + ListSort_UNKNOWN ListSort_SortBy = 0 + ListSort_NAME ListSort_SortBy = 1 + ListSort_LAST_RELEASED ListSort_SortBy = 2 +) + +var ListSort_SortBy_name = map[int32]string{ + 0: "UNKNOWN", + 1: "NAME", + 2: "LAST_RELEASED", +} +var ListSort_SortBy_value = map[string]int32{ + "UNKNOWN": 0, + "NAME": 1, + "LAST_RELEASED": 2, +} + +func (x ListSort_SortBy) String() string { + return proto.EnumName(ListSort_SortBy_name, int32(x)) +} +func (ListSort_SortBy) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} } + +// SortOrder defines sort orders to augment sorting operations. +type ListSort_SortOrder int32 + +const ( + ListSort_ASC ListSort_SortOrder = 0 + ListSort_DESC ListSort_SortOrder = 1 +) + +var ListSort_SortOrder_name = map[int32]string{ + 0: "ASC", + 1: "DESC", +} +var ListSort_SortOrder_value = map[string]int32{ + "ASC": 0, + "DESC": 1, +} + +func (x ListSort_SortOrder) String() string { + return proto.EnumName(ListSort_SortOrder_name, int32(x)) +} +func (ListSort_SortOrder) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 1} } + +// ListReleasesRequest requests a list of releases. +// +// Releases can be retrieved in chunks by setting limit and offset. +// +// Releases can be sorted according to a few pre-determined sort stategies. +type ListReleasesRequest struct { + // Limit is the maximum number of releases to be returned. + Limit int64 `protobuf:"varint,1,opt,name=limit" json:"limit,omitempty"` + // Offset is the last release name that was seen. The next listing + // operation will start with the name after this one. + // Example: If list one returns albert, bernie, carl, and sets 'next: dennis'. + // dennis is the offset. Supplying 'dennis' for the next request should + // cause the next batch to return a set of results starting with 'dennis'. + Offset string `protobuf:"bytes,2,opt,name=offset" json:"offset,omitempty"` + // SortBy is the sort field that the ListReleases server should sort data before returning. + SortBy ListSort_SortBy `protobuf:"varint,3,opt,name=sort_by,json=sortBy,enum=hapi.services.tiller.ListSort_SortBy" json:"sort_by,omitempty"` + // Filter is a regular expression used to filter which releases should be listed. + // + // Anything that matches the regexp will be included in the results. + Filter string `protobuf:"bytes,4,opt,name=filter" json:"filter,omitempty"` + // SortOrder is the ordering directive used for sorting. + SortOrder ListSort_SortOrder `protobuf:"varint,5,opt,name=sort_order,json=sortOrder,enum=hapi.services.tiller.ListSort_SortOrder" json:"sort_order,omitempty"` + StatusCodes []hapi_release3.Status_Code `protobuf:"varint,6,rep,packed,name=status_codes,json=statusCodes,enum=hapi.release.Status_Code" json:"status_codes,omitempty"` + // Namespace is the filter to select releases only from a specific namespace. + Namespace string `protobuf:"bytes,7,opt,name=namespace" json:"namespace,omitempty"` +} + +func (m *ListReleasesRequest) Reset() { *m = ListReleasesRequest{} } +func (m *ListReleasesRequest) String() string { return proto.CompactTextString(m) } +func (*ListReleasesRequest) ProtoMessage() {} +func (*ListReleasesRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *ListReleasesRequest) GetLimit() int64 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *ListReleasesRequest) GetOffset() string { + if m != nil { + return m.Offset + } + return "" +} + +func (m *ListReleasesRequest) GetSortBy() ListSort_SortBy { + if m != nil { + return m.SortBy + } + return ListSort_UNKNOWN +} + +func (m *ListReleasesRequest) GetFilter() string { + if m != nil { + return m.Filter + } + return "" +} + +func (m *ListReleasesRequest) GetSortOrder() ListSort_SortOrder { + if m != nil { + return m.SortOrder + } + return ListSort_ASC +} + +func (m *ListReleasesRequest) GetStatusCodes() []hapi_release3.Status_Code { + if m != nil { + return m.StatusCodes + } + return nil +} + +func (m *ListReleasesRequest) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +// ListSort defines sorting fields on a release list. +type ListSort struct { +} + +func (m *ListSort) Reset() { *m = ListSort{} } +func (m *ListSort) String() string { return proto.CompactTextString(m) } +func (*ListSort) ProtoMessage() {} +func (*ListSort) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } + +// ListReleasesResponse is a list of releases. +type ListReleasesResponse struct { + // Count is the expected total number of releases to be returned. + Count int64 `protobuf:"varint,1,opt,name=count" json:"count,omitempty"` + // Next is the name of the next release. If this is other than an empty + // string, it means there are more results. + Next string `protobuf:"bytes,2,opt,name=next" json:"next,omitempty"` + // Total is the total number of queryable releases. + Total int64 `protobuf:"varint,3,opt,name=total" json:"total,omitempty"` + // Releases is the list of found release objects. + Releases []*hapi_release5.Release `protobuf:"bytes,4,rep,name=releases" json:"releases,omitempty"` +} + +func (m *ListReleasesResponse) Reset() { *m = ListReleasesResponse{} } +func (m *ListReleasesResponse) String() string { return proto.CompactTextString(m) } +func (*ListReleasesResponse) ProtoMessage() {} +func (*ListReleasesResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } + +func (m *ListReleasesResponse) GetCount() int64 { + if m != nil { + return m.Count + } + return 0 +} + +func (m *ListReleasesResponse) GetNext() string { + if m != nil { + return m.Next + } + return "" +} + +func (m *ListReleasesResponse) GetTotal() int64 { + if m != nil { + return m.Total + } + return 0 +} + +func (m *ListReleasesResponse) GetReleases() []*hapi_release5.Release { + if m != nil { + return m.Releases + } + return nil +} + +// GetReleaseStatusRequest is a request to get the status of a release. +type GetReleaseStatusRequest struct { + // Name is the name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Version is the version of the release + Version int32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` +} + +func (m *GetReleaseStatusRequest) Reset() { *m = GetReleaseStatusRequest{} } +func (m *GetReleaseStatusRequest) String() string { return proto.CompactTextString(m) } +func (*GetReleaseStatusRequest) ProtoMessage() {} +func (*GetReleaseStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } + +func (m *GetReleaseStatusRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GetReleaseStatusRequest) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +// GetReleaseStatusResponse is the response indicating the status of the named release. +type GetReleaseStatusResponse struct { + // Name is the name of the release. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Info contains information about the release. + Info *hapi_release4.Info `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` + // Namespace the release was released into + Namespace string `protobuf:"bytes,3,opt,name=namespace" json:"namespace,omitempty"` +} + +func (m *GetReleaseStatusResponse) Reset() { *m = GetReleaseStatusResponse{} } +func (m *GetReleaseStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GetReleaseStatusResponse) ProtoMessage() {} +func (*GetReleaseStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } + +func (m *GetReleaseStatusResponse) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GetReleaseStatusResponse) GetInfo() *hapi_release4.Info { + if m != nil { + return m.Info + } + return nil +} + +func (m *GetReleaseStatusResponse) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +// GetReleaseContentRequest is a request to get the contents of a release. +type GetReleaseContentRequest struct { + // The name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Version is the version of the release + Version int32 `protobuf:"varint,2,opt,name=version" json:"version,omitempty"` +} + +func (m *GetReleaseContentRequest) Reset() { *m = GetReleaseContentRequest{} } +func (m *GetReleaseContentRequest) String() string { return proto.CompactTextString(m) } +func (*GetReleaseContentRequest) ProtoMessage() {} +func (*GetReleaseContentRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } + +func (m *GetReleaseContentRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GetReleaseContentRequest) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +// GetReleaseContentResponse is a response containing the contents of a release. +type GetReleaseContentResponse struct { + // The release content + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *GetReleaseContentResponse) Reset() { *m = GetReleaseContentResponse{} } +func (m *GetReleaseContentResponse) String() string { return proto.CompactTextString(m) } +func (*GetReleaseContentResponse) ProtoMessage() {} +func (*GetReleaseContentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} } + +func (m *GetReleaseContentResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +// UpdateReleaseRequest updates a release. +type UpdateReleaseRequest struct { + // The name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // Chart is the protobuf representation of a chart. + Chart *hapi_chart3.Chart `protobuf:"bytes,2,opt,name=chart" json:"chart,omitempty"` + // Values is a string containing (unparsed) YAML values. + Values *hapi_chart.Config `protobuf:"bytes,3,opt,name=values" json:"values,omitempty"` + // dry_run, if true, will run through the release logic, but neither create + DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + // DisableHooks causes the server to skip running any hooks for the upgrade. + DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Performs pods restart for resources if applicable + Recreate bool `protobuf:"varint,6,opt,name=recreate" json:"recreate,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,7,opt,name=timeout" json:"timeout,omitempty"` + // ResetValues will cause Tiller to ignore stored values, resetting to default values. + ResetValues bool `protobuf:"varint,8,opt,name=reset_values,json=resetValues" json:"reset_values,omitempty"` + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + Wait bool `protobuf:"varint,9,opt,name=wait" json:"wait,omitempty"` + // ReuseValues will cause Tiller to reuse the values from the last release. + // This is ignored if reset_values is set. + ReuseValues bool `protobuf:"varint,10,opt,name=reuse_values,json=reuseValues" json:"reuse_values,omitempty"` + // Force resource update through delete/recreate if needed. + Force bool `protobuf:"varint,11,opt,name=force" json:"force,omitempty"` +} + +func (m *UpdateReleaseRequest) Reset() { *m = UpdateReleaseRequest{} } +func (m *UpdateReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateReleaseRequest) ProtoMessage() {} +func (*UpdateReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} } + +func (m *UpdateReleaseRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *UpdateReleaseRequest) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *UpdateReleaseRequest) GetValues() *hapi_chart.Config { + if m != nil { + return m.Values + } + return nil +} + +func (m *UpdateReleaseRequest) GetDryRun() bool { + if m != nil { + return m.DryRun + } + return false +} + +func (m *UpdateReleaseRequest) GetDisableHooks() bool { + if m != nil { + return m.DisableHooks + } + return false +} + +func (m *UpdateReleaseRequest) GetRecreate() bool { + if m != nil { + return m.Recreate + } + return false +} + +func (m *UpdateReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *UpdateReleaseRequest) GetResetValues() bool { + if m != nil { + return m.ResetValues + } + return false +} + +func (m *UpdateReleaseRequest) GetWait() bool { + if m != nil { + return m.Wait + } + return false +} + +func (m *UpdateReleaseRequest) GetReuseValues() bool { + if m != nil { + return m.ReuseValues + } + return false +} + +func (m *UpdateReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + +// UpdateReleaseResponse is the response to an update request. +type UpdateReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *UpdateReleaseResponse) Reset() { *m = UpdateReleaseResponse{} } +func (m *UpdateReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateReleaseResponse) ProtoMessage() {} +func (*UpdateReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} } + +func (m *UpdateReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +type RollbackReleaseRequest struct { + // The name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // dry_run, if true, will run through the release logic but no create + DryRun bool `protobuf:"varint,2,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + // DisableHooks causes the server to skip running any hooks for the rollback + DisableHooks bool `protobuf:"varint,3,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Version is the version of the release to deploy. + Version int32 `protobuf:"varint,4,opt,name=version" json:"version,omitempty"` + // Performs pods restart for resources if applicable + Recreate bool `protobuf:"varint,5,opt,name=recreate" json:"recreate,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,6,opt,name=timeout" json:"timeout,omitempty"` + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + Wait bool `protobuf:"varint,7,opt,name=wait" json:"wait,omitempty"` + // Force resource update through delete/recreate if needed. + Force bool `protobuf:"varint,8,opt,name=force" json:"force,omitempty"` +} + +func (m *RollbackReleaseRequest) Reset() { *m = RollbackReleaseRequest{} } +func (m *RollbackReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*RollbackReleaseRequest) ProtoMessage() {} +func (*RollbackReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} } + +func (m *RollbackReleaseRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *RollbackReleaseRequest) GetDryRun() bool { + if m != nil { + return m.DryRun + } + return false +} + +func (m *RollbackReleaseRequest) GetDisableHooks() bool { + if m != nil { + return m.DisableHooks + } + return false +} + +func (m *RollbackReleaseRequest) GetVersion() int32 { + if m != nil { + return m.Version + } + return 0 +} + +func (m *RollbackReleaseRequest) GetRecreate() bool { + if m != nil { + return m.Recreate + } + return false +} + +func (m *RollbackReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *RollbackReleaseRequest) GetWait() bool { + if m != nil { + return m.Wait + } + return false +} + +func (m *RollbackReleaseRequest) GetForce() bool { + if m != nil { + return m.Force + } + return false +} + +// RollbackReleaseResponse is the response to an update request. +type RollbackReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *RollbackReleaseResponse) Reset() { *m = RollbackReleaseResponse{} } +func (m *RollbackReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*RollbackReleaseResponse) ProtoMessage() {} +func (*RollbackReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} } + +func (m *RollbackReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +// InstallReleaseRequest is the request for an installation of a chart. +type InstallReleaseRequest struct { + // Chart is the protobuf representation of a chart. + Chart *hapi_chart3.Chart `protobuf:"bytes,1,opt,name=chart" json:"chart,omitempty"` + // Values is a string containing (unparsed) YAML values. + Values *hapi_chart.Config `protobuf:"bytes,2,opt,name=values" json:"values,omitempty"` + // DryRun, if true, will run through the release logic, but neither create + // a release object nor deploy to Kubernetes. The release object returned + // in the response will be fake. + DryRun bool `protobuf:"varint,3,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"` + // Name is the candidate release name. This must be unique to the + // namespace, otherwise the server will return an error. If it is not + // supplied, the server will autogenerate one. + Name string `protobuf:"bytes,4,opt,name=name" json:"name,omitempty"` + // DisableHooks causes the server to skip running any hooks for the install. + DisableHooks bool `protobuf:"varint,5,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Namepace is the kubernetes namespace of the release. + Namespace string `protobuf:"bytes,6,opt,name=namespace" json:"namespace,omitempty"` + // ReuseName requests that Tiller re-uses a name, instead of erroring out. + ReuseName bool `protobuf:"varint,7,opt,name=reuse_name,json=reuseName" json:"reuse_name,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,8,opt,name=timeout" json:"timeout,omitempty"` + // wait, if true, will wait until all Pods, PVCs, and Services are in a ready state + // before marking the release as successful. It will wait for as long as timeout + Wait bool `protobuf:"varint,9,opt,name=wait" json:"wait,omitempty"` +} + +func (m *InstallReleaseRequest) Reset() { *m = InstallReleaseRequest{} } +func (m *InstallReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseRequest) ProtoMessage() {} +func (*InstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} } + +func (m *InstallReleaseRequest) GetChart() *hapi_chart3.Chart { + if m != nil { + return m.Chart + } + return nil +} + +func (m *InstallReleaseRequest) GetValues() *hapi_chart.Config { + if m != nil { + return m.Values + } + return nil +} + +func (m *InstallReleaseRequest) GetDryRun() bool { + if m != nil { + return m.DryRun + } + return false +} + +func (m *InstallReleaseRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *InstallReleaseRequest) GetDisableHooks() bool { + if m != nil { + return m.DisableHooks + } + return false +} + +func (m *InstallReleaseRequest) GetNamespace() string { + if m != nil { + return m.Namespace + } + return "" +} + +func (m *InstallReleaseRequest) GetReuseName() bool { + if m != nil { + return m.ReuseName + } + return false +} + +func (m *InstallReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *InstallReleaseRequest) GetWait() bool { + if m != nil { + return m.Wait + } + return false +} + +// InstallReleaseResponse is the response from a release installation. +type InstallReleaseResponse struct { + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` +} + +func (m *InstallReleaseResponse) Reset() { *m = InstallReleaseResponse{} } +func (m *InstallReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*InstallReleaseResponse) ProtoMessage() {} +func (*InstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} } + +func (m *InstallReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +// UninstallReleaseRequest represents a request to uninstall a named release. +type UninstallReleaseRequest struct { + // Name is the name of the release to delete. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // DisableHooks causes the server to skip running any hooks for the uninstall. + DisableHooks bool `protobuf:"varint,2,opt,name=disable_hooks,json=disableHooks" json:"disable_hooks,omitempty"` + // Purge removes the release from the store and make its name free for later use. + Purge bool `protobuf:"varint,3,opt,name=purge" json:"purge,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,4,opt,name=timeout" json:"timeout,omitempty"` +} + +func (m *UninstallReleaseRequest) Reset() { *m = UninstallReleaseRequest{} } +func (m *UninstallReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*UninstallReleaseRequest) ProtoMessage() {} +func (*UninstallReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} } + +func (m *UninstallReleaseRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *UninstallReleaseRequest) GetDisableHooks() bool { + if m != nil { + return m.DisableHooks + } + return false +} + +func (m *UninstallReleaseRequest) GetPurge() bool { + if m != nil { + return m.Purge + } + return false +} + +func (m *UninstallReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +// UninstallReleaseResponse represents a successful response to an uninstall request. +type UninstallReleaseResponse struct { + // Release is the release that was marked deleted. + Release *hapi_release5.Release `protobuf:"bytes,1,opt,name=release" json:"release,omitempty"` + // Info is an uninstall message + Info string `protobuf:"bytes,2,opt,name=info" json:"info,omitempty"` +} + +func (m *UninstallReleaseResponse) Reset() { *m = UninstallReleaseResponse{} } +func (m *UninstallReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*UninstallReleaseResponse) ProtoMessage() {} +func (*UninstallReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} } + +func (m *UninstallReleaseResponse) GetRelease() *hapi_release5.Release { + if m != nil { + return m.Release + } + return nil +} + +func (m *UninstallReleaseResponse) GetInfo() string { + if m != nil { + return m.Info + } + return "" +} + +// GetVersionRequest requests for version information. +type GetVersionRequest struct { +} + +func (m *GetVersionRequest) Reset() { *m = GetVersionRequest{} } +func (m *GetVersionRequest) String() string { return proto.CompactTextString(m) } +func (*GetVersionRequest) ProtoMessage() {} +func (*GetVersionRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} } + +type GetVersionResponse struct { + Version *hapi_version.Version `protobuf:"bytes,1,opt,name=Version" json:"Version,omitempty"` +} + +func (m *GetVersionResponse) Reset() { *m = GetVersionResponse{} } +func (m *GetVersionResponse) String() string { return proto.CompactTextString(m) } +func (*GetVersionResponse) ProtoMessage() {} +func (*GetVersionResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} } + +func (m *GetVersionResponse) GetVersion() *hapi_version.Version { + if m != nil { + return m.Version + } + return nil +} + +// GetHistoryRequest requests a release's history. +type GetHistoryRequest struct { + // The name of the release. + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // The maximum number of releases to include. + Max int32 `protobuf:"varint,2,opt,name=max" json:"max,omitempty"` +} + +func (m *GetHistoryRequest) Reset() { *m = GetHistoryRequest{} } +func (m *GetHistoryRequest) String() string { return proto.CompactTextString(m) } +func (*GetHistoryRequest) ProtoMessage() {} +func (*GetHistoryRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{17} } + +func (m *GetHistoryRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *GetHistoryRequest) GetMax() int32 { + if m != nil { + return m.Max + } + return 0 +} + +// GetHistoryResponse is received in response to a GetHistory rpc. +type GetHistoryResponse struct { + Releases []*hapi_release5.Release `protobuf:"bytes,1,rep,name=releases" json:"releases,omitempty"` +} + +func (m *GetHistoryResponse) Reset() { *m = GetHistoryResponse{} } +func (m *GetHistoryResponse) String() string { return proto.CompactTextString(m) } +func (*GetHistoryResponse) ProtoMessage() {} +func (*GetHistoryResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{18} } + +func (m *GetHistoryResponse) GetReleases() []*hapi_release5.Release { + if m != nil { + return m.Releases + } + return nil +} + +// TestReleaseRequest is a request to get the status of a release. +type TestReleaseRequest struct { + // Name is the name of the release + Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"` + // timeout specifies the max amount of time any kubernetes client command can run. + Timeout int64 `protobuf:"varint,2,opt,name=timeout" json:"timeout,omitempty"` + // cleanup specifies whether or not to attempt pod deletion after test completes + Cleanup bool `protobuf:"varint,3,opt,name=cleanup" json:"cleanup,omitempty"` +} + +func (m *TestReleaseRequest) Reset() { *m = TestReleaseRequest{} } +func (m *TestReleaseRequest) String() string { return proto.CompactTextString(m) } +func (*TestReleaseRequest) ProtoMessage() {} +func (*TestReleaseRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{19} } + +func (m *TestReleaseRequest) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *TestReleaseRequest) GetTimeout() int64 { + if m != nil { + return m.Timeout + } + return 0 +} + +func (m *TestReleaseRequest) GetCleanup() bool { + if m != nil { + return m.Cleanup + } + return false +} + +// TestReleaseResponse represents a message from executing a test +type TestReleaseResponse struct { + Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` + Status hapi_release1.TestRun_Status `protobuf:"varint,2,opt,name=status,enum=hapi.release.TestRun_Status" json:"status,omitempty"` +} + +func (m *TestReleaseResponse) Reset() { *m = TestReleaseResponse{} } +func (m *TestReleaseResponse) String() string { return proto.CompactTextString(m) } +func (*TestReleaseResponse) ProtoMessage() {} +func (*TestReleaseResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{20} } + +func (m *TestReleaseResponse) GetMsg() string { + if m != nil { + return m.Msg + } + return "" +} + +func (m *TestReleaseResponse) GetStatus() hapi_release1.TestRun_Status { + if m != nil { + return m.Status + } + return hapi_release1.TestRun_UNKNOWN +} + +func init() { + proto.RegisterType((*ListReleasesRequest)(nil), "hapi.services.tiller.ListReleasesRequest") + proto.RegisterType((*ListSort)(nil), "hapi.services.tiller.ListSort") + proto.RegisterType((*ListReleasesResponse)(nil), "hapi.services.tiller.ListReleasesResponse") + proto.RegisterType((*GetReleaseStatusRequest)(nil), "hapi.services.tiller.GetReleaseStatusRequest") + proto.RegisterType((*GetReleaseStatusResponse)(nil), "hapi.services.tiller.GetReleaseStatusResponse") + proto.RegisterType((*GetReleaseContentRequest)(nil), "hapi.services.tiller.GetReleaseContentRequest") + proto.RegisterType((*GetReleaseContentResponse)(nil), "hapi.services.tiller.GetReleaseContentResponse") + proto.RegisterType((*UpdateReleaseRequest)(nil), "hapi.services.tiller.UpdateReleaseRequest") + proto.RegisterType((*UpdateReleaseResponse)(nil), "hapi.services.tiller.UpdateReleaseResponse") + proto.RegisterType((*RollbackReleaseRequest)(nil), "hapi.services.tiller.RollbackReleaseRequest") + proto.RegisterType((*RollbackReleaseResponse)(nil), "hapi.services.tiller.RollbackReleaseResponse") + proto.RegisterType((*InstallReleaseRequest)(nil), "hapi.services.tiller.InstallReleaseRequest") + proto.RegisterType((*InstallReleaseResponse)(nil), "hapi.services.tiller.InstallReleaseResponse") + proto.RegisterType((*UninstallReleaseRequest)(nil), "hapi.services.tiller.UninstallReleaseRequest") + proto.RegisterType((*UninstallReleaseResponse)(nil), "hapi.services.tiller.UninstallReleaseResponse") + proto.RegisterType((*GetVersionRequest)(nil), "hapi.services.tiller.GetVersionRequest") + proto.RegisterType((*GetVersionResponse)(nil), "hapi.services.tiller.GetVersionResponse") + proto.RegisterType((*GetHistoryRequest)(nil), "hapi.services.tiller.GetHistoryRequest") + proto.RegisterType((*GetHistoryResponse)(nil), "hapi.services.tiller.GetHistoryResponse") + proto.RegisterType((*TestReleaseRequest)(nil), "hapi.services.tiller.TestReleaseRequest") + proto.RegisterType((*TestReleaseResponse)(nil), "hapi.services.tiller.TestReleaseResponse") + proto.RegisterEnum("hapi.services.tiller.ListSort_SortBy", ListSort_SortBy_name, ListSort_SortBy_value) + proto.RegisterEnum("hapi.services.tiller.ListSort_SortOrder", ListSort_SortOrder_name, ListSort_SortOrder_value) +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// Client API for ReleaseService service + +type ReleaseServiceClient interface { + // ListReleases retrieves release history. + // TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + ListReleases(ctx context.Context, in *ListReleasesRequest, opts ...grpc.CallOption) (ReleaseService_ListReleasesClient, error) + // GetReleasesStatus retrieves status information for the specified release. + GetReleaseStatus(ctx context.Context, in *GetReleaseStatusRequest, opts ...grpc.CallOption) (*GetReleaseStatusResponse, error) + // GetReleaseContent retrieves the release content (chart + value) for the specified release. + GetReleaseContent(ctx context.Context, in *GetReleaseContentRequest, opts ...grpc.CallOption) (*GetReleaseContentResponse, error) + // UpdateRelease updates release content. + UpdateRelease(ctx context.Context, in *UpdateReleaseRequest, opts ...grpc.CallOption) (*UpdateReleaseResponse, error) + // InstallRelease requests installation of a chart as a new release. + InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) + // UninstallRelease requests deletion of a named release. + UninstallRelease(ctx context.Context, in *UninstallReleaseRequest, opts ...grpc.CallOption) (*UninstallReleaseResponse, error) + // GetVersion returns the current version of the server. + GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) + // RollbackRelease rolls back a release to a previous version. + RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error) + // ReleaseHistory retrieves a releasse's history. + GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error) + // RunReleaseTest executes the tests defined of a named release + RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) +} + +type releaseServiceClient struct { + cc *grpc.ClientConn +} + +func NewReleaseServiceClient(cc *grpc.ClientConn) ReleaseServiceClient { + return &releaseServiceClient{cc} +} + +func (c *releaseServiceClient) ListReleases(ctx context.Context, in *ListReleasesRequest, opts ...grpc.CallOption) (ReleaseService_ListReleasesClient, error) { + stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[0], c.cc, "/hapi.services.tiller.ReleaseService/ListReleases", opts...) + if err != nil { + return nil, err + } + x := &releaseServiceListReleasesClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ReleaseService_ListReleasesClient interface { + Recv() (*ListReleasesResponse, error) + grpc.ClientStream +} + +type releaseServiceListReleasesClient struct { + grpc.ClientStream +} + +func (x *releaseServiceListReleasesClient) Recv() (*ListReleasesResponse, error) { + m := new(ListReleasesResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *releaseServiceClient) GetReleaseStatus(ctx context.Context, in *GetReleaseStatusRequest, opts ...grpc.CallOption) (*GetReleaseStatusResponse, error) { + out := new(GetReleaseStatusResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetReleaseStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) GetReleaseContent(ctx context.Context, in *GetReleaseContentRequest, opts ...grpc.CallOption) (*GetReleaseContentResponse, error) { + out := new(GetReleaseContentResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetReleaseContent", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) UpdateRelease(ctx context.Context, in *UpdateReleaseRequest, opts ...grpc.CallOption) (*UpdateReleaseResponse, error) { + out := new(UpdateReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/UpdateRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) InstallRelease(ctx context.Context, in *InstallReleaseRequest, opts ...grpc.CallOption) (*InstallReleaseResponse, error) { + out := new(InstallReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/InstallRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) UninstallRelease(ctx context.Context, in *UninstallReleaseRequest, opts ...grpc.CallOption) (*UninstallReleaseResponse, error) { + out := new(UninstallReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/UninstallRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) { + out := new(GetVersionResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetVersion", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) RollbackRelease(ctx context.Context, in *RollbackReleaseRequest, opts ...grpc.CallOption) (*RollbackReleaseResponse, error) { + out := new(RollbackReleaseResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/RollbackRelease", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) GetHistory(ctx context.Context, in *GetHistoryRequest, opts ...grpc.CallOption) (*GetHistoryResponse, error) { + out := new(GetHistoryResponse) + err := grpc.Invoke(ctx, "/hapi.services.tiller.ReleaseService/GetHistory", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *releaseServiceClient) RunReleaseTest(ctx context.Context, in *TestReleaseRequest, opts ...grpc.CallOption) (ReleaseService_RunReleaseTestClient, error) { + stream, err := grpc.NewClientStream(ctx, &_ReleaseService_serviceDesc.Streams[1], c.cc, "/hapi.services.tiller.ReleaseService/RunReleaseTest", opts...) + if err != nil { + return nil, err + } + x := &releaseServiceRunReleaseTestClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type ReleaseService_RunReleaseTestClient interface { + Recv() (*TestReleaseResponse, error) + grpc.ClientStream +} + +type releaseServiceRunReleaseTestClient struct { + grpc.ClientStream +} + +func (x *releaseServiceRunReleaseTestClient) Recv() (*TestReleaseResponse, error) { + m := new(TestReleaseResponse) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for ReleaseService service + +type ReleaseServiceServer interface { + // ListReleases retrieves release history. + // TODO: Allow filtering the set of releases by + // release status. By default, ListAllReleases returns the releases who + // current status is "Active". + ListReleases(*ListReleasesRequest, ReleaseService_ListReleasesServer) error + // GetReleasesStatus retrieves status information for the specified release. + GetReleaseStatus(context.Context, *GetReleaseStatusRequest) (*GetReleaseStatusResponse, error) + // GetReleaseContent retrieves the release content (chart + value) for the specified release. + GetReleaseContent(context.Context, *GetReleaseContentRequest) (*GetReleaseContentResponse, error) + // UpdateRelease updates release content. + UpdateRelease(context.Context, *UpdateReleaseRequest) (*UpdateReleaseResponse, error) + // InstallRelease requests installation of a chart as a new release. + InstallRelease(context.Context, *InstallReleaseRequest) (*InstallReleaseResponse, error) + // UninstallRelease requests deletion of a named release. + UninstallRelease(context.Context, *UninstallReleaseRequest) (*UninstallReleaseResponse, error) + // GetVersion returns the current version of the server. + GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) + // RollbackRelease rolls back a release to a previous version. + RollbackRelease(context.Context, *RollbackReleaseRequest) (*RollbackReleaseResponse, error) + // ReleaseHistory retrieves a releasse's history. + GetHistory(context.Context, *GetHistoryRequest) (*GetHistoryResponse, error) + // RunReleaseTest executes the tests defined of a named release + RunReleaseTest(*TestReleaseRequest, ReleaseService_RunReleaseTestServer) error +} + +func RegisterReleaseServiceServer(s *grpc.Server, srv ReleaseServiceServer) { + s.RegisterService(&_ReleaseService_serviceDesc, srv) +} + +func _ReleaseService_ListReleases_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(ListReleasesRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ReleaseServiceServer).ListReleases(m, &releaseServiceListReleasesServer{stream}) +} + +type ReleaseService_ListReleasesServer interface { + Send(*ListReleasesResponse) error + grpc.ServerStream +} + +type releaseServiceListReleasesServer struct { + grpc.ServerStream +} + +func (x *releaseServiceListReleasesServer) Send(m *ListReleasesResponse) error { + return x.ServerStream.SendMsg(m) +} + +func _ReleaseService_GetReleaseStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReleaseStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).GetReleaseStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/GetReleaseStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).GetReleaseStatus(ctx, req.(*GetReleaseStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_GetReleaseContent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetReleaseContentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).GetReleaseContent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/GetReleaseContent", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).GetReleaseContent(ctx, req.(*GetReleaseContentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_UpdateRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).UpdateRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/UpdateRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).UpdateRelease(ctx, req.(*UpdateReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_InstallRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InstallReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).InstallRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/InstallRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).InstallRelease(ctx, req.(*InstallReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_UninstallRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UninstallReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).UninstallRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/UninstallRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).UninstallRelease(ctx, req.(*UninstallReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetVersionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).GetVersion(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/GetVersion", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).GetVersion(ctx, req.(*GetVersionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_RollbackRelease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RollbackReleaseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).RollbackRelease(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/RollbackRelease", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).RollbackRelease(ctx, req.(*RollbackReleaseRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_GetHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ReleaseServiceServer).GetHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hapi.services.tiller.ReleaseService/GetHistory", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ReleaseServiceServer).GetHistory(ctx, req.(*GetHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ReleaseService_RunReleaseTest_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(TestReleaseRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(ReleaseServiceServer).RunReleaseTest(m, &releaseServiceRunReleaseTestServer{stream}) +} + +type ReleaseService_RunReleaseTestServer interface { + Send(*TestReleaseResponse) error + grpc.ServerStream +} + +type releaseServiceRunReleaseTestServer struct { + grpc.ServerStream +} + +func (x *releaseServiceRunReleaseTestServer) Send(m *TestReleaseResponse) error { + return x.ServerStream.SendMsg(m) +} + +var _ReleaseService_serviceDesc = grpc.ServiceDesc{ + ServiceName: "hapi.services.tiller.ReleaseService", + HandlerType: (*ReleaseServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetReleaseStatus", + Handler: _ReleaseService_GetReleaseStatus_Handler, + }, + { + MethodName: "GetReleaseContent", + Handler: _ReleaseService_GetReleaseContent_Handler, + }, + { + MethodName: "UpdateRelease", + Handler: _ReleaseService_UpdateRelease_Handler, + }, + { + MethodName: "InstallRelease", + Handler: _ReleaseService_InstallRelease_Handler, + }, + { + MethodName: "UninstallRelease", + Handler: _ReleaseService_UninstallRelease_Handler, + }, + { + MethodName: "GetVersion", + Handler: _ReleaseService_GetVersion_Handler, + }, + { + MethodName: "RollbackRelease", + Handler: _ReleaseService_RollbackRelease_Handler, + }, + { + MethodName: "GetHistory", + Handler: _ReleaseService_GetHistory_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "ListReleases", + Handler: _ReleaseService_ListReleases_Handler, + ServerStreams: true, + }, + { + StreamName: "RunReleaseTest", + Handler: _ReleaseService_RunReleaseTest_Handler, + ServerStreams: true, + }, + }, + Metadata: "hapi/services/tiller.proto", +} + +func init() { proto.RegisterFile("hapi/services/tiller.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 1217 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xdd, 0x6e, 0xe3, 0xc4, + 0x17, 0xaf, 0xf3, 0x9d, 0x93, 0x36, 0xff, 0x74, 0x9a, 0xb6, 0xae, 0xff, 0x0b, 0x2a, 0x46, 0xb0, + 0xd9, 0x85, 0x4d, 0x21, 0x70, 0x83, 0x84, 0x90, 0xba, 0xdd, 0xa8, 0x2d, 0x94, 0xae, 0xe4, 0x6c, + 0x17, 0x09, 0x01, 0x91, 0x9b, 0x4c, 0x5a, 0xb3, 0x8e, 0x27, 0x78, 0xc6, 0x65, 0x7b, 0xcb, 0x1d, + 0x8f, 0xc2, 0x5b, 0xf0, 0x1e, 0x5c, 0xc2, 0x83, 0x20, 0xcf, 0x87, 0xeb, 0x49, 0xed, 0xd6, 0xf4, + 0x26, 0x9e, 0x99, 0xf3, 0xfd, 0x3b, 0x67, 0xce, 0x9c, 0x80, 0x75, 0xe9, 0x2e, 0xbc, 0x3d, 0x8a, + 0xc3, 0x2b, 0x6f, 0x82, 0xe9, 0x1e, 0xf3, 0x7c, 0x1f, 0x87, 0xfd, 0x45, 0x48, 0x18, 0x41, 0xdd, + 0x98, 0xd6, 0x57, 0xb4, 0xbe, 0xa0, 0x59, 0x5b, 0x5c, 0x62, 0x72, 0xe9, 0x86, 0x4c, 0xfc, 0x0a, + 0x6e, 0x6b, 0x3b, 0x7d, 0x4e, 0x82, 0x99, 0x77, 0x21, 0x09, 0xc2, 0x44, 0x88, 0x7d, 0xec, 0x52, + 0xac, 0xbe, 0x9a, 0x90, 0xa2, 0x79, 0xc1, 0x8c, 0x48, 0xc2, 0xff, 0x35, 0x02, 0xc3, 0x94, 0x8d, + 0xc3, 0x28, 0x90, 0xc4, 0x1d, 0x8d, 0x48, 0x99, 0xcb, 0x22, 0xaa, 0x19, 0xbb, 0xc2, 0x21, 0xf5, + 0x48, 0xa0, 0xbe, 0x82, 0x66, 0xff, 0x59, 0x82, 0x8d, 0x13, 0x8f, 0x32, 0x47, 0x08, 0x52, 0x07, + 0xff, 0x12, 0x61, 0xca, 0x50, 0x17, 0xaa, 0xbe, 0x37, 0xf7, 0x98, 0x69, 0xec, 0x1a, 0xbd, 0xb2, + 0x23, 0x36, 0x68, 0x0b, 0x6a, 0x64, 0x36, 0xa3, 0x98, 0x99, 0xa5, 0x5d, 0xa3, 0xd7, 0x74, 0xe4, + 0x0e, 0x7d, 0x05, 0x75, 0x4a, 0x42, 0x36, 0x3e, 0xbf, 0x36, 0xcb, 0xbb, 0x46, 0xaf, 0x3d, 0xf8, + 0xa0, 0x9f, 0x85, 0x53, 0x3f, 0xb6, 0x34, 0x22, 0x21, 0xeb, 0xc7, 0x3f, 0xcf, 0xaf, 0x9d, 0x1a, + 0xe5, 0xdf, 0x58, 0xef, 0xcc, 0xf3, 0x19, 0x0e, 0xcd, 0x8a, 0xd0, 0x2b, 0x76, 0xe8, 0x10, 0x80, + 0xeb, 0x25, 0xe1, 0x14, 0x87, 0x66, 0x95, 0xab, 0xee, 0x15, 0x50, 0xfd, 0x32, 0xe6, 0x77, 0x9a, + 0x54, 0x2d, 0xd1, 0x97, 0xb0, 0x2a, 0x20, 0x19, 0x4f, 0xc8, 0x14, 0x53, 0xb3, 0xb6, 0x5b, 0xee, + 0xb5, 0x07, 0x3b, 0x42, 0x95, 0x82, 0x7f, 0x24, 0x40, 0x3b, 0x20, 0x53, 0xec, 0xb4, 0x04, 0x7b, + 0xbc, 0xa6, 0xe8, 0x11, 0x34, 0x03, 0x77, 0x8e, 0xe9, 0xc2, 0x9d, 0x60, 0xb3, 0xce, 0x3d, 0xbc, + 0x39, 0xb0, 0x7f, 0x82, 0x86, 0x32, 0x6e, 0x0f, 0xa0, 0x26, 0x42, 0x43, 0x2d, 0xa8, 0x9f, 0x9d, + 0x7e, 0x73, 0xfa, 0xf2, 0xbb, 0xd3, 0xce, 0x0a, 0x6a, 0x40, 0xe5, 0x74, 0xff, 0xdb, 0x61, 0xc7, + 0x40, 0xeb, 0xb0, 0x76, 0xb2, 0x3f, 0x7a, 0x35, 0x76, 0x86, 0x27, 0xc3, 0xfd, 0xd1, 0xf0, 0x45, + 0xa7, 0x64, 0xbf, 0x0b, 0xcd, 0xc4, 0x67, 0x54, 0x87, 0xf2, 0xfe, 0xe8, 0x40, 0x88, 0xbc, 0x18, + 0x8e, 0x0e, 0x3a, 0x86, 0xfd, 0xbb, 0x01, 0x5d, 0x3d, 0x45, 0x74, 0x41, 0x02, 0x8a, 0xe3, 0x1c, + 0x4d, 0x48, 0x14, 0x24, 0x39, 0xe2, 0x1b, 0x84, 0xa0, 0x12, 0xe0, 0xb7, 0x2a, 0x43, 0x7c, 0x1d, + 0x73, 0x32, 0xc2, 0x5c, 0x9f, 0x67, 0xa7, 0xec, 0x88, 0x0d, 0xfa, 0x14, 0x1a, 0x32, 0x74, 0x6a, + 0x56, 0x76, 0xcb, 0xbd, 0xd6, 0x60, 0x53, 0x07, 0x44, 0x5a, 0x74, 0x12, 0x36, 0xfb, 0x10, 0xb6, + 0x0f, 0xb1, 0xf2, 0x44, 0xe0, 0xa5, 0x2a, 0x26, 0xb6, 0xeb, 0xce, 0x31, 0x77, 0x26, 0xb6, 0xeb, + 0xce, 0x31, 0x32, 0xa1, 0x2e, 0xcb, 0x8d, 0xbb, 0x53, 0x75, 0xd4, 0xd6, 0x66, 0x60, 0xde, 0x56, + 0x24, 0xe3, 0xca, 0xd2, 0xf4, 0x21, 0x54, 0xe2, 0x9b, 0xc0, 0xd5, 0xb4, 0x06, 0x48, 0xf7, 0xf3, + 0x38, 0x98, 0x11, 0x87, 0xd3, 0xf5, 0x54, 0x95, 0x97, 0x53, 0x75, 0x94, 0xb6, 0x7a, 0x40, 0x02, + 0x86, 0x03, 0xf6, 0x30, 0xff, 0x4f, 0x60, 0x27, 0x43, 0x93, 0x0c, 0x60, 0x0f, 0xea, 0xd2, 0x35, + 0xae, 0x2d, 0x17, 0x57, 0xc5, 0x65, 0xff, 0x5d, 0x82, 0xee, 0xd9, 0x62, 0xea, 0x32, 0xac, 0x48, + 0x77, 0x38, 0xf5, 0x18, 0xaa, 0xbc, 0xa3, 0x48, 0x2c, 0xd6, 0x85, 0x6e, 0xd1, 0x76, 0x0e, 0xe2, + 0x5f, 0x47, 0xd0, 0xd1, 0x53, 0xa8, 0x5d, 0xb9, 0x7e, 0x84, 0x29, 0x07, 0x22, 0x41, 0x4d, 0x72, + 0xf2, 0x76, 0xe4, 0x48, 0x0e, 0xb4, 0x0d, 0xf5, 0x69, 0x78, 0x1d, 0xf7, 0x13, 0x7e, 0x05, 0x1b, + 0x4e, 0x6d, 0x1a, 0x5e, 0x3b, 0x51, 0x80, 0xde, 0x87, 0xb5, 0xa9, 0x47, 0xdd, 0x73, 0x1f, 0x8f, + 0x2f, 0x09, 0x79, 0x43, 0xf9, 0x2d, 0x6c, 0x38, 0xab, 0xf2, 0xf0, 0x28, 0x3e, 0x43, 0x56, 0x5c, + 0x49, 0x93, 0x10, 0xbb, 0x0c, 0x9b, 0x35, 0x4e, 0x4f, 0xf6, 0x31, 0x86, 0xcc, 0x9b, 0x63, 0x12, + 0x31, 0x7e, 0x75, 0xca, 0x8e, 0xda, 0xa2, 0xf7, 0x60, 0x35, 0xc4, 0x14, 0xb3, 0xb1, 0xf4, 0xb2, + 0xc1, 0x25, 0x5b, 0xfc, 0xec, 0xb5, 0x70, 0x0b, 0x41, 0xe5, 0x57, 0xd7, 0x63, 0x66, 0x93, 0x93, + 0xf8, 0x5a, 0x88, 0x45, 0x14, 0x2b, 0x31, 0x50, 0x62, 0x11, 0xc5, 0x52, 0xac, 0x0b, 0xd5, 0x19, + 0x09, 0x27, 0xd8, 0x6c, 0x71, 0x9a, 0xd8, 0xd8, 0x47, 0xb0, 0xb9, 0x04, 0xf2, 0x43, 0xf3, 0xf5, + 0x8f, 0x01, 0x5b, 0x0e, 0xf1, 0xfd, 0x73, 0x77, 0xf2, 0xa6, 0x40, 0xc6, 0x52, 0xe0, 0x96, 0xee, + 0x06, 0xb7, 0x9c, 0x01, 0x6e, 0xaa, 0x08, 0x2b, 0x5a, 0x11, 0x6a, 0xb0, 0x57, 0xf3, 0x61, 0xaf, + 0xe9, 0xb0, 0x2b, 0x4c, 0xeb, 0x29, 0x4c, 0x13, 0xc0, 0x1a, 0x69, 0xc0, 0xbe, 0x86, 0xed, 0x5b, + 0x51, 0x3e, 0x14, 0xb2, 0x3f, 0x4a, 0xb0, 0x79, 0x1c, 0x50, 0xe6, 0xfa, 0xfe, 0x12, 0x62, 0x49, + 0x3d, 0x1b, 0x85, 0xeb, 0xb9, 0xf4, 0x5f, 0xea, 0xb9, 0xac, 0x41, 0xae, 0xf2, 0x53, 0x49, 0xe5, + 0xa7, 0x50, 0x8d, 0x6b, 0x9d, 0xa5, 0xb6, 0xd4, 0x59, 0xd0, 0x3b, 0x00, 0xa2, 0x28, 0xb9, 0x72, + 0x01, 0x6d, 0x93, 0x9f, 0x9c, 0xca, 0x46, 0xa2, 0xb2, 0xd1, 0xc8, 0xce, 0x46, 0xaa, 0xc2, 0xed, + 0x63, 0xd8, 0x5a, 0x86, 0xea, 0xa1, 0xb0, 0xff, 0x66, 0xc0, 0xf6, 0x59, 0xe0, 0x65, 0x02, 0x9f, + 0x55, 0xaa, 0xb7, 0xa0, 0x28, 0x65, 0x40, 0xd1, 0x85, 0xea, 0x22, 0x0a, 0x2f, 0xb0, 0x84, 0x56, + 0x6c, 0xd2, 0x31, 0x56, 0xb4, 0x18, 0xed, 0x31, 0x98, 0xb7, 0x7d, 0x78, 0x60, 0x44, 0xb1, 0xd7, + 0xc9, 0x4b, 0xd0, 0x14, 0x5d, 0xdf, 0xde, 0x80, 0xf5, 0x43, 0xcc, 0x5e, 0x8b, 0x6b, 0x21, 0xc3, + 0xb3, 0x87, 0x80, 0xd2, 0x87, 0x37, 0xf6, 0xe4, 0x91, 0x6e, 0x4f, 0x8d, 0x45, 0x8a, 0x5f, 0x71, + 0xd9, 0x5f, 0x70, 0xdd, 0x47, 0x1e, 0x65, 0x24, 0xbc, 0xbe, 0x0b, 0xba, 0x0e, 0x94, 0xe7, 0xee, + 0x5b, 0xf9, 0x50, 0xc4, 0x4b, 0xfb, 0x90, 0x7b, 0x90, 0x88, 0x4a, 0x0f, 0xd2, 0xcf, 0xae, 0x51, + 0xec, 0xd9, 0xfd, 0x01, 0xd0, 0x2b, 0x9c, 0x4c, 0x00, 0xf7, 0xbc, 0x58, 0x2a, 0x09, 0x25, 0xbd, + 0xd0, 0x4c, 0xa8, 0x4f, 0x7c, 0xec, 0x06, 0xd1, 0x42, 0xa6, 0x4d, 0x6d, 0xed, 0x1f, 0x61, 0x43, + 0xd3, 0x2e, 0xfd, 0x8c, 0xe3, 0xa1, 0x17, 0x52, 0x7b, 0xbc, 0x44, 0x9f, 0x43, 0x4d, 0x8c, 0x45, + 0x5c, 0x77, 0x7b, 0xf0, 0x48, 0xf7, 0x9b, 0x2b, 0x89, 0x02, 0x39, 0x47, 0x39, 0x92, 0x77, 0xf0, + 0x57, 0x03, 0xda, 0xea, 0xa1, 0x17, 0x43, 0x1b, 0xf2, 0x60, 0x35, 0x3d, 0xd1, 0xa0, 0x27, 0xf9, + 0x33, 0xdd, 0xd2, 0x60, 0x6a, 0x3d, 0x2d, 0xc2, 0x2a, 0x22, 0xb0, 0x57, 0x3e, 0x31, 0x10, 0x85, + 0xce, 0xf2, 0xa0, 0x81, 0x9e, 0x65, 0xeb, 0xc8, 0x99, 0x6c, 0xac, 0x7e, 0x51, 0x76, 0x65, 0x16, + 0x5d, 0xf1, 0x9a, 0xd1, 0xa7, 0x03, 0x74, 0xaf, 0x1a, 0x7d, 0x20, 0xb1, 0xf6, 0x0a, 0xf3, 0x27, + 0x76, 0x7f, 0x86, 0x35, 0xed, 0x85, 0x43, 0x39, 0x68, 0x65, 0xcd, 0x1a, 0xd6, 0x47, 0x85, 0x78, + 0x13, 0x5b, 0x73, 0x68, 0xeb, 0x4d, 0x0a, 0xe5, 0x28, 0xc8, 0xec, 0xfa, 0xd6, 0xc7, 0xc5, 0x98, + 0x13, 0x73, 0x14, 0x3a, 0xcb, 0x3d, 0x24, 0x2f, 0x8f, 0x39, 0xfd, 0x2e, 0x2f, 0x8f, 0x79, 0xad, + 0xc9, 0x5e, 0x41, 0x2e, 0xc0, 0x4d, 0x0b, 0x41, 0x8f, 0x73, 0x13, 0xa2, 0x77, 0x1e, 0xab, 0x77, + 0x3f, 0x63, 0x62, 0x62, 0x01, 0xff, 0x5b, 0x7a, 0x63, 0x51, 0x0e, 0x34, 0xd9, 0x03, 0x87, 0xf5, + 0xac, 0x20, 0xf7, 0x52, 0x50, 0xb2, 0x2b, 0xdd, 0x11, 0x94, 0xde, 0xf2, 0xee, 0x08, 0x6a, 0xa9, + 0xc1, 0xd9, 0x2b, 0xc8, 0x83, 0xb6, 0x13, 0x05, 0xd2, 0x74, 0xdc, 0x16, 0x50, 0x8e, 0xf4, 0xed, + 0xae, 0x66, 0x3d, 0x29, 0xc0, 0x79, 0x73, 0xbf, 0x9f, 0xc3, 0xf7, 0x0d, 0xc5, 0x7a, 0x5e, 0xe3, + 0xff, 0x69, 0x3f, 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf3, 0x7c, 0x9c, 0x49, 0xc1, 0x0f, 0x00, + 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/version/version.pb.go b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/version/version.pb.go new file mode 100644 index 000000000..13c8568f0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/proto/hapi/version/version.pb.go @@ -0,0 +1,81 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: hapi/version/version.proto + +/* +Package version is a generated protocol buffer package. + +It is generated from these files: + hapi/version/version.proto + +It has these top-level messages: + Version +*/ +package version + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type Version struct { + // Sem ver string for the version + SemVer string `protobuf:"bytes,1,opt,name=sem_ver,json=semVer" json:"sem_ver,omitempty"` + GitCommit string `protobuf:"bytes,2,opt,name=git_commit,json=gitCommit" json:"git_commit,omitempty"` + GitTreeState string `protobuf:"bytes,3,opt,name=git_tree_state,json=gitTreeState" json:"git_tree_state,omitempty"` +} + +func (m *Version) Reset() { *m = Version{} } +func (m *Version) String() string { return proto.CompactTextString(m) } +func (*Version) ProtoMessage() {} +func (*Version) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } + +func (m *Version) GetSemVer() string { + if m != nil { + return m.SemVer + } + return "" +} + +func (m *Version) GetGitCommit() string { + if m != nil { + return m.GitCommit + } + return "" +} + +func (m *Version) GetGitTreeState() string { + if m != nil { + return m.GitTreeState + } + return "" +} + +func init() { + proto.RegisterType((*Version)(nil), "hapi.version.Version") +} + +func init() { proto.RegisterFile("hapi/version/version.proto", fileDescriptor0) } + +var fileDescriptor0 = []byte{ + // 151 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x48, 0x2c, 0xc8, + 0xd4, 0x2f, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0x83, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, + 0x42, 0x3c, 0x20, 0x39, 0x3d, 0xa8, 0x98, 0x52, 0x3a, 0x17, 0x7b, 0x18, 0x84, 0x29, 0x24, 0xce, + 0xc5, 0x5e, 0x9c, 0x9a, 0x1b, 0x5f, 0x96, 0x5a, 0x24, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x19, 0xc4, + 0x56, 0x9c, 0x9a, 0x1b, 0x96, 0x5a, 0x24, 0x24, 0xcb, 0xc5, 0x95, 0x9e, 0x59, 0x12, 0x9f, 0x9c, + 0x9f, 0x9b, 0x9b, 0x59, 0x22, 0xc1, 0x04, 0x96, 0xe3, 0x4c, 0xcf, 0x2c, 0x71, 0x06, 0x0b, 0x08, + 0xa9, 0x70, 0xf1, 0x81, 0xa4, 0x4b, 0x8a, 0x52, 0x53, 0xe3, 0x8b, 0x4b, 0x12, 0x4b, 0x52, 0x25, + 0x98, 0xc1, 0x4a, 0x78, 0xd2, 0x33, 0x4b, 0x42, 0x8a, 0x52, 0x53, 0x83, 0x41, 0x62, 0x4e, 0x9c, + 0x51, 0xec, 0x50, 0x3b, 0x93, 0xd8, 0xc0, 0x0e, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x20, + 0xcc, 0x0e, 0x1b, 0xa6, 0x00, 0x00, 0x00, +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/provenance/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/provenance/doc.go new file mode 100644 index 000000000..dacfa9e69 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/provenance/doc.go @@ -0,0 +1,37 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 "k8s.io/helm/pkg/provenance" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign.go b/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign.go new file mode 100644 index 000000000..ecd6612a3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign.go @@ -0,0 +1,409 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" + + "golang.org/x/crypto/openpgp" + "golang.org/x/crypto/openpgp/clearsign" + "golang.org/x/crypto/openpgp/packet" + + "k8s.io/helm/pkg/chartutil" + hapi "k8s.io/helm/pkg/proto/hapi/chart" +) + +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, fmt.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") + } + + // 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") + } + + 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 "", nil + } + + // Sign the buffer + w, err := clearsign.Encode(out, s.Entity.PrivateKey, &defaultPGPConfig) + if err != nil { + return "", err + } + _, err = io.Copy(w, b) + w.Close() + return out.String(), err +} + +// 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, fmt.Errorf("%s cannot be a directory", fname) + } + } + + // First verify the signature + sig, err := s.decodeSignature(sigpath) + if err != nil { + return ver, fmt.Errorf("failed to decode signature: %s", err) + } + + 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, fmt.Errorf("provenance does not contain a SHA for a file named %q", basename) + } else if sha != sum { + return ver, fmt.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 := chartutil.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() + io.Copy(hash, in) + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign_test.go b/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign_test.go new file mode 100644 index 000000000..388941deb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/provenance/sign_test.go @@ -0,0 +1,311 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 ( + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" + + pgperrors "golang.org/x/crypto/openpgp/errors" +) + +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 = `description: Test chart versioning +name: hashtest +version: 1.2.3 + +... +files: + hashtest-1.2.3.tgz: sha256:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75 +` + +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:8e90e879e2a04b1900570e1c198755e46e4706d70b0e79f5edabfac7900e4e75" { + 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) + } +} + +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/src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest-1.2.3.tgz b/src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/hashtest-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..1e89b524f4fc89e4a483ef8dc4fefdc79d64786f GIT binary patch literal 465 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 F^>pMLK0g2e literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/helm-test-key.secret b/src/vendor/github.com/kubernetes/helm/pkg/provenance/testdata/helm-test-key.secret new file mode 100644 index 0000000000000000000000000000000000000000..a966aef93ed97d01d764f29940738df6df2d9d24 GIT binary patch literal 2545 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 hashtest.sha256 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment.go b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment.go new file mode 100644 index 000000000..3b3d07933 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment.go @@ -0,0 +1,122 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 releasetesting + +import ( + "bytes" + "fmt" + "log" + "time" + + "k8s.io/kubernetes/pkg/apis/core" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/tiller/environment" +) + +// Environment encapsulates information about where test suite executes and returns results +type Environment struct { + Namespace string + KubeClient environment.KubeClient + Stream services.ReleaseService_RunReleaseTestServer + Timeout int64 +} + +func (env *Environment) createTestPod(test *test) error { + b := bytes.NewBufferString(test.manifest) + if err := env.KubeClient.Create(env.Namespace, b, env.Timeout, false); err != nil { + log.Printf(err.Error()) + test.result.Info = err.Error() + test.result.Status = release.TestRun_FAILURE + return err + } + + return nil +} + +func (env *Environment) getTestPodStatus(test *test) (core.PodPhase, error) { + b := bytes.NewBufferString(test.manifest) + status, err := env.KubeClient.WaitAndGetCompletedPodPhase(env.Namespace, b, time.Duration(env.Timeout)*time.Second) + if err != nil { + log.Printf("Error getting status for pod %s: %s", test.result.Name, err) + test.result.Info = err.Error() + test.result.Status = release.TestRun_UNKNOWN + return status, err + } + + return status, err +} + +func (env *Environment) streamResult(r *release.TestRun) error { + switch r.Status { + case release.TestRun_SUCCESS: + if err := env.streamSuccess(r.Name); err != nil { + return err + } + case release.TestRun_FAILURE: + if err := env.streamFailed(r.Name); err != nil { + return err + } + + default: + if err := env.streamUnknown(r.Name, r.Info); err != nil { + return err + } + } + return nil +} + +func (env *Environment) streamRunning(name string) error { + msg := "RUNNING: " + name + return env.streamMessage(msg, release.TestRun_RUNNING) +} + +func (env *Environment) streamError(info string) error { + msg := "ERROR: " + info + return env.streamMessage(msg, release.TestRun_FAILURE) +} + +func (env *Environment) streamFailed(name string) error { + msg := fmt.Sprintf("FAILED: %s, run `kubectl logs %s --namespace %s` for more info", name, name, env.Namespace) + return env.streamMessage(msg, release.TestRun_FAILURE) +} + +func (env *Environment) streamSuccess(name string) error { + msg := fmt.Sprintf("PASSED: %s", name) + return env.streamMessage(msg, release.TestRun_SUCCESS) +} + +func (env *Environment) streamUnknown(name, info string) error { + msg := fmt.Sprintf("UNKNOWN: %s: %s", name, info) + return env.streamMessage(msg, release.TestRun_UNKNOWN) +} + +func (env *Environment) streamMessage(msg string, status release.TestRun_Status) error { + resp := &services.TestReleaseResponse{Msg: msg, Status: status} + return env.Stream.Send(resp) +} + +// DeleteTestPods deletes resources given in testManifests +func (env *Environment) DeleteTestPods(testManifests []string) { + for _, testManifest := range testManifests { + err := env.KubeClient.Delete(env.Namespace, bytes.NewBufferString(testManifest)) + if err != nil { + env.streamError(err.Error()) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment_test.go b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment_test.go new file mode 100644 index 000000000..0199b74eb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/environment_test.go @@ -0,0 +1,182 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 releasetesting + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + tillerEnv "k8s.io/helm/pkg/tiller/environment" +) + +func TestCreateTestPodSuccess(t *testing.T) { + env := testEnvFixture() + test := testFixture() + + err := env.createTestPod(test) + if err != nil { + t.Errorf("Expected no error, got an error: %s", err) + } +} + +func TestCreateTestPodFailure(t *testing.T) { + env := testEnvFixture() + env.KubeClient = newCreateFailingKubeClient() + test := testFixture() + + err := env.createTestPod(test) + if err == nil { + t.Errorf("Expected error, got no error") + } + + if test.result.Info == "" { + t.Errorf("Expected error to be saved in test result info but found empty string") + } + + if test.result.Status != release.TestRun_FAILURE { + t.Errorf("Expected test result status to be failure but got: %v", test.result.Status) + } +} + +func TestDeleteTestPods(t *testing.T) { + mockTestSuite := testSuiteFixture([]string{manifestWithTestSuccessHook}) + mockTestEnv := newMockTestingEnvironment() + mockTestEnv.KubeClient = newGetFailingKubeClient() + + mockTestEnv.DeleteTestPods(mockTestSuite.TestManifests) + + stream := mockTestEnv.Stream.(*mockStream) + if len(stream.messages) != 0 { + t.Errorf("Expected 0 errors, got at least one: %v", stream.messages) + } + + for _, testManifest := range mockTestSuite.TestManifests { + if _, err := mockTestEnv.KubeClient.Get(mockTestEnv.Namespace, bytes.NewBufferString(testManifest)); err == nil { + t.Error("Expected error, got nil") + } + } +} + +func TestDeleteTestPodsFailingDelete(t *testing.T) { + mockTestSuite := testSuiteFixture([]string{manifestWithTestSuccessHook}) + mockTestEnv := newMockTestingEnvironment() + mockTestEnv.KubeClient = newDeleteFailingKubeClient() + + mockTestEnv.DeleteTestPods(mockTestSuite.TestManifests) + + stream := mockTestEnv.Stream.(*mockStream) + if len(stream.messages) != 1 { + t.Errorf("Expected 1 error, got: %v", len(stream.messages)) + } +} + +func TestStreamMessage(t *testing.T) { + mockTestEnv := newMockTestingEnvironment() + + expectedMessage := "testing streamMessage" + expectedStatus := release.TestRun_SUCCESS + err := mockTestEnv.streamMessage(expectedMessage, expectedStatus) + if err != nil { + t.Errorf("Expected no errors, got 1: %s", err) + } + + stream := mockTestEnv.Stream.(*mockStream) + if len(stream.messages) != 1 { + t.Errorf("Expected 1 message, got: %v", len(stream.messages)) + } + + if stream.messages[0].Msg != expectedMessage { + t.Errorf("Expected message: %s, got: %s", expectedMessage, stream.messages[0]) + } + if stream.messages[0].Status != expectedStatus { + t.Errorf("Expected status: %v, got: %v", expectedStatus, stream.messages[0].Status) + } +} + +type MockTestingEnvironment struct { + *Environment +} + +func newMockTestingEnvironment() *MockTestingEnvironment { + tEnv := mockTillerEnvironment() + + return &MockTestingEnvironment{ + Environment: &Environment{ + Namespace: "default", + KubeClient: tEnv.KubeClient, + Timeout: 5, + Stream: &mockStream{}, + }, + } +} + +func (mte MockTestingEnvironment) streamRunning(name string) error { return nil } +func (mte MockTestingEnvironment) streamError(info string) error { return nil } +func (mte MockTestingEnvironment) streamFailed(name string) error { return nil } +func (mte MockTestingEnvironment) streamSuccess(name string) error { return nil } +func (mte MockTestingEnvironment) streamUnknown(name, info string) error { return nil } +func (mte MockTestingEnvironment) streamMessage(msg string, status release.TestRun_Status) error { + mte.Stream.Send(&services.TestReleaseResponse{Msg: msg, Status: status}) + return nil +} + +type getFailingKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func newGetFailingKubeClient() *getFailingKubeClient { + return &getFailingKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +func (p *getFailingKubeClient) Get(ns string, r io.Reader) (string, error) { + return "", errors.New("in the end, they did not find Nemo") +} + +type deleteFailingKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func newDeleteFailingKubeClient() *deleteFailingKubeClient { + return &deleteFailingKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +func (p *deleteFailingKubeClient) Delete(ns string, r io.Reader) error { + return errors.New("delete failed") +} + +type createFailingKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func newCreateFailingKubeClient() *createFailingKubeClient { + return &createFailingKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +func (p *createFailingKubeClient) Create(ns string, r io.Reader, t int64, shouldWait bool) error { + return errors.New("We ran out of budget and couldn't create finding-nemo") +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite.go b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite.go new file mode 100644 index 000000000..2e42400ce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite.go @@ -0,0 +1,193 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 releasetesting + +import ( + "fmt" + "strings" + + "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/timestamp" + "k8s.io/kubernetes/pkg/apis/core" + + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +// TestSuite what tests are run, results, and metadata +type TestSuite struct { + StartedAt *timestamp.Timestamp + CompletedAt *timestamp.Timestamp + TestManifests []string + Results []*release.TestRun +} + +type test struct { + manifest string + expectedSuccess bool + result *release.TestRun +} + +// NewTestSuite takes a release object and returns a TestSuite object with test definitions +// extracted from the release +func NewTestSuite(rel *release.Release) (*TestSuite, error) { + testManifests, err := extractTestManifestsFromHooks(rel.Hooks) + if err != nil { + return nil, err + } + + results := []*release.TestRun{} + + return &TestSuite{ + TestManifests: testManifests, + Results: results, + }, nil +} + +// Run executes tests in a test suite and stores a result within a given environment +func (ts *TestSuite) Run(env *Environment) error { + ts.StartedAt = timeconv.Now() + + if len(ts.TestManifests) == 0 { + // TODO: make this better, adding test run status on test suite is weird + env.streamMessage("No Tests Found", release.TestRun_UNKNOWN) + } + + for _, testManifest := range ts.TestManifests { + test, err := newTest(testManifest) + if err != nil { + return err + } + + test.result.StartedAt = timeconv.Now() + if err := env.streamRunning(test.result.Name); err != nil { + return err + } + test.result.Status = release.TestRun_RUNNING + + resourceCreated := true + if err := env.createTestPod(test); err != nil { + resourceCreated = false + if streamErr := env.streamError(test.result.Info); streamErr != nil { + return err + } + } + + resourceCleanExit := true + status := core.PodUnknown + if resourceCreated { + status, err = env.getTestPodStatus(test) + if err != nil { + resourceCleanExit = false + if streamErr := env.streamError(test.result.Info); streamErr != nil { + return streamErr + } + } + } + + if resourceCreated && resourceCleanExit { + if err := test.assignTestResult(status); err != nil { + return err + } + + if err := env.streamResult(test.result); err != nil { + return err + } + } + + test.result.CompletedAt = timeconv.Now() + ts.Results = append(ts.Results, test.result) + } + + ts.CompletedAt = timeconv.Now() + return nil +} + +func (t *test) assignTestResult(podStatus core.PodPhase) error { + switch podStatus { + case core.PodSucceeded: + if t.expectedSuccess { + t.result.Status = release.TestRun_SUCCESS + } else { + t.result.Status = release.TestRun_FAILURE + } + case core.PodFailed: + if !t.expectedSuccess { + t.result.Status = release.TestRun_SUCCESS + } else { + t.result.Status = release.TestRun_FAILURE + } + default: + t.result.Status = release.TestRun_UNKNOWN + } + + return nil +} + +func expectedSuccess(hookTypes []string) (bool, error) { + for _, hookType := range hookTypes { + hookType = strings.ToLower(strings.TrimSpace(hookType)) + if hookType == hooks.ReleaseTestSuccess { + return true, nil + } else if hookType == hooks.ReleaseTestFailure { + return false, nil + } + } + return false, fmt.Errorf("No %s or %s hook found", hooks.ReleaseTestSuccess, hooks.ReleaseTestFailure) +} + +func extractTestManifestsFromHooks(h []*release.Hook) ([]string, error) { + testHooks := hooks.FilterTestHooks(h) + + tests := []string{} + for _, h := range testHooks { + individualTests := util.SplitManifests(h.Manifest) + for _, t := range individualTests { + tests = append(tests, t) + } + } + return tests, nil +} + +func newTest(testManifest string) (*test, error) { + var sh util.SimpleHead + err := yaml.Unmarshal([]byte(testManifest), &sh) + if err != nil { + return nil, err + } + + if sh.Kind != "Pod" { + return nil, fmt.Errorf("%s is not a pod", sh.Metadata.Name) + } + + hookTypes := sh.Metadata.Annotations[hooks.HookAnno] + expected, err := expectedSuccess(strings.Split(hookTypes, ",")) + if err != nil { + return nil, err + } + + name := strings.TrimSuffix(sh.Metadata.Name, ",") + return &test{ + manifest: testManifest, + expectedSuccess: expected, + result: &release.TestRun{ + Name: name, + }, + }, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite_test.go b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite_test.go new file mode 100644 index 000000000..e6cc8bcf5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releasetesting/test_suite_test.go @@ -0,0 +1,343 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 releasetesting + +import ( + "io" + "io/ioutil" + "testing" + "time" + + "github.com/golang/protobuf/ptypes/timestamp" + "golang.org/x/net/context" + grpc "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "k8s.io/kubernetes/pkg/apis/core" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" + tillerEnv "k8s.io/helm/pkg/tiller/environment" +) + +const manifestWithTestSuccessHook = ` +apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command +` + +const manifestWithTestFailureHook = ` +apiVersion: v1 +kind: Pod +metadata: + name: gold-rush, + annotations: + "helm.sh/hook": test-failure +spec: + containers: + - name: gold-finding-test + image: fake-gold-finding-image + cmd: fake-gold-finding-command +` +const manifestWithInstallHooks = `apiVersion: v1 +kind: ConfigMap +metadata: + name: test-cm + annotations: + "helm.sh/hook": post-install,pre-delete +data: + name: value +` + +func TestNewTestSuite(t *testing.T) { + rel := releaseStub() + + _, err := NewTestSuite(rel) + if err != nil { + t.Errorf("%s", err) + } +} + +func TestRun(t *testing.T) { + + testManifests := []string{manifestWithTestSuccessHook, manifestWithTestFailureHook} + ts := testSuiteFixture(testManifests) + if err := ts.Run(testEnvFixture()); err != nil { + t.Errorf("%s", err) + } + + if ts.StartedAt == nil { + t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) + } + + if ts.CompletedAt == nil { + t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) + } + + if len(ts.Results) != 2 { + t.Errorf("Expected 2 test result. Got %v", len(ts.Results)) + } + + result := ts.Results[0] + if result.StartedAt == nil { + t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt) + } + + if result.CompletedAt == nil { + t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt) + } + + if result.Name != "finding-nemo" { + t.Errorf("Expected test name to be finding-nemo. Got: %v", result.Name) + } + + if result.Status != release.TestRun_SUCCESS { + t.Errorf("Expected test result to be successful, got: %v", result.Status) + } + + result2 := ts.Results[1] + if result2.StartedAt == nil { + t.Errorf("Expected test StartedAt to not be nil. Got: %v", result2.StartedAt) + } + + if result2.CompletedAt == nil { + t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result2.CompletedAt) + } + + if result2.Name != "gold-rush" { + t.Errorf("Expected test name to be gold-rush, Got: %v", result2.Name) + } + + if result2.Status != release.TestRun_FAILURE { + t.Errorf("Expected test result to be successful, got: %v", result2.Status) + } + +} + +func TestRunEmptyTestSuite(t *testing.T) { + ts := testSuiteFixture([]string{}) + mockTestEnv := testEnvFixture() + if err := ts.Run(mockTestEnv); err != nil { + t.Errorf("%s", err) + } + + if ts.StartedAt == nil { + t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) + } + + if ts.CompletedAt == nil { + t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) + } + + if len(ts.Results) != 0 { + t.Errorf("Expected 0 test result. Got %v", len(ts.Results)) + } + + stream := mockTestEnv.Stream.(*mockStream) + if len(stream.messages) == 0 { + t.Errorf("Expected at least one message, Got: %v", len(stream.messages)) + } else { + msg := stream.messages[0].Msg + if msg != "No Tests Found" { + t.Errorf("Expected message 'No Tests Found', Got: %v", msg) + } + } + +} + +func TestRunSuccessWithTestFailureHook(t *testing.T) { + ts := testSuiteFixture([]string{manifestWithTestFailureHook}) + env := testEnvFixture() + env.KubeClient = newPodFailedKubeClient() + if err := ts.Run(env); err != nil { + t.Errorf("%s", err) + } + + if ts.StartedAt == nil { + t.Errorf("Expected StartedAt to not be nil. Got: %v", ts.StartedAt) + } + + if ts.CompletedAt == nil { + t.Errorf("Expected CompletedAt to not be nil. Got: %v", ts.CompletedAt) + } + + if len(ts.Results) != 1 { + t.Errorf("Expected 1 test result. Got %v", len(ts.Results)) + } + + result := ts.Results[0] + if result.StartedAt == nil { + t.Errorf("Expected test StartedAt to not be nil. Got: %v", result.StartedAt) + } + + if result.CompletedAt == nil { + t.Errorf("Expected test CompletedAt to not be nil. Got: %v", result.CompletedAt) + } + + if result.Name != "gold-rush" { + t.Errorf("Expected test name to be gold-rush, Got: %v", result.Name) + } + + if result.Status != release.TestRun_SUCCESS { + t.Errorf("Expected test result to be successful, got: %v", result.Status) + } +} + +func TestExtractTestManifestsFromHooks(t *testing.T) { + rel := releaseStub() + testManifests, err := extractTestManifestsFromHooks(rel.Hooks) + if err != nil { + t.Errorf("Expected no error, Got: %s", err) + } + + if len(testManifests) != 1 { + t.Errorf("Expected 1 test manifest, Got: %v", len(testManifests)) + } +} + +func chartStub() *chart.Chart { + return &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "nemo", + }, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithTestSuccessHook)}, + }, + } +} + +func releaseStub() *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + return &release.Release{ + Name: "lost-fish", + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, + Description: "a release stub", + }, + Chart: chartStub(), + Config: &chart.Config{Raw: `name: value`}, + Version: 1, + Hooks: []*release.Hook{ + { + Name: "finding-nemo", + Kind: "Pod", + Path: "finding-nemo", + Manifest: manifestWithTestSuccessHook, + Events: []release.Hook_Event{ + release.Hook_RELEASE_TEST_SUCCESS, + }, + }, + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithInstallHooks, + Events: []release.Hook_Event{ + release.Hook_POST_INSTALL, + release.Hook_PRE_DELETE, + }, + }, + }, + } +} + +func testFixture() *test { + return &test{ + manifest: manifestWithTestSuccessHook, + result: &release.TestRun{}, + } +} + +func testSuiteFixture(testManifests []string) *TestSuite { + testResults := []*release.TestRun{} + ts := &TestSuite{ + TestManifests: testManifests, + Results: testResults, + } + + return ts +} + +func testEnvFixture() *Environment { + return newMockTestingEnvironment().Environment +} + +func mockTillerEnvironment() *tillerEnv.Environment { + e := tillerEnv.New() + e.Releases = storage.Init(driver.NewMemory()) + e.KubeClient = newPodSucceededKubeClient() + return e +} + +type mockStream struct { + stream grpc.ServerStream + messages []*services.TestReleaseResponse +} + +func (rs *mockStream) Send(m *services.TestReleaseResponse) error { + rs.messages = append(rs.messages, m) + return nil +} + +func (rs mockStream) SetHeader(m metadata.MD) error { return nil } +func (rs mockStream) SendHeader(m metadata.MD) error { return nil } +func (rs mockStream) SetTrailer(m metadata.MD) {} +func (rs mockStream) SendMsg(v interface{}) error { return nil } +func (rs mockStream) RecvMsg(v interface{}) error { return nil } +func (rs mockStream) Context() context.Context { return helm.NewContext() } + +type podSucceededKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func newPodSucceededKubeClient() *podSucceededKubeClient { + return &podSucceededKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +func (p *podSucceededKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) { + return core.PodSucceeded, nil +} + +type podFailedKubeClient struct { + tillerEnv.PrintingKubeClient +} + +func newPodFailedKubeClient() *podFailedKubeClient { + return &podFailedKubeClient{ + PrintingKubeClient: tillerEnv.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +func (p *podFailedKubeClient) WaitAndGetCompletedPodPhase(ns string, r io.Reader, timeout time.Duration) (core.PodPhase, error) { + return core.PodFailed, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter.go new file mode 100644 index 000000000..fdd2cc381 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter.go @@ -0,0 +1,78 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/releaseutil" + +import rspb "k8s.io/helm/pkg/proto/hapi/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_Code) FilterFunc { + return FilterFunc(func(rls *rspb.Release) bool { + if rls == nil { + return true + } + return rls.GetInfo().GetStatus().Code == status + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter_test.go new file mode 100644 index 000000000..590952363 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/filter_test.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/releaseutil" + +import ( + "testing" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestFilterAny(t *testing.T) { + ls := Any(StatusFilter(rspb.Status_DELETED)).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.Code != rspb.Status_DELETED: + t.Fatalf("expected DELETED result, got '%s'", r1.Info.Status.Code) + case r1.Info.Status.Code != rspb.Status_DELETED: + t.Fatalf("expected DELETED result, got '%s'", r1.Info.Status.Code) + } +} + +func TestFilterAll(t *testing.T) { + fn := FilterFunc(func(rls *rspb.Release) bool { + // true if not deleted and version < 4 + v0 := !StatusFilter(rspb.Status_DELETED).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.Code == rspb.Status_DELETED: + t.Fatal("got release with status DELTED") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest.go new file mode 100644 index 000000000..a0449cc55 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest.go @@ -0,0 +1,59 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "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. In the current implementation, the file name is just + // a place holder, and doesn't have any further meaning. + 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 +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest_test.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest_test.go new file mode 100644 index 000000000..7906279ad --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/manifest_test.go @@ -0,0 +1,61 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/releaseutil" + +import ( + "reflect" + "testing" +) + +const manifestFile = ` + +--- +apiVersion: v1 +kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test-success +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-success +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command` + +func TestSplitManifest(t *testing.T) { + manifests := SplitManifests(manifestFile) + 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/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter.go new file mode 100644 index 000000000..1b744d72c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/releaseutil" + +import ( + "sort" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +type sorter struct { + list []*rspb.Release + less func(int, int) bool +} + +func (s *sorter) Len() int { return len(s.list) } +func (s *sorter) Less(i, j int) bool { return s.less(i, j) } +func (s *sorter) Swap(i, j int) { s.list[i], s.list[j] = s.list[j], s.list[i] } + +// 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) { + s := &sorter{list: list} + s.less = func(i, j int) bool { + ni := s.list[i].Name + nj := s.list[j].Name + return ni < nj + } + sort.Sort(s) +} + +// SortByDate returns the list of releases sorted by a +// release's last deployed time (in seconds). +func SortByDate(list []*rspb.Release) { + s := &sorter{list: list} + + s.less = func(i, j int) bool { + ti := s.list[i].Info.LastDeployed.Seconds + tj := s.list[j].Info.LastDeployed.Seconds + return ti < tj + } + sort.Sort(s) +} + +// SortByRevision returns the list of releases sorted by a +// release's revision number (release.Version). +func SortByRevision(list []*rspb.Release) { + s := &sorter{list: list} + s.less = func(i, j int) bool { + vi := s.list[i].Version + vj := s.list[j].Version + return vi < vj + } + sort.Sort(s) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter_test.go new file mode 100644 index 000000000..7d4e31e2e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/releaseutil/sorter_test.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/releaseutil" + +import ( + "testing" + "time" + + rspb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/timeconv" +) + +// note: this test data is shared with filter_test.go. + +var releases = []*rspb.Release{ + tsRelease("quiet-bear", 2, 2000, rspb.Status_SUPERSEDED), + tsRelease("angry-bird", 4, 3000, rspb.Status_DEPLOYED), + tsRelease("happy-cats", 1, 4000, rspb.Status_DELETED), + tsRelease("vocal-dogs", 3, 6000, rspb.Status_DELETED), +} + +func tsRelease(name string, vers int32, dur time.Duration, code rspb.Status_Code) *rspb.Release { + tmsp := timeconv.Timestamp(time.Now().Add(time.Duration(dur))) + info := &rspb.Info{Status: &rspb.Status{Code: code}, LastDeployed: tmsp} + 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.Seconds + tj := releases[j].Info.LastDeployed.Seconds + 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 + }) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo.go new file mode 100644 index 000000000..438f66d7c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo.go @@ -0,0 +1,274 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/repo" + +import ( + "fmt" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/provenance" +) + +// Entry represents a collection of parameters for chart repository +type Entry struct { + Name string `json:"name"` + Cache string `json:"cache"` + URL string `json:"url"` + Username string `json:"username"` + Password string `json:"password"` + CertFile string `json:"certFile"` + KeyFile string `json:"keyFile"` + CAFile string `json:"caFile"` +} + +// ChartRepository represents a chart repository +type ChartRepository struct { + Config *Entry + ChartPaths []string + IndexFile *IndexFile + Client getter.Getter +} + +// NewChartRepository constructs ChartRepository +func NewChartRepository(cfg *Entry, getters getter.Providers) (*ChartRepository, error) { + u, err := url.Parse(cfg.URL) + if err != nil { + return nil, fmt.Errorf("invalid chart URL format: %s", cfg.URL) + } + + getterConstructor, err := getters.ByScheme(u.Scheme) + if err != nil { + return nil, fmt.Errorf("Could not find protocol handler for: %s", u.Scheme) + } + client, err := getterConstructor(cfg.URL, cfg.CertFile, cfg.KeyFile, cfg.CAFile) + if err != nil { + return nil, fmt.Errorf("Could not construct protocol handler for: %s error: %v", u.Scheme, err) + } + + return &ChartRepository{ + Config: cfg, + IndexFile: NewIndexFile(), + Client: client, + }, 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. +func (r *ChartRepository) Load() error { + dirInfo, err := os.Stat(r.Config.Name) + if err != nil { + return err + } + if !dirInfo.IsDir() { + return fmt.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 nil + } + 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. +// +// cachePath is prepended to any index that does not have an absolute path. This +// is for pre-2.2.0 repo files. +func (r *ChartRepository) DownloadIndexFile(cachePath string) error { + var indexURL string + parsedURL, err := url.Parse(r.Config.URL) + if err != nil { + return err + } + parsedURL.Path = strings.TrimSuffix(parsedURL.Path, "/") + "/index.yaml" + + indexURL = parsedURL.String() + + r.setCredentials() + resp, err := r.Client.Get(indexURL) + if err != nil { + return err + } + + index, err := ioutil.ReadAll(resp) + if err != nil { + return err + } + + if _, err := loadIndex(index); err != nil { + return err + } + + // In Helm 2.2.0 the config.cache was accidentally switched to an absolute + // path, which broke backward compatibility. This fixes it by prepending a + // global cache path to relative paths. + // + // It is changed on DownloadIndexFile because that was the method that + // originally carried the cache path. + cp := r.Config.Cache + if !filepath.IsAbs(cp) { + cp = filepath.Join(cachePath, cp) + } + + return ioutil.WriteFile(cp, index, 0644) +} + +// If HttpGetter is used, this method sets the configured repository credentials on the HttpGetter. +func (r *ChartRepository) setCredentials() { + if t, ok := r.Client.(*getter.HttpGetter); ok { + t.SetCredentials(r.Config.Username, r.Config.Password) + } +} + +// 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 := chartutil.Load(path) + if err != nil { + return err + } + + digest, err := provenance.DigestFile(path) + if err != nil { + return err + } + + if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) { + r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest) + } + // 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) { + + // Download and write the index file to a temporary location + tempIndexFile, err := ioutil.TempFile("", "tmp-repo-file") + if err != nil { + return "", fmt.Errorf("cannot write index file for repository requested") + } + defer os.Remove(tempIndexFile.Name()) + + c := Entry{ + URL: repoURL, + Username: username, + Password: password, + CertFile: certFile, + KeyFile: keyFile, + CAFile: caFile, + } + r, err := NewChartRepository(&c, getters) + if err != nil { + return "", err + } + if err := r.DownloadIndexFile(tempIndexFile.Name()); err != nil { + return "", fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", repoURL, err) + } + + // Read the index file for the repository to get chart information and return chart URL + repoIndex, err := LoadIndexFile(tempIndexFile.Name()) + 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 "", fmt.Errorf("%s not found in %s repository", errMsg, repoURL) + } + + if len(cv.URLs) == 0 { + return "", fmt.Errorf("%s has no downloadable URLs", errMsg) + } + + chartURL := cv.URLs[0] + + absoluteChartURL, err := ResolveReferenceURL(repoURL, chartURL) + if err != nil { + return "", fmt.Errorf("failed to make chart URL absolute: %v", err) + } + + return absoluteChartURL, nil +} + +// ResolveReferenceURL resolves refURL relative to baseURL. +// If refURL is absolute, it simply returns refURL. +func ResolveReferenceURL(baseURL, refURL string) (string, error) { + parsedBaseURL, err := url.Parse(baseURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %v", baseURL, err) + } + + parsedRefURL, err := url.Parse(refURL) + if err != nil { + return "", fmt.Errorf("failed to parse %s as URL: %v", refURL, err) + } + + return parsedBaseURL.ResolveReference(parsedRefURL).String(), nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo_test.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo_test.go new file mode 100644 index 000000000..948ee12d3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/chartrepo_test.go @@ -0,0 +1,297 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "reflect" + "strings" + "testing" + "time" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const ( + testRepository = "testdata/repository" + testURL = "http://example-charts.com" +) + +func TestLoadChartRepository(t *testing.T) { + r, err := NewChartRepository(&Entry{ + Name: testRepository, + URL: testURL, + }, getter.All(environment.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(environment.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) +} + +func verifyIndex(t *testing.T, actual *IndexFile) { + var empty time.Time + if actual.Generated == 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 keyrwords.") + } + if len(g.Maintainers) != 2 { + t.Error("Expected 2 maintainers.") + } + if g.Created == 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 +} + +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(environment.EnvSettings{})) + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "https://kubernetes-charts.storage.googleapis.com/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(environment.EnvSettings{})) + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "https://kubernetes-charts.storage.googleapis.com/nginx-0.1.0.tgz" { + t.Errorf("%s is not the valid URL", chartURL) + } +} + +func TestErrorFindChartInRepoURL(t *testing.T) { + _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", getter.All(environment.EnvSettings{})) + if err == nil { + t.Errorf("Expected error for bad chart URL, but did not get any errors") + } + if err != nil && !strings.Contains(err.Error(), `Looks like "http://someserver/something" is not a valid chart repository or cannot be reached: Get http://someserver/something/index.yaml`) { + 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() + + _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", getter.All(environment.EnvSettings{})) + if err == nil { + t.Errorf("Expected error for chart not found, but did not get any errors") + } + if err != nil && 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) + } + + _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", getter.All(environment.EnvSettings{})) + if err == nil { + t.Errorf("Expected error for chart not found, but did not get any errors") + } + if err != nil && 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) + } + + _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", getter.All(environment.EnvSettings{})) + if err == nil { + t.Errorf("Expected error for no chart URLs available, but did not get any errors") + } + if err != nil && 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", "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz") + if err != nil { + t.Errorf("%s", err) + } + if chartURL != "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz" { + t.Errorf("%s", chartURL) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/doc.go new file mode 100644 index 000000000..fb8b3f4b2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/doc.go @@ -0,0 +1,93 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 frobniz. + 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/src/vendor/github.com/kubernetes/helm/pkg/repo/index.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/index.go new file mode 100644 index 000000000..174ceea01 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/index.go @@ -0,0 +1,329 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/Masterminds/semver" + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/urlutil" +) + +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") +) + +// 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 { + APIVersion string `json:"apiVersion"` + Generated time.Time `json:"generated"` + Entries map[string]ChartVersions `json:"entries"` + PublicKeys []string `json:"publicKeys,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 + } + return loadIndex(b) +} + +// Add adds a file to the index +// This can leave the index in an unsorted state +func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) { + u := filename + if baseURL != "" { + var err error + _, file := filepath.Split(filename) + u, err = urlutil.URLJoin(baseURL, file) + if err != nil { + u = filepath.Join(baseURL, file) + } + } + cr := &ChartVersion{ + URLs: []string{u}, + Metadata: md, + Digest: digest, + Created: time.Now(), + } + if ee, ok := i.Entries[md.Name]; !ok { + i.Entries[md.Name] = ChartVersions{cr} + } else { + i.Entries[md.Name] = append(ee, cr) + } +} + +// 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 highest version. +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 len(version) == 0 { + constraint, _ = semver.NewConstraint("*") + } else { + var err error + constraint, err = semver.NewConstraint(version) + if err != nil { + return nil, err + } + } + + for _, ver := range vs { + test, err := semver.NewVersion(ver.Version) + if err != nil { + continue + } + + if constraint.Check(test) { + return ver, nil + } + } + return nil, fmt.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 ioutil.WriteFile(dest, 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) + } + } + } +} + +// Need both JSON and YAML annotations until we get rid of gopkg.in/yaml.v2 + +// 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"` +} + +// 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) + parentURL, err := urlutil.URLJoin(baseURL, parentDir) + if err != nil { + parentURL = filepath.Join(baseURL, parentDir) + } + + c, err := chartutil.Load(arch) + if err != nil { + // Assume this is not a chart. + continue + } + hash, err := provenance.DigestFile(arch) + if err != nil { + return index, err + } + index.Add(c.Metadata, fname, parentURL, hash) + } + return index, nil +} + +// loadIndex loads an index file and does minimal validity checking. +// +// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails. +func loadIndex(data []byte) (*IndexFile, error) { + i := &IndexFile{} + if err := yaml.Unmarshal(data, i); err != nil { + return i, err + } + i.SortEntries() + if i.APIVersion == "" { + // When we leave Beta, we should remove legacy support and just + // return this error: + //return i, ErrNoAPIVersion + return loadUnversionedIndex(data) + } + return i, nil +} + +// unversionedEntry represents a deprecated pre-Alpha.5 format. +// +// This will be removed prior to v2.0.0 +type unversionedEntry struct { + Checksum string `json:"checksum"` + URL string `json:"url"` + Chartfile *chart.Metadata `json:"chartfile"` +} + +// loadUnversionedIndex loads a pre-Alpha.5 index.yaml file. +// +// This format is deprecated. This function will be removed prior to v2.0.0. +func loadUnversionedIndex(data []byte) (*IndexFile, error) { + fmt.Fprintln(os.Stderr, "WARNING: Deprecated index file format. Try 'helm repo update'") + i := map[string]unversionedEntry{} + + // This gets around an error in the YAML parser. Instead of parsing as YAML, + // we convert to JSON, and then decode again. + var err error + data, err = yaml.YAMLToJSON(data) + if err != nil { + return nil, err + } + if err := json.Unmarshal(data, &i); err != nil { + return nil, err + } + + if len(i) == 0 { + return nil, ErrNoAPIVersion + } + ni := NewIndexFile() + for n, item := range i { + if item.Chartfile == nil || item.Chartfile.Name == "" { + parts := strings.Split(n, "-") + ver := "" + if len(parts) > 1 { + ver = strings.TrimSuffix(parts[1], ".tgz") + } + item.Chartfile = &chart.Metadata{Name: parts[0], Version: ver} + } + ni.Add(item.Chartfile, item.URL, "", item.Checksum) + } + return ni, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/index_test.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/index_test.go new file mode 100644 index 000000000..ba426b174 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/index_test.go @@ -0,0 +1,352 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "path/filepath" + "testing" + + "k8s.io/helm/pkg/getter" + "k8s.io/helm/pkg/helm/environment" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +const ( + testfile = "testdata/local-index.yaml" + unorderedTestfile = "testdata/local-index-unordered.yaml" + testRepo = "test-repo" +) + +func TestIndexFile(t *testing.T) { + i := NewIndexFile() + i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890") + i.Add(&chart.Metadata{Name: "cutter", Version: "0.1.1"}, "cutter-0.1.1.tgz", "http://example.com/charts", "sha256:1234567890abc") + i.Add(&chart.Metadata{Name: "cutter", Version: "0.1.0"}, "cutter-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890abc") + i.Add(&chart.Metadata{Name: "cutter", Version: "0.2.0"}, "cutter-0.2.0.tgz", "http://example.com/charts", "sha256:1234567890abc") + i.SortEntries() + + if i.APIVersion != APIVersionV1 { + t.Error("Expected API version v1") + } + + if len(i.Entries) != 2 { + t.Errorf("Expected 2 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 two 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) + } +} + +func TestLoadIndex(t *testing.T) { + b, err := ioutil.ReadFile(testfile) + if err != nil { + t.Fatal(err) + } + i, err := loadIndex(b) + if err != nil { + t.Fatal(err) + } + verifyLocalIndex(t, i) +} + +func TestLoadIndexFile(t *testing.T) { + i, err := LoadIndexFile(testfile) + if err != nil { + t.Fatal(err) + } + verifyLocalIndex(t, i) +} + +func TestLoadUnorderedIndex(t *testing.T) { + b, err := ioutil.ReadFile(unorderedTestfile) + if err != nil { + t.Fatal(err) + } + i, err := loadIndex(b) + if err != nil { + t.Fatal(err) + } + verifyLocalIndex(t, i) +} + +func TestMerge(t *testing.T) { + ind1 := NewIndexFile() + ind1.Add(&chart.Metadata{ + Name: "dreadnought", + Version: "0.1.0", + }, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa") + + ind2 := NewIndexFile() + ind2.Add(&chart.Metadata{ + Name: "dreadnought", + Version: "0.2.0", + }, "dreadnought-0.2.0.tgz", "http://example.com", "aaaabbbb") + ind2.Add(&chart.Metadata{ + Name: "doughnut", + Version: "0.2.0", + }, "doughnut-0.2.0.tgz", "http://example.com", "ccccbbbb") + + ind1.Merge(ind2) + + if len(ind1.Entries) != 2 { + t.Errorf("Expected 2 entries, got %d", len(ind1.Entries)) + vs := ind1.Entries["dreadnaught"] + if len(vs) != 2 { + t.Errorf("Expected 2 versions, got %d", len(vs)) + } + v := vs[0] + if 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) { + srv, err := startLocalServerForTests(nil) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + dirName, err := ioutil.TempDir("", "tmp") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dirName) + + indexFilePath := filepath.Join(dirName, testRepo+"-index.yaml") + r, err := NewChartRepository(&Entry{ + Name: testRepo, + URL: srv.URL, + Cache: indexFilePath, + }, getter.All(environment.EnvSettings{})) + if err != nil { + t.Errorf("Problem creating chart repository from %s: %v", testRepo, err) + } + + if err := r.DownloadIndexFile(""); err != nil { + t.Errorf("%#v", err) + } + + if _, err := os.Stat(indexFilePath); err != nil { + t.Errorf("error finding created index file: %#v", err) + } + + b, err := ioutil.ReadFile(indexFilePath) + if err != nil { + t.Errorf("error reading index file: %#v", err) + } + + i, err := loadIndex(b) + if err != nil { + t.Errorf("Index %q failed to parse: %s", testfile, err) + return + } + + verifyLocalIndex(t, 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.Errorf("'alpine' section not found.") + return + } + + if l := len(alpine); l != 1 { + t.Errorf("'alpine' should have 1 chart, got %d", l) + return + } + + nginx, ok := i.Entries["nginx"] + if !ok || len(nginx) != 2 { + t.Error("Expected 2 nginx entries") + return + } + + expects := []*ChartVersion{ + { + Metadata: &chart.Metadata{ + Name: "alpine", + Description: "string", + Version: "1.0.0", + Keywords: []string{"linux", "alpine", "small", "sumtin"}, + Home: "https://github.com/something", + }, + URLs: []string{ + "https://kubernetes-charts.storage.googleapis.com/alpine-1.0.0.tgz", + "http://storage2.googleapis.com/kubernetes-charts/alpine-1.0.0.tgz", + }, + Digest: "sha256:1234567890abcdef", + }, + { + Metadata: &chart.Metadata{ + Name: "nginx", + Description: "string", + Version: "0.2.0", + Keywords: []string{"popular", "web server", "proxy"}, + Home: "https://github.com/something/else", + }, + URLs: []string{ + "https://kubernetes-charts.storage.googleapis.com/nginx-0.2.0.tgz", + }, + Digest: "sha256:1234567890abcdef", + }, + { + Metadata: &chart.Metadata{ + Name: "nginx", + Description: "string", + Version: "0.1.0", + Keywords: []string{"popular", "web server", "proxy"}, + Home: "https://github.com/something", + }, + URLs: []string{ + "https://kubernetes-charts.storage.googleapis.com/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 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 len(frob.Digest) == 0 { + 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 TestLoadUnversionedIndex(t *testing.T) { + data, err := ioutil.ReadFile("testdata/unversioned-index.yaml") + if err != nil { + t.Fatal(err) + } + + ind, err := loadUnversionedIndex(data) + if err != nil { + t.Fatal(err) + } + + if l := len(ind.Entries); l != 2 { + t.Fatalf("Expected 2 entries, got %d", l) + } + + if l := len(ind.Entries["mysql"]); l != 3 { + t.Fatalf("Expected 3 mysql versions, got %d", l) + } +} + +func TestIndexAdd(t *testing.T) { + i := NewIndexFile() + i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890") + + 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]) + } + + i.Add(&chart.Metadata{Name: "alpine", Version: "0.1.0"}, "/home/charts/alpine-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890") + + 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]) + } + + i.Add(&chart.Metadata{Name: "deis", Version: "0.1.0"}, "/home/charts/deis-0.1.0.tgz", "http://example.com/charts/", "sha256:1234567890") + + 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]) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/local.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/local.go new file mode 100644 index 000000000..f13a4d0ac --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/local.go @@ -0,0 +1,137 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 ( + "fmt" + htemplate "html/template" + "io/ioutil" + "net/http" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/provenance" +) + +const indexHTMLTemplate = ` + + + Helm Repository + +

Helm Charts Repository

+ + +

Last Generated: {{.Index.Generated}}

+ + +` + +// RepositoryServer is an HTTP handler for serving a chart repository. +type RepositoryServer struct { + RepoPath string +} + +// ServeHTTP implements the http.Handler interface. +func (s *RepositoryServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + uri := r.URL.Path + switch uri { + case "/", "/charts/", "/charts/index.html", "/charts/index": + w.Header().Set("Content-Type", "text/html; charset=utf-8") + s.htmlIndex(w, r) + default: + file := strings.TrimPrefix(uri, "/charts/") + http.ServeFile(w, r, filepath.Join(s.RepoPath, file)) + } +} + +// StartLocalRepo starts a web server and serves files from the given path +func StartLocalRepo(path, address string) error { + if address == "" { + address = "127.0.0.1:8879" + } + s := &RepositoryServer{RepoPath: path} + return http.ListenAndServe(address, s) +} + +func (s *RepositoryServer) htmlIndex(w http.ResponseWriter, r *http.Request) { + t := htemplate.Must(htemplate.New("index.html").Parse(indexHTMLTemplate)) + // load index + lrp := filepath.Join(s.RepoPath, "index.yaml") + i, err := LoadIndexFile(lrp) + if err != nil { + http.Error(w, err.Error(), 500) + return + } + data := map[string]interface{}{ + "Index": i, + } + if err := t.Execute(w, data); err != nil { + fmt.Fprintf(w, "Template error: %s", err) + } +} + +// AddChartToLocalRepo saves a chart in the given path and then reindexes the index file +func AddChartToLocalRepo(ch *chart.Chart, path string) error { + _, err := chartutil.Save(ch, path) + if err != nil { + return err + } + return Reindex(ch, path+"/index.yaml") +} + +// Reindex adds an entry to the index file at the given path +func Reindex(ch *chart.Chart, path string) error { + name := ch.Metadata.Name + "-" + ch.Metadata.Version + y, err := LoadIndexFile(path) + if err != nil { + return err + } + found := false + for k := range y.Entries { + if k == name { + found = true + break + } + } + if !found { + dig, err := provenance.DigestFile(path) + if err != nil { + return err + } + + y.Add(ch.Metadata, name+".tgz", "http://127.0.0.1:8879/charts", "sha256:"+dig) + + out, err := yaml.Marshal(y) + if err != nil { + return err + } + + ioutil.WriteFile(path, out, 0644) + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/local_test.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/local_test.go new file mode 100644 index 000000000..1e5359dee --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/local_test.go @@ -0,0 +1,67 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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" + "net/http" + "strings" + "testing" +) + +func TestRepositoryServer(t *testing.T) { + expectedIndexYAML, err := ioutil.ReadFile("testdata/server/index.yaml") + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + path string + expect string + }{ + {"index YAML", "/charts/index.yaml", string(expectedIndexYAML)}, + {"index HTML", "/charts/index.html", ""}, + {"charts root", "/charts/", ""}, + {"root", "/", ""}, + {"file", "/test.txt", "Hello World"}, + } + + s := &RepositoryServer{RepoPath: "testdata/server"} + srv, err := startLocalServerForTests(s) + if err != nil { + t.Fatal(err) + } + defer srv.Close() + + for _, tt := range tests { + res, err := http.Get(srv.URL + tt.path) + if err != nil { + t.Errorf("%s: error getting %s: %s", tt.name, tt.path, err) + continue + } + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("%s: error reading %s: %s", tt.name, tt.path, err) + } + res.Body.Close() + if !strings.Contains(string(body), tt.expect) { + t.Errorf("%s: expected to find %q in %q", tt.name, tt.expect, string(body)) + } + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/repo.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/repo.go new file mode 100644 index 000000000..b5bba164e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/repo.go @@ -0,0 +1,150 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/repo" + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "time" + + "github.com/ghodss/yaml" +) + +// ErrRepoOutOfDate indicates that the repository file is out of date, but +// is fixable. +var ErrRepoOutOfDate = errors.New("repository file is out of date") + +// RepoFile represents the repositories.yaml file in $HELM_HOME +// TODO: change type name to File in Helm 3 to resolve linter warning +type RepoFile struct { // nolint + APIVersion string `json:"apiVersion"` + Generated time.Time `json:"generated"` + Repositories []*Entry `json:"repositories"` +} + +// NewRepoFile generates an empty repositories file. +// +// Generated and APIVersion are automatically set. +func NewRepoFile() *RepoFile { + return &RepoFile{ + APIVersion: APIVersionV1, + Generated: time.Now(), + Repositories: []*Entry{}, + } +} + +// LoadRepositoriesFile takes a file at the given path and returns a RepoFile object +// +// If this returns ErrRepoOutOfDate, it also returns a recovered RepoFile that +// can be saved as a replacement to the out of date file. +func LoadRepositoriesFile(path string) (*RepoFile, error) { + b, err := ioutil.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf( + "Couldn't load repositories file (%s).\n"+ + "You might need to run `helm init` (or "+ + "`helm init --client-only` if tiller is "+ + "already installed)", path) + } + return nil, err + } + + r := &RepoFile{} + err = yaml.Unmarshal(b, r) + if err != nil { + return nil, err + } + + // File is either corrupt, or is from before v2.0.0-Alpha.5 + if r.APIVersion == "" { + m := map[string]string{} + if err = yaml.Unmarshal(b, &m); err != nil { + return nil, err + } + r := NewRepoFile() + for k, v := range m { + r.Add(&Entry{ + Name: k, + URL: v, + Cache: fmt.Sprintf("%s-index.yaml", k), + }) + } + return r, ErrRepoOutOfDate + } + + return r, nil +} + +// Add adds one or more repo entries to a repo file. +func (r *RepoFile) 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 *RepoFile) Update(re ...*Entry) { + for _, target := range re { + found := false + for j, repo := range r.Repositories { + if repo.Name == target.Name { + r.Repositories[j] = target + found = true + break + } + } + if !found { + r.Add(target) + } + } +} + +// Has returns true if the given name is already a repository name. +func (r *RepoFile) Has(name string) bool { + for _, rf := range r.Repositories { + if rf.Name == name { + return true + } + } + return false +} + +// Remove removes the entry from the list of repositories. +func (r *RepoFile) 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 *RepoFile) WriteFile(path string, perm os.FileMode) error { + data, err := yaml.Marshal(r) + if err != nil { + return err + } + return ioutil.WriteFile(path, data, perm) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/repo_test.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/repo_test.go new file mode 100644 index 000000000..4b5bcdbf5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/repo_test.go @@ -0,0 +1,227 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "testing" +import "io/ioutil" +import "os" +import "strings" + +const testRepositoriesFile = "testdata/repositories.yaml" + +func TestRepoFile(t *testing.T) { + rf := NewRepoFile() + rf.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + 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") + } + if stable.Cache != "stable-index.yaml" { + t.Error("Wrong cache name for stable") + } +} + +func TestNewRepositoriesFile(t *testing.T) { + expects := NewRepoFile() + expects.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + repofile, err := LoadRepositoriesFile(testRepositoriesFile) + if err != nil { + t.Errorf("%q could not be loaded: %s", testRepositoriesFile, err) + } + + if len(expects.Repositories) != len(repofile.Repositories) { + t.Fatalf("Unexpected repo data: %#v", repofile.Repositories) + } + + for i, expect := range expects.Repositories { + got := repofile.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) + } + if expect.Cache != got.Cache { + t.Errorf("Expected cache %q, got %q", expect.Cache, got.Cache) + } + } +} + +func TestNewPreV1RepositoriesFile(t *testing.T) { + r, err := LoadRepositoriesFile("testdata/old-repositories.yaml") + if err != nil && err != ErrRepoOutOfDate { + t.Fatal(err) + } + if len(r.Repositories) != 3 { + t.Fatalf("Expected 3 repos: %#v", r) + } + + // Because they are parsed as a map, we lose ordering. + found := false + for _, rr := range r.Repositories { + if rr.Name == "best-charts-ever" { + found = true + } + } + if !found { + t.Errorf("expected the best charts ever. Got %#v", r.Repositories) + } +} + +func TestRemoveRepository(t *testing.T) { + sampleRepository := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + 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 := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + newRepoName := "sample" + sampleRepository.Update(&Entry{Name: newRepoName, + URL: "https://example.com/sample", + Cache: "sample-index.yaml", + }) + + 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", + Cache: "sample-index.yaml", + }) + + 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 := NewRepoFile() + sampleRepository.Add( + &Entry{ + Name: "stable", + URL: "https://example.com/stable/charts", + Cache: "stable-index.yaml", + }, + &Entry{ + Name: "incubator", + URL: "https://example.com/incubator", + Cache: "incubator-index.yaml", + }, + ) + + repoFile, err := ioutil.TempFile("", "helm-repo") + if err != nil { + t.Errorf("failed to create test-file (%v)", err) + } + defer os.Remove(repoFile.Name()) + if err := sampleRepository.WriteFile(repoFile.Name(), 744); err != nil { + t.Errorf("failed to write file (%v)", err) + } + + repos, err := LoadRepositoriesFile(repoFile.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) { + _, err := LoadRepositoriesFile("/this/path/does/not/exist.yaml") + if err == nil { + t.Errorf("expected err to be non-nil when path does not exist") + } else if !strings.Contains(err.Error(), "You might need to run `helm init`") { + t.Errorf("expected prompt to run `helm init` when repositories file does not exist") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/doc.go new file mode 100644 index 000000000..34d4bc6b0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server.go new file mode 100644 index 000000000..8ea9103a0 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server.go @@ -0,0 +1,172 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "net/http/httptest" + "os" + "path/filepath" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/repo" +) + +// 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. +func NewTempServer(glob string) (*Server, helmpath.Home, error) { + tdir, err := ioutil.TempDir("", "helm-repotest-") + tdirh := helmpath.Home(tdir) + if err != nil { + return nil, tdirh, err + } + srv := NewServer(tdir) + + if glob != "" { + if _, err := srv.CopyCharts(glob); err != nil { + srv.Stop() + return srv, tdirh, err + } + } + + return srv, tdirh, 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(helmpath.Home(docroot), "test", srv.URL()); err != nil { + panic(err) + } + return srv +} + +// Server is an implementation of a repository server for testing. +type Server struct { + docroot string + srv *httptest.Server +} + +// 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, 0755); 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, 0755) +} + +func (s *Server) start() { + s.srv = httptest.NewServer(http.FileServer(http.Dir(s.docroot))) +} + +// 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 symboic link to the repositories/cache directory. +// +// This makes it possible to simulate a local cache of a repository. +func (s *Server) LinkIndices() error { + destfile := "test-index.yaml" + // Link the index.yaml file to the + lstart := filepath.Join(s.docroot, "index.yaml") + ldest := filepath.Join(s.docroot, "repository/cache", destfile) + return os.Symlink(lstart, ldest) +} + +// setTestingRepository sets up a testing repository.yaml with only the given name/URL. +func setTestingRepository(home helmpath.Home, name, url string) error { + r := repo.NewRepoFile() + r.Add(&repo.Entry{ + Name: name, + URL: url, + Cache: home.CacheIndex(name), + }) + os.MkdirAll(filepath.Join(home.Repository(), name), 0755) + return r.WriteFile(home.RepositoryFile(), 0644) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server_test.go b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server_test.go new file mode 100644 index 000000000..61c056172 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/server_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/repo" +) + +// Young'n, in these here parts, we test our tests. + +func TestServer(t *testing.T) { + docroot, err := ioutil.TempDir("", "helm-repotest-") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(docroot) + + srv := NewServer(docroot) + defer srv.Stop() + + c, err := srv.CopyCharts("testdata/*.tgz") + if err != nil { + // Some versions of Go don't correctly fire defer on Fatal. + t.Error(err) + return + } + + 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") + if err != nil { + t.Error(err) + return + } + + 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.Error(err) + return + } + + data, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + t.Error(err) + return + } + + m := repo.NewIndexFile() + if err := yaml.Unmarshal(data, m); err != nil { + t.Error(err) + return + } + + if l := len(m.Entries); l != 1 { + t.Errorf("Expected 1 entry, got %d", l) + return + } + + expect := "examplechart" + if !m.Has(expect, "0.1.0") { + t.Errorf("missing %q", expect) + } + + res, err = http.Get(srv.URL() + "/index.yaml-nosuchthing") + if err != nil { + t.Error(err) + return + } + if res.StatusCode != 404 { + t.Errorf("Expected 404, got %d", res.StatusCode) + } +} + +func TestNewTempServer(t *testing.T) { + srv, tdir, err := NewTempServer("testdata/examplechart-0.1.0.tgz") + if err != nil { + t.Fatal(err) + } + defer func() { + srv.Stop() + os.RemoveAll(tdir.String()) + }() + + if _, err := os.Stat(tdir.String()); err != nil { + t.Fatal(err) + } + + res, err := http.Head(srv.URL() + "/examplechart-0.1.0.tgz") + if err != nil { + t.Error(err) + } + if res.StatusCode != 200 { + t.Errorf("Expected 200, got %d", res.StatusCode) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/repo/repotest/testdata/examplechart-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..aec86c64002af0b6d3d114e15de120306ba24baa GIT binary patch literal 558 zcmV+}0@3{+iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PL1ui_|<6#`n6P;=Ihwt7zKpS_bxRnGqBfg^>lXByG>ManmH^ z&&-Y&es)h%R%b_KL5GorJ`AU6I5|n^`8^EY^1(=KdTxEbh>`91AkU7ef;6wH^ducV zi?Y1*h5`YMzDK==6Ha2e1Y-2fiq|Gb;=d}ZU-*A9@qZG{;6p^& zs>JH}?P1%af;tG<3e^$4%?XVHY%~u!$1YD z7b|GVV=~qWpQkt;KV$V*o2Pg;(RXZ5*WENSk8E~!9LH%ln_{?Zjh2oVjFvUwdTp;61hm5!jvM&xHWSS= zd`%N&VPsA(C6UH-OVb;ud~Q2x*6%MlPW^jKmKQ{S-2c|Cfy|9{B$L=G4)rR}LGMQ^ z2p09fKkk3G6}W-xder}I&+(XWR{0_px#a!dbw0d%PqeL|dhjY^{u`YKgYAb`fB2WN zB%*gCj#a94F5_t7F_OB073-di7oY3XV+XYNbWexMF7lqe7nwoXwR$?S_sg!zQ)N_P zQ|rXe_V?^uW!k1KCX_}F9~F_&`H@ZIH#OaA08hv7%HJe_i>@^Nt#@8wzUL07)y>A6 z;`P_=pZBW*00000fHgAN74{h|uhx&R)ypf#mH+?%00000aB<0yv9C55`-`Ib|Nr>@ z{~ucUjpApDpD4bk_?F^piZ3V*DL$gOLve%REsB?}jgIrj#(Dlm00000003|a4RoS9 zVqi?xv28W`=o6V_v8q0D&bEddi`lJqUu(N7b5`gw@2L(GLM1YaJ0jIx9Ui?qdxTV0 z2mgRhp;IMx;zCWIP<@VlZu8xN5_f2)*i|xN)HpR1Dlak1RGi@8mcO>OZnocEaiF zKb)55)%71O)PJnQ@%oP^FLxgN{`SMG*Z)TS2fzyh^&bEL0001&9_l{;00000003Tm zRsBb~!Pu`0>OX#@_>STmiZ3ZXuWLU(rnptter(jWAAi=z`33bK00000003~77S?wN zaoP%xWi=kvQRY^8EUxpIK4nRzN7ZOl>rt&usy(U)%j!KE!-9`38D|4&xz}CK51Nht zUH@^0AD>@d|KT`+=TE=?BWMc8ZTY^7`j2&(9S;12(Zgqpjre3#^660J(;>|3am}9{ z?tE@W@P#?V=OrCqlBc}RX1+A%`J%~!pLdt6)g=Gl-?_PcZ^uf))t}>}{P%o!l>dIy z3H&Pmo2?e|e=Rn}?Q!1nNxr9|&Ii#O{#D?7b5q$P#yr7|4QBQIu7qQTuIm zpkkdRDm9$n*0~5r8&oc$w6AZ7LMdTr4lp{~imQEMTGwS=t}Hx1ll(V|`chU!cPl@~ zDZl^infz}BPJr)!TZc*iAIP{=#hF|Ho;3fve*4(>|FxPeAM^h@Y>Jy=)b*ktRTI7* zUZ@q-Nf;Dzc9FB9oftln8|r@5W37wdfu%;L|xIrIN` z%`(|)rh=35pKbtW=YP*>`vLNQ9q0xu*5dJFYnZ%G=pwBz%h-qp-Pgro>TpOE literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/sprocket-1.1.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/sprocket-1.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..595e9cc0397068b278d521615b97ae3319eb3623 GIT binary patch literal 814 zcmV+}1JV2+iwG0|32ul0|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PNe%ZqrH>2k_HMcVp?+SNGlrnCmX9_#`X6H(&v8fb67B6ZK<3NLo0*jqRyDvFrY4I*N|Y zl?;byygM;h#vSogz@quS?|0hmYU}&{{MK)^ck2DBJ!p1<7O;ii$axBFxB}n*(SGJ1 z?uLj^hbqZUBC{^rj}y%j%{V*#vv90frr2e%F?rYX@EK2Yfo>>MYlx#GsUqFA8mzBm zFH6jGt5IKRHCQAg9$6_*GQ*P$Rdp(+JP}FUt}H*kGx)xYiQr_TQ&7X|8{G5}na|De^G%YQ4_ z-l6<2!zSBh<5$o6i9i<0Dy+KP=_i9i@p5QzC%JOUtg(kYMaR+@R-;CpL#J$KNmdRY z4~3BI_}Hz&`+`N-=Y=rL zIg(05&dCWy_?K4a-<>W1Tz!-;@-r^ae`|Iu7|;Lh9h(1_qUS%k|A2Me?egF+0P%14 z>8;&^^{uY}X%faL1ZGkyE|LrTNGB(_LWca3DdXi_hu%PjGD1(Q&>JKsE_&tv_1>`P zp~|qFipo`jmk$S?!d!X=wTZal)saq&R3mR0>sLZAeyo6djipT8fk z`mP8egb+dqAxmWPHFyO8>yu-9vR#|o5<&`)b5x|=bfG->4@BdHN|KDtY zPd30u8{mTt@ZJV^X9K*o0bbhxhc>{04RFr}*s=jOZGb=QaX+6oR{T(e5JCtcgb+f0 zGqc637nQO?qiWQ%tzKVBug4W=S*Q@Zz%H&ug~mCxsM8DPi(Tu}XNq0nY^5wr%4cC( sQ47;LTd0d~;Y@{V)Ag^c=Y`n<*;T(FLdXhz4*&rF|3}zP2LM6<0NoysXZNE)pp|do>SG% zl2mzzQk&%B;GDay78|AQGUc{YrWMSxUAyHt+-bXvTaIIK!de$NrkXs{B88zuLhEj> zy@5<;>VaCXx1gQsKI(K$$j0+;JRFEr8$%I=Ms}Pmvk!P4ZO556|IP9ITW!a(ZOexF zcP-Z?j4yK#^SI*k-{r5KK4Y%o6os3Fgm*fx@9!OK?))Ys3gBskp^TMEMHtL&BdQdP zPY4pO5Av?zsmMIcyE2WXmYE6Kk(nI!%~U4Jgc|J&Ek>m* z&?+68wf#=acj?avLDrsJ_jd1HCr#TLh1KoGU3UA{%gY`Wf*=TjAP9mWD?}$70dF~|^LX8 zqsnZ$*6b-|c121um8YKU5XNLAg0RD4)m8rR!E_6;C>nnOU8q>HM`0!>UnsV*&s5(Z zNcIFSvVAEc0?M%lk!jWe6<_)A>wK4ux41*Lm#S_Y=(prh4v$spgWeHn2P*C`%dibj ze3^M^kmym>{vgO0d_>(wgOnZDZLVVf{NnQ;oI6mc&VM+z3#g?`9JXb7H^mS?{GZ-w-W04KN4XsvkO@O&Wrz6(>-(kv)ytr{;z~>w#Uk; zXT2bl%u}N{ofVm0(9cs*tOW)wBXFY#!R$HY8#I&NXqkXWTwN^cSr8ZNMR~1pr9$ai zG9&(vbB0cq6AI4re|Q0$j{i1yn;iXr6~OyfsMyJgF>>B_%$Gfphnh9uwJ!7E$` Z2!bF8f*=TjAPBPd`~~*!Et~*Q004wYsG literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/repository/universe/zarthal-1.0.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..90cb34bd5f36439ee2e986c21c766b55a4442ec8 GIT binary patch literal 1121 zcmV-n1fKgJiwFR8Z?Rbb1MQs8ZsSB8$7fgK5{X>|Jiw41SM|TK<0R5btPr9cKu8q0 z>}6t48mo3DvYr0fW#I)la^c9GJ7?a27vK#zz#H&$5+{w*B)c?qx9#_%{xr77o*CEq z{h6`#Gt95#*@2RwN;AXmIBj9RkZC@UG20my(Q@5MNmNVA78bLG?YU0N7oNu~+Yz46 z_}T7;u%2<2%an#vM_+Ft!s1CDCF-f|S#I01+>S6hZQpkt+jSaFhs%q#o@E=t>-etS zc8khjwbGkb-t>Ke*JGya=5_W_=(u0#HL1GqP4(Vh*n^2JSk`|bT$^$4ItNu!>c33n zFqFZD^TdeL`(Fo+Rk84* z(;%M^RE`-|i+qCRbObE>oubtL9jf~Z+w8yas`~Fatrp|8=;S@O|Dno)G#cfR9(MWR zAj&xXNk02oGzL0RT|UV3QP(upnM_8pG6J16j@9|8PQ$F**ysIJ_l8maf;P+R_Iap7 z88#9b4RaFu-xX&n+K;I{6(=8GY>o4wEIK+ID1Jbd{7}hcasE*Eba;O06<^Z;^-%Fo z<4iTChsrUWP^XjIvJK0yV7D%1t^Yrf@mOU$u?SoJzrP#{7RJHG{I3>7#s4qQ|EiI| za_voz9bBDK|97bFD{S-sw(u+Z@3|iQe=Y3sXMFnXc|VF35A?)eXT?T8IvS_42n~#x zYi$ZIz_gjvI`ClCzs5sXAxh2$ra z6O!j7pOHKv*&})9@wA=49^K>}0RR9100000xPL6CFI$()jO|YjX3Iy-zSd?>rYzIr zG*Df}*QX<+7FN@dEiS0$JDZ=rl<$mPOqRxG8PupOKXGS^OlxeOevMtoa^@J;x_oYB n8%;Ltc-|%e00000000000000000000fV;terNp1g0C)fZNQq8~ literal 0 HcmV?d00001 diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/index.yaml new file mode 100644 index 000000000..ec529f110 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/index.yaml @@ -0,0 +1,39 @@ +apiVersion: v1 +entries: + nginx: + - urls: + - https://kubernetes-charts.storage.googleapis.com/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://kubernetes-charts.storage.googleapis.com/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://kubernetes-charts.storage.googleapis.com/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/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/test.txt b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/test.txt new file mode 100644 index 000000000..557db03de --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/server/test.txt @@ -0,0 +1 @@ +Hello World diff --git a/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/unversioned-index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/unversioned-index.yaml new file mode 100644 index 000000000..7299c66dc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/repo/testdata/unversioned-index.yaml @@ -0,0 +1,64 @@ +memcached-0.1.0: + name: memcached + url: https://mumoshu.github.io/charts/memcached-0.1.0.tgz + created: 2016-08-04 02:05:02.259205055 +0000 UTC + checksum: ce9b76576c4b4eb74286fa30a978c56d69e7a522 + chartfile: + name: memcached + home: http://https://hub.docker.com/_/memcached/ + sources: [] + version: 0.1.0 + description: A simple Memcached cluster + keywords: [] + maintainers: + - name: Matt Butcher + email: mbutcher@deis.com + engine: "" +mysql-0.2.0: + name: mysql + url: https://mumoshu.github.io/charts/mysql-0.2.0.tgz + created: 2016-08-04 00:42:47.517342022 +0000 UTC + checksum: aa5edd2904d639b0b6295f1c7cf4c0a8e4f77dd3 + chartfile: + name: mysql + home: https://www.mysql.com/ + sources: [] + version: 0.2.0 + description: Chart running MySQL. + keywords: [] + maintainers: + - name: Matt Fisher + email: mfisher@deis.com + engine: "" +mysql-0.2.1: + name: mysql + url: https://mumoshu.github.io/charts/mysql-0.2.1.tgz + created: 2016-08-04 02:40:29.717829534 +0000 UTC + checksum: 9d9f056171beefaaa04db75680319ca4edb6336a + chartfile: + name: mysql + home: https://www.mysql.com/ + sources: [] + version: 0.2.1 + description: Chart running MySQL. + keywords: [] + maintainers: + - name: Matt Fisher + email: mfisher@deis.com + engine: "" +mysql-0.2.2: + name: mysql + url: https://mumoshu.github.io/charts/mysql-0.2.2.tgz + created: 2016-08-04 02:40:29.71841952 +0000 UTC + checksum: 6d6810e76a5987943faf0040ec22990d9fb141c7 + chartfile: + name: mysql + home: https://www.mysql.com/ + sources: [] + version: 0.2.2 + description: Chart running MySQL. + keywords: [] + maintainers: + - name: Matt Fisher + email: mfisher@deis.com + engine: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver.go b/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver.go new file mode 100644 index 000000000..ec8ea2cce --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver.go @@ -0,0 +1,153 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 resolver + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/Masterminds/semver" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/helm/helmpath" + "k8s.io/helm/pkg/provenance" + "k8s.io/helm/pkg/repo" +) + +// Resolver resolves dependencies from semantic version ranges to a particular version. +type Resolver struct { + chartpath string + helmhome helmpath.Home +} + +// New creates a new resolver for a given chart and a given helm home. +func New(chartpath string, helmhome helmpath.Home) *Resolver { + return &Resolver{ + chartpath: chartpath, + helmhome: helmhome, + } +} + +// Resolve resolves dependencies and returns a lock file with the resolution. +func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]string, d string) (*chartutil.RequirementsLock, error) { + + // Now we clone the dependencies, locking as we go. + locked := make([]*chartutil.Dependency, len(reqs.Dependencies)) + missing := []string{} + for i, d := range reqs.Dependencies { + if strings.HasPrefix(d.Repository, "file://") { + + if _, err := GetLocalPath(d.Repository, r.chartpath); err != nil { + return nil, err + } + + locked[i] = &chartutil.Dependency{ + Name: d.Name, + Repository: d.Repository, + Version: d.Version, + } + continue + } + constraint, err := semver.NewConstraint(d.Version) + if err != nil { + return nil, fmt.Errorf("dependency %q has an invalid version/constraint format: %s", d.Name, err) + } + + repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name])) + if err != nil { + return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err) + } + + vs, ok := repoIndex.Entries[d.Name] + if !ok { + return nil, fmt.Errorf("%s chart not found in repo %s", d.Name, d.Repository) + } + + locked[i] = &chartutil.Dependency{ + Name: d.Name, + Repository: d.Repository, + } + found := false + // The version are already sorted and hence the first one to satisfy the constraint is used + for _, ver := range vs { + v, err := semver.NewVersion(ver.Version) + if err != nil || len(ver.URLs) == 0 { + // Not a legit entry. + continue + } + if constraint.Check(v) { + found = true + locked[i].Version = v.Original() + break + } + } + + if !found { + missing = append(missing, d.Name) + } + } + if len(missing) > 0 { + return nil, fmt.Errorf("Can't get a valid version for repositories %s. Try changing the version constraint in requirements.yaml", strings.Join(missing, ", ")) + } + return &chartutil.RequirementsLock{ + Generated: time.Now(), + Digest: d, + Dependencies: locked, + }, nil +} + +// HashReq generates a hash of the requirements. +// +// This should be used only to compare against another hash generated by this +// function. +func HashReq(req *chartutil.Requirements) (string, error) { + data, err := json.Marshal(req) + if err != nil { + return "", err + } + s, err := provenance.Digest(bytes.NewBuffer(data)) + return "sha256:" + s, err +} + +// GetLocalPath generates absolute local path when use +// "file://" in repository of requirements +func GetLocalPath(repo string, chartpath string) (string, error) { + var depPath string + var err error + p := strings.TrimPrefix(repo, "file://") + + // root path is absolute + if strings.HasPrefix(p, "/") { + if depPath, err = filepath.Abs(p); err != nil { + return "", err + } + } else { + depPath = filepath.Join(chartpath, p) + } + + if _, err = os.Stat(depPath); os.IsNotExist(err) { + return "", fmt.Errorf("directory %s not found", depPath) + } else if err != nil { + return "", err + } + + return depPath, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver_test.go b/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver_test.go new file mode 100644 index 000000000..78a0bc46c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/resolver/resolver_test.go @@ -0,0 +1,171 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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 resolver + +import ( + "testing" + + "k8s.io/helm/pkg/chartutil" +) + +func TestResolve(t *testing.T) { + tests := []struct { + name string + req *chartutil.Requirements + expect *chartutil.RequirementsLock + err bool + }{ + { + name: "version failure", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "http://example.com", Version: ">a1"}, + }, + }, + err: true, + }, + { + name: "cache index failure", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "oedipus-rex", Repository: "http://example.com", Version: "1.0.0"}, + }, + }, + err: true, + }, + { + name: "chart not found failure", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "redis", Repository: "http://example.com", Version: "1.0.0"}, + }, + }, + err: true, + }, + { + name: "constraint not satisfied failure", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "alpine", Repository: "http://example.com", Version: ">=1.0.0"}, + }, + }, + err: true, + }, + { + name: "valid lock", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "alpine", Repository: "http://example.com", Version: ">=0.1.0"}, + }, + }, + expect: &chartutil.RequirementsLock{ + Dependencies: []*chartutil.Dependency{ + {Name: "alpine", Repository: "http://example.com", Version: "0.2.0"}, + }, + }, + }, + { + name: "repo from valid local path", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + }, + }, + expect: &chartutil.RequirementsLock{ + Dependencies: []*chartutil.Dependency{ + {Name: "signtest", Repository: "file://../../../../cmd/helm/testdata/testcharts/signtest", Version: "0.1.0"}, + }, + }, + }, + { + name: "repo from invalid local path", + req: &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "notexist", Repository: "file://../testdata/notexist", Version: "0.1.0"}, + }, + }, + err: true, + }, + } + + repoNames := map[string]string{"alpine": "kubernetes-charts", "redis": "kubernetes-charts"} + r := New("testdata/chartpath", "testdata/helmhome") + for _, tt := range tests { + hash, err := HashReq(tt.req) + if err != nil { + t.Fatal(err) + } + + l, err := r.Resolve(tt.req, repoNames, hash) + if err != nil { + if tt.err { + continue + } + t.Fatal(err) + } + + if tt.err { + t.Fatalf("Expected error in test %q", tt.name) + } + + if h, err := HashReq(tt.req); err != nil { + t.Fatal(err) + } else if h != l.Digest { + t.Errorf("%q: hashes don't match.", tt.name) + } + + // Check fields. + if len(l.Dependencies) != len(tt.req.Dependencies) { + t.Errorf("%s: wrong number of dependencies in lock", tt.name) + } + d0 := l.Dependencies[0] + e0 := tt.expect.Dependencies[0] + if d0.Name != e0.Name { + t.Errorf("%s: expected name %s, got %s", tt.name, e0.Name, d0.Name) + } + if d0.Repository != e0.Repository { + t.Errorf("%s: expected repo %s, got %s", tt.name, e0.Repository, d0.Repository) + } + if d0.Version != e0.Version { + t.Errorf("%s: expected version %s, got %s", tt.name, e0.Version, d0.Version) + } + } +} + +func TestHashReq(t *testing.T) { + expect := "sha256:e70e41f8922e19558a8bf62f591a8b70c8e4622e3c03e5415f09aba881f13885" + req := &chartutil.Requirements{ + Dependencies: []*chartutil.Dependency{ + {Name: "alpine", Version: "0.1.0", Repository: "http://localhost:8879/charts"}, + }, + } + h, err := HashReq(req) + if err != nil { + t.Fatal(err) + } + if expect != h { + t.Errorf("Expected %q, got %q", expect, h) + } + + req = &chartutil.Requirements{Dependencies: []*chartutil.Dependency{}} + h, err = HashReq(req) + if err != nil { + t.Fatal(err) + } + if expect == h { + t.Errorf("Expected %q != %q", expect, h) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml b/src/vendor/github.com/kubernetes/helm/pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml new file mode 100644 index 000000000..e2d438701 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/resolver/testdata/helmhome/repository/cache/kubernetes-charts-index.yaml @@ -0,0 +1,49 @@ +apiVersion: v1 +entries: + alpine: + - name: alpine + urls: + - https://kubernetes-charts.storage.googleapis.com/alpine-0.1.0.tgz + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d + home: https://k8s.io/helm + sources: + - https://github.com/kubernetes/helm + version: 0.2.0 + description: Deploy a basic Alpine Linux pod + keywords: [] + maintainers: [] + engine: "" + icon: "" + - name: alpine + urls: + - https://kubernetes-charts.storage.googleapis.com/alpine-0.2.0.tgz + checksum: 0e6661f193211d7a5206918d42f5c2a9470b737d + home: https://k8s.io/helm + sources: + - https://github.com/kubernetes/helm + version: 0.1.0 + description: Deploy a basic Alpine Linux pod + keywords: [] + maintainers: [] + engine: "" + icon: "" + mariadb: + - name: mariadb + urls: + - https://kubernetes-charts.storage.googleapis.com/mariadb-0.3.0.tgz + checksum: 65229f6de44a2be9f215d11dbff311673fc8ba56 + home: https://mariadb.org + sources: + - https://github.com/bitnami/bitnami-docker-mariadb + version: 0.3.0 + description: Chart for MariaDB + keywords: + - mariadb + - mysql + - database + - sql + maintainers: + - name: Bitnami + email: containers@bitnami.com + engine: gotpl + icon: "" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/rudder/client.go b/src/vendor/github.com/kubernetes/helm/pkg/rudder/client.go new file mode 100644 index 000000000..219bb010a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/rudder/client.go @@ -0,0 +1,91 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 rudder // import "k8s.io/helm/pkg/rudder" + +import ( + "fmt" + + "golang.org/x/net/context" + "google.golang.org/grpc" + + rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder" +) + +// GrpcPort specifies port on which rudder will spawn a server +const ( + GrpcPort = 10001 +) + +var grpcAddr = fmt.Sprintf("127.0.0.1:%d", GrpcPort) + +// InstallRelease calls Rudder InstallRelease method which should create provided release +func InstallRelease(rel *rudderAPI.InstallReleaseRequest) (*rudderAPI.InstallReleaseResponse, error) { + //TODO(mkwiek): parametrize this + conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + + defer conn.Close() + client := rudderAPI.NewReleaseModuleServiceClient(conn) + return client.InstallRelease(context.Background(), rel) +} + +// UpgradeRelease calls Rudder UpgradeRelease method which should perform update +func UpgradeRelease(req *rudderAPI.UpgradeReleaseRequest) (*rudderAPI.UpgradeReleaseResponse, error) { + conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer conn.Close() + client := rudderAPI.NewReleaseModuleServiceClient(conn) + return client.UpgradeRelease(context.Background(), req) +} + +// RollbackRelease calls Rudder RollbackRelease method which should perform update +func RollbackRelease(req *rudderAPI.RollbackReleaseRequest) (*rudderAPI.RollbackReleaseResponse, error) { + conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer conn.Close() + client := rudderAPI.NewReleaseModuleServiceClient(conn) + return client.RollbackRelease(context.Background(), req) +} + +// ReleaseStatus calls Rudder ReleaseStatus method which should perform update +func ReleaseStatus(req *rudderAPI.ReleaseStatusRequest) (*rudderAPI.ReleaseStatusResponse, error) { + conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + defer conn.Close() + client := rudderAPI.NewReleaseModuleServiceClient(conn) + return client.ReleaseStatus(context.Background(), req) +} + +// DeleteRelease calls Rudder DeleteRelease method which should uninstall provided release +func DeleteRelease(rel *rudderAPI.DeleteReleaseRequest) (*rudderAPI.DeleteReleaseResponse, error) { + conn, err := grpc.Dial(grpcAddr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + + defer conn.Close() + client := rudderAPI.NewReleaseModuleServiceClient(conn) + return client.DeleteRelease(context.Background(), rel) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps.go new file mode 100644 index 000000000..51fa8f8f6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps.go @@ -0,0 +1,258 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "fmt" + "strconv" + "strings" + "time" + + 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" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + + rspb "k8s.io/helm/pkg/proto/hapi/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 internalversion.ConfigMapInterface + Log func(string, ...interface{}) +} + +// NewConfigMaps initializes a new ConfigMaps wrapping an implementation of +// the kubernetes ConfigMapsInterface. +func NewConfigMaps(impl internalversion.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(key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound(key) + } + + 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": "TILLER"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := cfgmaps.impl.List(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 + } + 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, fmt.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(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(labels["NAME"]) + } + + 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("CREATED_AT", 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(obj); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists(key) + } + + 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("MODIFIED_AT", 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(obj) + 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 { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseExists(rls.Name) + } + + cfgmaps.Log("delete: failed to get release %q: %s", key, err) + return nil, err + } + // delete the release + if err = cfgmaps.impl.Delete(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 string of a release's binary protobuf encoding. +// +// The following labels are used within each configmap: +// +// "MODIFIED_AT" - timestamp indicating when this configmap was last modified. (set in Update) +// "CREATED_AT" - timestamp indicating when this configmap was created. (set in Create) +// "VERSION" - version of the release. +// "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants) +// "OWNER" - owner of the configmap, currently "TILLER". +// "NAME" - name of the release. +// +func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*core.ConfigMap, error) { + const owner = "TILLER" + + // 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", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) + lbs.set("VERSION", strconv.Itoa(int(rls.Version))) + + // create and return configmap object + return &core.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Data: map[string]string{"release": s}, + }, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps_test.go new file mode 100644 index 000000000..7501ad9cb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/cfgmaps_test.go @@ -0,0 +1,186 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "reflect" + "testing" + + "github.com/gogo/protobuf/proto" + "k8s.io/kubernetes/pkg/apis/core" + + rspb "k8s.io/helm/pkg/proto/hapi/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 := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + 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 {%q}, got {%q}", rel, got) + } +} + +func TestUNcompressedConfigMapGet(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // 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 := proto.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]*core.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 {%q}, got {%q}", rel, got) + } +} + +func TestConfigMapList(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", rspb.Status_DELETED), + releaseStub("key-2", 1, "default", rspb.Status_DELETED), + releaseStub("key-3", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-4", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-5", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("key-6", 1, "default", rspb.Status_SUPERSEDED), + }...) + + // list all deleted releases + del, err := cfgmaps.List(func(rel *rspb.Release) bool { + return rel.Info.Status.Code == rspb.Status_DELETED + }) + // 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.Code == rspb.Status_DEPLOYED + }) + // 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.Code == rspb.Status_SUPERSEDED + }) + // 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 TestConfigMapCreate(t *testing.T) { + cfgmaps := newTestFixtureCfgMaps(t) + + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // 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 {%q}, got {%q}", rel, got) + } +} + +func TestConfigMapUpdate(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...) + + // modify release status code + rel.Info.Status.Code = rspb.Status_SUPERSEDED + + // 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.Code != got.Info.Status.Code { + t.Errorf("Expected status %s, got status %s", rel.Info.Status.Code, got.Info.Status.Code) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/driver.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/driver.go new file mode 100644 index 000000000..e01d35d64 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/driver.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "fmt" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +var ( + // ErrReleaseNotFound indicates that a release is not found. + ErrReleaseNotFound = func(release string) error { return fmt.Errorf("release: %q not found", release) } + // ErrReleaseExists indicates that a release already exists. + ErrReleaseExists = func(release string) error { return fmt.Errorf("release: %q already exists", release) } + // ErrInvalidKey indicates that a release key could not be parsed. + ErrInvalidKey = func(release string) error { return fmt.Errorf("release: %q invalid key", release) } +) + +// 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 Tiller releases from some underlying storage mechanism, +// e.g. memory, configmaps. +type Driver interface { + Creator + Updator + Deletor + Queryor + Name() string +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels.go new file mode 100644 index 000000000..8668d665b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels_test.go new file mode 100644 index 000000000..af0bd24e5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/labels_test.go @@ -0,0 +1,49 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/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/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory.go new file mode 100644 index 000000000..ceb0d67dd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory.go @@ -0,0 +1,176 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/proto/hapi/release" +) + +var _ Driver = (*Memory)(nil) + +// MemoryDriverName is the string name of this driver. +const MemoryDriverName = "Memory" + +// Memory is the in-memory storage driver implementation. +type Memory struct { + sync.RWMutex + cache map[string]records +} + +// NewMemory initializes a new memory driver. +func NewMemory() *Memory { + return &Memory{cache: map[string]records{}} +} + +// 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()) + + switch elems := strings.Split(key, ".v"); len(elems) { + case 2: + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey(key) + } + if recs, ok := mem.cache[name]; ok { + if r := recs.Get(key); r != nil { + return r.rls, nil + } + } + return nil, ErrReleaseNotFound(key) + default: + return nil, ErrInvalidKey(key) + } +} + +// 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 _, recs := range mem.cache { + recs.Iter(func(_ int, rec *record) bool { + if filter(rec.rls) { + ls = append(ls, rec.rls) + } + return true + }) + } + 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 _, recs := range mem.cache { + 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 + }) + } + 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()) + + if recs, ok := mem.cache[rls.Name]; ok { + if err := recs.Add(newRecord(key, rls)); err != nil { + return err + } + mem.cache[rls.Name] = recs + return nil + } + mem.cache[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()) + + if rs, ok := mem.cache[rls.Name]; ok && rs.Exists(key) { + rs.Replace(key, newRecord(key, rls)) + return nil + } + return ErrReleaseNotFound(rls.Name) +} + +// Delete deletes a release or returns ErrReleaseNotFound. +func (mem *Memory) Delete(key string) (*rspb.Release, error) { + defer unlock(mem.wlock()) + + elems := strings.Split(key, ".v") + + if len(elems) != 2 { + return nil, ErrInvalidKey(key) + } + + name, ver := elems[0], elems[1] + if _, err := strconv.Atoi(ver); err != nil { + return nil, ErrInvalidKey(key) + } + if recs, ok := mem.cache[name]; ok { + if r := recs.Remove(key); r != nil { + // recs.Remove changes the slice reference, so we have to re-assign it. + mem.cache[name] = recs + return r.rls, nil + } + } + return nil, ErrReleaseNotFound(key) +} + +// 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/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory_test.go new file mode 100644 index 000000000..1062071e7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/memory_test.go @@ -0,0 +1,196 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/proto/hapi/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 success", + releaseStub("rls-c", 1, "default", rspb.Status_DEPLOYED), + false, + }, + { + "create should fail (release already exists)", + releaseStub("rls-a", 1, "default", rspb.Status_DEPLOYED), + 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) + } + } + } +} + +func TestMemoryGet(t *testing.T) { + var tests = []struct { + desc string + key string + err bool + }{ + {"release key should exist", "rls-a.v1", false}, + {"release key should not exist", "rls-a.v5", true}, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + 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) + } + } + } +} + +func TestMemoryQuery(t *testing.T) { + var tests = []struct { + desc string + xlen int + lbs map[string]string + }{ + { + "should be 2 query results", + 2, + map[string]string{"STATUS": "DEPLOYED"}, + }, + } + + ts := tsFixtureMemory(t) + for _, tt := range tests { + 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.Status_SUPERSEDED), + false, + }, + { + "update release does not exist", + "rls-z.v1", + releaseStub("rls-z", 1, "default", rspb.Status_DELETED), + 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 + } + + 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 %s, actual %s\n", tt.rls, r) + } + } +} + +func TestMemoryDelete(t *testing.T) { + var tests = []struct { + desc string + key string + err bool + }{ + {"release key should exist", "rls-a.v1", false}, + {"release key should not exist", "rls-a.v5", true}, + } + + ts := tsFixtureMemory(t) + start, err := ts.Query(map[string]string{"NAME": "rls-a"}) + if err != nil { + t.Errorf("Query failed: %s", err) + } + startLen := len(start) + for _, tt := range tests { + 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 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. + end, err := ts.Query(map[string]string{"NAME": "rls-a"}) + if err != nil { + t.Errorf("Query failed: %s", err) + } + endLen := len(end) + + if startLen <= endLen { + t.Errorf("expected start %d to be less than end %d", startLen, endLen) + for _, ee := range end { + t.Logf("Name: %s, Version: %d", ee.Name, ee.Version) + } + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/mock_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/mock_test.go new file mode 100644 index 000000000..979d11cb6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/mock_test.go @@ -0,0 +1,222 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "fmt" + "testing" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +func releaseStub(name string, vers int32, namespace string, code rspb.Status_Code) *rspb.Release { + return &rspb.Release{ + Name: name, + Version: vers, + Namespace: namespace, + Info: &rspb.Info{Status: &rspb.Status{Code: code}}, + } +} + +func testKey(name string, vers int32) 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.Status_DEPLOYED), + releaseStub("rls-a", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-a", 3, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-a", 2, "default", rspb.Status_SUPERSEDED), + // rls-b + releaseStub("rls-b", 4, "default", rspb.Status_DEPLOYED), + releaseStub("rls-b", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-b", 3, "default", rspb.Status_SUPERSEDED), + releaseStub("rls-b", 2, "default", rspb.Status_SUPERSEDED), + } + + 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 { + internalversion.ConfigMapInterface + + objects map[string]*core.ConfigMap +} + +// Init initializes the MockConfigMapsInterface with the set of releases. +func (mock *MockConfigMapsInterface) Init(t *testing.T, releases ...*rspb.Release) { + mock.objects = map[string]*core.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(name string, options metav1.GetOptions) (*core.ConfigMap, error) { + object, ok := mock.objects[name] + if !ok { + return nil, apierrors.NewNotFound(core.Resource("tests"), name) + } + return object, nil +} + +// List returns the a of ConfigMaps. +func (mock *MockConfigMapsInterface) List(opts metav1.ListOptions) (*core.ConfigMapList, error) { + var list core.ConfigMapList + for _, cfgmap := range mock.objects { + list.Items = append(list.Items, *cfgmap) + } + return &list, nil +} + +// Create creates a new ConfigMap. +func (mock *MockConfigMapsInterface) Create(cfgmap *core.ConfigMap) (*core.ConfigMap, error) { + name := cfgmap.ObjectMeta.Name + if object, ok := mock.objects[name]; ok { + return object, apierrors.NewAlreadyExists(core.Resource("tests"), name) + } + mock.objects[name] = cfgmap + return cfgmap, nil +} + +// Update updates a ConfigMap. +func (mock *MockConfigMapsInterface) Update(cfgmap *core.ConfigMap) (*core.ConfigMap, error) { + name := cfgmap.ObjectMeta.Name + if _, ok := mock.objects[name]; !ok { + return nil, apierrors.NewNotFound(core.Resource("tests"), name) + } + mock.objects[name] = cfgmap + return cfgmap, nil +} + +// Delete deletes a ConfigMap by name. +func (mock *MockConfigMapsInterface) Delete(name string, opts *metav1.DeleteOptions) error { + if _, ok := mock.objects[name]; !ok { + return apierrors.NewNotFound(core.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 { + internalversion.SecretInterface + + objects map[string]*core.Secret +} + +// Init initializes the MockSecretsInterface with the set of releases. +func (mock *MockSecretsInterface) Init(t *testing.T, releases ...*rspb.Release) { + mock.objects = map[string]*core.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(name string, options metav1.GetOptions) (*core.Secret, error) { + object, ok := mock.objects[name] + if !ok { + return nil, apierrors.NewNotFound(core.Resource("tests"), name) + } + return object, nil +} + +// List returns the a of Secret. +func (mock *MockSecretsInterface) List(opts metav1.ListOptions) (*core.SecretList, error) { + var list core.SecretList + for _, secret := range mock.objects { + list.Items = append(list.Items, *secret) + } + return &list, nil +} + +// Create creates a new Secret. +func (mock *MockSecretsInterface) Create(secret *core.Secret) (*core.Secret, error) { + name := secret.ObjectMeta.Name + if object, ok := mock.objects[name]; ok { + return object, apierrors.NewAlreadyExists(core.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Update updates a Secret. +func (mock *MockSecretsInterface) Update(secret *core.Secret) (*core.Secret, error) { + name := secret.ObjectMeta.Name + if _, ok := mock.objects[name]; !ok { + return nil, apierrors.NewNotFound(core.Resource("tests"), name) + } + mock.objects[name] = secret + return secret, nil +} + +// Delete deletes a Secret by name. +func (mock *MockSecretsInterface) Delete(name string, opts *metav1.DeleteOptions) error { + if _, ok := mock.objects[name]; !ok { + return apierrors.NewNotFound(core.Resource("tests"), name) + } + delete(mock.objects, name) + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records.go new file mode 100644 index 000000000..ce72308a8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records.go @@ -0,0 +1,135 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "sort" + "strconv" + + "github.com/golang/protobuf/proto" + + rspb "k8s.io/helm/pkg/proto/hapi/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(r.key) + } + + *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) FindByVersion(vers int32) (int, bool) { + i := sort.Search(len(rs), func(i int) bool { + return rs[i].rls.Version == vers + }) + if i < len(rs) && rs[i].rls.Version == vers { + return i, true + } + return i, false +} + +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", "TILLER") + lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) + lbs.set("VERSION", strconv.Itoa(int(rls.Version))) + + return &record{key: key, lbs: lbs, rls: proto.Clone(rls).(*rspb.Release)} +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records_test.go new file mode 100644 index 000000000..79380afb8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/records_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "testing" + + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestRecordsAdd(t *testing.T) { + rs := records([]*record{ + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.Status_SUPERSEDED)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.Status_DEPLOYED)), + }) + + 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.Status_SUPERSEDED)), + }, + { + "add already existing key", + "rls-a.v1", + true, + newRecord("rls-a.v1", releaseStub("rls-a", 1, "default", rspb.Status_DEPLOYED)), + }, + } + + 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.Status_SUPERSEDED)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.Status_DEPLOYED)), + }) + + 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.Status_SUPERSEDED)), + newRecord("rls-a.v2", releaseStub("rls-a", 2, "default", rspb.Status_DEPLOYED)), + }) + + 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)) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets.go new file mode 100644 index 000000000..e8f3984f6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets.go @@ -0,0 +1,258 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "fmt" + "strconv" + "strings" + "time" + + 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" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" + + rspb "k8s.io/helm/pkg/proto/hapi/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 internalversion.SecretInterface + Log func(string, ...interface{}) +} + +// NewSecrets initializes a new Secrets wrapping an implmenetation of +// the kubernetes SecretsInterface. +func NewSecrets(impl internalversion.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(key, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseNotFound(key) + } + + secrets.Log("get: failed to get %q: %s", key, err) + return nil, err + } + // found the secret, decode the base64 data string + r, err := decodeRelease(string(obj.Data["release"])) + if err != nil { + secrets.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 +// secret fails to retrieve the releases. +func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { + lsel := kblabels.Set{"OWNER": "TILLER"}.AsSelector() + opts := metav1.ListOptions{LabelSelector: lsel.String()} + + list, err := secrets.impl.List(opts) + if err != nil { + secrets.Log("list: failed to list: %s", err) + return nil, err + } + + 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 + } + 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, fmt.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(opts) + if err != nil { + secrets.Log("query: failed to query with labels: %s", err) + return nil, err + } + + if len(list.Items) == 0 { + return nil, ErrReleaseNotFound(labels["NAME"]) + } + + 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("CREATED_AT", strconv.Itoa(int(time.Now().Unix()))) + + // create a new secret to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + secrets.Log("create: failed to encode release %q: %s", rls.Name, err) + return err + } + // push the secret object out into the kubiverse + if _, err := secrets.impl.Create(obj); err != nil { + if apierrors.IsAlreadyExists(err) { + return ErrReleaseExists(rls.Name) + } + + secrets.Log("create: failed to create: %s", err) + return err + } + 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("MODIFIED_AT", strconv.Itoa(int(time.Now().Unix()))) + + // create a new secret object to hold the release + obj, err := newSecretsObject(key, rls, lbs) + if err != nil { + secrets.Log("update: failed to encode release %q: %s", rls.Name, err) + return err + } + // push the secret object out into the kubiverse + _, err = secrets.impl.Update(obj) + if err != nil { + secrets.Log("update: failed to update: %s", err) + return err + } + return nil +} + +// 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 { + if apierrors.IsNotFound(err) { + return nil, ErrReleaseExists(rls.Name) + } + + secrets.Log("delete: failed to get release %q: %s", key, err) + return nil, err + } + // delete the release + if err = secrets.impl.Delete(key, &metav1.DeleteOptions{}); err != nil { + return rls, err + } + return rls, nil +} + +// newSecretsObject constructs a kubernetes Secret object +// to store a release. Each secret data entry is the base64 +// encoded string of a release's binary protobuf encoding. +// +// The following labels are used within each secret: +// +// "MODIFIED_AT" - timestamp indicating when this secret was last modified. (set in Update) +// "CREATED_AT" - timestamp indicating when this secret was created. (set in Create) +// "VERSION" - version of the release. +// "STATUS" - status of the release (see proto/hapi/release.status.pb.go for variants) +// "OWNER" - owner of the secret, currently "TILLER". +// "NAME" - name of the release. +// +func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*core.Secret, error) { + const owner = "TILLER" + + // 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", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) + lbs.set("VERSION", strconv.Itoa(int(rls.Version))) + + // create and return secret object + return &core.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: key, + Labels: lbs.toMap(), + }, + Data: map[string][]byte{"release": []byte(s)}, + }, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets_test.go new file mode 100644 index 000000000..e6f62e702 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/secrets_test.go @@ -0,0 +1,186 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. +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" + "reflect" + "testing" + + "github.com/gogo/protobuf/proto" + "k8s.io/kubernetes/pkg/apis/core" + + rspb "k8s.io/helm/pkg/proto/hapi/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 := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + 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 {%q}, got {%q}", rel, got) + } +} + +func TestUNcompressedSecretGet(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // 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 := proto.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]*core.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 {%q}, got {%q}", rel, got) + } +} + +func TestSecretList(t *testing.T) { + secrets := newTestFixtureSecrets(t, []*rspb.Release{ + releaseStub("key-1", 1, "default", rspb.Status_DELETED), + releaseStub("key-2", 1, "default", rspb.Status_DELETED), + releaseStub("key-3", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-4", 1, "default", rspb.Status_DEPLOYED), + releaseStub("key-5", 1, "default", rspb.Status_SUPERSEDED), + releaseStub("key-6", 1, "default", rspb.Status_SUPERSEDED), + }...) + + // list all deleted releases + del, err := secrets.List(func(rel *rspb.Release) bool { + return rel.Info.Status.Code == rspb.Status_DELETED + }) + // 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.Code == rspb.Status_DEPLOYED + }) + // 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.Code == rspb.Status_SUPERSEDED + }) + // 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 TestSecretCreate(t *testing.T) { + secrets := newTestFixtureSecrets(t) + + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + // 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 {%q}, got {%q}", rel, got) + } +} + +func TestSecretUpdate(t *testing.T) { + vers := int32(1) + name := "smug-pigeon" + namespace := "default" + key := testKey(name, vers) + rel := releaseStub(name, vers, namespace, rspb.Status_DEPLOYED) + + secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...) + + // modify release status code + rel.Info.Status.Code = rspb.Status_SUPERSEDED + + // 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.Code != got.Info.Status.Code { + t.Errorf("Expected status %s, got status %s", rel.Info.Status.Code, got.Info.Status.Code) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/util.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/util.go new file mode 100644 index 000000000..65fb17e7c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/driver/util.go @@ -0,0 +1,85 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage/driver" + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "io/ioutil" + + "github.com/golang/protobuf/proto" + rspb "k8s.io/helm/pkg/proto/hapi/release" +) + +var b64 = base64.StdEncoding + +var magicGzip = []byte{0x1f, 0x8b, 0x08} + +// encodeRelease encodes a release returning a base64 encoded +// gzipped binary protobuf encoding representation, or error. +func encodeRelease(rls *rspb.Release) (string, error) { + b, err := proto.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 in data into a release +// type. Data must contain a base64 encoded string of a +// valid protobuf encoding of a 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 bytes.Equal(b[0:3], magicGzip) { + r, err := gzip.NewReader(bytes.NewReader(b)) + if err != nil { + return nil, err + } + b2, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + b = b2 + } + + var rls rspb.Release + // unmarshal protobuf bytes + if err := proto.Unmarshal(b, &rls); err != nil { + return nil, err + } + return &rls, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/storage/storage.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/storage.go new file mode 100644 index 000000000..4b39e0bb2 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/storage.go @@ -0,0 +1,244 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage" + +import ( + "fmt" + "strings" + + rspb "k8s.io/helm/pkg/proto/hapi/release" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/storage/driver" +) + +// 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 int32) (*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 failed to store the +// release, or a release with identical an 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. + s.removeLeastRecent(rls.Name, s.MaxHistory-1) + } + return s.Driver.Create(makeKey(rls.Name, rls.Version), rls) +} + +// Update update 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 int32) (*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 }) +} + +// ListDeleted returns all releases with Status == DELETED. An error is returned +// if the storage backend fails to retrieve the releases. +func (s *Storage) ListDeleted() ([]*rspb.Release, error) { + s.Log("listing deleted releases in storage") + return s.Driver.List(func(rls *rspb.Release) bool { + return relutil.StatusFilter(rspb.Status_DELETED).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.Status_DEPLOYED).Check(rls) + }) +} + +// ListFilterAll returns the set of releases satisfying the predicate +// (filter0 && filter1 && ... && filterN), i.e. a Release is included in the results +// if and only if all filters return true. +func (s *Storage) ListFilterAll(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { + s.Log("listing all releases with filter") + return s.Driver.List(func(rls *rspb.Release) bool { + return relutil.All(fns...).Check(rls) + }) +} + +// ListFilterAny returns the set of releases satisfying the predicate +// (filter0 || filter1 || ... || filterN), i.e. a Release is included in the results +// if at least one of the filters returns true. +func (s *Storage) ListFilterAny(fns ...relutil.FilterFunc) ([]*rspb.Release, error) { + s.Log("listing any releases with filter") + return s.Driver.List(func(rls *rspb.Release) bool { + return relutil.Any(fns...).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 { + if strings.Contains(err.Error(), "not found") { + return nil, fmt.Errorf("%q has no deployed releases", name) + } + return nil, err + } + + if len(ls) == 0 { + return nil, fmt.Errorf("%q has no deployed releases", name) + } + + return ls[0], err +} + +// 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": "TILLER", + "STATUS": "DEPLOYED", + }) + if err == nil { + return ls, nil + } + if strings.Contains(err.Error(), "not found") { + return nil, fmt.Errorf("%q has no deployed releases", 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": "TILLER"}) +} + +// removeLeastRecent removes items from history until the lengh 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 + } + overage := len(h) - max + + // We want oldest to newest + relutil.SortByRevision(h) + + // Delete as many as possible. In the case of API throughput limitations, + // multiple invocations of this function will eventually delete them all. + toDelete := h[0:overage] + errors := []error{} + for _, rel := range toDelete { + key := makeKey(name, rel.Version) + _, innerErr := s.Delete(name, rel.Version) + if innerErr != nil { + s.Log("error pruning %s from release history: %s", key, innerErr) + errors = append(errors, innerErr) + } + } + + s.Log("Pruned %d record(s) from %s with %d error(s)", len(toDelete), name, len(errors)) + switch c := len(errors); c { + case 0: + return nil + case 1: + return errors[0] + default: + return fmt.Errorf("encountered %d deletion errors. First is: %s", c, errors[0]) + } +} + +// 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, fmt.Errorf("no revision for release %q", name) + } + + relutil.Reverse(h, relutil.SortByRevision) + return h[0], nil +} + +// makeKey concatenates a release name and version into +// a string with format ```#v```. +// This key is used to uniquely identify storage objects. +func makeKey(rlsname string, version int32) string { + return fmt.Sprintf("%s.v%d", 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/src/vendor/github.com/kubernetes/helm/pkg/storage/storage_test.go b/src/vendor/github.com/kubernetes/helm/pkg/storage/storage_test.go new file mode 100644 index 000000000..fb2824de7 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/storage/storage_test.go @@ -0,0 +1,350 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 "k8s.io/helm/pkg/storage" + +import ( + "fmt" + "reflect" + "testing" + + rspb "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/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 %q, got %q", 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.Status_DEPLOYED, + }.ToRelease() + + assertErrNil(t.Fatal, storage.Create(rls), "StoreRelease") + + // modify the release + rls.Info.Status.Code = rspb.Status_DELETED + 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 %q, got %q", 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 %q, got %q", 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.Status_SUPERSEDED}.ToRelease() + rls1 := ReleaseTestData{Name: "livid-human", Status: rspb.Status_SUPERSEDED}.ToRelease() + rls2 := ReleaseTestData{Name: "relaxed-cat", Status: rspb.Status_SUPERSEDED}.ToRelease() + rls3 := ReleaseTestData{Name: "hungry-hippo", Status: rspb.Status_DEPLOYED}.ToRelease() + rls4 := ReleaseTestData{Name: "angry-beaver", Status: rspb.Status_DEPLOYED}.ToRelease() + rls5 := ReleaseTestData{Name: "opulent-frog", Status: rspb.Status_DELETED}.ToRelease() + rls6 := ReleaseTestData{Name: "happy-liger", Status: rspb.Status_DELETED}.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) + }{ + {"ListDeleted", 2, storage.ListDeleted}, + {"ListDeployed", 2, storage.ListDeployed}, + {"ListReleases", 7, storage.ListReleases}, + } + + 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 = int32(4) + + // setup storage with test releases + setup := func() { + // release records + rls0 := ReleaseTestData{Name: name, Version: 1, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_DEPLOYED}.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.Code != rspb.Status_DEPLOYED: + t.Fatalf("Expected release status 'DEPLOYED', actual %s\n", rls.Info.Status.Code) + } +} + +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.Status_SUPERSEDED}.ToRelease() + rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_DEPLOYED}.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) + } +} + +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.Status_SUPERSEDED}.ToRelease() + rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_DEPLOYED}.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.Status_DEPLOYED}.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 := int(item.Version) + if expect := i + 3; v != expect { + t.Errorf("Expected release %d, got %d", expect, v) + } + } +} + +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.Status_SUPERSEDED}.ToRelease() + rls1 := ReleaseTestData{Name: name, Version: 2, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls2 := ReleaseTestData{Name: name, Version: 3, Status: rspb.Status_SUPERSEDED}.ToRelease() + rls3 := ReleaseTestData{Name: name, Version: 4, Status: rspb.Status_FAILED}.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) + } +} + +type ReleaseTestData struct { + Name string + Version int32 + Manifest string + Namespace string + Status rspb.Status_Code +} + +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: &rspb.Status{Code: 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/src/vendor/github.com/kubernetes/helm/pkg/strvals/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/strvals/doc.go new file mode 100644 index 000000000..d2b859e67 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/strvals/doc.go @@ -0,0 +1,32 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser.go b/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser.go new file mode 100644 index 000000000..8a16adf7e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser.go @@ -0,0 +1,340 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + "errors" + "fmt" + "io" + "strconv" + "strings" + + "github.com/ghodss/yaml" +) + +// ErrNotList indicates that a non-list was treated as a list. +var ErrNotList = errors.New("not a list") + +// 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 string(d), 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() +} + +// ParseIntoString parses a strvals line nad 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() +} + +// parser is a simple parser that takes a strvals line and parses it into a +// map representation. +type parser struct { + sc *bytes.Buffer + data map[string]interface{} + st bool +} + +func newParser(sc *bytes.Buffer, data map[string]interface{}, stringBool bool) *parser { + return &parser{sc: sc, data: data, st: stringBool} +} + +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{}) error { + stop := runeSet([]rune{'=', '[', ',', '.'}) + for { + switch k, last, err := runesUntil(t.sc, stop); { + case err != nil: + if len(k) == 0 { + return err + } + return fmt.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 fmt.Errorf("error parsing index: %s", err) + } + 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 == '=': + //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: + v, e := t.val() + set(data, string(k), typedVal(v, t.st)) + return e + default: + return e + } + + case last == ',': + // No value given. Set the value to empty string. Return error. + set(data, string(k), "") + return fmt.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 fmt.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{}) []interface{} { + if len(list) <= index { + newlist := make([]interface{}, index+1) + copy(newlist, list) + list = newlist + } + list[index] = val + return list +} + +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) { + stop := runeSet([]rune{'[', '.', '='}) + switch k, last, err := runesUntil(t.sc, stop); { + case len(k) > 0: + return list, fmt.Errorf("unexpected data at end of array index: %q", k) + case err != nil: + return list, err + case last == '=': + vl, e := t.valList() + switch e { + case nil: + return setIndex(list, i, vl), nil + case io.EOF: + return setIndex(list, i, ""), err + case ErrNotList: + v, e := t.val() + return setIndex(list, i, typedVal(v, t.st)), e + default: + return list, e + } + case last == '[': + // now we have a nested list. Read the index and handle. + i, err := t.keyIndex() + if err != nil { + return list, fmt.Errorf("error parsing index: %s", err) + } + // Now we need to get the value after the ]. + list2, err := t.listItem(list, i) + return setIndex(list, i, list2), err + case last == '.': + // We have a nested object. Send to t.key + inner := map[string]interface{}{} + if len(list) > i { + inner = list[i].(map[string]interface{}) + } + + // Recurse + e := t.key(inner) + return setIndex(list, i, inner), e + default: + return nil, fmt.Errorf("parse error: unexpected token %v", last) + } +} + +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 v, 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() + } + list = append(list, typedVal(v, t.st)) + return list, nil + case last == ',': + list = append(list, typedVal(v, t.st)) + } + } +} + +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 strings.EqualFold(val, "true") { + return true + } + + if strings.EqualFold(val, "false") { + return false + } + + // If this value does not start with zero, and not returnString, try parsing it to an int + if !st && len(val) != 0 && val[0] != '0' { + if iv, err := strconv.ParseInt(val, 10, 64); err == nil { + return iv + } + } + + return val +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser_test.go b/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser_test.go new file mode 100644 index 000000000..3f9828498 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/strvals/parser_test.go @@ -0,0 +1,372 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. +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" + + "github.com/ghodss/yaml" +) + +func TestSetIndex(t *testing.T) { + tests := []struct { + name string + initial []interface{} + expect []interface{} + add int + val int + }{ + { + name: "short", + initial: []interface{}{0, 1}, + expect: []interface{}{0, 1, 2}, + add: 2, + val: 2, + }, + { + name: "equal", + initial: []interface{}{0, 1}, + expect: []interface{}{0, 2}, + add: 1, + val: 2, + }, + { + name: "long", + initial: []interface{}{0, 1, 2, 3, 4, 5}, + expect: []interface{}{0, 1, 2, 4, 4, 5}, + add: 3, + val: 4, + }, + } + + for _, tt := range tests { + got := setIndex(tt.initial, tt.add, tt.val) + if len(got) != len(tt.expect) { + t.Fatalf("%s: Expected length %d, got %d", tt.name, len(tt.expect), len(got)) + } + + if gg := got[tt.add].(int); gg != tt.val { + t.Errorf("%s, Expected value %d, got %d", tt.name, tt.val, gg) + } + } +} + +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, + }, + } + tests := []struct { + str string + expect map[string]interface{} + err bool + }{ + { + "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: "long_int=1234567890", + expect: map[string]interface{}{"long_int": 1234567890}, + }, + { + 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,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: "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}}}, + }, + } + + 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) { + got := map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "overwrite", + "inner2": "value2", + }, + } + input := "outer.inner1=value1,outer.inner3=value3" + expect := map[string]interface{}{ + "outer": map[string]interface{}{ + "inner1": "value1", + "inner2": "value2", + "inner3": "value3", + }, + } + + if err := ParseInto(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 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\n" + if o != expect { + t.Errorf("Expected %q, got %q", expect, o) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk.go b/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk.go new file mode 100644 index 000000000..77fa04153 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk.go @@ -0,0 +1,115 @@ +/* +Copyright (c) for portions of walk.go are held by The Go Authors, 2009 and are provided under +the BSD license. + +https://github.com/golang/go/blob/master/LICENSE + +Copyright 2017 The Kubernetes Authors All rights reserved. +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 sympath + +import ( + "fmt" + "os" + "path/filepath" + "sort" +) + +// Walk walks the file tree rooted at root, calling walkFn for each file or directory +// in the tree, including root. All errors that arise visiting files and directories +// are filtered by walkFn. The files are walked in lexical order, which makes the +// output deterministic but means that for very large directories Walk can be +// inefficient. Walk follows symbolic links. +func Walk(root string, walkFn filepath.WalkFunc) error { + info, err := os.Lstat(root) + if err != nil { + err = walkFn(root, nil, err) + } else { + err = symwalk(root, info, walkFn) + } + if err == filepath.SkipDir { + return nil + } + return err +} + +// readDirNames reads the directory named by dirname and returns +// a sorted list of directory entries. +func readDirNames(dirname string) ([]string, error) { + f, err := os.Open(dirname) + if err != nil { + return nil, err + } + names, err := f.Readdirnames(-1) + f.Close() + if err != nil { + return nil, err + } + sort.Strings(names) + return names, nil +} + +// symwalk recursively descends path, calling walkFn. +func symwalk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { + // Recursively walk symlinked directories. + if IsSymlink(info) { + resolved, err := filepath.EvalSymlinks(path) + if err != nil { + return fmt.Errorf("error evaluating symlink %s: %s", path, err) + } + if info, err = os.Lstat(resolved); err != nil { + return err + } + if err := symwalk(resolved, info, walkFn); err != nil && err != filepath.SkipDir { + return err + } + } + + if err := walkFn(path, info, nil); err != nil { + return err + } + + if !info.IsDir() { + return nil + } + + names, err := readDirNames(path) + if err != nil { + return walkFn(path, info, err) + } + + for _, name := range names { + filename := filepath.Join(path, name) + fileInfo, err := os.Lstat(filename) + if err != nil { + if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { + return err + } + } else { + err = symwalk(filename, fileInfo, walkFn) + if err != nil { + if (!fileInfo.IsDir() && !IsSymlink(fileInfo)) || err != filepath.SkipDir { + return err + } + } + } + } + return nil +} + +// IsSymlink is used to determine if the fileinfo is a symbolic link. +func IsSymlink(fi os.FileInfo) bool { + return fi.Mode()&os.ModeSymlink != 0 +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk_test.go b/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk_test.go new file mode 100644 index 000000000..d86d8dabd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/sympath/walk_test.go @@ -0,0 +1,134 @@ +/* +Copyright (c) for portions of walk_test.go are held by The Go Authors, 2009 and are provided under +the BSD license. + +https://github.com/golang/go/blob/master/LICENSE + +Copyright 2017 The Kubernetes Authors All rights reserved. +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 sympath + +import ( + "os" + "path/filepath" + "testing" +) + +type Node struct { + name string + entries []*Node // nil if the entry is a file + mark int +} + +var tree = &Node{ + "testdata", + []*Node{ + {"a", nil, 0}, + {"b", []*Node{}, 0}, + {"c", nil, 0}, + { + "d", + []*Node{ + {"x", nil, 0}, + {"y", []*Node{}, 0}, + { + "z", + []*Node{ + {"u", nil, 0}, + {"v", nil, 0}, + {"w", nil, 0}, + }, + 0, + }, + }, + 0, + }, + }, + 0, +} + +func walkTree(n *Node, path string, f func(path string, n *Node)) { + f(path, n) + for _, e := range n.entries { + walkTree(e, filepath.Join(path, e.name), f) + } +} + +func makeTree(t *testing.T) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.entries == nil { + fd, err := os.Create(path) + if err != nil { + t.Errorf("makeTree: %v", err) + return + } + fd.Close() + } else { + os.Mkdir(path, 0770) + } + }) +} + +func checkMarks(t *testing.T, report bool) { + walkTree(tree, tree.name, func(path string, n *Node) { + if n.mark != 1 && report { + t.Errorf("node %s mark = %d; expected 1", path, n.mark) + } + n.mark = 0 + }) +} + +// Assumes that each node name is unique. Good enough for a test. +// If clear is true, any incoming error is cleared before return. The errors +// are always accumulated, though. +func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { + if err != nil { + *errors = append(*errors, err) + if clear { + return nil + } + return err + } + name := info.Name() + walkTree(tree, tree.name, func(path string, n *Node) { + if n.name == name { + n.mark++ + } + }) + return nil +} + +func TestWalk(t *testing.T) { + makeTree(t) + errors := make([]error, 0, 10) + clear := true + markFn := func(path string, info os.FileInfo, err error) error { + return mark(info, err, &errors, clear) + } + // Expect no errors. + err := Walk(tree.name, markFn) + if err != nil { + t.Fatalf("no error expected, found: %s", err) + } + if len(errors) != 0 { + t.Fatalf("unexpected errors: %s", errors) + } + checkMarks(t, true) + + // cleanup + if err := os.RemoveAll(tree.name); err != nil { + t.Errorf("removeTree: %v", err) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment.go new file mode 100644 index 000000000..366fdf522 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment.go @@ -0,0 +1,227 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 environment describes the operating environment for Tiller. + +Tiller's environment encapsulates all of the service dependencies Tiller has. +These dependencies are expressed as interfaces so that alternate implementations +(mocks, etc.) can be easily generated. +*/ +package environment + +import ( + "io" + "time" + + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl/resource" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/engine" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" +) + +// DefaultTillerNamespace is the default namespace for Tiller. +const DefaultTillerNamespace = "kube-system" + +// GoTplEngine is the name of the Go template engine, as registered in the EngineYard. +const GoTplEngine = "gotpl" + +// DefaultEngine points to the engine that the EngineYard should treat as the +// default. A chart that does not specify an engine may be run through the +// default engine. +var DefaultEngine = GoTplEngine + +// EngineYard maps engine names to engine implementations. +type EngineYard map[string]Engine + +// Get retrieves a template engine by name. +// +// If no matching template engine is found, the second return value will +// be false. +func (y EngineYard) Get(k string) (Engine, bool) { + e, ok := y[k] + return e, ok +} + +// Default returns the default template engine. +// +// The default is specified by DefaultEngine. +// +// If the default template engine cannot be found, this panics. +func (y EngineYard) Default() Engine { + d, ok := y[DefaultEngine] + if !ok { + // This is a developer error! + panic("Default template engine does not exist") + } + return d +} + +// Engine represents a template engine that can render templates. +// +// For some engines, "rendering" includes both compiling and executing. (Other +// engines do not distinguish between phases.) +// +// The engine returns a map where the key is the named output entity (usually +// a file name) and the value is the rendered content of the template. +// +// An Engine must be capable of executing multiple concurrent requests, but +// without tainting one request's environment with data from another request. +type Engine interface { + // Render renders a chart. + // + // It receives a chart, a config, and a map of overrides to the config. + // Overrides are assumed to be passed from the system, not the user. + Render(*chart.Chart, chartutil.Values) (map[string]string, error) +} + +// KubeClient represents a client capable of communicating with the Kubernetes API. +// +// A KubeClient must be concurrency safe. +type KubeClient interface { + // Create creates one or more resources. + // + // namespace must contain a valid existing namespace. + // + // reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n"). + Create(namespace string, reader io.Reader, timeout int64, shouldWait bool) error + + // Get gets one or more resources. Returned string hsa the format like kubectl + // provides with the column headers separating the resource types. + // + // namespace must contain a valid existing namespace. + // + // reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n"). + Get(namespace string, reader io.Reader) (string, error) + + // Delete destroys one or more resources. + // + // namespace must contain a valid existing namespace. + // + // reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n"). + Delete(namespace string, reader io.Reader) error + + // Watch the resource in reader until it is "ready". + // + // For Jobs, "ready" means the job ran to completion (excited without error). + // For all other kinds, it means the kind was created or modified without + // error. + WatchUntilReady(namespace string, reader io.Reader, timeout int64, shouldWait bool) error + + // Update updates one or more resources or creates the resource + // if it doesn't exist. + // + // namespace must contain a valid existing namespace. + // + // reader must contain a YAML stream (one or more YAML documents separated + // by "\n---\n"). + Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error + + Build(namespace string, reader io.Reader) (kube.Result, error) + BuildUnstructured(namespace string, reader io.Reader) (kube.Result, error) + + // WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase + // and returns said phase (PodSucceeded or PodFailed qualify). + WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) +} + +// PrintingKubeClient implements KubeClient, but simply prints the reader to +// the given output. +type PrintingKubeClient struct { + Out io.Writer +} + +// Create prints the values of what would be created with a real KubeClient. +func (p *PrintingKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { + _, err := io.Copy(p.Out, r) + return err +} + +// Get prints the values of what would be created with a real KubeClient. +func (p *PrintingKubeClient) Get(ns string, r io.Reader) (string, error) { + _, err := io.Copy(p.Out, r) + return "", err +} + +// Delete implements KubeClient delete. +// +// It only prints out the content to be deleted. +func (p *PrintingKubeClient) Delete(ns string, r io.Reader) error { + _, err := io.Copy(p.Out, r) + return err +} + +// WatchUntilReady implements KubeClient WatchUntilReady. +func (p *PrintingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { + _, err := io.Copy(p.Out, r) + return err +} + +// Update implements KubeClient Update. +func (p *PrintingKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { + _, err := io.Copy(p.Out, modifiedReader) + return err +} + +// Build implements KubeClient Build. +func (p *PrintingKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} + +// BuildUnstructured implements KubeClient BuildUnstructured. +func (p *PrintingKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} + +// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase. +func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { + _, err := io.Copy(p.Out, reader) + return core.PodUnknown, err +} + +// Environment provides the context for executing a client request. +// +// All services in a context are concurrency safe. +type Environment struct { + // EngineYard provides access to the known template engines. + EngineYard EngineYard + // Releases stores records of releases. + Releases *storage.Storage + // KubeClient is a Kubernetes API client. + KubeClient KubeClient +} + +// New returns an environment initialized with the defaults. +func New() *Environment { + e := engine.New() + var ey EngineYard = map[string]Engine{ + // Currently, the only template engine we support is the GoTpl one. But + // we can easily add some here. + GoTplEngine: e, + } + + return &Environment{ + EngineYard: ey, + Releases: storage.Init(driver.NewMemory()), + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment_test.go new file mode 100644 index 000000000..d8c82b901 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/environment/environment_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 environment + +import ( + "bytes" + "io" + "testing" + "time" + + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/kubectl/resource" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/chart" +) + +type mockEngine struct { + out map[string]string +} + +func (e *mockEngine) Render(chrt *chart.Chart, v chartutil.Values) (map[string]string, error) { + return e.out, nil +} + +type mockKubeClient struct{} + +func (k *mockKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { + return nil +} +func (k *mockKubeClient) Get(ns string, r io.Reader) (string, error) { + return "", nil +} +func (k *mockKubeClient) Delete(ns string, r io.Reader) error { + return nil +} +func (k *mockKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { + return nil +} +func (k *mockKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { + return nil +} +func (k *mockKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} +func (k *mockKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} +func (k *mockKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { + return core.PodUnknown, nil +} + +func (k *mockKubeClient) WaitAndGetCompletedPodStatus(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { + return "", nil +} + +var _ Engine = &mockEngine{} +var _ KubeClient = &mockKubeClient{} +var _ KubeClient = &PrintingKubeClient{} + +func TestEngine(t *testing.T) { + eng := &mockEngine{out: map[string]string{"albatross": "test"}} + + env := New() + env.EngineYard = EngineYard(map[string]Engine{"test": eng}) + + if engine, ok := env.EngineYard.Get("test"); !ok { + t.Errorf("failed to get engine from EngineYard") + } else if out, err := engine.Render(&chart.Chart{}, map[string]interface{}{}); err != nil { + t.Errorf("unexpected template error: %s", err) + } else if out["albatross"] != "test" { + t.Errorf("expected 'test', got %q", out["albatross"]) + } +} + +func TestKubeClient(t *testing.T) { + kc := &mockKubeClient{} + env := New() + env.KubeClient = kc + + manifests := map[string]string{ + "foo": "name: value\n", + "bar": "name: value\n", + } + + b := bytes.NewBuffer(nil) + for _, content := range manifests { + b.WriteString("\n---\n") + b.WriteString(content) + } + + if err := env.KubeClient.Create("sharry-bobbins", b, 300, false); err != nil { + t.Errorf("Kubeclient failed: %s", err) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter.go new file mode 100644 index 000000000..42d546620 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter.go @@ -0,0 +1,53 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "sort" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +// sortByHookWeight does an in-place sort of hooks by their supplied weight. +func sortByHookWeight(hooks []*release.Hook) []*release.Hook { + hs := newHookWeightSorter(hooks) + sort.Sort(hs) + return hs.hooks +} + +type hookWeightSorter struct { + hooks []*release.Hook +} + +func newHookWeightSorter(h []*release.Hook) *hookWeightSorter { + return &hookWeightSorter{ + hooks: h, + } +} + +func (hs *hookWeightSorter) Len() int { return len(hs.hooks) } + +func (hs *hookWeightSorter) Swap(i, j int) { + hs.hooks[i], hs.hooks[j] = hs.hooks[j], hs.hooks[i] +} + +func (hs *hookWeightSorter) Less(i, j int) bool { + if hs.hooks[i].Weight == hs.hooks[j].Weight { + return hs.hooks[i].Name < hs.hooks[j].Name + } + return hs.hooks[i].Weight < hs.hooks[j].Weight +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter_test.go new file mode 100644 index 000000000..ac5b9bf8d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hook_sorter_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" +) + +func TestHookSorter(t *testing.T) { + hooks := []*release.Hook{ + { + Name: "g", + Kind: "pre-install", + Weight: 99, + }, + { + Name: "f", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "b", + Kind: "pre-install", + Weight: -3, + }, + { + Name: "e", + Kind: "pre-install", + Weight: 3, + }, + { + Name: "a", + Kind: "pre-install", + Weight: -10, + }, + { + Name: "c", + Kind: "pre-install", + Weight: 0, + }, + { + Name: "d", + Kind: "pre-install", + Weight: 3, + }, + } + + res := sortByHookWeight(hooks) + got := "" + expect := "abcdefg" + for _, r := range res { + got += r.Name + } + if got != expect { + t.Errorf("Expected %q, got %q", expect, got) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks.go new file mode 100644 index 000000000..e1e965d08 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks.go @@ -0,0 +1,233 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "log" + "path" + "strconv" + "strings" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" +) + +var events = map[string]release.Hook_Event{ + hooks.PreInstall: release.Hook_PRE_INSTALL, + hooks.PostInstall: release.Hook_POST_INSTALL, + hooks.PreDelete: release.Hook_PRE_DELETE, + hooks.PostDelete: release.Hook_POST_DELETE, + hooks.PreUpgrade: release.Hook_PRE_UPGRADE, + hooks.PostUpgrade: release.Hook_POST_UPGRADE, + hooks.PreRollback: release.Hook_PRE_ROLLBACK, + hooks.PostRollback: release.Hook_POST_ROLLBACK, + hooks.ReleaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS, + hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE, +} + +// deletePolices represents a mapping between the key in the annotation for label deleting policy and its real meaning +var deletePolices = map[string]release.Hook_DeletePolicy{ + hooks.HookSucceeded: release.Hook_SUCCEEDED, + hooks.HookFailed: release.Hook_FAILED, + hooks.BeforeHookCreation: release.Hook_BEFORE_HOOK_CREATION, +} + +// Manifest represents a manifest file, which has a name and some content. +type Manifest struct { + Name string + Content string + Head *util.SimpleHead +} + +type result struct { + hooks []*release.Hook + generic []Manifest +} + +type manifestFile struct { + entries map[string]string + path string + apis chartutil.VersionSet +} + +// 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, sort SortOrder) ([]*release.Hook, []Manifest, error) { + result := &result{} + + for filePath, c := range files { + + // 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 len(strings.TrimSpace(c)) == 0 { + log.Printf("info: manifest %q is empty. Skipping.", filePath) + continue + } + + manifestFile := &manifestFile{ + entries: util.SplitManifests(c), + path: filePath, + apis: apis, + } + + if err := manifestFile.sort(result); err != nil { + return result.hooks, result.generic, err + } + } + + return result.hooks, sortByKind(result.generic, sort), 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 { + for _, m := range file.entries { + var entry util.SimpleHead + err := yaml.Unmarshal([]byte(m), &entry) + + if err != nil { + e := fmt.Errorf("YAML parse error on %s: %s", file.path, err) + return e + } + + if entry.Version != "" && !file.apis.Has(entry.Version) { + return fmt.Errorf("apiVersion %q in %s is not available", entry.Version, file.path) + } + + if !hasAnyAnnotation(entry) { + result.generic = append(result.generic, Manifest{ + Name: file.path, + Content: m, + Head: &entry, + }) + continue + } + + hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno] + 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.Hook_Event{}, + Weight: hw, + DeletePolicies: []release.Hook_DeletePolicy{}, + } + + 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, hooks.HookDeleteAnno, func(value string) { + policy, exist := deletePolices[value] + if exist { + h.DeletePolicies = append(h.DeletePolicies, policy) + } else { + log.Printf("info: skipping unknown hook delete policy: %q", value) + } + }) + } + + return nil +} + +func hasAnyAnnotation(entry util.SimpleHead) bool { + if entry.Metadata == nil || + entry.Metadata.Annotations == nil || + len(entry.Metadata.Annotations) == 0 { + return false + } + + return true +} + +func calculateHookWeight(entry util.SimpleHead) int32 { + hws := entry.Metadata.Annotations[hooks.HookWeightAnno] + hw, err := strconv.Atoi(hws) + if err != nil { + hw = 0 + } + + return int32(hw) +} + +func operateAnnotationValues(entry util.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/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks_test.go new file mode 100644 index 000000000..658f859f4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/hooks_test.go @@ -0,0 +1,246 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "reflect" + "testing" + + "github.com/ghodss/yaml" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/proto/hapi/release" + util "k8s.io/helm/pkg/releaseutil" +) + +func TestSortManifests(t *testing.T) { + + data := []struct { + name []string + path string + kind []string + hooks map[string][]release.Hook_Event + manifest string + }{ + { + name: []string{"first"}, + path: "one", + kind: []string{"Job"}, + hooks: map[string][]release.Hook_Event{"first": {release.Hook_PRE_INSTALL}}, + 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.Hook_Event{"second": {release.Hook_POST_INSTALL}}, + 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.Hook_Event{"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.Hook_Event{"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.Hook_Event{"fifth": {release.Hook_POST_DELETE, release.Hook_POST_INSTALL}}, + 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.Hook_Event{"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.Hook_Event{"seventh": nil}, + manifest: "", + }, + { + name: []string{"eighth", "example-test"}, + path: "eight", + kind: []string{"ConfigMap", "Pod"}, + hooks: map[string][]release.Hook_Event{"eighth": nil, "example-test": {release.Hook_RELEASE_TEST_SUCCESS}}, + manifest: `kind: ConfigMap +apiVersion: v1 +metadata: + name: eighth +data: + name: value +--- +apiVersion: v1 +kind: Pod +metadata: + name: example-test + annotations: + "helm.sh/hook": test-success +`, + }, + } + + manifests := make(map[string]string, len(data)) + for _, o := range data { + manifests[o.path] = o.manifest + } + + hs, generic, err := sortManifests(manifests, chartutil.NewVersionSet("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 := util.SplitManifests(s.manifest) + + for _, m := range manifests { + var sh util.SimpleHead + err := yaml.Unmarshal([]byte(m), &sh) + if 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 err == nil && s.hooks[name] == nil { + another := Manifest{ + Content: m, + Name: name, + Head: &sh, + } + sorted = append(sorted, another) + } + } + } + + sorted = sortByKind(sorted, InstallOrder) + for i, m := range generic { + if m.Content != sorted[i].Content { + t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content) + } + } +} + +func TestVersionSet(t *testing.T) { + vs := chartutil.NewVersionSet("v1", "v1beta1", "extensions/alpha5", "batch/v1") + + if l := len(vs); l != 4 { + t.Errorf("Expected 4, got %d", l) + } + + if !vs.Has("extensions/alpha5") { + t.Error("No match for alpha5") + } + + if vs.Has("nosuch/extension") { + t.Error("Found nonexistent extension") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter.go new file mode 100644 index 000000000..f367e65c8 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter.go @@ -0,0 +1,148 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "sort" +) + +// SortOrder is an ordering of Kinds. +type SortOrder []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 SortOrder = []string{ + "Namespace", + "ResourceQuota", + "LimitRange", + "Secret", + "ConfigMap", + "StorageClass", + "PersistentVolume", + "PersistentVolumeClaim", + "ServiceAccount", + "CustomResourceDefinition", + "ClusterRole", + "ClusterRoleBinding", + "Role", + "RoleBinding", + "Service", + "DaemonSet", + "Pod", + "ReplicationController", + "ReplicaSet", + "Deployment", + "StatefulSet", + "Job", + "CronJob", + "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 SortOrder = []string{ + "APIService", + "Ingress", + "Service", + "CronJob", + "Job", + "StatefulSet", + "Deployment", + "ReplicaSet", + "ReplicationController", + "Pod", + "DaemonSet", + "RoleBinding", + "Role", + "ClusterRoleBinding", + "ClusterRole", + "CustomResourceDefinition", + "ServiceAccount", + "PersistentVolumeClaim", + "PersistentVolume", + "StorageClass", + "ConfigMap", + "Secret", + "LimitRange", + "ResourceQuota", + "Namespace", +} + +// sortByKind does an in-place sort of manifests by Kind. +// +// Results are sorted by 'ordering' +func sortByKind(manifests []Manifest, ordering SortOrder) []Manifest { + ks := newKindSorter(manifests, ordering) + sort.Sort(ks) + return ks.manifests +} + +type kindSorter struct { + ordering map[string]int + manifests []Manifest +} + +func newKindSorter(m []Manifest, s SortOrder) *kindSorter { + o := make(map[string]int, len(s)) + for v, k := range s { + o[k] = v + } + + return &kindSorter{ + manifests: m, + ordering: o, + } +} + +func (k *kindSorter) Len() int { return len(k.manifests) } + +func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] } + +func (k *kindSorter) Less(i, j int) bool { + a := k.manifests[i] + b := k.manifests[j] + first, aok := k.ordering[a.Head.Kind] + second, bok := k.ordering[b.Head.Kind] + // if same kind (including unknown) sub sort alphanumeric + if first == second { + // if both are unknown and of different kind sort by kind alphabetically + if !aok && !bok && a.Head.Kind != b.Head.Kind { + return a.Head.Kind < b.Head.Kind + } + return a.Name < b.Name + } + // unknown kind is last + if !aok { + return false + } + if !bok { + return true + } + // sort different kinds + return first < second +} + +// SortByKind sorts manifests in InstallOrder +func SortByKind(manifests []Manifest) []Manifest { + ordering := InstallOrder + ks := newKindSorter(manifests, ordering) + sort.Sort(ks) + return ks.manifests +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter_test.go new file mode 100644 index 000000000..ef7296e89 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/kind_sorter_test.go @@ -0,0 +1,217 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "bytes" + "testing" + + util "k8s.io/helm/pkg/releaseutil" +) + +func TestKindSorter(t *testing.T) { + manifests := []Manifest{ + { + Name: "i", + Head: &util.SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "j", + Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "e", + Head: &util.SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "u", + Head: &util.SimpleHead{Kind: "CronJob"}, + }, + { + Name: "2", + Head: &util.SimpleHead{Kind: "CustomResourceDefinition"}, + }, + { + Name: "n", + Head: &util.SimpleHead{Kind: "DaemonSet"}, + }, + { + Name: "r", + Head: &util.SimpleHead{Kind: "Deployment"}, + }, + { + Name: "!", + Head: &util.SimpleHead{Kind: "HonkyTonkSet"}, + }, + { + Name: "v", + Head: &util.SimpleHead{Kind: "Ingress"}, + }, + { + Name: "t", + Head: &util.SimpleHead{Kind: "Job"}, + }, + { + Name: "c", + Head: &util.SimpleHead{Kind: "LimitRange"}, + }, + { + Name: "a", + Head: &util.SimpleHead{Kind: "Namespace"}, + }, + { + Name: "f", + Head: &util.SimpleHead{Kind: "PersistentVolume"}, + }, + { + Name: "g", + Head: &util.SimpleHead{Kind: "PersistentVolumeClaim"}, + }, + { + Name: "o", + Head: &util.SimpleHead{Kind: "Pod"}, + }, + { + Name: "q", + Head: &util.SimpleHead{Kind: "ReplicaSet"}, + }, + { + Name: "p", + Head: &util.SimpleHead{Kind: "ReplicationController"}, + }, + { + Name: "b", + Head: &util.SimpleHead{Kind: "ResourceQuota"}, + }, + { + Name: "k", + Head: &util.SimpleHead{Kind: "Role"}, + }, + { + Name: "l", + Head: &util.SimpleHead{Kind: "RoleBinding"}, + }, + { + Name: "d", + Head: &util.SimpleHead{Kind: "Secret"}, + }, + { + Name: "m", + Head: &util.SimpleHead{Kind: "Service"}, + }, + { + Name: "h", + Head: &util.SimpleHead{Kind: "ServiceAccount"}, + }, + { + Name: "s", + Head: &util.SimpleHead{Kind: "StatefulSet"}, + }, + { + Name: "1", + Head: &util.SimpleHead{Kind: "StorageClass"}, + }, + { + Name: "w", + Head: &util.SimpleHead{Kind: "APIService"}, + }, + } + + for _, test := range []struct { + description string + order SortOrder + expected string + }{ + {"install", InstallOrder, "abcde1fgh2ijklmnopqrstuvw!"}, + {"uninstall", UninstallOrder, "wvmutsrqponlkji2hgf1edcba!"}, + } { + 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() + for _, r := range sortByKind(manifests, test.order) { + buf.WriteString(r.Name) + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + }) + } +} + +// TestKindSorterSubSort verifies manifests of same kind are also sorted alphanumeric +func TestKindSorterSubSort(t *testing.T) { + manifests := []Manifest{ + { + Name: "a", + Head: &util.SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "A", + Head: &util.SimpleHead{Kind: "ClusterRole"}, + }, + { + Name: "0", + Head: &util.SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "1", + Head: &util.SimpleHead{Kind: "ConfigMap"}, + }, + { + Name: "z", + Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "!", + Head: &util.SimpleHead{Kind: "ClusterRoleBinding"}, + }, + { + Name: "u2", + Head: &util.SimpleHead{Kind: "Unknown"}, + }, + { + Name: "u1", + Head: &util.SimpleHead{Kind: "Unknown"}, + }, + { + Name: "t3", + Head: &util.SimpleHead{Kind: "Unknown2"}, + }, + } + for _, test := range []struct { + description string + order SortOrder + expected string + }{ + // expectation is sorted by kind (unknown is last) and then sub sorted alphabetically within each group + {"cm,clusterRole,clusterRoleBinding,Unknown,Unknown2", InstallOrder, "01Aa!zu1u2t3"}, + } { + var buf bytes.Buffer + t.Run(test.description, func(t *testing.T) { + defer buf.Reset() + for _, r := range sortByKind(manifests, test.order) { + buf.WriteString(r.Name) + } + if got := buf.String(); got != test.expected { + t.Errorf("Expected %q, got %q", test.expected, got) + } + }) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content.go new file mode 100644 index 000000000..fd783d6b6 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content.go @@ -0,0 +1,39 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/proto/hapi/services" +) + +// GetReleaseContent gets all of the stored information for the given release. +func (s *ReleaseServer) GetReleaseContent(c ctx.Context, req *services.GetReleaseContentRequest) (*services.GetReleaseContentResponse, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("releaseContent: Release name is invalid: %s", req.Name) + return nil, err + } + + if req.Version <= 0 { + rel, err := s.env.Releases.Last(req.Name) + return &services.GetReleaseContentResponse{Release: rel}, err + } + + rel, err := s.env.Releases.Get(req.Name, req.Version) + return &services.GetReleaseContentResponse{Release: rel}, err +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content_test.go new file mode 100644 index 000000000..7c003f709 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_content_test.go @@ -0,0 +1,42 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestGetReleaseContent(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseContent(c, &services.GetReleaseContentRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Release.Chart.Metadata.Name != rel.Chart.Metadata.Name { + t.Errorf("Expected %q, got %q", rel.Chart.Metadata.Name, res.Release.Chart.Metadata.Name) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history.go new file mode 100644 index 000000000..0dd525978 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "golang.org/x/net/context" + + tpb "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" +) + +// GetHistory gets the history for a given release. +func (s *ReleaseServer) GetHistory(ctx context.Context, req *tpb.GetHistoryRequest) (*tpb.GetHistoryResponse, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("getHistory: Release name is invalid: %s", req.Name) + return nil, err + } + + s.Log("getting history for release %s", req.Name) + h, err := s.env.Releases.History(req.Name) + if err != nil { + return nil, err + } + + relutil.Reverse(h, relutil.SortByRevision) + + var resp tpb.GetHistoryResponse + for i := 0; i < min(len(h), int(req.Max)); i++ { + resp.Releases = append(resp.Releases, h[i]) + } + + return &resp, nil +} + +func min(x, y int) int { + if x < y { + return x + } + return y +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history_test.go new file mode 100644 index 000000000..5df98410f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_history_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "reflect" + "testing" + + "k8s.io/helm/pkg/helm" + rpb "k8s.io/helm/pkg/proto/hapi/release" + tpb "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestGetHistory_WithRevisions(t *testing.T) { + mk := func(name string, vers int32, code rpb.Status_Code) *rpb.Release { + return &rpb.Release{ + Name: name, + Version: vers, + Info: &rpb.Info{Status: &rpb.Status{Code: code}}, + } + } + + // GetReleaseHistoryTests + tests := []struct { + desc string + req *tpb.GetHistoryRequest + res *tpb.GetHistoryResponse + }{ + { + desc: "get release with history and default limit (max=256)", + req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 256}, + res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + mk("angry-bird", 2, rpb.Status_SUPERSEDED), + mk("angry-bird", 1, rpb.Status_SUPERSEDED), + }}, + }, + { + desc: "get release with history using result limit (max=2)", + req: &tpb.GetHistoryRequest{Name: "angry-bird", Max: 2}, + res: &tpb.GetHistoryResponse{Releases: []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + }}, + }, + } + + // test release history for release 'angry-bird' + hist := []*rpb.Release{ + mk("angry-bird", 4, rpb.Status_DEPLOYED), + mk("angry-bird", 3, rpb.Status_SUPERSEDED), + mk("angry-bird", 2, rpb.Status_SUPERSEDED), + mk("angry-bird", 1, rpb.Status_SUPERSEDED), + } + + srv := rsFixture() + for _, rls := range hist { + if err := srv.env.Releases.Create(rls); err != nil { + t.Fatalf("Failed to create release: %s", err) + } + } + + // run tests + for _, tt := range tests { + res, err := srv.GetHistory(helm.NewContext(), tt.req) + if err != nil { + t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) + } + if !reflect.DeepEqual(res, tt.res) { + t.Fatalf("%s:\nExpected:\n\t%+v\nActual\n\t%+v", tt.desc, tt.res, res) + } + } +} + +func TestGetHistory_WithNoRevisions(t *testing.T) { + tests := []struct { + desc string + req *tpb.GetHistoryRequest + }{ + { + desc: "get release with no history", + req: &tpb.GetHistoryRequest{Name: "sad-panda", Max: 256}, + }, + } + + // create release 'sad-panda' with no revision history + rls := namedReleaseStub("sad-panda", rpb.Status_DEPLOYED) + srv := rsFixture() + srv.env.Releases.Create(rls) + + for _, tt := range tests { + res, err := srv.GetHistory(helm.NewContext(), tt.req) + if err != nil { + t.Fatalf("%s:\nFailed to get History of %q: %s", tt.desc, tt.req.Name, err) + } + if len(res.Releases) > 1 { + t.Fatalf("%s:\nExpected zero items, got %d", tt.desc, len(res.Releases)) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install.go new file mode 100644 index 000000000..8e7fd3acd --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install.go @@ -0,0 +1,225 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "strings" + + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +// InstallRelease installs a release and stores the release record. +func (s *ReleaseServer) InstallRelease(c ctx.Context, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { + s.Log("preparing install for %s", req.Name) + rel, err := s.prepareRelease(req) + if err != nil { + s.Log("failed install prepare step: %s", err) + res := &services.InstallReleaseResponse{Release: rel} + + // On dry run, append the manifest contents to a failed release. This is + // a stop-gap until we can revisit an error backchannel post-2.0. + if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { + err = fmt.Errorf("%s\n%s", err, rel.Manifest) + } + return res, err + } + + s.Log("performing install for %s", req.Name) + res, err := s.performRelease(rel, req) + if err != nil { + s.Log("failed install perform step: %s", err) + } + return res, err +} + +// prepareRelease builds a release for an install operation. +func (s *ReleaseServer) prepareRelease(req *services.InstallReleaseRequest) (*release.Release, error) { + if req.Chart == nil { + return nil, errMissingChart + } + + name, err := s.uniqName(req.Name, req.ReuseName) + if err != nil { + return nil, err + } + + caps, err := capabilities(s.clientset.Discovery()) + if err != nil { + return nil, err + } + + revision := 1 + ts := timeconv.Now() + options := chartutil.ReleaseOptions{ + Name: name, + Time: ts, + Namespace: req.Namespace, + Revision: revision, + IsInstall: true, + } + valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + if err != nil { + return nil, err + } + + hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + if err != nil { + // Return a release with partial data so that client can show debugging + // information. + rel := &release.Release{ + Name: name, + Namespace: req.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_UNKNOWN}, + Description: fmt.Sprintf("Install failed: %s", err), + }, + Version: 0, + } + if manifestDoc != nil { + rel.Manifest = manifestDoc.String() + } + return rel, err + } + + // Store a release. + rel := &release.Release{ + Name: name, + Namespace: req.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: ts, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_PENDING_INSTALL}, + Description: "Initial install underway", // Will be overwritten. + }, + Manifest: manifestDoc.String(), + Hooks: hooks, + Version: int32(revision), + } + if len(notesTxt) > 0 { + rel.Info.Status.Notes = notesTxt + } + + err = validateManifest(s.env.KubeClient, req.Namespace, manifestDoc.Bytes()) + return rel, err +} + +// performRelease runs a release. +func (s *ReleaseServer) performRelease(r *release.Release, req *services.InstallReleaseRequest) (*services.InstallReleaseResponse, error) { + res := &services.InstallReleaseResponse{Release: r} + + if req.DryRun { + s.Log("dry run for %s", r.Name) + res.Release.Info.Description = "Dry run complete" + return res, nil + } + + // pre-install hooks + if !req.DisableHooks { + if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PreInstall, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("install hooks disabled for %s", req.Name) + } + + switch h, err := s.env.Releases.History(req.Name); { + // if this is a replace operation, append to the release history + case req.ReuseName && err == nil && len(h) >= 1: + s.Log("name reuse for %s requested, replacing release", req.Name) + // get latest release revision + relutil.Reverse(h, relutil.SortByRevision) + + // old release + old := h[0] + + // update old release status + old.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(old, true) + + // update new release with next revision number + // so as to append to the old release's history + r.Version = old.Version + 1 + updateReq := &services.UpdateReleaseRequest{ + Wait: req.Wait, + Recreate: false, + Timeout: req.Timeout, + } + s.recordRelease(r, false) + if err := s.ReleaseModule.Update(old, r, updateReq, s.env); err != nil { + msg := fmt.Sprintf("Release replace %q failed: %s", r.Name, err) + s.Log("warning: %s", msg) + old.Info.Status.Code = release.Status_SUPERSEDED + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(old, true) + s.recordRelease(r, true) + return res, err + } + + default: + // nothing to replace, create as normal + // regular manifests + s.recordRelease(r, false) + if err := s.ReleaseModule.Create(r, req, s.env); err != nil { + msg := fmt.Sprintf("Release %q failed: %s", r.Name, err) + s.Log("warning: %s", msg) + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(r, true) + return res, fmt.Errorf("release %s failed: %s", r.Name, err) + } + } + + // post-install hooks + if !req.DisableHooks { + if err := s.execHook(r.Hooks, r.Name, r.Namespace, hooks.PostInstall, req.Timeout); err != nil { + msg := fmt.Sprintf("Release %q failed post-install: %s", r.Name, err) + s.Log("warning: %s", msg) + r.Info.Status.Code = release.Status_FAILED + r.Info.Description = msg + s.recordRelease(r, true) + return res, err + } + } + + r.Info.Status.Code = release.Status_DEPLOYED + r.Info.Description = "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. + s.recordRelease(r, true) + + return res, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install_test.go new file mode 100644 index 000000000..793a36e94 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_install_test.go @@ -0,0 +1,500 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "strings" + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/version" +) + +func TestInstallRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_WithNotes(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText)}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + if rel.Info.Status.Notes != notesText { + t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_WithNotesRendered(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText + " {{.Release.Name}}")}, + }, + }, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + if res.Release.Namespace != "spaced" { + t.Errorf("Expected release namespace 'spaced', got '%s'.", res.Release.Namespace) + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if len(rel.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(rel.Hooks)) + } + if rel.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", rel.Hooks[0].Manifest) + } + + expectedNotes := fmt.Sprintf("%s %s", notesText, res.Release.Name) + if rel.Info.Status.Notes != expectedNotes { + t.Fatalf("Expected '%s', got '%s'", expectedNotes, rel.Info.Status.Notes) + } + + if rel.Hooks[0].Events[0] != release.Hook_POST_INSTALL { + t.Errorf("Expected event 0 is post install") + } + if rel.Hooks[0].Events[1] != release.Hook_PRE_DELETE { + t.Errorf("Expected event 0 is pre-delete") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(rel.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(rel.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_TillerVersion(t *testing.T) { + version.Version = "2.2.0" + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", TillerVersion: ">=2.2.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Expected valid range. Got %q", err) + } +} + +func TestInstallRelease_WrongTillerVersion(t *testing.T) { + version.Version = "2.2.0" + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", TillerVersion: "<2.0.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err == nil { + t.Fatalf("Expected to fail because of wrong version") + } + + expect := "Chart incompatible with Tiller" + if !strings.Contains(err.Error(), expect) { + t.Errorf("Expected %q to contain %q", err.Error(), expect) + } +} + +func TestInstallRelease_WithChartAndDependencyNotes(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText)}, + }, + Dependencies: []*chart.Chart{ + { + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + {Name: "templates/NOTES.txt", Data: []byte(notesText + " child")}, + }, + }, + }, + }, + } + + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + rel, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + t.Logf("rel: %v", rel) + + if rel.Info.Status.Notes != notesText { + t.Fatalf("Expected '%s', got '%s'", notesText, rel.Info.Status.Notes) + } + + if rel.Info.Description != "Install complete" { + t.Errorf("unexpected description: %s", rel.Info.Description) + } +} + +func TestInstallRelease_DryRun(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + DryRun: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "---\n# Source: hello/templates/goodbye\ngoodbye: world") { + t.Errorf("unexpected output: %s", res.Release.Manifest) + } + + if !strings.Contains(res.Release.Manifest, "hello: Earth") { + t.Errorf("Should contain partial content. %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "hello: {{ template \"_planet\" . }}") { + t.Errorf("Should not contain partial templates itself. %s", res.Release.Manifest) + } + + if strings.Contains(res.Release.Manifest, "empty") { + t.Errorf("Should not contain template data for an empty file. %s", res.Release.Manifest) + } + + if _, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version); err == nil { + t.Errorf("Expected no stored release.") + } + + if l := len(res.Release.Hooks); l != 1 { + t.Fatalf("Expected 1 hook, got %d", l) + } + + if res.Release.Hooks[0].LastRun != nil { + t.Error("Expected hook to not be marked as run.") + } + + if res.Release.Info.Description != "Dry run complete" { + t.Errorf("unexpected description: %s", res.Release.Info.Description) + } +} + +func TestInstallRelease_NoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + DisableHooks: true, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Errorf("Failed install: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } +} + +func TestInstallRelease_FailedHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + rs.env.KubeClient = newHookFailingKubeClient() + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + } + res, err := rs.InstallRelease(c, req) + if err == nil { + t.Error("Expected failed install") + } + + if hl := res.Release.Info.Status.Code; hl != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %d", hl) + } +} + +func TestInstallRelease_ReuseName(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Info.Status.Code = release.Status_DELETED + rs.env.Releases.Create(rel) + + req := &services.InstallReleaseRequest{ + Chart: chartStub(), + ReuseName: true, + Name: rel.Name, + } + res, err := rs.InstallRelease(c, req) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + if res.Release.Name != rel.Name { + t.Errorf("expected %q, got %q", rel.Name, res.Release.Name) + } + + getreq := &services.GetReleaseStatusRequest{Name: rel.Name, Version: 0} + getres, err := rs.GetReleaseStatus(c, getreq) + if err != nil { + t.Errorf("Failed to retrieve release: %s", err) + } + if getres.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Release status is %q", getres.Info.Status.Code) + } +} + +func TestInstallRelease_KubeVersion(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", KubeVersion: ">=0.0.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + fmt.Println(err) + if err != nil { + t.Fatalf("Expected valid range. Got %q", err) + } +} + +func TestInstallRelease_WrongKubeVersion(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + // TODO: Refactor this into a mock. + req := &services.InstallReleaseRequest{ + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello", KubeVersion: ">=5.0.0"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + }, + } + _, err := rs.InstallRelease(c, req) + if err == nil { + t.Fatalf("Expected to fail because of wrong version") + } + + expect := "Chart requires kubernetesVersion" + if !strings.Contains(err.Error(), expect) { + t.Errorf("Expected %q to contain %q", err.Error(), expect) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list.go new file mode 100644 index 000000000..72c21d97c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list.go @@ -0,0 +1,178 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "github.com/golang/protobuf/proto" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "regexp" +) + +// ListReleases lists the releases found by the server. +func (s *ReleaseServer) ListReleases(req *services.ListReleasesRequest, stream services.ReleaseService_ListReleasesServer) error { + if len(req.StatusCodes) == 0 { + req.StatusCodes = []release.Status_Code{release.Status_DEPLOYED} + } + + //rels, err := s.env.Releases.ListDeployed() + rels, err := s.env.Releases.ListFilterAll(func(r *release.Release) bool { + for _, sc := range req.StatusCodes { + if sc == r.Info.Status.Code { + return true + } + } + return false + }) + if err != nil { + return err + } + + if req.Namespace != "" { + rels, err = filterByNamespace(req.Namespace, rels) + if err != nil { + return err + } + } + + if len(req.Filter) != 0 { + rels, err = filterReleases(req.Filter, rels) + if err != nil { + return err + } + } + + total := int64(len(rels)) + + switch req.SortBy { + case services.ListSort_NAME: + relutil.SortByName(rels) + case services.ListSort_LAST_RELEASED: + relutil.SortByDate(rels) + } + + if req.SortOrder == services.ListSort_DESC { + ll := len(rels) + rr := make([]*release.Release, ll) + for i, item := range rels { + rr[ll-i-1] = item + } + rels = rr + } + + l := int64(len(rels)) + if req.Offset != "" { + + i := -1 + for ii, cur := range rels { + if cur.Name == req.Offset { + i = ii + } + } + if i == -1 { + return fmt.Errorf("offset %q not found", req.Offset) + } + + if len(rels) < i { + return fmt.Errorf("no items after %q", req.Offset) + } + + rels = rels[i:] + l = int64(len(rels)) + } + + if req.Limit == 0 { + req.Limit = ListDefaultLimit + } + + next := "" + if l > req.Limit { + next = rels[req.Limit].Name + rels = rels[0:req.Limit] + l = int64(len(rels)) + } + res := &services.ListReleasesResponse{ + Next: next, + Count: l, + Total: total, + } + chunks := s.partition(rels[:min(len(rels), int(req.Limit))], maxMsgSize-proto.Size(res)) + for res.Releases = range chunks { + if err := stream.Send(res); err != nil { + for range chunks { // drain + } + return err + } + } + return nil +} + +// partition packs releases into slices upto the capacity cap in bytes. +func (s *ReleaseServer) partition(rels []*release.Release, cap int) <-chan []*release.Release { + chunks := make(chan []*release.Release, 1) + go func() { + var ( + fill = 0 // fill is space available to fill + size int // size is size of a release + ) + var chunk []*release.Release + for _, rls := range rels { + if size = proto.Size(rls); size+fill > cap { + // Over-cap, push chunk onto channel to send over gRPC stream + s.Log("partitioned at %d with %d releases (cap=%d)", fill, len(chunk), cap) + chunks <- chunk + // reset paritioning state + chunk = chunk[:0] + fill = 0 + } + chunk = append(chunk, rls) + fill += size + } + if len(chunk) > 0 { + // send remaining if any + chunks <- chunk + } + close(chunks) + }() + return chunks +} + +func filterByNamespace(namespace string, rels []*release.Release) ([]*release.Release, error) { + matches := []*release.Release{} + for _, r := range rels { + if namespace == r.Namespace { + matches = append(matches, r) + } + } + return matches, nil +} + +func filterReleases(filter string, rels []*release.Release) ([]*release.Release, error) { + preg, err := regexp.Compile(filter) + if err != nil { + return rels, err + } + matches := []*release.Release{} + for _, r := range rels { + if preg.MatchString(r.Name) { + matches = append(matches, r) + } + } + return matches, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list_test.go new file mode 100644 index 000000000..64877422a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_list_test.go @@ -0,0 +1,233 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestListReleases(t *testing.T) { + rs := rsFixture() + num := 7 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = fmt.Sprintf("rel-%d", i) + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + if err := rs.ListReleases(&services.ListReleasesRequest{Offset: "", Limit: 64}, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != num { + t.Errorf("Expected %d releases, got %d", num, len(mrs.val.Releases)) + } +} + +func TestListReleasesByStatus(t *testing.T) { + rs := rsFixture() + stubs := []*release.Release{ + namedReleaseStub("kamal", release.Status_DEPLOYED), + namedReleaseStub("astrolabe", release.Status_DELETED), + namedReleaseStub("octant", release.Status_FAILED), + namedReleaseStub("sextant", release.Status_UNKNOWN), + } + for _, stub := range stubs { + if err := rs.env.Releases.Create(stub); err != nil { + t.Fatalf("Could not create stub: %s", err) + } + } + + tests := []struct { + statusCodes []release.Status_Code + names []string + }{ + { + names: []string{"kamal"}, + statusCodes: []release.Status_Code{release.Status_DEPLOYED}, + }, + { + names: []string{"astrolabe"}, + statusCodes: []release.Status_Code{release.Status_DELETED}, + }, + { + names: []string{"kamal", "octant"}, + statusCodes: []release.Status_Code{release.Status_DEPLOYED, release.Status_FAILED}, + }, + { + names: []string{"kamal", "astrolabe", "octant", "sextant"}, + statusCodes: []release.Status_Code{ + release.Status_DEPLOYED, + release.Status_DELETED, + release.Status_FAILED, + release.Status_UNKNOWN, + }, + }, + } + + for i, tt := range tests { + mrs := &mockListServer{} + if err := rs.ListReleases(&services.ListReleasesRequest{StatusCodes: tt.statusCodes, Offset: "", Limit: 64}, mrs); err != nil { + t.Fatalf("Failed listing %d: %s", i, err) + } + + if len(tt.names) != len(mrs.val.Releases) { + t.Fatalf("Expected %d releases, got %d", len(tt.names), len(mrs.val.Releases)) + } + + for _, name := range tt.names { + found := false + for _, rel := range mrs.val.Releases { + if rel.Name == name { + found = true + } + } + if !found { + t.Errorf("%d: Did not find name %q", i, name) + } + } + } +} + +func TestListReleasesSort(t *testing.T) { + rs := rsFixture() + + // Put them in by reverse order so that the mock doesn't "accidentally" + // sort. + num := 7 + for i := num; i > 0; i-- { + rel := releaseStub() + rel.Name = fmt.Sprintf("rel-%d", i) + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + limit := 6 + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: int64(limit), + SortBy: services.ListSort_NAME, + } + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != limit { + t.Errorf("Expected %d releases, got %d", limit, len(mrs.val.Releases)) + } + + for i := 0; i < limit; i++ { + n := fmt.Sprintf("rel-%d", i+1) + if mrs.val.Releases[i].Name != n { + t.Errorf("Expected %q, got %q", n, mrs.val.Releases[i].Name) + } + } +} + +func TestListReleasesFilter(t *testing.T) { + rs := rsFixture() + names := []string{ + "axon", + "dendrite", + "neuron", + "neuroglia", + "synapse", + "nucleus", + "organelles", + } + num := 7 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = names[i] + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: 64, + Filter: "neuro[a-z]+", + SortBy: services.ListSort_NAME, + } + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != 2 { + t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) + } + + if mrs.val.Releases[0].Name != "neuroglia" { + t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) + } + if mrs.val.Releases[1].Name != "neuron" { + t.Errorf("Unexpected sort order: %v.", mrs.val.Releases) + } +} + +func TestReleasesNamespace(t *testing.T) { + rs := rsFixture() + + names := []string{ + "axon", + "dendrite", + "neuron", + "ribosome", + } + + namespaces := []string{ + "default", + "test123", + "test123", + "cerebellum", + } + num := 4 + for i := 0; i < num; i++ { + rel := releaseStub() + rel.Name = names[i] + rel.Namespace = namespaces[i] + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + } + + mrs := &mockListServer{} + req := &services.ListReleasesRequest{ + Offset: "", + Limit: 64, + Namespace: "test123", + } + + if err := rs.ListReleases(req, mrs); err != nil { + t.Fatalf("Failed listing: %s", err) + } + + if len(mrs.val.Releases) != 2 { + t.Errorf("Expected 2 releases, got %d", len(mrs.val.Releases)) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_modules.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_modules.go new file mode 100644 index 000000000..876e1ba37 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_modules.go @@ -0,0 +1,183 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "bytes" + "errors" + "fmt" + "log" + "strings" + + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/release" + rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/rudder" + "k8s.io/helm/pkg/tiller/environment" +) + +// ReleaseModule is an interface that allows ReleaseServer to run operations on release via either local implementation or Rudder service +type ReleaseModule interface { + Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error + Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error + Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error + Status(r *release.Release, req *services.GetReleaseStatusRequest, env *environment.Environment) (string, error) + Delete(r *release.Release, req *services.UninstallReleaseRequest, env *environment.Environment) (string, []error) +} + +// LocalReleaseModule is a local implementation of ReleaseModule +type LocalReleaseModule struct { + clientset internalclientset.Interface +} + +// Create creates a release via kubeclient from provided environment +func (m *LocalReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error { + b := bytes.NewBufferString(r.Manifest) + return env.KubeClient.Create(r.Namespace, b, req.Timeout, req.Wait) +} + +// Update performs an update from current to target release +func (m *LocalReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error { + c := bytes.NewBufferString(current.Manifest) + t := bytes.NewBufferString(target.Manifest) + return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait) +} + +// Rollback performs a rollback from current to target release +func (m *LocalReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error { + c := bytes.NewBufferString(current.Manifest) + t := bytes.NewBufferString(target.Manifest) + return env.KubeClient.Update(target.Namespace, c, t, req.Force, req.Recreate, req.Timeout, req.Wait) +} + +// Status returns kubectl-like formatted status of release objects +func (m *LocalReleaseModule) Status(r *release.Release, req *services.GetReleaseStatusRequest, env *environment.Environment) (string, error) { + return env.KubeClient.Get(r.Namespace, bytes.NewBufferString(r.Manifest)) +} + +// Delete deletes the release and returns manifests that were kept in the deletion process +func (m *LocalReleaseModule) Delete(rel *release.Release, req *services.UninstallReleaseRequest, env *environment.Environment) (kept string, errs []error) { + vs, err := GetVersionSet(m.clientset.Discovery()) + if err != nil { + return rel.Manifest, []error{fmt.Errorf("Could not get apiVersions from Kubernetes: %v", err)} + } + return DeleteRelease(rel, vs, env.KubeClient) +} + +// RemoteReleaseModule is a ReleaseModule which calls Rudder service to operate on a release +type RemoteReleaseModule struct{} + +// Create calls rudder.InstallRelease +func (m *RemoteReleaseModule) Create(r *release.Release, req *services.InstallReleaseRequest, env *environment.Environment) error { + request := &rudderAPI.InstallReleaseRequest{Release: r} + _, err := rudder.InstallRelease(request) + return err +} + +// Update calls rudder.UpgradeRelease +func (m *RemoteReleaseModule) Update(current, target *release.Release, req *services.UpdateReleaseRequest, env *environment.Environment) error { + upgrade := &rudderAPI.UpgradeReleaseRequest{ + Current: current, + Target: target, + Recreate: req.Recreate, + Timeout: req.Timeout, + Wait: req.Wait, + Force: req.Force, + } + _, err := rudder.UpgradeRelease(upgrade) + return err +} + +// Rollback calls rudder.Rollback +func (m *RemoteReleaseModule) Rollback(current, target *release.Release, req *services.RollbackReleaseRequest, env *environment.Environment) error { + rollback := &rudderAPI.RollbackReleaseRequest{ + Current: current, + Target: target, + Recreate: req.Recreate, + Timeout: req.Timeout, + Wait: req.Wait, + } + _, err := rudder.RollbackRelease(rollback) + return err +} + +// Status returns status retrieved from rudder.ReleaseStatus +func (m *RemoteReleaseModule) Status(r *release.Release, req *services.GetReleaseStatusRequest, env *environment.Environment) (string, error) { + statusRequest := &rudderAPI.ReleaseStatusRequest{Release: r} + resp, err := rudder.ReleaseStatus(statusRequest) + if resp == nil { + return "", err + } + return resp.Info.Status.Resources, err +} + +// Delete calls rudder.DeleteRelease +func (m *RemoteReleaseModule) Delete(r *release.Release, req *services.UninstallReleaseRequest, env *environment.Environment) (string, []error) { + deleteRequest := &rudderAPI.DeleteReleaseRequest{Release: r} + resp, err := rudder.DeleteRelease(deleteRequest) + + errs := make([]error, 0) + result := "" + + if err != nil { + errs = append(errs, err) + } + if resp != nil { + result = resp.Release.Manifest + } + return result, errs +} + +// DeleteRelease is a helper that allows Rudder to delete a release without exposing most of Tiller inner functions +func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) { + manifests := relutil.SplitManifests(rel.Manifest) + _, files, err := sortManifests(manifests, vs, 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 rel.Manifest, []error{fmt.Errorf("corrupted release record. You must manually delete the resources: %s", err)} + } + + filesToKeep, filesToDelete := filterManifestsToKeep(files) + if len(filesToKeep) > 0 { + kept = summarizeKeptManifests(filesToKeep, kubeClient, rel.Namespace) + } + + errs = []error{} + for _, file := range filesToDelete { + b := bytes.NewBufferString(strings.TrimSpace(file.Content)) + if b.Len() == 0 { + continue + } + if err := kubeClient.Delete(rel.Namespace, b); err != nil { + log.Printf("uninstall: Failed deletion of %q: %s", rel.Name, err) + if err == kube.ErrNoObjectsVisited { + // Rewrite the message from "no objects visited" + err = errors.New("object not found, skipping delete") + } + errs = append(errs, err) + } + } + return kept, errs +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback.go new file mode 100644 index 000000000..fa3d943f4 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback.go @@ -0,0 +1,163 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +// RollbackRelease rolls back to a previous version of the given release. +func (s *ReleaseServer) RollbackRelease(c ctx.Context, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { + s.Log("preparing rollback of %s", req.Name) + currentRelease, targetRelease, err := s.prepareRollback(req) + if err != nil { + return nil, err + } + + if !req.DryRun { + s.Log("creating rolled back release for %s", req.Name) + if err := s.env.Releases.Create(targetRelease); err != nil { + return nil, err + } + } + s.Log("performing rollback of %s", req.Name) + res, err := s.performRollback(currentRelease, targetRelease, req) + if err != nil { + return res, err + } + + if !req.DryRun { + s.Log("updating status for rolled back release for %s", req.Name) + if err := s.env.Releases.Update(targetRelease); err != nil { + return res, err + } + } + + return res, nil +} + +// prepareRollback finds the previous release and prepares a new release object with +// the previous release's configuration +func (s *ReleaseServer) prepareRollback(req *services.RollbackReleaseRequest) (*release.Release, *release.Release, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("prepareRollback: Release name is invalid: %s", req.Name) + return nil, nil, err + } + + if req.Version < 0 { + return nil, nil, errInvalidRevision + } + + currentRelease, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, nil, err + } + + previousVersion := req.Version + if req.Version == 0 { + previousVersion = currentRelease.Version - 1 + } + + s.Log("rolling back %s (current: v%d, target: v%d)", req.Name, currentRelease.Version, previousVersion) + + previousRelease, err := s.env.Releases.Get(req.Name, previousVersion) + if err != nil { + return nil, nil, err + } + + // Store a new release object with previous release's configuration + targetRelease := &release.Release{ + Name: req.Name, + Namespace: currentRelease.Namespace, + Chart: previousRelease.Chart, + Config: previousRelease.Config, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: timeconv.Now(), + Status: &release.Status{ + Code: release.Status_PENDING_ROLLBACK, + Notes: previousRelease.Info.Status.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 (s *ReleaseServer) performRollback(currentRelease, targetRelease *release.Release, req *services.RollbackReleaseRequest) (*services.RollbackReleaseResponse, error) { + res := &services.RollbackReleaseResponse{Release: targetRelease} + + if req.DryRun { + s.Log("dry run for %s", targetRelease.Name) + return res, nil + } + + // pre-rollback hooks + if !req.DisableHooks { + if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PreRollback, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("rollback hooks disabled for %s", req.Name) + } + + if err := s.ReleaseModule.Rollback(currentRelease, targetRelease, req, s.env); err != nil { + msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) + s.Log("warning: %s", msg) + currentRelease.Info.Status.Code = release.Status_SUPERSEDED + targetRelease.Info.Status.Code = release.Status_FAILED + targetRelease.Info.Description = msg + s.recordRelease(currentRelease, true) + s.recordRelease(targetRelease, true) + return res, err + } + + // post-rollback hooks + if !req.DisableHooks { + if err := s.execHook(targetRelease.Hooks, targetRelease.Name, targetRelease.Namespace, hooks.PostRollback, req.Timeout); err != nil { + return res, err + } + } + + deployed, err := s.env.Releases.DeployedAll(currentRelease.Name) + if err != nil { + return nil, err + } + // Supersede all previous deployments, see issue #2941. + for _, r := range deployed { + s.Log("superseding previous deployment %d", r.Version) + r.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(r, true) + } + + targetRelease.Info.Status.Code = release.Status_DEPLOYED + + return res, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback_test.go new file mode 100644 index 000000000..b73501a36 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_rollback_test.go @@ -0,0 +1,254 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "strings" + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestRollbackRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + upgradedRel.Hooks = []*release.Hook{ + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithRollbackHooks, + Events: []release.Hook_Event{ + release.Hook_PRE_ROLLBACK, + release.Hook_POST_ROLLBACK, + }, + }, + } + + upgradedRel.Manifest = "hello world" + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + } + res, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if res.Release.Name != rel.Name { + t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) + } + + if res.Release.Namespace != rel.Namespace { + t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) + } + + if res.Release.Version != 3 { + t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) + } + + updated, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(updated.Hooks) != 2 { + t.Fatalf("Expected 2 hooks, got %d", len(updated.Hooks)) + } + + if updated.Hooks[0].Manifest != manifestWithHook { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + anotherUpgradedRelease := upgradeReleaseVersion(upgradedRel) + rs.env.Releases.Update(upgradedRel) + rs.env.Releases.Create(anotherUpgradedRelease) + + res, err = rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + updated, err = rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Errorf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if len(updated.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) + } + + if updated.Hooks[0].Manifest != manifestWithRollbackHooks { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + if res.Release.Version != 4 { + t.Errorf("Expected release version to be %v, got %v", 3, res.Release.Version) + } + + if updated.Hooks[0].Events[0] != release.Hook_PRE_ROLLBACK { + t.Errorf("Expected event 0 to be pre rollback") + } + + if updated.Hooks[0].Events[1] != release.Hook_POST_ROLLBACK { + t.Errorf("Expected event 1 to be post rollback") + } + + if len(res.Release.Manifest) == 0 { + t.Errorf("No manifest returned: %v", res.Release) + } + + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if !strings.Contains(updated.Manifest, "hello world") { + t.Errorf("unexpected output: %s", rel.Manifest) + } + + if res.Release.Info.Description != "Rollback to 2" { + t.Errorf("Expected rollback to 2, got %q", res.Release.Info.Description) + } +} + +func TestRollbackWithReleaseVersion(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.Log = t.Logf + rs.env.Releases.Log = t.Logf + rel2 := releaseStub() + rel2.Name = "other" + rs.env.Releases.Create(rel2) + rel := releaseStub() + rs.env.Releases.Create(rel) + v2 := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(v2) + v3 := upgradeReleaseVersion(v2) + // retain the original release as DEPLOYED while the update should fail + v2.Info.Status.Code = release.Status_DEPLOYED + v3.Info.Status.Code = release.Status_FAILED + rs.env.Releases.Update(v2) + rs.env.Releases.Create(v3) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Version: 1, + } + + _, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + // check that v2 is now in a SUPERSEDED state + oldRel, err := rs.env.Releases.Get(rel.Name, 2) + if err != nil { + t.Fatalf("Failed to retrieve v1: %s", err) + } + if oldRel.Info.Status.Code != release.Status_SUPERSEDED { + t.Errorf("Expected v2 to be in a SUPERSEDED state, got %q", oldRel.Info.Status.Code) + } + // make sure we didn't update some other deployments. + otherRel, err := rs.env.Releases.Get(rel2.Name, 1) + if err != nil { + t.Fatalf("Failed to retrieve other v1: %s", err) + } + if otherRel.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Expected other deployed release to stay untouched, got %q", otherRel.Info.Status.Code) + } +} + +func TestRollbackReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Hooks = []*release.Hook{ + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithRollbackHooks, + Events: []release.Hook_Event{ + release.Hook_PRE_ROLLBACK, + release.Hook_POST_ROLLBACK, + }, + }, + } + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + } + + res, err := rs.RollbackRelease(c, req) + if err != nil { + t.Fatalf("Failed rollback: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } +} + +func TestRollbackReleaseFailure(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.RollbackReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + } + + rs.env.KubeClient = newUpdateFailingKubeClient() + res, err := rs.RollbackRelease(c, req) + if err == nil { + t.Error("Expected failed rollback") + } + + if targetStatus := res.Release.Info.Status.Code; targetStatus != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %v", targetStatus) + } + + oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) + if err != nil { + t.Errorf("Expected to be able to get previous release") + } + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_SUPERSEDED { + t.Errorf("Expected SUPERSEDED status on previous Release version. Got %v", oldStatus) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server.go new file mode 100644 index 000000000..7c4bc62cf --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server.go @@ -0,0 +1,444 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "bytes" + "errors" + "fmt" + "path" + "regexp" + "strings" + + "github.com/technosophos/moniker" + "gopkg.in/yaml.v2" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/discovery" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/tiller/environment" + "k8s.io/helm/pkg/timeconv" + "k8s.io/helm/pkg/version" +) + +// releaseNameMaxLen is the maximum length of a release name. +// +// As of Kubernetes 1.4, the max limit on a name is 63 chars. We reserve 10 for +// charts to add data. Effectively, that gives us 53 chars. +// See https://github.com/kubernetes/helm/issues/1528 +const releaseNameMaxLen = 53 + +// 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" + +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") + //errInvalidName indicates that an invalid release name was provided + errInvalidName = errors.New("invalid release name, must match regex ^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$ and the length must not longer than 53") +) + +// ListDefaultLimit is the default limit for number of items returned in a list. +var ListDefaultLimit int64 = 512 + +// ValidName is a regular expression for names. +// +// According to the Kubernetes help text, the regular expression it uses is: +// +// (([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])? +// +// We modified that. First, we added start and end delimiters. Second, we changed +// the final ? to + to require that the pattern match at least once. This modification +// prevents an empty string from matching. +var ValidName = regexp.MustCompile("^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])+$") + +// ReleaseServer implements the server-side gRPC endpoint for the HAPI services. +type ReleaseServer struct { + ReleaseModule + env *environment.Environment + clientset internalclientset.Interface + Log func(string, ...interface{}) +} + +// NewReleaseServer creates a new release server. +func NewReleaseServer(env *environment.Environment, clientset internalclientset.Interface, useRemote bool) *ReleaseServer { + var releaseModule ReleaseModule + if useRemote { + releaseModule = &RemoteReleaseModule{} + } else { + releaseModule = &LocalReleaseModule{ + clientset: clientset, + } + } + + return &ReleaseServer{ + env: env, + clientset: clientset, + ReleaseModule: releaseModule, + Log: func(_ string, _ ...interface{}) {}, + } +} + +// 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 req.ResetValues flag is set, in which case the +// request values are not altered. +func (s *ReleaseServer) reuseValues(req *services.UpdateReleaseRequest, current *release.Release) error { + if req.ResetValues { + // If ResetValues is set, we comletely ignore current.Config. + s.Log("resetting values to the chart's original version") + return nil + } + + // If the ReuseValues flag is set, we always copy the old values over the new config's values. + if req.ReuseValues { + s.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 { + err := fmt.Errorf("failed to rebuild old values: %s", err) + s.Log("%s", err) + return err + } + nv, err := oldVals.YAML() + if err != nil { + return err + } + + // merge new values with current + req.Values.Raw = current.Config.Raw + "\n" + req.Values.Raw + req.Chart.Values = &chart.Config{Raw: nv} + + // yaml unmarshal and marshal to remove duplicate keys + y := map[string]interface{}{} + if err := yaml.Unmarshal([]byte(req.Values.Raw), &y); err != nil { + return err + } + data, err := yaml.Marshal(y) + if err != nil { + return err + } + + req.Values.Raw = string(data) + return nil + } + + // If req.Values is empty, but current.Config is not, copy current into the + // request. + if (req.Values == nil || req.Values.Raw == "" || req.Values.Raw == "{}\n") && + current.Config != nil && + current.Config.Raw != "" && + current.Config.Raw != "{}\n" { + s.Log("copying values from %s (v%d) to new release.", current.Name, current.Version) + req.Values = current.Config + } + return nil +} + +func (s *ReleaseServer) uniqName(start string, reuse bool) (string, error) { + + // If a name is supplied, we check to see if that name is taken. If not, it + // is granted. If reuse is true and a deleted release with that name exists, + // we re-grant it. Otherwise, an error is returned. + if start != "" { + + if len(start) > releaseNameMaxLen { + return "", fmt.Errorf("release name %q exceeds max length of %d", start, releaseNameMaxLen) + } + + h, err := s.env.Releases.History(start) + if err != nil || len(h) < 1 { + return start, nil + } + relutil.Reverse(h, relutil.SortByRevision) + rel := h[0] + + if st := rel.Info.Status.Code; reuse && (st == release.Status_DELETED || st == release.Status_FAILED) { + // Allowe re-use of names if the previous release is marked deleted. + s.Log("name %s exists but is not in use, reusing name", start) + return start, nil + } else if reuse { + return "", errors.New("cannot re-use a name that is still in use") + } + + return "", fmt.Errorf("a release named %s already exists.\nRun: helm ls --all %s; to check the status of the release\nOr run: helm del --purge %s; to delete it", start, start, start) + } + + maxTries := 5 + for i := 0; i < maxTries; i++ { + namer := moniker.New() + name := namer.NameSep("-") + if len(name) > releaseNameMaxLen { + name = name[:releaseNameMaxLen] + } + if _, err := s.env.Releases.Get(name, 1); strings.Contains(err.Error(), "not found") { + return name, nil + } + s.Log("info: generated name %s is taken. Searching again.", name) + } + s.Log("warning: No available release names found after %d tries", maxTries) + return "ERROR", errors.New("no available release name found") +} + +func (s *ReleaseServer) engine(ch *chart.Chart) environment.Engine { + renderer := s.env.EngineYard.Default() + if ch.Metadata.Engine != "" { + if r, ok := s.env.EngineYard.Get(ch.Metadata.Engine); ok { + renderer = r + } else { + s.Log("warning: %s requested non-existent template engine %s", ch.Metadata.Name, ch.Metadata.Engine) + } + } + return renderer +} + +// capabilities builds a Capabilities from discovery information. +func capabilities(disc discovery.DiscoveryInterface) (*chartutil.Capabilities, error) { + sv, err := disc.ServerVersion() + if err != nil { + return nil, err + } + vs, err := GetVersionSet(disc) + if err != nil { + return nil, fmt.Errorf("Could not get apiVersions from Kubernetes: %s", err) + } + return &chartutil.Capabilities{ + APIVersions: vs, + KubeVersion: sv, + TillerVersion: version.GetVersionProto(), + }, nil +} + +// GetVersionSet retrieves a set of available k8s API versions +func GetVersionSet(client discovery.ServerGroupsInterface) (chartutil.VersionSet, error) { + groups, err := client.ServerGroups() + if err != nil { + return chartutil.DefaultVersionSet, err + } + + // FIXME: The Kubernetes test fixture for cli appears to always return nil + // for calls to Discovery().ServerGroups(). 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 groups.Size() == 0 { + return chartutil.DefaultVersionSet, nil + } + + versions := metav1.ExtractGroupVersions(groups) + return chartutil.NewVersionSet(versions...), nil +} + +func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values, vs chartutil.VersionSet) ([]*release.Hook, *bytes.Buffer, string, error) { + // Guard to make sure Tiller is at the right version to handle this chart. + sver := version.GetVersion() + if ch.Metadata.TillerVersion != "" && + !version.IsCompatibleRange(ch.Metadata.TillerVersion, sver) { + return nil, nil, "", fmt.Errorf("Chart incompatible with Tiller %s", sver) + } + + if ch.Metadata.KubeVersion != "" { + cap, _ := values["Capabilities"].(*chartutil.Capabilities) + gitVersion := cap.KubeVersion.String() + k8sVersion := strings.Split(gitVersion, "+")[0] + if !version.IsCompatibleRange(ch.Metadata.KubeVersion, k8sVersion) { + return nil, nil, "", fmt.Errorf("Chart requires kubernetesVersion: %s which is incompatible with Kubernetes %s", ch.Metadata.KubeVersion, k8sVersion) + } + } + + s.Log("rendering %s chart using values", ch.GetMetadata().Name) + renderer := s.engine(ch) + files, err := renderer.Render(ch, values) + if err != nil { + return nil, nil, "", err + } + + // 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. + notes := "" + for k, v := range files { + if strings.HasSuffix(k, notesFileSuffix) { + // Only apply the notes if it belongs to the parent chart + // Note: Do not use filePath.Join since it creates a path with \ which is not expected + if k == path.Join(ch.Metadata.Name, "templates", notesFileSuffix) { + notes = v + } + delete(files, k) + } + } + + // 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. + hooks, manifests, err := sortManifests(files, vs, 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. + b := bytes.NewBuffer(nil) + for name, content := range files { + if len(strings.TrimSpace(content)) == 0 { + continue + } + b.WriteString("\n---\n# Source: " + name + "\n") + b.WriteString(content) + } + return nil, b, "", err + } + + // Aggregate all valid manifests into one big doc. + b := bytes.NewBuffer(nil) + for _, m := range manifests { + b.WriteString("\n---\n# Source: " + m.Name + "\n") + b.WriteString(m.Content) + } + + return hooks, b, notes, nil +} + +// recordRelease with an update operation in case reuse has been set. +func (s *ReleaseServer) recordRelease(r *release.Release, reuse bool) { + if reuse { + if err := s.env.Releases.Update(r); err != nil { + s.Log("warning: Failed to update release %s: %s", r.Name, err) + } + } else if err := s.env.Releases.Create(r); err != nil { + s.Log("warning: Failed to record release %s: %s", r.Name, err) + } +} + +func (s *ReleaseServer) execHook(hs []*release.Hook, name, namespace, hook string, timeout int64) error { + kubeCli := s.env.KubeClient + code, ok := events[hook] + if !ok { + return fmt.Errorf("unknown hook %s", hook) + } + + s.Log("executing %d %s hooks for %s", len(hs), hook, name) + executingHooks := []*release.Hook{} + for _, h := range hs { + for _, e := range h.Events { + if e == code { + executingHooks = append(executingHooks, h) + } + } + } + + executingHooks = sortByHookWeight(executingHooks) + + for _, h := range executingHooks { + if err := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.BeforeHookCreation, name, namespace, hook, kubeCli); err != nil { + return err + } + + b := bytes.NewBufferString(h.Manifest) + if err := kubeCli.Create(namespace, b, timeout, false); err != nil { + s.Log("warning: Release %s %s %s failed: %s", name, hook, h.Path, err) + return err + } + // No way to rewind a bytes.Buffer()? + b.Reset() + b.WriteString(h.Manifest) + + if err := kubeCli.WatchUntilReady(namespace, b, timeout, false); err != nil { + s.Log("warning: Release %s %s %s could not complete: %s", name, hook, h.Path, err) + // If a hook is failed, checkout 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 := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.HookFailed, name, namespace, hook, kubeCli); err != nil { + return err + } + return err + } + } + + s.Log("hooks complete for %s %s", hook, name) + // If all hooks are succeeded, checkout 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 := s.deleteHookIfShouldBeDeletedByDeletePolicy(h, hooks.HookSucceeded, name, namespace, hook, kubeCli); err != nil { + return err + } + h.LastRun = timeconv.Now() + } + + return nil +} + +func validateManifest(c environment.KubeClient, ns string, manifest []byte) error { + r := bytes.NewReader(manifest) + _, err := c.BuildUnstructured(ns, r) + return err +} + +func validateReleaseName(releaseName string) error { + if releaseName == "" { + return errMissingRelease + } + + if !ValidName.MatchString(releaseName) || (len(releaseName) > releaseNameMaxLen) { + return errInvalidName + } + + return nil +} + +func (s *ReleaseServer) deleteHookIfShouldBeDeletedByDeletePolicy(h *release.Hook, policy string, name, namespace, hook string, kubeCli environment.KubeClient) error { + b := bytes.NewBufferString(h.Manifest) + if hookHasDeletePolicy(h, policy) { + s.Log("deleting %s hook %s for release %s due to %q policy", hook, h.Name, name, policy) + if errHookDelete := kubeCli.Delete(namespace, b); errHookDelete != nil { + s.Log("warning: Release %s %s %S could not be deleted: %s", name, hook, h.Path, errHookDelete) + return errHookDelete + } + } + return nil +} + +// hookShouldBeDeleted 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 string) bool { + if dp, ok := deletePolices[policy]; ok { + for _, v := range h.DeletePolicies { + if dp == v { + return true + } + } + } + return false +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server_test.go new file mode 100644 index 000000000..6c4d42e04 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_server_test.go @@ -0,0 +1,811 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "regexp" + "testing" + "time" + + "github.com/ghodss/yaml" + "github.com/golang/protobuf/ptypes/timestamp" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" + "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/kubectl/resource" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/storage" + "k8s.io/helm/pkg/storage/driver" + "k8s.io/helm/pkg/tiller/environment" +) + +const notesText = "my notes here" + +var manifestWithHook = `kind: ConfigMap +metadata: + name: test-cm + annotations: + "helm.sh/hook": post-install,pre-delete +data: + name: value` + +var manifestWithTestHook = `kind: Pod +metadata: + name: finding-nemo, + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: nemo-test + image: fake-image + cmd: fake-command +` + +var manifestWithKeep = `kind: ConfigMap +metadata: + name: test-cm-keep + annotations: + "helm.sh/resource-policy": keep +data: + name: value +` + +var manifestWithUpgradeHooks = `kind: ConfigMap +metadata: + name: test-cm + annotations: + "helm.sh/hook": post-upgrade,pre-upgrade +data: + name: value` + +var manifestWithRollbackHooks = `kind: ConfigMap +metadata: + name: test-cm + annotations: + "helm.sh/hook": post-rollback,pre-rollback +data: + name: value +` + +func rsFixture() *ReleaseServer { + clientset := fake.NewSimpleClientset() + return &ReleaseServer{ + ReleaseModule: &LocalReleaseModule{ + clientset: clientset, + }, + env: MockEnvironment(), + clientset: clientset, + Log: func(_ string, _ ...interface{}) {}, + } +} + +// chartStub creates a fully stubbed out chart. +func chartStub() *chart.Chart { + return &chart.Chart{ + // TODO: This should be more complete. + Metadata: &chart.Metadata{ + Name: "hello", + }, + // This adds basic templates, partials, and hooks. + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {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}}`)}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + } +} + +// releaseStub creates a release stub, complete with the chartStub as its chart. +func releaseStub() *release.Release { + return namedReleaseStub("angry-panda", release.Status_DEPLOYED) +} + +func namedReleaseStub(name string, status release.Status_Code) *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + return &release.Release{ + Name: name, + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: status}, + Description: "Named Release Stub", + }, + Chart: chartStub(), + Config: &chart.Config{Raw: `name: value`}, + Version: 1, + Hooks: []*release.Hook{ + { + Name: "test-cm", + Kind: "ConfigMap", + Path: "test-cm", + Manifest: manifestWithHook, + Events: []release.Hook_Event{ + release.Hook_POST_INSTALL, + release.Hook_PRE_DELETE, + }, + }, + { + Name: "finding-nemo", + Kind: "Pod", + Path: "finding-nemo", + Manifest: manifestWithTestHook, + Events: []release.Hook_Event{ + release.Hook_RELEASE_TEST_SUCCESS, + }, + }, + }, + } +} + +func upgradeReleaseVersion(rel *release.Release) *release.Release { + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + + rel.Info.Status.Code = release.Status_SUPERSEDED + return &release.Release{ + Name: rel.Name, + Info: &release.Info{ + FirstDeployed: rel.Info.FirstDeployed, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, + }, + Chart: rel.Chart, + Config: rel.Config, + Version: rel.Version + 1, + } +} + +func TestValidName(t *testing.T) { + for name, valid := range map[string]error{ + "nina pinta santa-maria": errInvalidName, + "nina-pinta-santa-maria": nil, + "-nina": errInvalidName, + "pinta-": errInvalidName, + "santa-maria": nil, + "niña": errInvalidName, + "...": errInvalidName, + "pinta...": errInvalidName, + "santa...maria": nil, + "": errMissingRelease, + " ": errInvalidName, + ".nina.": errInvalidName, + "nina.pinta": nil, + "abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcdefghi-abcd": errInvalidName, + } { + if valid != validateReleaseName(name) { + t.Errorf("Expected %q to be %t", name, valid) + } + } +} + +func TestGetVersionSet(t *testing.T) { + rs := rsFixture() + vs, err := GetVersionSet(rs.clientset.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.") + } +} + +func TestUniqName(t *testing.T) { + rs := rsFixture() + + rel1 := releaseStub() + rel2 := releaseStub() + rel2.Name = "happy-panda" + rel2.Info.Status.Code = release.Status_DELETED + + rs.env.Releases.Create(rel1) + rs.env.Releases.Create(rel2) + + tests := []struct { + name string + expect string + reuse bool + err bool + }{ + {"first", "first", false, false}, + {"", "[a-z]+-[a-z]+", false, false}, + {"angry-panda", "", false, true}, + {"happy-panda", "", false, true}, + {"happy-panda", "happy-panda", true, false}, + {"hungry-hungry-hungry-hungry-hungry-hungry-hungry-hungry-hippos", "", true, true}, // Exceeds max name length + } + + for _, tt := range tests { + u, err := rs.uniqName(tt.name, tt.reuse) + if err != nil { + if tt.err { + continue + } + t.Fatal(err) + } + if tt.err { + t.Errorf("Expected an error for %q", tt.name) + } + if match, err := regexp.MatchString(tt.expect, u); err != nil { + t.Fatal(err) + } else if !match { + t.Errorf("Expected %q to match %q", u, tt.expect) + } + } +} + +func releaseWithKeepStub(rlsName string) *release.Release { + ch := &chart.Chart{ + Metadata: &chart.Metadata{ + Name: "bunnychart", + }, + Templates: []*chart.Template{ + {Name: "templates/configmap", Data: []byte(manifestWithKeep)}, + }, + } + + date := timestamp.Timestamp{Seconds: 242085845, Nanos: 0} + return &release.Release{ + Name: rlsName, + Info: &release.Info{ + FirstDeployed: &date, + LastDeployed: &date, + Status: &release.Status{Code: release.Status_DEPLOYED}, + }, + Chart: ch, + Config: &chart.Config{Raw: `name: value`}, + Version: 1, + Manifest: manifestWithKeep, + } +} + +func MockEnvironment() *environment.Environment { + e := environment.New() + e.Releases = storage.Init(driver.NewMemory()) + e.KubeClient = &environment.PrintingKubeClient{Out: ioutil.Discard} + return e +} + +func newUpdateFailingKubeClient() *updateFailingKubeClient { + return &updateFailingKubeClient{ + PrintingKubeClient: environment.PrintingKubeClient{Out: os.Stdout}, + } + +} + +type updateFailingKubeClient struct { + environment.PrintingKubeClient +} + +func (u *updateFailingKubeClient) Update(namespace string, originalReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { + return errors.New("Failed update in kube client") +} + +func newHookFailingKubeClient() *hookFailingKubeClient { + return &hookFailingKubeClient{ + PrintingKubeClient: environment.PrintingKubeClient{Out: ioutil.Discard}, + } +} + +type hookFailingKubeClient struct { + environment.PrintingKubeClient +} + +func (h *hookFailingKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { + return errors.New("Failed watch") +} + +type mockListServer struct { + val *services.ListReleasesResponse +} + +func (l *mockListServer) Send(res *services.ListReleasesResponse) error { + l.val = res + return nil +} + +func (l *mockListServer) Context() context.Context { return helm.NewContext() } +func (l *mockListServer) SendMsg(v interface{}) error { return nil } +func (l *mockListServer) RecvMsg(v interface{}) error { return nil } +func (l *mockListServer) SendHeader(m metadata.MD) error { return nil } +func (l *mockListServer) SetTrailer(m metadata.MD) {} +func (l *mockListServer) SetHeader(m metadata.MD) error { return nil } + +type mockRunReleaseTestServer struct{} + +func (rs mockRunReleaseTestServer) Send(m *services.TestReleaseResponse) error { + return nil +} +func (rs mockRunReleaseTestServer) SetHeader(m metadata.MD) error { return nil } +func (rs mockRunReleaseTestServer) SendHeader(m metadata.MD) error { return nil } +func (rs mockRunReleaseTestServer) SetTrailer(m metadata.MD) {} +func (rs mockRunReleaseTestServer) SendMsg(v interface{}) error { return nil } +func (rs mockRunReleaseTestServer) RecvMsg(v interface{}) error { return nil } +func (rs mockRunReleaseTestServer) Context() context.Context { return helm.NewContext() } + +type mockHooksManifest struct { + Metadata struct { + Name string + Annotations map[string]string + } +} +type mockHooksKubeClient struct { + Resources map[string]*mockHooksManifest +} + +var errResourceExists = errors.New("resource already exists") + +func (kc *mockHooksKubeClient) makeManifest(r io.Reader) (*mockHooksManifest, error) { + b, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + manifest := &mockHooksManifest{} + err = yaml.Unmarshal(b, manifest) + if err != nil { + return nil, err + } + + return manifest, nil +} +func (kc *mockHooksKubeClient) Create(ns string, r io.Reader, timeout int64, shouldWait bool) error { + manifest, err := kc.makeManifest(r) + if err != nil { + return err + } + + if _, hasKey := kc.Resources[manifest.Metadata.Name]; hasKey { + return errResourceExists + } + + kc.Resources[manifest.Metadata.Name] = manifest + + return nil +} +func (kc *mockHooksKubeClient) Get(ns string, r io.Reader) (string, error) { + return "", nil +} +func (kc *mockHooksKubeClient) Delete(ns string, r io.Reader) error { + manifest, err := kc.makeManifest(r) + if err != nil { + return err + } + + delete(kc.Resources, manifest.Metadata.Name) + + return nil +} +func (kc *mockHooksKubeClient) WatchUntilReady(ns string, r io.Reader, timeout int64, shouldWait bool) error { + paramManifest, err := kc.makeManifest(r) + if err != nil { + return err + } + + manifest, hasManifest := kc.Resources[paramManifest.Metadata.Name] + if !hasManifest { + return fmt.Errorf("mockHooksKubeClient.WatchUntilReady: no such resource %s found", paramManifest.Metadata.Name) + } + + if manifest.Metadata.Annotations["mockHooksKubeClient/Emulate"] == "hook-failed" { + return fmt.Errorf("mockHooksKubeClient.WatchUntilReady: hook-failed") + } + + return nil +} +func (kc *mockHooksKubeClient) Update(ns string, currentReader, modifiedReader io.Reader, force bool, recreate bool, timeout int64, shouldWait bool) error { + return nil +} +func (kc *mockHooksKubeClient) Build(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} +func (kc *mockHooksKubeClient) BuildUnstructured(ns string, reader io.Reader) (kube.Result, error) { + return []*resource.Info{}, nil +} +func (kc *mockHooksKubeClient) WaitAndGetCompletedPodPhase(namespace string, reader io.Reader, timeout time.Duration) (core.PodPhase, error) { + return core.PodUnknown, nil +} + +func deletePolicyStub(kubeClient *mockHooksKubeClient) *ReleaseServer { + e := environment.New() + e.Releases = storage.Init(driver.NewMemory()) + e.KubeClient = kubeClient + + clientset := fake.NewSimpleClientset() + return &ReleaseServer{ + ReleaseModule: &LocalReleaseModule{ + clientset: clientset, + }, + env: e, + clientset: clientset, + Log: func(_ string, _ ...interface{}) {}, + } +} + +func deletePolicyHookStub(hookName string, extraAnnotations map[string]string, DeletePolicies []release.Hook_DeletePolicy) *release.Hook { + extraAnnotationsStr := "" + for k, v := range extraAnnotations { + extraAnnotationsStr += fmt.Sprintf(" \"%s\": \"%s\"\n", k, v) + } + + return &release.Hook{ + Name: hookName, + Kind: "Job", + Path: hookName, + Manifest: fmt.Sprintf(`kind: Job +metadata: + name: %s + annotations: + "helm.sh/hook": pre-install,pre-upgrade +%sdata: +name: value`, hookName, extraAnnotationsStr), + Events: []release.Hook_Event{ + release.Hook_PRE_INSTALL, + release.Hook_PRE_UPGRADE, + }, + DeletePolicies: DeletePolicies, + } +} + +func execHookShouldSucceed(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string) error { + err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) + if err != nil { + return fmt.Errorf("expected hook %s to be successful: %s", hook.Name, err) + } + return nil +} + +func execHookShouldFail(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string) error { + err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) + if err == nil { + return fmt.Errorf("expected hook %s to be failed", hook.Name) + } + return nil +} + +func execHookShouldFailWithError(rs *ReleaseServer, hook *release.Hook, releaseName string, namespace string, hookType string, expectedError error) error { + err := rs.execHook([]*release.Hook{hook}, releaseName, namespace, hookType, 600) + if err != expectedError { + return fmt.Errorf("expected hook %s to fail with error %v, got %v", hook.Name, expectedError, err) + } + return nil +} + +type deletePolicyContext struct { + ReleaseServer *ReleaseServer + ReleaseName string + Namespace string + HookName string + KubeClient *mockHooksKubeClient +} + +func newDeletePolicyContext() *deletePolicyContext { + kubeClient := &mockHooksKubeClient{ + Resources: make(map[string]*mockHooksManifest), + } + + return &deletePolicyContext{ + KubeClient: kubeClient, + ReleaseServer: deletePolicyStub(kubeClient), + ReleaseName: "flying-carp", + Namespace: "river", + HookName: "migration-job", + } +} + +func TestSuccessfulHookWithoutDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + hook := deletePolicyHookStub(ctx.HookName, nil, nil) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be created by kube client", hook.Name) + } +} + +func TestFailedHookWithoutDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{"mockHooksKubeClient/Emulate": "hook-failed"}, + nil, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be created by kube client", hook.Name) + } +} + +func TestSuccessfulHookWithSucceededDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{"helm.sh/hook-delete-policy": "hook-succeeded"}, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED}, + ) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) + } +} + +func TestSuccessfulHookWithFailedDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{"helm.sh/hook-delete-policy": "hook-failed"}, + []release.Hook_DeletePolicy{release.Hook_FAILED}, + ) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) + } +} + +func TestFailedHookWithSucceededDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "mockHooksKubeClient/Emulate": "hook-failed", + "helm.sh/hook-delete-policy": "hook-succeeded", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED}, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook failed", hook.Name) + } +} + +func TestFailedHookWithFailedDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "mockHooksKubeClient/Emulate": "hook-failed", + "helm.sh/hook-delete-policy": "hook-failed", + }, + []release.Hook_DeletePolicy{release.Hook_FAILED}, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook failed", hook.Name) + } +} + +func TestSuccessfulHookWithSuccededOrFailedDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_FAILED}, + ) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) + } +} + +func TestFailedHookWithSuccededOrFailedDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "mockHooksKubeClient/Emulate": "hook-failed", + "helm.sh/hook-delete-policy": "hook-succeeded,hook-failed", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_FAILED}, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook failed", hook.Name) + } +} + +func TestHookAlreadyExists(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, nil, nil) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) + } + + err = execHookShouldFailWithError(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade, errResourceExists) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after already exists error", hook.Name) + } +} + +func TestHookDeletingWithBeforeHookCreationDeletePolicy(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{"helm.sh/hook-delete-policy": "before-hook-creation"}, + []release.Hook_DeletePolicy{release.Hook_BEFORE_HOOK_CREATION}, + ) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) + } + + err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook succeeded", hook.Name) + } +} + +func TestSuccessfulHookWithMixedDeletePolicies(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, + ) + + err := execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) + } + + err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) + } +} + +func TestFailedHookWithMixedDeletePolicies(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "mockHooksKubeClient/Emulate": "hook-failed", + "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook failed", hook.Name) + } + + err = execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook failed", hook.Name) + } +} + +func TestFailedThenSuccessfulHookWithMixedDeletePolicies(t *testing.T) { + ctx := newDeletePolicyContext() + + hook := deletePolicyHookStub(ctx.HookName, + map[string]string{ + "mockHooksKubeClient/Emulate": "hook-failed", + "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, + ) + + err := execHookShouldFail(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreInstall) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; !hasResource { + t.Errorf("expected resource %s to be existing after hook failed", hook.Name) + } + + hook = deletePolicyHookStub(ctx.HookName, + map[string]string{ + "helm.sh/hook-delete-policy": "hook-succeeded,before-hook-creation", + }, + []release.Hook_DeletePolicy{release.Hook_SUCCEEDED, release.Hook_BEFORE_HOOK_CREATION}, + ) + + err = execHookShouldSucceed(ctx.ReleaseServer, hook, ctx.ReleaseName, ctx.Namespace, hooks.PreUpgrade) + if err != nil { + t.Error(err) + } + + if _, hasResource := ctx.KubeClient.Resources[hook.Name]; hasResource { + t.Errorf("expected resource %s to be unexisting after hook succeeded", hook.Name) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status.go new file mode 100644 index 000000000..e0d75877d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "errors" + "fmt" + + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +// GetReleaseStatus gets the status information for a named release. +func (s *ReleaseServer) GetReleaseStatus(c ctx.Context, req *services.GetReleaseStatusRequest) (*services.GetReleaseStatusResponse, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("getStatus: Release name is invalid: %s", req.Name) + return nil, err + } + + var rel *release.Release + + if req.Version <= 0 { + var err error + rel, err = s.env.Releases.Last(req.Name) + if err != nil { + return nil, fmt.Errorf("getting deployed release %q: %s", req.Name, err) + } + } else { + var err error + if rel, err = s.env.Releases.Get(req.Name, req.Version); err != nil { + return nil, fmt.Errorf("getting release '%s' (v%d): %s", req.Name, req.Version, err) + } + } + + if rel.Info == nil { + return nil, errors.New("release info is missing") + } + if rel.Chart == nil { + return nil, errors.New("release chart is missing") + } + + sc := rel.Info.Status.Code + statusResp := &services.GetReleaseStatusResponse{ + Name: rel.Name, + Namespace: rel.Namespace, + Info: rel.Info, + } + + // Ok, we got the status of the release as we had jotted down, now we need to match the + // manifest we stashed away with reality from the cluster. + resp, err := s.ReleaseModule.Status(rel, req, s.env) + if sc == release.Status_DELETED || sc == release.Status_FAILED { + // Skip errors if this is already deleted or failed. + return statusResp, nil + } else if err != nil { + s.Log("warning: Get for %s failed: %v", rel.Name, err) + return nil, err + } + rel.Info.Status.Resources = resp + return statusResp, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status_test.go new file mode 100644 index 000000000..4ba0f6cd5 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_status_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestGetReleaseStatus(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Errorf("Error getting release content: %s", err) + } + + if res.Name != rel.Name { + t.Errorf("Expected name %q, got %q", rel.Name, res.Name) + } + if res.Info.Status.Code != release.Status_DEPLOYED { + t.Errorf("Expected %d, got %d", release.Status_DEPLOYED, res.Info.Status.Code) + } +} + +func TestGetReleaseStatusDeleted(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rel.Info.Status.Code = release.Status_DELETED + if err := rs.env.Releases.Create(rel); err != nil { + t.Fatalf("Could not store mock release: %s", err) + } + + res, err := rs.GetReleaseStatus(c, &services.GetReleaseStatusRequest{Name: rel.Name, Version: 1}) + if err != nil { + t.Fatalf("Error getting release content: %s", err) + } + + if res.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected %d, got %d", release.Status_DELETED, res.Info.Status.Code) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing.go new file mode 100644 index 000000000..a44b67e6f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing.go @@ -0,0 +1,72 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + reltesting "k8s.io/helm/pkg/releasetesting" +) + +// RunReleaseTest runs pre-defined tests stored as hooks on a given release +func (s *ReleaseServer) RunReleaseTest(req *services.TestReleaseRequest, stream services.ReleaseService_RunReleaseTestServer) error { + + if err := validateReleaseName(req.Name); err != nil { + s.Log("releaseTest: Release name is invalid: %s", req.Name) + return err + } + + // finds the non-deleted release with the given name + rel, err := s.env.Releases.Last(req.Name) + if err != nil { + return err + } + + testEnv := &reltesting.Environment{ + Namespace: rel.Namespace, + KubeClient: s.env.KubeClient, + Timeout: req.Timeout, + Stream: stream, + } + s.Log("running tests for release %s", rel.Name) + tSuite, err := reltesting.NewTestSuite(rel) + if err != nil { + s.Log("error creating test suite for %s: %s", rel.Name, err) + return err + } + + if err := tSuite.Run(testEnv); err != nil { + s.Log("error running test suite for %s: %s", rel.Name, err) + return err + } + + rel.Info.Status.LastTestSuiteRun = &release.TestSuite{ + StartedAt: tSuite.StartedAt, + CompletedAt: tSuite.CompletedAt, + Results: tSuite.Results, + } + + if req.Cleanup { + testEnv.DeleteTestPods(tSuite.TestManifests) + } + + if err := s.env.Releases.Update(rel); err != nil { + s.Log("test: Failed to store updated release: %s", err) + } + + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing_test.go new file mode 100644 index 000000000..f8d92ebcc --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_testing_test.go @@ -0,0 +1,36 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "testing" + + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestRunReleaseTest(t *testing.T) { + rs := rsFixture() + rel := namedReleaseStub("nemo", release.Status_DEPLOYED) + rs.env.Releases.Create(rel) + + req := &services.TestReleaseRequest{Name: "nemo", Timeout: 2} + err := rs.RunReleaseTest(req, mockRunReleaseTestServer{}) + if err != nil { + t.Fatalf("failed to run release tests on %s: %s", rel.Name, err) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall.go new file mode 100644 index 000000000..423b6e7ef --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall.go @@ -0,0 +1,128 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "strings" + + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + relutil "k8s.io/helm/pkg/releaseutil" + "k8s.io/helm/pkg/timeconv" +) + +// UninstallRelease deletes all of the resources associated with this release, and marks the release DELETED. +func (s *ReleaseServer) UninstallRelease(c ctx.Context, req *services.UninstallReleaseRequest) (*services.UninstallReleaseResponse, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("uninstallRelease: Release name is invalid: %s", req.Name) + return nil, err + } + + rels, err := s.env.Releases.History(req.Name) + if err != nil { + s.Log("uninstall: Release not loaded: %s", req.Name) + return nil, err + } + if len(rels) < 1 { + return nil, errMissingRelease + } + + relutil.SortByRevision(rels) + rel := rels[len(rels)-1] + + // TODO: Are there any cases where we want to force a delete even if it's + // already marked deleted? + if rel.Info.Status.Code == release.Status_DELETED { + if req.Purge { + if err := s.purgeReleases(rels...); err != nil { + s.Log("uninstall: Failed to purge the release: %s", err) + return nil, err + } + return &services.UninstallReleaseResponse{Release: rel}, nil + } + return nil, fmt.Errorf("the release named %q is already deleted", req.Name) + } + + s.Log("uninstall: Deleting %s", req.Name) + rel.Info.Status.Code = release.Status_DELETING + rel.Info.Deleted = timeconv.Now() + rel.Info.Description = "Deletion in progress (or silently failed)" + res := &services.UninstallReleaseResponse{Release: rel} + + if !req.DisableHooks { + if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PreDelete, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("delete hooks disabled for %s", req.Name) + } + + // From here on out, the release is currently considered to be in Status_DELETING + // state. + if err := s.env.Releases.Update(rel); err != nil { + s.Log("uninstall: Failed to store updated release: %s", err) + } + + kept, errs := s.ReleaseModule.Delete(rel, req, s.env) + res.Info = kept + + es := make([]string, 0, len(errs)) + for _, e := range errs { + s.Log("error: %v", e) + es = append(es, e.Error()) + } + + if !req.DisableHooks { + if err := s.execHook(rel.Hooks, rel.Name, rel.Namespace, hooks.PostDelete, req.Timeout); err != nil { + es = append(es, err.Error()) + } + } + + rel.Info.Status.Code = release.Status_DELETED + rel.Info.Description = "Deletion complete" + + if req.Purge { + s.Log("purge requested for %s", req.Name) + err := s.purgeReleases(rels...) + if err != nil { + s.Log("uninstall: Failed to purge the release: %s", err) + } + return res, err + } + + if err := s.env.Releases.Update(rel); err != nil { + s.Log("uninstall: Failed to store updated release: %s", err) + } + + if len(es) > 0 { + return res, fmt.Errorf("deletion completed with %d error(s): %s", len(es), strings.Join(es, "; ")) + } + return res, nil +} + +func (s *ReleaseServer) purgeReleases(rels ...*release.Release) error { + for _, rel := range rels { + if _, err := s.env.Releases.Delete(rel.Name, rel.Version); err != nil { + return err + } + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall_test.go new file mode 100644 index 000000000..20bfd2486 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_uninstall_test.go @@ -0,0 +1,178 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "strings" + "testing" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestUninstallRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != "angry-panda" { + t.Errorf("Expected angry-panda, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Release.Hooks[0].LastRun.Seconds == 0 { + t.Error("Expected LastRun to be greater than zero.") + } + + if res.Release.Info.Deleted.Seconds <= 0 { + t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) + } + + if res.Release.Info.Description != "Deletion complete" { + t.Errorf("Expected Deletion complete, got %q", res.Release.Info.Description) + } +} + +func TestUninstallPurgeRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + upgradedRel := upgradeReleaseVersion(rel) + rs.env.Releases.Update(rel) + rs.env.Releases.Create(upgradedRel) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + Purge: true, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != "angry-panda" { + t.Errorf("Expected angry-panda, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Release.Info.Deleted.Seconds <= 0 { + t.Errorf("Expected valid UNIX date, got %d", res.Release.Info.Deleted.Seconds) + } + rels, err := rs.GetHistory(helm.NewContext(), &services.GetHistoryRequest{Name: "angry-panda"}) + if err != nil { + t.Fatal(err) + } + if len(rels.Releases) != 0 { + t.Errorf("Expected no releases in storage, got %d", len(rels.Releases)) + } +} + +func TestUninstallPurgeDeleteRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + } + + _, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + req2 := &services.UninstallReleaseRequest{ + Name: "angry-panda", + Purge: true, + } + + _, err2 := rs.UninstallRelease(c, req2) + if err2 != nil && err2.Error() != "'angry-panda' has no deployed releases" { + t.Errorf("Failed uninstall: %s", err2) + } +} + +func TestUninstallReleaseWithKeepPolicy(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + name := "angry-bunny" + rs.env.Releases.Create(releaseWithKeepStub(name)) + + req := &services.UninstallReleaseRequest{ + Name: name, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Fatalf("Failed uninstall: %s", err) + } + + if res.Release.Name != name { + t.Errorf("Expected angry-bunny, got %q", res.Release.Name) + } + + if res.Release.Info.Status.Code != release.Status_DELETED { + t.Errorf("Expected status code to be DELETED, got %d", res.Release.Info.Status.Code) + } + + if res.Info == "" { + t.Errorf("Expected response info to not be empty") + } else { + if !strings.Contains(res.Info, "[ConfigMap] test-cm-keep") { + t.Errorf("unexpected output: %s", res.Info) + } + } +} + +func TestUninstallReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rs.env.Releases.Create(releaseStub()) + + req := &services.UninstallReleaseRequest{ + Name: "angry-panda", + DisableHooks: true, + } + + res, err := rs.UninstallRelease(c, req) + if err != nil { + t.Errorf("Failed uninstall: %s", err) + } + + // The default value for a protobuf timestamp is nil. + if res.Release.Hooks[0].LastRun != nil { + t.Errorf("Expected LastRun to be zero, got %d.", res.Release.Hooks[0].LastRun.Seconds) + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update.go new file mode 100644 index 000000000..6f5d37331 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update.go @@ -0,0 +1,293 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "strings" + + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/chartutil" + "k8s.io/helm/pkg/hooks" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/timeconv" +) + +// UpdateRelease takes an existing release and new information, and upgrades the release. +func (s *ReleaseServer) UpdateRelease(c ctx.Context, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + if err := validateReleaseName(req.Name); err != nil { + s.Log("updateRelease: Release name is invalid: %s", req.Name) + return nil, err + } + s.Log("preparing update for %s", req.Name) + currentRelease, updatedRelease, err := s.prepareUpdate(req) + if err != nil { + if req.Force { + // Use the --force, Luke. + return s.performUpdateForce(req) + } + return nil, err + } + + if !req.DryRun { + s.Log("creating updated release for %s", req.Name) + if err := s.env.Releases.Create(updatedRelease); err != nil { + return nil, err + } + } + + s.Log("performing update for %s", req.Name) + res, err := s.performUpdate(currentRelease, updatedRelease, req) + if err != nil { + return res, err + } + + if !req.DryRun { + s.Log("updating status for updated release for %s", req.Name) + if err := s.env.Releases.Update(updatedRelease); err != nil { + return res, err + } + } + + return res, nil +} + +// prepareUpdate builds an updated release for an update operation. +func (s *ReleaseServer) prepareUpdate(req *services.UpdateReleaseRequest) (*release.Release, *release.Release, error) { + if req.Chart == nil { + return nil, nil, errMissingChart + } + + // finds the deployed release with the given name + currentRelease, err := s.env.Releases.Deployed(req.Name) + if err != nil { + return nil, nil, err + } + + // determine if values will be reused + if err := s.reuseValues(req, currentRelease); err != nil { + return nil, nil, err + } + + // finds the non-deleted release with the given name + lastRelease, err := s.env.Releases.Last(req.Name) + if 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 + + ts := timeconv.Now() + options := chartutil.ReleaseOptions{ + Name: req.Name, + Time: ts, + Namespace: currentRelease.Namespace, + IsUpgrade: true, + Revision: int(revision), + } + + caps, err := capabilities(s.clientset.Discovery()) + if err != nil { + return nil, nil, err + } + valuesToRender, err := chartutil.ToRenderValuesCaps(req.Chart, req.Values, options, caps) + if err != nil { + return nil, nil, err + } + + hooks, manifestDoc, notesTxt, err := s.renderResources(req.Chart, valuesToRender, caps.APIVersions) + if err != nil { + return nil, nil, err + } + + // Store an updated release. + updatedRelease := &release.Release{ + Name: req.Name, + Namespace: currentRelease.Namespace, + Chart: req.Chart, + Config: req.Values, + Info: &release.Info{ + FirstDeployed: currentRelease.Info.FirstDeployed, + LastDeployed: ts, + Status: &release.Status{Code: release.Status_PENDING_UPGRADE}, + Description: "Preparing upgrade", // This should be overwritten later. + }, + Version: revision, + Manifest: manifestDoc.String(), + Hooks: hooks, + } + + if len(notesTxt) > 0 { + updatedRelease.Info.Status.Notes = notesTxt + } + err = validateManifest(s.env.KubeClient, currentRelease.Namespace, manifestDoc.Bytes()) + return currentRelease, updatedRelease, err +} + +// performUpdateForce performs the same action as a `helm delete && helm install --replace`. +func (s *ReleaseServer) performUpdateForce(req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + // find the last release with the given name + oldRelease, err := s.env.Releases.Last(req.Name) + if err != nil { + return nil, err + } + + newRelease, err := s.prepareRelease(&services.InstallReleaseRequest{ + Chart: req.Chart, + Values: req.Values, + DryRun: req.DryRun, + Name: req.Name, + DisableHooks: req.DisableHooks, + Namespace: oldRelease.Namespace, + ReuseName: true, + Timeout: req.Timeout, + Wait: req.Wait, + }) + res := &services.UpdateReleaseResponse{Release: newRelease} + if err != nil { + s.Log("failed update prepare step: %s", err) + // On dry run, append the manifest contents to a failed release. This is + // a stop-gap until we can revisit an error backchannel post-2.0. + if req.DryRun && strings.HasPrefix(err.Error(), "YAML parse error") { + err = fmt.Errorf("%s\n%s", err, newRelease.Manifest) + } + return res, err + } + + // From here on out, the release is considered to be in Status_DELETING or Status_DELETED + // state. There is no turning back. + oldRelease.Info.Status.Code = release.Status_DELETING + oldRelease.Info.Deleted = timeconv.Now() + oldRelease.Info.Description = "Deletion in progress (or silently failed)" + s.recordRelease(oldRelease, true) + + // pre-delete hooks + if !req.DisableHooks { + if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PreDelete, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("hooks disabled for %s", req.Name) + } + + // delete manifests from the old release + _, errs := s.ReleaseModule.Delete(oldRelease, nil, s.env) + + oldRelease.Info.Status.Code = release.Status_DELETED + oldRelease.Info.Description = "Deletion complete" + s.recordRelease(oldRelease, true) + + if len(errs) > 0 { + es := make([]string, 0, len(errs)) + for _, e := range errs { + s.Log("error: %v", e) + es = append(es, e.Error()) + } + return res, fmt.Errorf("Upgrade --force successfully deleted the previous release, but encountered %d error(s) and cannot continue: %s", len(es), strings.Join(es, "; ")) + } + + // post-delete hooks + if !req.DisableHooks { + if err := s.execHook(oldRelease.Hooks, oldRelease.Name, oldRelease.Namespace, hooks.PostDelete, req.Timeout); err != nil { + return res, err + } + } + + // pre-install hooks + if !req.DisableHooks { + if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PreInstall, req.Timeout); err != nil { + return res, err + } + } + + // update new release with next revision number so as to append to the old release's history + newRelease.Version = oldRelease.Version + 1 + s.recordRelease(newRelease, false) + if err := s.ReleaseModule.Update(oldRelease, newRelease, req, s.env); err != nil { + msg := fmt.Sprintf("Upgrade %q failed: %s", newRelease.Name, err) + s.Log("warning: %s", msg) + newRelease.Info.Status.Code = release.Status_FAILED + newRelease.Info.Description = msg + s.recordRelease(newRelease, true) + return res, err + } + + // post-install hooks + if !req.DisableHooks { + if err := s.execHook(newRelease.Hooks, newRelease.Name, newRelease.Namespace, hooks.PostInstall, req.Timeout); err != nil { + msg := fmt.Sprintf("Release %q failed post-install: %s", newRelease.Name, err) + s.Log("warning: %s", msg) + newRelease.Info.Status.Code = release.Status_FAILED + newRelease.Info.Description = msg + s.recordRelease(newRelease, true) + return res, err + } + } + + newRelease.Info.Status.Code = release.Status_DEPLOYED + newRelease.Info.Description = "Upgrade complete" + s.recordRelease(newRelease, true) + + return res, nil +} + +func (s *ReleaseServer) performUpdate(originalRelease, updatedRelease *release.Release, req *services.UpdateReleaseRequest) (*services.UpdateReleaseResponse, error) { + res := &services.UpdateReleaseResponse{Release: updatedRelease} + + if req.DryRun { + s.Log("dry run for %s", updatedRelease.Name) + res.Release.Info.Description = "Dry run complete" + return res, nil + } + + // pre-upgrade hooks + if !req.DisableHooks { + if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PreUpgrade, req.Timeout); err != nil { + return res, err + } + } else { + s.Log("update hooks disabled for %s", req.Name) + } + if err := s.ReleaseModule.Update(originalRelease, updatedRelease, req, s.env); err != nil { + msg := fmt.Sprintf("Upgrade %q failed: %s", updatedRelease.Name, err) + s.Log("warning: %s", msg) + updatedRelease.Info.Status.Code = release.Status_FAILED + updatedRelease.Info.Description = msg + s.recordRelease(originalRelease, true) + s.recordRelease(updatedRelease, true) + return res, err + } + + // post-upgrade hooks + if !req.DisableHooks { + if err := s.execHook(updatedRelease.Hooks, updatedRelease.Name, updatedRelease.Namespace, hooks.PostUpgrade, req.Timeout); err != nil { + return res, err + } + } + + originalRelease.Info.Status.Code = release.Status_SUPERSEDED + s.recordRelease(originalRelease, true) + + updatedRelease.Info.Status.Code = release.Status_DEPLOYED + updatedRelease.Info.Description = "Upgrade complete" + + return res, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update_test.go new file mode 100644 index 000000000..a1b9a4bff --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_update_test.go @@ -0,0 +1,446 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "strings" + "testing" + + "github.com/golang/protobuf/proto" + + "k8s.io/helm/pkg/helm" + "k8s.io/helm/pkg/proto/hapi/chart" + "k8s.io/helm/pkg/proto/hapi/release" + "k8s.io/helm/pkg/proto/hapi/services" +) + +func TestUpdateRelease(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + if res.Release.Name == "" { + t.Errorf("Expected release name.") + } + + if res.Release.Name != rel.Name { + t.Errorf("Updated release name does not match previous release name. Expected %s, got %s", rel.Name, res.Release.Name) + } + + if res.Release.Namespace != rel.Namespace { + t.Errorf("Expected release namespace '%s', got '%s'.", rel.Namespace, res.Release.Namespace) + } + + updated := compareStoredAndReturnedRelease(t, *rs, *res) + + if len(updated.Hooks) != 1 { + t.Fatalf("Expected 1 hook, got %d", len(updated.Hooks)) + } + if updated.Hooks[0].Manifest != manifestWithUpgradeHooks { + t.Errorf("Unexpected manifest: %v", updated.Hooks[0].Manifest) + } + + if updated.Hooks[0].Events[0] != release.Hook_POST_UPGRADE { + t.Errorf("Expected event 0 to be post upgrade") + } + + if updated.Hooks[0].Events[1] != release.Hook_PRE_UPGRADE { + t.Errorf("Expected event 0 to be pre upgrade") + } + + if len(updated.Manifest) == 0 { + t.Errorf("Expected manifest in %v", res) + } + + if res.Release.Config == nil { + t.Errorf("Got release without config: %#v", res.Release) + } else if res.Release.Config.Raw != rel.Config.Raw { + t.Errorf("Expected release values %q, got %q", rel.Config.Raw, res.Release.Config.Raw) + } + + if !strings.Contains(updated.Manifest, "---\n# Source: hello/templates/hello\nhello: world") { + t.Errorf("unexpected output: %s", updated.Manifest) + } + + if res.Release.Version != 2 { + t.Errorf("Expected release version to be %v, got %v", 2, res.Release.Version) + } + + edesc := "Upgrade complete" + if got := res.Release.Info.Description; got != edesc { + t.Errorf("Expected description %q, got %q", edesc, got) + } +} +func TestUpdateRelease_ResetValues(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + ResetValues: true, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + // This should have been unset. Config: &chart.Config{Raw: `name: value`}, + if res.Release.Config != nil && res.Release.Config.Raw != "" { + t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) + } +} + +// This is a regression test for bug found in issue #3655 +func TestUpdateRelease_ComplexReuseValues(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + + installReq := &services.InstallReleaseRequest{ + Namespace: "spaced", + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithHook)}, + }, + Values: &chart.Config{Raw: "defaultFoo: defaultBar"}, + }, + Values: &chart.Config{Raw: "foo: bar"}, + } + + fmt.Println("Running Install release with foo: bar override") + installResp, err := rs.InstallRelease(c, installReq) + if err != nil { + t.Fatal(err) + } + + rel := installResp.Release + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + Values: &chart.Config{Raw: "defaultFoo: defaultBar"}, + }, + } + + fmt.Println("Running Update release with no overrides and no reuse-values flag") + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + expect := "foo: bar" + if res.Release.Config != nil && res.Release.Config.Raw != expect { + t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Config.Raw) + } + + rel = res.Release + req = &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + Values: &chart.Config{Raw: "defaultFoo: defaultBar"}, + }, + Values: &chart.Config{Raw: "foo2: bar2"}, + ReuseValues: true, + } + + fmt.Println("Running Update release with foo2: bar2 override and reuse-values") + res, err = rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + // This should have the newly-passed overrides. + expect = "foo: bar\nfoo2: bar2\n" + if res.Release.Config != nil && res.Release.Config.Raw != expect { + t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) + } + + rel = res.Release + req = &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + Values: &chart.Config{Raw: "defaultFoo: defaultBar"}, + }, + Values: &chart.Config{Raw: "foo: baz"}, + ReuseValues: true, + } + + fmt.Println("Running Update release with foo=baz override with reuse-values flag") + res, err = rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + expect = "foo: baz\nfoo2: bar2\n" + if res.Release.Config != nil && res.Release.Config.Raw != expect { + t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Config.Raw) + } +} + +func TestUpdateRelease_ReuseValues(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + // Since reuseValues is set, this should get ignored. + Values: &chart.Config{Raw: "foo: bar\n"}, + }, + Values: &chart.Config{Raw: "name2: val2"}, + ReuseValues: true, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + // This should have been overwritten with the old value. + expect := "name: value\n" + if res.Release.Chart.Values != nil && res.Release.Chart.Values.Raw != expect { + t.Errorf("Expected chart values to be %q, got %q", expect, res.Release.Chart.Values.Raw) + } + // This should have the newly-passed overrides and any other computed values. `name: value` comes from release Config via releaseStub() + expect = "name: value\nname2: val2\n" + if res.Release.Config != nil && res.Release.Config.Raw != expect { + t.Errorf("Expected request config to be %q, got %q", expect, res.Release.Config.Raw) + } + compareStoredAndReturnedRelease(t, *rs, *res) +} + +func TestUpdateRelease_ResetReuseValues(t *testing.T) { + // This verifies that when both reset and reuse are set, reset wins. + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + ResetValues: true, + ReuseValues: true, + } + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + // This should have been unset. Config: &chart.Config{Raw: `name: value`}, + if res.Release.Config != nil && res.Release.Config.Raw != "" { + t.Errorf("Expected chart config to be empty, got %q", res.Release.Config.Raw) + } + compareStoredAndReturnedRelease(t, *rs, *res) +} + +func TestUpdateReleaseFailure(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + rs.env.KubeClient = newUpdateFailingKubeClient() + rs.Log = t.Logf + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/something", Data: []byte("hello: world")}, + }, + }, + } + + res, err := rs.UpdateRelease(c, req) + if err == nil { + t.Error("Expected failed update") + } + + if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_FAILED { + t.Errorf("Expected FAILED release. Got %d", updatedStatus) + } + + compareStoredAndReturnedRelease(t, *rs, *res) + + expectedDescription := "Upgrade \"angry-panda\" failed: Failed update in kube client" + if got := res.Release.Info.Description; got != expectedDescription { + t.Errorf("Expected description %q, got %q", expectedDescription, got) + } + + oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) + if err != nil { + t.Errorf("Expected to be able to get previous release") + } + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_DEPLOYED { + t.Errorf("Expected Deployed status on previous Release version. Got %v", oldStatus) + } +} + +func TestUpdateReleaseFailure_Force(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := namedReleaseStub("forceful-luke", release.Status_FAILED) + rs.env.Releases.Create(rel) + rs.Log = t.Logf + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/something", Data: []byte("text: 'Did you ever hear the tragedy of Darth Plagueis the Wise? I thought not. It’s not a story the Jedi would tell you. It’s a Sith legend. Darth Plagueis was a Dark Lord of the Sith, so powerful and so wise he could use the Force to influence the Midichlorians to create life... He had such a knowledge of the Dark Side that he could even keep the ones he cared about from dying. The Dark Side of the Force is a pathway to many abilities some consider to be unnatural. He became so powerful... The only thing he was afraid of was losing his power, which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew, then his apprentice killed him in his sleep. Ironic. He could save others from death, but not himself.'")}, + }, + }, + Force: true, + } + + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Errorf("Expected successful update, got %v", err) + } + + if updatedStatus := res.Release.Info.Status.Code; updatedStatus != release.Status_DEPLOYED { + t.Errorf("Expected DEPLOYED release. Got %d", updatedStatus) + } + + compareStoredAndReturnedRelease(t, *rs, *res) + + expectedDescription := "Upgrade complete" + if got := res.Release.Info.Description; got != expectedDescription { + t.Errorf("Expected description %q, got %q", expectedDescription, got) + } + + oldRelease, err := rs.env.Releases.Get(rel.Name, rel.Version) + if err != nil { + t.Errorf("Expected to be able to get previous release") + } + if oldStatus := oldRelease.Info.Status.Code; oldStatus != release.Status_DELETED { + t.Errorf("Expected Deleted status on previous Release version. Got %v", oldStatus) + } +} + +func TestUpdateReleaseNoHooks(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: &chart.Chart{ + Metadata: &chart.Metadata{Name: "hello"}, + Templates: []*chart.Template{ + {Name: "templates/hello", Data: []byte("hello: world")}, + {Name: "templates/hooks", Data: []byte(manifestWithUpgradeHooks)}, + }, + }, + } + + res, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } + + if hl := res.Release.Hooks[0].LastRun; hl != nil { + t.Errorf("Expected that no hooks were run. Got %d", hl) + } + +} + +func TestUpdateReleaseNoChanges(t *testing.T) { + c := helm.NewContext() + rs := rsFixture() + rel := releaseStub() + rs.env.Releases.Create(rel) + + req := &services.UpdateReleaseRequest{ + Name: rel.Name, + DisableHooks: true, + Chart: rel.GetChart(), + } + + _, err := rs.UpdateRelease(c, req) + if err != nil { + t.Fatalf("Failed updated: %s", err) + } +} + +func compareStoredAndReturnedRelease(t *testing.T, rs ReleaseServer, res services.UpdateReleaseResponse) *release.Release { + storedRelease, err := rs.env.Releases.Get(res.Release.Name, res.Release.Version) + if err != nil { + t.Fatalf("Expected release for %s (%v).", res.Release.Name, rs.env.Releases) + } + + if !proto.Equal(storedRelease, res.Release) { + t.Errorf("Stored release doesn't match returned Release") + } + + return storedRelease +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_version.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_version.go new file mode 100644 index 000000000..66b7137bb --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/release_version.go @@ -0,0 +1,30 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + ctx "golang.org/x/net/context" + + "k8s.io/helm/pkg/proto/hapi/services" + "k8s.io/helm/pkg/version" +) + +// GetVersion sends the server version. +func (s *ReleaseServer) GetVersion(c ctx.Context, req *services.GetVersionRequest) (*services.GetVersionResponse, error) { + v := version.GetVersionProto() + return &services.GetVersionResponse{Version: v}, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/resource_policy.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/resource_policy.go new file mode 100644 index 000000000..66da1283f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/resource_policy.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "bytes" + "strings" + + "k8s.io/helm/pkg/kube" + "k8s.io/helm/pkg/tiller/environment" +) + +// 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" + +func filterManifestsToKeep(manifests []Manifest) ([]Manifest, []Manifest) { + remaining := []Manifest{} + keep := []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[resourcePolicyAnno] + if !ok { + remaining = append(remaining, m) + continue + } + + resourcePolicyType = strings.ToLower(strings.TrimSpace(resourcePolicyType)) + if resourcePolicyType == keepPolicy { + keep = append(keep, m) + } + + } + return keep, remaining +} + +func summarizeKeptManifests(manifests []Manifest, kubeClient environment.KubeClient, namespace string) string { + var message string + for _, m := range manifests { + // check if m is in fact present from k8s client's POV. + output, err := kubeClient.Get(namespace, bytes.NewBufferString(m.Content)) + if err != nil || strings.Contains(output, kube.MissingGetHeader) { + continue + } + + details := "[" + m.Head.Kind + "] " + m.Head.Metadata.Name + "\n" + if message == "" { + message = "These resources were kept due to the resource policy:\n" + } + message = message + details + } + return message +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tiller/server.go b/src/vendor/github.com/kubernetes/helm/pkg/tiller/server.go new file mode 100644 index 000000000..818cfd47a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tiller/server.go @@ -0,0 +1,96 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tiller + +import ( + "fmt" + "log" + "strings" + + goprom "github.com/grpc-ecosystem/go-grpc-prometheus" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "k8s.io/helm/pkg/version" +) + +// maxMsgSize use 20MB as the default message size limit. +// grpc library default is 4MB +const maxMsgSize = 1024 * 1024 * 20 + +// DefaultServerOpts returns the set of default grpc ServerOption's that Tiller requires. +func DefaultServerOpts() []grpc.ServerOption { + return []grpc.ServerOption{ + grpc.MaxRecvMsgSize(maxMsgSize), + grpc.MaxSendMsgSize(maxMsgSize), + grpc.UnaryInterceptor(newUnaryInterceptor()), + grpc.StreamInterceptor(newStreamInterceptor()), + } +} + +// NewServer creates a new grpc server. +func NewServer(opts ...grpc.ServerOption) *grpc.Server { + return grpc.NewServer(append(DefaultServerOpts(), opts...)...) +} + +func newUnaryInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + if err := checkClientVersion(ctx); err != nil { + // whitelist GetVersion() from the version check + if _, m := splitMethod(info.FullMethod); m != "GetVersion" { + log.Println(err) + return nil, err + } + } + return goprom.UnaryServerInterceptor(ctx, req, info, handler) + } +} + +func newStreamInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if err := checkClientVersion(ss.Context()); err != nil { + log.Println(err) + return err + } + return goprom.StreamServerInterceptor(srv, ss, info, handler) + } +} + +func splitMethod(fullMethod string) (string, string) { + if frags := strings.Split(fullMethod, "/"); len(frags) == 3 { + return frags[1], frags[2] + } + return "unknown", "unknown" +} + +func versionFromContext(ctx context.Context) string { + if md, ok := metadata.FromIncomingContext(ctx); ok { + if v, ok := md["x-helm-api-client"]; ok && len(v) > 0 { + return v[0] + } + } + return "" +} + +func checkClientVersion(ctx context.Context) error { + clientVersion := versionFromContext(ctx) + if !version.IsCompatible(clientVersion, version.GetVersion()) { + return fmt.Errorf("incompatible versions client[%s] server[%s]", clientVersion, version.GetVersion()) + } + return nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/timeconv/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/doc.go new file mode 100644 index 000000000..235167391 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 timeconv contains utilities for converting time. + +The gRPC/Protobuf libraries contain time implementations that require conversion +to and from Go times. This library provides utilities and convenience functions +for performing conversions. +*/ +package timeconv // import "k8s.io/helm/pkg/timeconv" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv.go b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv.go new file mode 100644 index 000000000..24ff10f4e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv.go @@ -0,0 +1,58 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 timeconv + +import ( + "time" + + "github.com/golang/protobuf/ptypes/timestamp" +) + +// Now creates a timestamp.Timestamp representing the current time. +func Now() *timestamp.Timestamp { + return Timestamp(time.Now()) +} + +// Timestamp converts a time.Time to a protobuf *timestamp.Timestamp. +func Timestamp(t time.Time) *timestamp.Timestamp { + return ×tamp.Timestamp{ + Seconds: t.Unix(), + Nanos: int32(t.Nanosecond()), + } +} + +// Time converts a protobuf *timestamp.Timestamp to a time.Time. +func Time(ts *timestamp.Timestamp) time.Time { + return time.Unix(ts.Seconds, int64(ts.Nanos)) +} + +// Format formats a *timestamp.Timestamp into a string. +// +// This follows the rules for time.Time.Format(). +func Format(ts *timestamp.Timestamp, layout string) string { + return Time(ts).Format(layout) +} + +// String formats the timestamp into a user-friendly string. +// +// Currently, this uses the 'time.ANSIC' format string, but there is no guarantee +// that this will not change. +// +// This is a convenience function for formatting timestamps for user display. +func String(ts *timestamp.Timestamp) string { + return Format(ts, time.ANSIC) +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv_test.go b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv_test.go new file mode 100644 index 000000000..f673df3c9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/timeconv/timeconv_test.go @@ -0,0 +1,62 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 timeconv + +import ( + "testing" + "time" +) + +func TestNow(t *testing.T) { + now := time.Now() + ts := Now() + var drift int64 = 5 + if ts.Seconds < int64(now.Second())-drift { + t.Errorf("Unexpected time drift: %d", ts.Seconds) + } +} + +func TestTimestamp(t *testing.T) { + now := time.Now() + ts := Timestamp(now) + + if now.Unix() != ts.Seconds { + t.Errorf("Unexpected time drift: %d to %d", now.Second(), ts.Seconds) + } + + if now.Nanosecond() != int(ts.Nanos) { + t.Errorf("Unexpected nano drift: %d to %d", now.Nanosecond(), ts.Nanos) + } +} + +func TestTime(t *testing.T) { + nowts := Now() + now := Time(nowts) + + if now.Unix() != nowts.Seconds { + t.Errorf("Unexpected time drift %d", now.Unix()) + } +} + +func TestFormat(t *testing.T) { + now := time.Now() + nowts := Timestamp(now) + + if now.Format(time.ANSIC) != Format(nowts, time.ANSIC) { + t.Error("Format mismatch") + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/cfg.go b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/cfg.go new file mode 100644 index 000000000..9ce3109e1 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/cfg.go @@ -0,0 +1,82 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" +) + +// Options represents configurable options used to create client and server TLS configurations. +type Options struct { + CaCertFile string + // If either the KeyFile or CertFile is empty, ClientConfig() will not load them, + // preventing Helm from authenticating to Tiller. They are required to be non-empty + // when calling ServerConfig, otherwise an error is returned. + KeyFile string + CertFile string + // Client-only options + InsecureSkipVerify bool + // Server-only options + ClientAuth tls.ClientAuthType +} + +// ClientConfig retusn a TLS configuration for use by a Helm client. +func ClientConfig(opts Options) (cfg *tls.Config, err error) { + var cert *tls.Certificate + var pool *x509.CertPool + + if opts.CertFile != "" || opts.KeyFile != "" { + if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("could not load x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err) + } + return nil, fmt.Errorf("could not read x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err) + } + } + if !opts.InsecureSkipVerify && opts.CaCertFile != "" { + if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil { + return nil, err + } + } + + cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool} + return cfg, nil +} + +// ServerConfig returns a TLS configuration for use by the Tiller server. +func ServerConfig(opts Options) (cfg *tls.Config, err error) { + var cert *tls.Certificate + var pool *x509.CertPool + + if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("could not load x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err) + } + return nil, fmt.Errorf("could not read x509 key pair (cert: %q, key: %q): %v", opts.CertFile, opts.KeyFile, err) + } + if opts.ClientAuth >= tls.VerifyClientCertIfGiven && opts.CaCertFile != "" { + if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil { + return nil, err + } + } + + cfg = &tls.Config{MinVersion: tls.VersionTLS12, ClientAuth: opts.ClientAuth, Certificates: []tls.Certificate{*cert}, ClientCAs: pool} + return cfg, nil +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tls.go b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tls.go new file mode 100644 index 000000000..df698fd4e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tls.go @@ -0,0 +1,71 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tlsutil + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" +) + +// NewClientTLS returns tls.Config appropriate for client auth. +func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) { + cert, err := CertFromFilePair(certFile, keyFile) + if err != nil { + return nil, err + } + config := tls.Config{ + Certificates: []tls.Certificate{*cert}, + } + if caFile != "" { + cp, err := CertPoolFromFile(caFile) + if err != nil { + return nil, err + } + config.RootCAs = cp + } + return &config, nil +} + +// CertPoolFromFile returns an x509.CertPool containing the certificates +// in the given PEM-encoded file. +// Returns an error if the file could not be read, a certificate could not +// be parsed, or if the file does not contain any certificates +func CertPoolFromFile(filename string) (*x509.CertPool, error) { + b, err := ioutil.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("can't read CA file: %v", filename) + } + cp := x509.NewCertPool() + if !cp.AppendCertsFromPEM(b) { + return nil, fmt.Errorf("failed to append certificates from file: %s", filename) + } + return cp, nil +} + +// CertFromFilePair returns an tls.Certificate containing the +// certificates public/private key pair from a pair of given PEM-encoded files. +// Returns an error if the file could not be read, a certificate could not +// be parsed, or if the file does not contain any certificates +func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("can't load key pair from cert %s and key %s: %s", certFile, keyFile, err) + } + return &cert, err +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tlsutil_test.go b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tlsutil_test.go new file mode 100644 index 000000000..4f04d50ab --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/tlsutil/tlsutil_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 tlsutil + +import ( + "crypto/tls" + "path/filepath" + "testing" +) + +const tlsTestDir = "../../testdata" + +const ( + testCaCertFile = "ca.pem" + testCertFile = "crt.pem" + testKeyFile = "key.pem" +) + +func TestClientConfig(t *testing.T) { + opts := Options{ + CaCertFile: testfile(t, testCaCertFile), + CertFile: testfile(t, testCertFile), + KeyFile: testfile(t, testKeyFile), + InsecureSkipVerify: false, + } + + cfg, err := ClientConfig(opts) + if err != nil { + t.Fatalf("error building tls client config: %v", err) + } + + if got := len(cfg.Certificates); got != 1 { + t.Fatalf("expecting 1 client certificates, got %d", got) + } + if cfg.InsecureSkipVerify { + t.Fatalf("insecure skip verify mistmatch, expecting false") + } + if cfg.RootCAs == nil { + t.Fatalf("mismatch tls RootCAs, expecting non-nil") + } +} + +func TestServerConfig(t *testing.T) { + opts := Options{ + CaCertFile: testfile(t, testCaCertFile), + CertFile: testfile(t, testCertFile), + KeyFile: testfile(t, testKeyFile), + ClientAuth: tls.RequireAndVerifyClientCert, + } + + cfg, err := ServerConfig(opts) + if err != nil { + t.Fatalf("error building tls server config: %v", err) + } + if got := cfg.MinVersion; got != tls.VersionTLS12 { + t.Errorf("expecting TLS version 1.2, got %d", got) + } + if got := cfg.ClientCAs; got == nil { + t.Errorf("expecting non-nil CA pool") + } +} + +func testfile(t *testing.T, file string) (path string) { + var err error + if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil { + t.Fatalf("error getting absolute path to test file %q: %v", file, err) + } + return path +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil.go b/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil.go new file mode 100644 index 000000000..fb67708ae --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil.go @@ -0,0 +1,87 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 urlutil + +import ( + "net/url" + "path" + "path/filepath" + "strings" +) + +// URLJoin joins a base URL to one or more path components. +// +// It's like filepath.Join for URLs. If the baseURL is pathish, this will still +// perform a join. +// +// If the URL is unparsable, this returns an error. +func URLJoin(baseURL string, paths ...string) (string, error) { + u, err := url.Parse(baseURL) + if err != nil { + return "", err + } + // We want path instead of filepath because path always uses /. + all := []string{u.Path} + all = append(all, paths...) + u.Path = path.Join(all...) + return u.String(), nil +} + +// Equal normalizes two URLs and then compares for equality. +func Equal(a, b string) bool { + au, err := url.Parse(a) + if err != nil { + a = filepath.Clean(a) + b = filepath.Clean(b) + // If urls are paths, return true only if they are an exact match + return a == b + } + bu, err := url.Parse(b) + if err != nil { + return false + } + + for _, u := range []*url.URL{au, bu} { + if u.Path == "" { + u.Path = "/" + } + u.Path = filepath.Clean(u.Path) + } + return au.String() == bu.String() +} + +// ExtractHostname returns hostname from URL +func ExtractHostname(addr string) (string, error) { + u, err := url.Parse(addr) + if err != nil { + return "", err + } + return stripPort(u.Host), nil +} + +// Backported from Go 1.8 because Circle is still on 1.7 +func stripPort(hostport string) string { + colon := strings.IndexByte(hostport, ':') + if colon == -1 { + return hostport + } + if i := strings.IndexByte(hostport, ']'); i != -1 { + return strings.TrimPrefix(hostport[:i], "[") + } + return hostport[:colon] + +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil_test.go b/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil_test.go new file mode 100644 index 000000000..f0c82c0a9 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/urlutil/urlutil_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 urlutil + +import "testing" + +func TestUrlJoin(t *testing.T) { + tests := []struct { + name, url, expect string + paths []string + }{ + {name: "URL, one path", url: "http://example.com", paths: []string{"hello"}, expect: "http://example.com/hello"}, + {name: "Long URL, one path", url: "http://example.com/but/first", paths: []string{"slurm"}, expect: "http://example.com/but/first/slurm"}, + {name: "URL, two paths", url: "http://example.com", paths: []string{"hello", "world"}, expect: "http://example.com/hello/world"}, + {name: "URL, no paths", url: "http://example.com", paths: []string{}, expect: "http://example.com"}, + {name: "basepath, two paths", url: "../example.com", paths: []string{"hello", "world"}, expect: "../example.com/hello/world"}, + } + + for _, tt := range tests { + if got, err := URLJoin(tt.url, tt.paths...); err != nil { + t.Errorf("%s: error %q", tt.name, err) + } else if got != tt.expect { + t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got) + } + } +} + +func TestEqual(t *testing.T) { + for _, tt := range []struct { + a, b string + match bool + }{ + {"http://example.com", "http://example.com", true}, + {"http://example.com", "http://another.example.com", false}, + {"https://example.com", "https://example.com", true}, + {"http://example.com/", "http://example.com", true}, + {"https://example.com", "http://example.com", false}, + {"http://example.com/foo", "http://example.com/foo/", true}, + {"http://example.com/foo//", "http://example.com/foo/", true}, + {"http://example.com/./foo/", "http://example.com/foo/", true}, + {"http://example.com/bar/../foo/", "http://example.com/foo/", true}, + {"/foo", "/foo", true}, + {"/foo", "/foo/", true}, + {"/foo/.", "/foo/", true}, + } { + if tt.match != Equal(tt.a, tt.b) { + t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match) + } + } +} + +func TestExtractHostname(t *testing.T) { + tests := map[string]string{ + "http://example.com": "example.com", + "https://example.com/foo": "example.com", + "https://example.com:31337/not/with/a/bang/but/a/whimper": "example.com", + } + for start, expect := range tests { + if got, _ := ExtractHostname(start); got != expect { + t.Errorf("Got %q, expected %q", got, expect) + } + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/version/compatible.go b/src/vendor/github.com/kubernetes/helm/pkg/version/compatible.go new file mode 100644 index 000000000..735610778 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/version/compatible.go @@ -0,0 +1,65 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 // import "k8s.io/helm/pkg/version" + +import ( + "fmt" + "strings" + + "github.com/Masterminds/semver" +) + +// IsCompatible tests if a client and server version are compatible. +func IsCompatible(client, server string) bool { + if isUnreleased(client) || isUnreleased(server) { + return true + } + cv, err := semver.NewVersion(client) + if err != nil { + return false + } + sv, err := semver.NewVersion(server) + if err != nil { + return false + } + + constraint := fmt.Sprintf("^%d.%d.x", cv.Major(), cv.Minor()) + if cv.Prerelease() != "" || sv.Prerelease() != "" { + constraint = cv.String() + } + + return IsCompatibleRange(constraint, server) +} + +// 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) +} + +func isUnreleased(v string) bool { + return strings.HasSuffix(v, "unreleased") +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/version/compatible_test.go b/src/vendor/github.com/kubernetes/helm/pkg/version/compatible_test.go new file mode 100644 index 000000000..adc1c489e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/version/compatible_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 version // import "k8s.io/helm/pkg/version" + +import "testing" + +func TestIsCompatible(t *testing.T) { + tests := []struct { + client string + server 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.0.1", "v2.0.0", true}, + {"v2.0.0", "v2.1.1", true}, + {"v2.1.0", "v2.0.1", false}, + } + + for _, tt := range tests { + if IsCompatible(tt.client, tt.server) != tt.expected { + t.Errorf("expected client(%s) and server(%s) to be %v", tt.client, tt.server, tt.expected) + } + } +} + +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/src/vendor/github.com/kubernetes/helm/pkg/version/doc.go b/src/vendor/github.com/kubernetes/helm/pkg/version/doc.go new file mode 100644 index 000000000..23c9e500d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/version/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 version // import "k8s.io/helm/pkg/version" diff --git a/src/vendor/github.com/kubernetes/helm/pkg/version/version.go b/src/vendor/github.com/kubernetes/helm/pkg/version/version.go new file mode 100644 index 000000000..43f1ad40a --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/version/version.go @@ -0,0 +1,54 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 // import "k8s.io/helm/pkg/version" + +import "k8s.io/helm/pkg/proto/hapi/version" + +var ( + // Version is the current version of the Helm. + // Update this whenever making a new release. + // The version is of the format Major.Minor.Patch[-Prerelease][+BuildMetadata] + // + // Increment major number for new feature additions and behavioral changes. + // Increment minor number for bug fixes and performance enhancements. + // Increment patch number for critical fixes to existing releases. + Version = "v2.9" + + // BuildMetadata is extra build time data + BuildMetadata = "unreleased" + // GitCommit is the git sha1 + GitCommit = "" + // GitTreeState is the state of the git tree + GitTreeState = "" +) + +// GetVersion returns the semver string of the version +func GetVersion() string { + if BuildMetadata == "" { + return Version + } + return Version + "+" + BuildMetadata +} + +// GetVersionProto returns protobuf representing the version +func GetVersionProto() *version.Version { + return &version.Version{ + SemVer: GetVersion(), + GitCommit: GitCommit, + GitTreeState: GitTreeState, + } +} diff --git a/src/vendor/github.com/kubernetes/helm/pkg/version/version_test.go b/src/vendor/github.com/kubernetes/helm/pkg/version/version_test.go new file mode 100644 index 000000000..e0e4cac0f --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/pkg/version/version_test.go @@ -0,0 +1,47 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 version // import "k8s.io/helm/pkg/version" + +import "testing" +import "k8s.io/helm/pkg/proto/hapi/version" + +func TestGetVersionProto(t *testing.T) { + tests := []struct { + version string + buildMetadata string + gitCommit string + gitTreeState string + expected version.Version + }{ + {"", "", "", "", version.Version{SemVer: "", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "", "", "", version.Version{SemVer: "v1.0.0", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: ""}}, + {"v1.0.0", "79d5c5f7", "0d399baec2acda578a217d1aec8d7d707c71e44d", "clean", version.Version{SemVer: "v1.0.0+79d5c5f7", GitCommit: "0d399baec2acda578a217d1aec8d7d707c71e44d", GitTreeState: "clean"}}, + } + for _, tt := range tests { + Version = tt.version + BuildMetadata = tt.buildMetadata + GitCommit = tt.gitCommit + GitTreeState = tt.gitTreeState + if versionProto := GetVersionProto(); *versionProto != tt.expected { + t.Errorf("expected Semver(%s), GitCommit(%s) and GitTreeState(%s) to be %v", tt.expected, tt.gitCommit, tt.gitTreeState, *versionProto) + } + } + +} diff --git a/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile new file mode 100644 index 000000000..53757cd8d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile @@ -0,0 +1,26 @@ +# Copyright 2016 The Kubernetes 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. + +FROM alpine:3.3 + +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + +ENV HOME /tmp + +COPY tiller /tiller + +EXPOSE 44134 + +CMD ["/tiller"] + diff --git a/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.experimental b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.experimental new file mode 100644 index 000000000..990bcde51 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.experimental @@ -0,0 +1,26 @@ +# Copyright 2017 The Kubernetes 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. + +FROM alpine:3.3 + +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + +ENV HOME /tmp + +COPY tiller /tiller + +EXPOSE 44134 + +CMD ["/tiller", "--experimental-release"] + diff --git a/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.rudder b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.rudder new file mode 100644 index 000000000..6bb3a2d92 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/rootfs/Dockerfile.rudder @@ -0,0 +1,25 @@ +# Copyright 2017 The Kubernetes 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. + +FROM alpine:3.3 + +RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* + +ENV HOME /tmp + +COPY rudder /rudder + +EXPOSE 10001 + +CMD ["/rudder"] diff --git a/src/vendor/github.com/kubernetes/helm/rootfs/README.md b/src/vendor/github.com/kubernetes/helm/rootfs/README.md new file mode 100644 index 000000000..cf8b2e61e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/rootfs/README.md @@ -0,0 +1,27 @@ +# RootFS + +This directory stores all files that should be copied to the rootfs of a +Docker container. The files should be stored according to the correct +directory structure of the destination container. For example: + +``` +rootfs/bin -> /bin +rootfs/usr/local/share -> /usr/local/share +``` + +## Dockerfile + +A Dockerfile in the rootfs is used to build the image. Where possible, +compilation should not be done in this Dockerfile, since we are +interested in deploying the smallest possible images. + +Example: + +```Dockerfile +FROM alpine:3.2 + +COPY . / + +ENTRYPOINT ["/usr/local/bin/boot"] +``` + diff --git a/src/vendor/github.com/kubernetes/helm/scripts/completions.bash b/src/vendor/github.com/kubernetes/helm/scripts/completions.bash new file mode 100644 index 000000000..c24f3d257 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/completions.bash @@ -0,0 +1,1677 @@ +# bash completion for helm -*- shell-script -*- + +__debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__my_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__handle_reply() +{ + __debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%%=*}" + __index_of_word "${flag}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + COMPREPLY=() + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zfs completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + __ltrim_colon_completions "$cur" +} + +# The arguments should be in the form "ext1|ext2|extn" +__handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__handle_flag() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __debug "${FUNCNAME[0]}: looking for ${flagname}" + if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + + # skip the argument to a two word flag + if __contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__handle_noun() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__handle_command() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_$(basename "${words[c]//:/__}")" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F $next_command >/dev/null && $next_command +} + +__handle_word() +{ + if [[ $c -ge $cword ]]; then + __handle_reply + return + fi + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __handle_flag + elif __contains_word "${words[c]}" "${commands[@]}"; then + __handle_command + elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then + __handle_command + else + __handle_noun + fi + __handle_word +} + +_helm_completion() +{ + last_command="helm_completion" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("bash") + must_have_one_noun+=("zsh") + noun_aliases=() +} + +_helm_create() +{ + last_command="helm_create" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--starter=") + two_word_flags+=("-p") + local_nonpersistent_flags+=("--starter=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_delete() +{ + last_command="helm_delete" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--purge") + local_nonpersistent_flags+=("--purge") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_build() +{ + last_command="helm_dependency_build" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_list() +{ + last_command="helm_dependency_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_update() +{ + last_command="helm_dependency_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--skip-refresh") + local_nonpersistent_flags+=("--skip-refresh") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency() +{ + last_command="helm_dependency" + commands=() + commands+=("build") + commands+=("list") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_fetch() +{ + last_command="helm_fetch" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--destination=") + two_word_flags+=("-d") + local_nonpersistent_flags+=("--destination=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--prov") + local_nonpersistent_flags+=("--prov") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--untar") + local_nonpersistent_flags+=("--untar") + flags+=("--untardir=") + local_nonpersistent_flags+=("--untardir=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_hooks() +{ + last_command="helm_get_hooks" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_manifest() +{ + last_command="helm_get_manifest" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_values() +{ + last_command="helm_get_values" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--all") + flags+=("-a") + local_nonpersistent_flags+=("--all") + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get() +{ + last_command="helm_get" + commands=() + commands+=("hooks") + commands+=("manifest") + commands+=("values") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_history() +{ + last_command="helm_history" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--max=") + local_nonpersistent_flags+=("--max=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_home() +{ + last_command="helm_home" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_init() +{ + last_command="helm_init" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--canary-image") + local_nonpersistent_flags+=("--canary-image") + flags+=("--client-only") + flags+=("-c") + local_nonpersistent_flags+=("--client-only") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--local-repo-url=") + local_nonpersistent_flags+=("--local-repo-url=") + flags+=("--net-host") + local_nonpersistent_flags+=("--net-host") + flags+=("--service-account=") + local_nonpersistent_flags+=("--service-account=") + flags+=("--skip-refresh") + local_nonpersistent_flags+=("--skip-refresh") + flags+=("--stable-repo-url=") + local_nonpersistent_flags+=("--stable-repo-url=") + flags+=("--tiller-image=") + two_word_flags+=("-i") + local_nonpersistent_flags+=("--tiller-image=") + flags+=("--tiller-tls") + local_nonpersistent_flags+=("--tiller-tls") + flags+=("--tiller-tls-cert=") + local_nonpersistent_flags+=("--tiller-tls-cert=") + flags+=("--tiller-tls-key=") + local_nonpersistent_flags+=("--tiller-tls-key=") + flags+=("--tiller-tls-verify") + local_nonpersistent_flags+=("--tiller-tls-verify") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--upgrade") + local_nonpersistent_flags+=("--upgrade") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect_chart() +{ + last_command="helm_inspect_chart" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect_values() +{ + last_command="helm_inspect_values" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect() +{ + last_command="helm_inspect" + commands=() + commands+=("chart") + commands+=("values") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_install() +{ + last_command="helm_install" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--name=") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--name=") + flags+=("--name-template=") + local_nonpersistent_flags+=("--name-template=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--replace") + local_nonpersistent_flags+=("--replace") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--set=") + local_nonpersistent_flags+=("--set=") + flags+=("--set-string=") + local_nonpersistent_flags+=("--set-string=") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--values=") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--values=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_lint() +{ + last_command="helm_lint" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--strict") + local_nonpersistent_flags+=("--strict") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_list() +{ + last_command="helm_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--all") + local_nonpersistent_flags+=("--all") + flags+=("--date") + flags+=("-d") + local_nonpersistent_flags+=("--date") + flags+=("--deleted") + local_nonpersistent_flags+=("--deleted") + flags+=("--deleting") + local_nonpersistent_flags+=("--deleting") + flags+=("--deployed") + local_nonpersistent_flags+=("--deployed") + flags+=("--failed") + local_nonpersistent_flags+=("--failed") + flags+=("--max=") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--max=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--offset=") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--offset=") + flags+=("--reverse") + flags+=("-r") + local_nonpersistent_flags+=("--reverse") + flags+=("--short") + flags+=("-q") + local_nonpersistent_flags+=("--short") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_package() +{ + last_command="helm_package" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--destination=") + two_word_flags+=("-d") + local_nonpersistent_flags+=("--destination=") + flags+=("--key=") + local_nonpersistent_flags+=("--key=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--save") + local_nonpersistent_flags+=("--save") + flags+=("--sign") + local_nonpersistent_flags+=("--sign") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_install() +{ + last_command="helm_plugin_install" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_list() +{ + last_command="helm_plugin_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_remove() +{ + last_command="helm_plugin_remove" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_update() +{ + last_command="helm_plugin_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin() +{ + last_command="helm_plugin" + commands=() + commands+=("install") + commands+=("list") + commands+=("remove") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_add() +{ + last_command="helm_repo_add" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--no-update") + local_nonpersistent_flags+=("--no-update") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_index() +{ + last_command="helm_repo_index" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--merge=") + local_nonpersistent_flags+=("--merge=") + flags+=("--url=") + local_nonpersistent_flags+=("--url=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_list() +{ + last_command="helm_repo_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_remove() +{ + last_command="helm_repo_remove" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_update() +{ + last_command="helm_repo_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo() +{ + last_command="helm_repo" + commands=() + commands+=("add") + commands+=("index") + commands+=("list") + commands+=("remove") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_reset() +{ + last_command="helm_reset" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--force") + flags+=("-f") + local_nonpersistent_flags+=("--force") + flags+=("--remove-helm-home") + local_nonpersistent_flags+=("--remove-helm-home") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_rollback() +{ + last_command="helm_rollback" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--recreate-pods") + local_nonpersistent_flags+=("--recreate-pods") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_search() +{ + last_command="helm_search" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--regexp") + flags+=("-r") + local_nonpersistent_flags+=("--regexp") + flags+=("--version=") + two_word_flags+=("-v") + local_nonpersistent_flags+=("--version=") + flags+=("--versions") + flags+=("-l") + local_nonpersistent_flags+=("--versions") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_serve() +{ + last_command="helm_serve" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--address=") + local_nonpersistent_flags+=("--address=") + flags+=("--repo-path=") + local_nonpersistent_flags+=("--repo-path=") + flags+=("--url=") + local_nonpersistent_flags+=("--url=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_status() +{ + last_command="helm_status" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_test() +{ + last_command="helm_test" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--cleanup") + local_nonpersistent_flags+=("--cleanup") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_upgrade() +{ + last_command="helm_upgrade" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--disable-hooks") + local_nonpersistent_flags+=("--disable-hooks") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") + flags+=("--install") + flags+=("-i") + local_nonpersistent_flags+=("--install") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--recreate-pods") + local_nonpersistent_flags+=("--recreate-pods") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--reset-values") + local_nonpersistent_flags+=("--reset-values") + flags+=("--reuse-values") + local_nonpersistent_flags+=("--reuse-values") + flags+=("--set=") + local_nonpersistent_flags+=("--set=") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--values=") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--values=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_verify() +{ + last_command="helm_verify" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_version() +{ + last_command="helm_version" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--client") + flags+=("-c") + local_nonpersistent_flags+=("--client") + flags+=("--server") + flags+=("-s") + local_nonpersistent_flags+=("--server") + flags+=("--short") + local_nonpersistent_flags+=("--short") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm() +{ + last_command="helm" + commands=() + commands+=("completion") + commands+=("create") + commands+=("delete") + commands+=("dependency") + commands+=("fetch") + commands+=("get") + commands+=("history") + commands+=("home") + commands+=("init") + commands+=("inspect") + commands+=("install") + commands+=("lint") + commands+=("list") + commands+=("package") + commands+=("plugin") + commands+=("repo") + commands+=("reset") + commands+=("rollback") + commands+=("search") + commands+=("serve") + commands+=("status") + commands+=("test") + commands+=("upgrade") + commands+=("verify") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--home=") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_helm() +{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __my_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("helm") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_helm helm +else + complete -o default -o nospace -F __start_helm helm +fi + +# ex: ts=4 sw=4 et filetype=sh diff --git a/src/vendor/github.com/kubernetes/helm/scripts/coverage.sh b/src/vendor/github.com/kubernetes/helm/scripts/coverage.sh new file mode 100755 index 000000000..1863d5835 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/coverage.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. + +set -euo pipefail + +covermode=${COVERMODE:-atomic} +coverdir=$(mktemp -d /tmp/coverage.XXXXXXXXXX) +profile="${coverdir}/cover.out" + +hash goveralls 2>/dev/null || go get github.com/mattn/goveralls +hash godir 2>/dev/null || go get github.com/Masterminds/godir + +generate_cover_data() { + for d in $(godir) ; do + ( + local output="${coverdir}/${d//\//-}.cover" + go test -coverprofile="${output}" -covermode="$covermode" "$d" + ) + done + + echo "mode: $covermode" >"$profile" + grep -h -v "^mode:" "$coverdir"/*.cover >>"$profile" +} + +push_to_coveralls() { + goveralls -coverprofile="${profile}" -service=circle-ci +} + +generate_cover_data +go tool cover -func "${profile}" + +case "${1-}" in + --html) + go tool cover -html "${profile}" + ;; + --coveralls) + push_to_coveralls + ;; +esac + diff --git a/src/vendor/github.com/kubernetes/helm/scripts/get b/src/vendor/github.com/kubernetes/helm/scripts/get new file mode 100755 index 000000000..943ca4158 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/get @@ -0,0 +1,232 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. + +# The install script is based off of the MIT-licensed script from glide, +# the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get + +PROJECT_NAME="helm" + +: ${HELM_INSTALL_DIR:="/usr/local/bin"} + +# initArch discovers the architecture for this system. +initArch() { + ARCH=$(uname -m) + case $ARCH in + armv5*) ARCH="armv5";; + armv6*) ARCH="armv6";; + armv7*) ARCH="armv7";; + aarch64) ARCH="arm64";; + x86) ARCH="386";; + x86_64) ARCH="amd64";; + i686) ARCH="386";; + i386) ARCH="386";; + esac +} + +# initOS discovers the operating system for this system. +initOS() { + OS=$(echo `uname`|tr '[:upper:]' '[:lower:]') + + case "$OS" in + # Minimalist GNU for Windows + mingw*) OS='windows';; + esac +} + +# runs the given command as root (detects if we are root already) +runAsRoot() { + local CMD="$*" + + if [ $EUID -ne 0 ]; then + CMD="sudo $CMD" + fi + + $CMD +} + +# verifySupported checks that the os/arch combination is supported for +# binary builds. +verifySupported() { + local supported="darwin-386\ndarwin-amd64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-ppc64le\nwindows-386\nwindows-amd64" + if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then + echo "No prebuilt binary for ${OS}-${ARCH}." + echo "To build from source, go to https://github.com/kubernetes/helm" + exit 1 + fi + + if ! type "curl" > /dev/null && ! type "wget" > /dev/null; then + echo "Either curl or wget is required" + exit 1 + fi +} + +# checkDesiredVersion checks if the desired version is available. +checkDesiredVersion() { + # Use the GitHub releases webpage for the project to find the desired version for this project. + local release_url="https://github.com/kubernetes/helm/releases/${DESIRED_VERSION:-latest}" + if type "curl" > /dev/null; then + TAG=$(curl -SsL $release_url | awk '/\/tag\//' | grep -v no-underline | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}') + elif type "wget" > /dev/null; then + TAG=$(wget -q -O - $release_url | awk '/\/tag\//' | grep -v no-underline | cut -d '"' -f 2 | awk '{n=split($NF,a,"/");print a[n]}' | awk 'a !~ $0{print}; {a=$0}') + fi + if [ "x$TAG" == "x" ]; then + echo "Cannot determine ${DESIRED_VERSION} tag." + exit 1 + fi +} + +# checkHelmInstalledVersion checks which version of helm is installed and +# if it needs to be changed. +checkHelmInstalledVersion() { + if [[ -f "${HELM_INSTALL_DIR}/${PROJECT_NAME}" ]]; then + local version=$(helm version | grep '^Client' | cut -d'"' -f2) + if [[ "$version" == "$TAG" ]]; then + echo "Helm ${version} is already ${DESIRED_VERSION:-latest}" + return 0 + else + echo "Helm ${TAG} is available. Changing from version ${version}." + return 1 + fi + else + return 1 + fi +} + +# downloadFile downloads the latest binary package and also the checksum +# for that binary. +downloadFile() { + HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz" + DOWNLOAD_URL="https://kubernetes-helm.storage.googleapis.com/$HELM_DIST" + CHECKSUM_URL="$DOWNLOAD_URL.sha256" + HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)" + HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST" + HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256" + echo "Downloading $DOWNLOAD_URL" + if type "curl" > /dev/null; then + curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE" + elif type "wget" > /dev/null; then + wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL" + fi + if type "curl" > /dev/null; then + curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE" + elif type "wget" > /dev/null; then + wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL" + fi +} + +# installFile verifies the SHA256 for the file, then unpacks and +# installs it. +installFile() { + HELM_TMP="$HELM_TMP_ROOT/$PROJECT_NAME" + local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}') + local expected_sum=$(cat ${HELM_SUM_FILE}) + if [ "$sum" != "$expected_sum" ]; then + echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting." + exit 1 + fi + + mkdir -p "$HELM_TMP" + tar xf "$HELM_TMP_FILE" -C "$HELM_TMP" + HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/$PROJECT_NAME" + echo "Preparing to install into ${HELM_INSTALL_DIR}" + runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR" +} + +# fail_trap is executed if an error occurs. +fail_trap() { + result=$? + if [ "$result" != "0" ]; then + if [[ -n "$INPUT_ARGUMENTS" ]]; then + echo "Failed to install $PROJECT_NAME with the arguments provided: $INPUT_ARGUMENTS" + help + else + echo "Failed to install $PROJECT_NAME" + fi + echo -e "\tFor support, go to https://github.com/kubernetes/helm." + fi + cleanup + exit $result +} + +# testVersion tests the installed client to make sure it is working. +testVersion() { + set +e + echo "$PROJECT_NAME installed into $HELM_INSTALL_DIR/$PROJECT_NAME" + HELM="$(which $PROJECT_NAME)" + if [ "$?" = "1" ]; then + echo "$PROJECT_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?' + exit 1 + fi + set -e + echo "Run '$PROJECT_NAME init' to configure $PROJECT_NAME." +} + +# help provides possible cli installation arguments +help () { + echo "Accepted cli arguments are:" + echo -e "\t[--help|-h ] ->> prints this help" + echo -e "\t[--version|-v ] . When not defined it defaults to latest" + echo -e "\te.g. --version v2.4.0 or -v latest" +} + +# cleanup temporary files to avoid https://github.com/kubernetes/helm/issues/2977 +cleanup() { + if [[ -d "${HELM_TMP_ROOT:-}" ]]; then + rm -rf "$HELM_TMP_ROOT" + fi +} + +# Execution + +#Stop execution on any error +trap "fail_trap" EXIT +set -e + +# Parsing input arguments (if any) +export INPUT_ARGUMENTS="${@}" +set -u +while [[ $# -gt 0 ]]; do + case $1 in + '--version'|-v) + shift + if [[ $# -ne 0 ]]; then + export DESIRED_VERSION="${1}" + else + echo -e "Please provide the desired version. e.g. --version v2.4.0 or -v latest" + exit 0 + fi + ;; + '--help'|-h) + help + exit 0 + ;; + *) exit 1 + ;; + esac + shift +done +set +u + +initArch +initOS +verifySupported +checkDesiredVersion +if ! checkHelmInstalledVersion; then + downloadFile + installFile +fi +testVersion +cleanup diff --git a/src/vendor/github.com/kubernetes/helm/scripts/sync-repo.sh b/src/vendor/github.com/kubernetes/helm/scripts/sync-repo.sh new file mode 100755 index 000000000..3795b1a7c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/sync-repo.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. + +# Bash 'Strict Mode' +# http://redsymbol.net/articles/unofficial-bash-strict-mode +set -euo pipefail +IFS=$'\n\t' + +# Helper Functions ------------------------------------------------------------- + +# Display error message and exit +error_exit() { + echo "error: ${1:-"unknown error"}" 1>&2 + exit 1 +} + +# Checks if a command exists. Returns 1 or 0 +command_exists() { + hash "${1}" 2>/dev/null +} + +# Program Functions ------------------------------------------------------------ + +verify_prereqs() { + echo "Verifying Prerequisites...." + if command_exists gsutil; then + echo "Thumbs up! Looks like you have gsutil. Let's continue." + else + error_exit "Couldn't find gsutil. Bailing out." + fi +} + +confirm() { + case $response in + [yY][eE][sS]|[yY]) + true + ;; + *) + false + ;; + esac +} + +# Main ------------------------------------------------------------------------- + +main() { + if [ "$#" -ne 2 ]; then + error_exit "Illegal number of parameters. You must pass in local directory path and a GCS bucket name" + fi + + echo "Getting ready to sync your local directory ($1) to a remote repository at gs://$2" + + verify_prereqs + + # dry run of the command + gsutil rsync -d -n $1 gs://$2 + + read -p "Are you sure you would like to continue with these changes? [y/N]} " confirm + if [[ $confirm =~ [yY](es)* ]]; then + gsutil rsync -d $1 gs://$2 + else + error_exit "Discontinuing sync process." + fi + + echo "Your remote chart repository now matches the contents of the $1 directory!" + +} + +main "${@:-}" diff --git a/src/vendor/github.com/kubernetes/helm/scripts/update-docs.sh b/src/vendor/github.com/kubernetes/helm/scripts/update-docs.sh new file mode 100755 index 000000000..e014b537e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/update-docs.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Copyright 2017 The Kubernetes Authors All rights reserved. +# +# 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. + +set -euo pipefail + +source scripts/util.sh + +if LANG=C sed --help 2>&1 | grep -q GNU; then + SED="sed" +elif which gsed &>/dev/null; then + SED="gsed" +else + echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 + exit 1 +fi + +kube::util::ensure-temp-dir + +export HELM_NO_PLUGINS=1 + +# Reset Helm Home because it is used in the generation of docs. +OLD_HELM_HOME=${HELM_HOME:-} +HELM_HOME="$HOME/.helm" +bin/helm init --client-only +mkdir -p ${KUBE_TEMP}/docs/helm +bin/helm docs --dir ${KUBE_TEMP}/docs/helm +HELM_HOME=$OLD_HELM_HOME + +FILES=$(find ${KUBE_TEMP} -type f) + +${SED} -i -e "s:${HOME}:~:" ${FILES} + +for i in ${FILES}; do + ret=0 + truepath=$(echo ${i} | ${SED} "s:${KUBE_TEMP}/::") + diff -NauprB -I 'Auto generated' "${i}" "${truepath}" > /dev/null || ret=$? + if [[ $ret -ne 0 ]]; then + echo "${truepath} changed. Updating.." + cp "${i}" "${truepath}" + fi +done diff --git a/src/vendor/github.com/kubernetes/helm/scripts/util.sh b/src/vendor/github.com/kubernetes/helm/scripts/util.sh new file mode 100644 index 000000000..09caaf972 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/util.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. + +set -euo pipefail + +# Example: kube::util::trap_add 'echo "in trap DEBUG"' DEBUG +# See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal +kube::util::trap_add() { + local trap_add_cmd + trap_add_cmd=$1 + shift + + for trap_add_name in "$@"; do + local existing_cmd + local new_cmd + + # Grab the currently defined trap commands for this trap + existing_cmd=`trap -p "${trap_add_name}" | awk -F"'" '{print $2}'` + + if [[ -z "${existing_cmd}" ]]; then + new_cmd="${trap_add_cmd}" + else + new_cmd="${existing_cmd};${trap_add_cmd}" + fi + + # Assign the test + trap "${new_cmd}" "${trap_add_name}" + done +} + +# Opposite of kube::util::ensure-temp-dir() +kube::util::cleanup-temp-dir() { + rm -rf "${KUBE_TEMP}" +} + +# Create a temp dir that'll be deleted at the end of this bash session. +# +# Vars set: +# KUBE_TEMP +kube::util::ensure-temp-dir() { + if [[ -z ${KUBE_TEMP-} ]]; then + KUBE_TEMP=$(mktemp -d 2>/dev/null || mktemp -d -t kubernetes.XXXXXX) + kube::util::trap_add kube::util::cleanup-temp-dir EXIT + fi +} diff --git a/src/vendor/github.com/kubernetes/helm/scripts/validate-go.sh b/src/vendor/github.com/kubernetes/helm/scripts/validate-go.sh new file mode 100755 index 000000000..2ecf5dfb3 --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/validate-go.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. +set -euo pipefail + +exit_code=0 + +if ! hash gometalinter.v1 2>/dev/null ; then + go get -u gopkg.in/alecthomas/gometalinter.v1 + gometalinter.v1 --install +fi + +echo +echo "==> Running static validations <==" +# Run linters that should return errors +gometalinter.v1 \ + --disable-all \ + --enable deadcode \ + --severity deadcode:error \ + --enable gofmt \ + --enable ineffassign \ + --enable misspell \ + --enable vet \ + --tests \ + --vendor \ + --deadline 60s \ + ./... || exit_code=1 + +echo +echo "==> Running linters <==" +# Run linters that should return warnings +gometalinter.v1 \ + --disable-all \ + --enable golint \ + --vendor \ + --skip proto \ + --deadline 60s \ + ./... || : + +exit $exit_code diff --git a/src/vendor/github.com/kubernetes/helm/scripts/validate-license.sh b/src/vendor/github.com/kubernetes/helm/scripts/validate-license.sh new file mode 100755 index 000000000..fe7ec481b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/validate-license.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# Copyright 2016 The Kubernetes Authors All rights reserved. +# +# 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. +set -euo pipefail +IFS=$'\n\t' + +find_files() { + find . -not \( \ + \( \ + -wholename './vendor' \ + -o -wholename './pkg/proto' \ + -o -wholename '*testdata*' \ + \) -prune \ + \) \ + \( -name '*.go' -o -name '*.sh' -o -name 'Dockerfile' \) +} + +failed=($(find_files | xargs grep -L 'Licensed under the Apache License, Version 2.0 (the "License");')) +if (( ${#failed[@]} > 0 )); then + echo "Some source files are missing license headers." + for f in "${failed[@]}"; do + echo " $f" + done + exit 1 +fi diff --git a/src/vendor/github.com/kubernetes/helm/scripts/verify-docs.sh b/src/vendor/github.com/kubernetes/helm/scripts/verify-docs.sh new file mode 100755 index 000000000..b0b799eac --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/scripts/verify-docs.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Copyright 2017 The Kubernetes Authors All rights reserved. +# +# 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. + +set -euo pipefail + +source scripts/util.sh + +if LANG=C sed --help 2>&1 | grep -q GNU; then + SED="sed" +elif which gsed &>/dev/null; then + SED="gsed" +else + echo "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2 + exit 1 +fi + +kube::util::ensure-temp-dir + +export HELM_NO_PLUGINS=1 + +# Reset Helm Home because it is used in the generation of docs. +OLD_HELM_HOME=${HELM_HOME:-} +HELM_HOME="$HOME/.helm" +bin/helm init --client-only +mkdir -p ${KUBE_TEMP}/docs/helm +bin/helm docs --dir ${KUBE_TEMP}/docs/helm +HELM_HOME=$OLD_HELM_HOME + + +FILES=$(find ${KUBE_TEMP} -type f) + +${SED} -i -e "s:${HOME}:~:" ${FILES} +ret=0 +for i in ${FILES}; do + diff -NauprB -I 'Auto generated' ${i} $(echo ${i} | ${SED} "s:${KUBE_TEMP}/::") || ret=$? +done +if [[ $ret -eq 0 ]]; then + echo "helm docs up to date." +else + echo "helm docs are out of date. Please run \"make docs\"" + exit 1 +fi diff --git a/src/vendor/github.com/kubernetes/helm/testdata/ca.pem b/src/vendor/github.com/kubernetes/helm/testdata/ca.pem new file mode 100644 index 000000000..79d854a8d --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/testdata/ca.pem @@ -0,0 +1,35 @@ +-----BEGIN CERTIFICATE----- +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 +mlK3dw== +-----END CERTIFICATE----- diff --git a/src/vendor/github.com/kubernetes/helm/testdata/crt.pem b/src/vendor/github.com/kubernetes/helm/testdata/crt.pem new file mode 100644 index 000000000..226b3a71b --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/testdata/crt.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCDCCAvCgAwIBAgIJAMADBPQSkgPMMA0GCSqGSIb3DQEBCwUAMF0xCzAJBgNV +BAYTAlVTMQswCQYDVQQIEwJDTzEQMA4GA1UEBxMHQm91bGRlcjEPMA0GA1UEChMG +VGlsbGVyMQ8wDQYDVQQLEwZUaWxsZXIxDTALBgNVBAMTBEhlbG0wHhcNMTcwNDA0 +MTYwNzM4WhcNMTgwNDA0MTYwNzM4WjARMQ8wDQYDVQQDEwZjbGllbnQwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDnyxxZtTKZLOYyEDmo1pY8m6A1tot1 +UuiSxtwp4rNYIaVyCbpdKrNr68q6dRs40vEWGfH415OzFjK3RpbzdSqeB4U+toUl +bIYjf9N4/ZrAjqBO+Xd+JKUkhKcZIbMJHb2kOzqOL7LSWlKcyGCY/x7Tj4qdka9R +QiXB7zVUEqcTa13A+/rdrPWgzK/xGIYh7cCehOixxXSmfcCHR573BDC5j6s9KozA +T84obBgEgsVgu1+d+n1D+cqAr7ppSZTMWs/f+DwwJG/VWblIYsCuN3yNHLaYsL9M +MTw1ogulcRmFNyw9CSXdyVCxGjh/++sQ2f47TpadI+IzknrBkfPL7+zt2IyaORch +uGsdX+IwQl3aZjayMx7YjYSSbQIfpSF9y4KVPz4RHEUn10hsX/8qXPzitbXVLh7p +b9lUMGPHchTm/dd+oZAbL1TUIJQOJn2vGDMKsuBswBg12YNdhAp55EDZx54CCiM2 +sRtlVNTpkatr7Rvd5CDFuLAzwHnrEKTy5EOUrS9aYzqKaGOrMI+k1OCTp3LwLdPX +d7OV9+ZuSLHX6gvF4uAucK8HLp3Visj0GeWL7OzpTv2imjNX5C1wPH7UR6UsF+dg +bzqZOP63e5WR1eEqth5ieE+5jQ8nxvPF//qKHQNlgbD93Y3B3UfmjrnP1chgqFn9 +IAXWFsyZ7I8bXQIDAQABoxcwFTATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG +9w0BAQsFAAOCAgEAPIXMQOAgb2VlfS59HrvpdqbIapIfs/xBgPKlNfwNO3UpSYyq +XVK1xekLI+mEE639YP/oSc7HX2OrJi3SX5Ofzs0s9h+BNTXPqw1ju+G34cF8MKc0 +acynThdcI4eZGc2fKSAIw6RN7iIln74Sf4MNmEuQu6Dnq4QkZKAWtnY7Uq5ooFJS +JA+Joqif8SvEvMgq02XdUhjijlBAanxI/xp64k37k18+pHAxcS22HzrjwDQ4ELqY +gBq9g20JYXoUxjBFUfj+cxBx+LBKfPVTpcbicI4wwP4a2BA6LDUHgcnSMhle1zeq +pHuOIOT6XqYLhO0Yr7WRG9Yzuxs0GV4TH+FlDpDHWL8XG0gjDUZ/2viPlKBr+FoN +inW8jqQ2NYMzYF9zHNzXVGK+5oyH4Y7r/8WxQLfdSR/5S1DXPLSkzkYbduHf9UmF +Dvh6NrCGU0UxypA1NvF5o11cnTQ22GPywVSc0ILKWDRlu8DiGq71bYQu8hTTkTnb +2hOr5JHcGaloms7WM3q0hc2PIhwYXw2V3b9I9lbnvv3Y/yKPNN7IzU5No6siRuIH +paj83V0flMWj1EqJMDxk9ECHgDyl/1ftgJVx1G/f/+UnXoRdR2kFqVVeJTeSIZi7 +dSsAOIMN/weZMZF55Q61vgUgYXKp4g2/Zk8BJn0cx9pjEMIw/pc7Eq1x/R8= +-----END CERTIFICATE----- diff --git a/src/vendor/github.com/kubernetes/helm/testdata/key.pem b/src/vendor/github.com/kubernetes/helm/testdata/key.pem new file mode 100644 index 000000000..8b2cde82e --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/testdata/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/src/vendor/github.com/kubernetes/helm/versioning.mk b/src/vendor/github.com/kubernetes/helm/versioning.mk new file mode 100644 index 000000000..d1c348f9c --- /dev/null +++ b/src/vendor/github.com/kubernetes/helm/versioning.mk @@ -0,0 +1,57 @@ +MUTABLE_VERSION := canary + +GIT_COMMIT = $(shell git rev-parse HEAD) +GIT_SHA = $(shell git rev-parse --short HEAD) +GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) +GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") + +ifdef VERSION + DOCKER_VERSION = $(VERSION) + BINARY_VERSION = $(VERSION) +endif + +DOCKER_VERSION ?= git-${GIT_SHA} +BINARY_VERSION ?= ${GIT_TAG} + +# Only set Version if building a tag or VERSION is set +ifneq ($(BINARY_VERSION),) + LDFLAGS += -X k8s.io/helm/pkg/version.Version=${BINARY_VERSION} +endif + +# Clear the "unreleased" string in BuildMetadata +ifneq ($(GIT_TAG),) + LDFLAGS += -X k8s.io/helm/pkg/version.BuildMetadata= +endif +LDFLAGS += -X k8s.io/helm/pkg/version.GitCommit=${GIT_COMMIT} +LDFLAGS += -X k8s.io/helm/pkg/version.GitTreeState=${GIT_DIRTY} + +IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${DOCKER_VERSION} +IMAGE_RUDDER := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME_RUDDER}:${DOCKER_VERSION} +MUTABLE_IMAGE := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME}:${MUTABLE_VERSION} +MUTABLE_IMAGE_RUDDER := ${DOCKER_REGISTRY}/${IMAGE_PREFIX}/${SHORT_NAME_RUDDER}:${MUTABLE_VERSION} + +DOCKER_PUSH = docker push +ifeq ($(DOCKER_REGISTRY),gcr.io) + DOCKER_PUSH = gcloud docker push +endif + +info: + @echo "Version: ${VERSION}" + @echo "Git Tag: ${GIT_TAG}" + @echo "Git Commit: ${GIT_COMMIT}" + @echo "Git Tree State: ${GIT_DIRTY}" + @echo "Docker Version: ${DOCKER_VERSION}" + @echo "Registry: ${DOCKER_REGISTRY}" + @echo "Immutable Image: ${IMAGE}" + @echo "Mutable Image: ${MUTABLE_IMAGE}" + +.PHONY: docker-push +docker-push: docker-mutable-push docker-immutable-push + +.PHONY: docker-immutable-push +docker-immutable-push: + ${DOCKER_PUSH} ${IMAGE} + +.PHONY: docker-mutable-push +docker-mutable-push: + ${DOCKER_PUSH} ${MUTABLE_IMAGE}