diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 1942471bf..00f15cd2f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -39,7 +39,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # pinv3.23.1 + uses: github/codeql-action/init@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # pinv3.24.10 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # pinv3.23.1 + uses: github/codeql-action/autobuild@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # pinv3.24.10 # â„šī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -64,4 +64,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # pinv3.23.1 + uses: github/codeql-action/analyze@4355270be187e1b672a7a1c7c7bae5afdc1ab94a # pinv3.24.10 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index dcc982dc9..bedc0937a 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -13,10 +13,10 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # pin@v4.1.1 - name: Setup Go - uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # pin@5.0.0 with: go-version: "1.21" - name: golangci-lint - uses: golangci/golangci-lint-action@3a919529898de77ec3da873e3063ca4b10e7f5cc #pin@3.7.0 + uses: golangci/golangci-lint-action@3cfe3a4abbb849e10058ce4af15d205b6da42804 #pin@4.0.0 with: version: v1.55 diff --git a/.gitignore b/.gitignore index ef9806616..cdb68ceb5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ .idea/ .vimrc .vscode/ +.devcontainer/ _dist/ _dist_versions/ bin/ diff --git a/cmd/helm/completion.go b/cmd/helm/completion.go index 93b9e8eab..9f31ea741 100644 --- a/cmd/helm/completion.go +++ b/cmd/helm/completion.go @@ -103,7 +103,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: bashCompDesc, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return runCompletionBash(out, cmd) }, } @@ -115,7 +115,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: zshCompDesc, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return runCompletionZsh(out, cmd) }, } @@ -127,7 +127,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: fishCompDesc, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return runCompletionFish(out, cmd) }, } @@ -139,7 +139,7 @@ func newCompletionCmd(out io.Writer) *cobra.Command { Long: powershellCompDesc, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { return runCompletionPowershell(out, cmd) }, } diff --git a/cmd/helm/create.go b/cmd/helm/create.go index fe5cc540a..b79752efb 100644 --- a/cmd/helm/create.go +++ b/cmd/helm/create.go @@ -64,7 +64,7 @@ func newCreateCmd(out io.Writer) *cobra.Command { Short: "create a new chart with the given name", Long: createDesc, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // Allow file completion when completing the argument for the name // which could be a path @@ -73,7 +73,7 @@ func newCreateCmd(out io.Writer) *cobra.Command { // No more completions, so disable file completion return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.name = args[0] o.starterDir = helmpath.DataPath("starters") return o.run(out) diff --git a/cmd/helm/dependency.go b/cmd/helm/dependency.go index 03874742c..228c73c80 100644 --- a/cmd/helm/dependency.go +++ b/cmd/helm/dependency.go @@ -106,7 +106,7 @@ func newDependencyListCmd(out io.Writer) *cobra.Command { Short: "list the dependencies for the given chart", Long: dependencyListDesc, Args: require.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { chartpath := "." if len(args) > 0 { chartpath = filepath.Clean(args[0]) diff --git a/cmd/helm/dependency_build.go b/cmd/helm/dependency_build.go index 1ee46d3d2..2cf0c6c81 100644 --- a/cmd/helm/dependency_build.go +++ b/cmd/helm/dependency_build.go @@ -49,7 +49,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm Short: "rebuild the charts/ directory based on the Chart.lock file", Long: dependencyBuildDesc, Args: require.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { chartpath := "." if len(args) > 0 { chartpath = filepath.Clean(args[0]) diff --git a/cmd/helm/dependency_update.go b/cmd/helm/dependency_update.go index ad0188f17..cb6e9c0cc 100644 --- a/cmd/helm/dependency_update.go +++ b/cmd/helm/dependency_update.go @@ -52,7 +52,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com Short: "update charts/ based on the contents of Chart.yaml", Long: dependencyUpDesc, Args: require.MaximumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { chartpath := "." if len(args) > 0 { chartpath = filepath.Clean(args[0]) diff --git a/cmd/helm/docs.go b/cmd/helm/docs.go index 6e9788f26..182aeb9f6 100644 --- a/cmd/helm/docs.go +++ b/cmd/helm/docs.go @@ -59,7 +59,7 @@ func newDocsCmd(out io.Writer) *cobra.Command { Hidden: true, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { o.topCmd = cmd.Root() return o.run(out) }, @@ -70,7 +70,7 @@ func newDocsCmd(out io.Writer) *cobra.Command { f.StringVar(&o.docTypeString, "type", "markdown", "the type of documentation to generate (markdown, man, bash)") f.BoolVar(&o.generateHeaders, "generate-headers", false, "generate standard headers for markdown files") - cmd.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + cmd.RegisterFlagCompletionFunc("type", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{"bash", "man", "markdown"}, cobra.ShellCompDirectiveNoFileComp }) diff --git a/cmd/helm/env.go b/cmd/helm/env.go index 3754b748d..83ee32895 100644 --- a/cmd/helm/env.go +++ b/cmd/helm/env.go @@ -36,7 +36,7 @@ func newEnvCmd(out io.Writer) *cobra.Command { Short: "helm client environment information", Long: envHelp, Args: require.MaximumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { keys := getSortedEnvVarKeys() return keys, cobra.ShellCompDirectiveNoFileComp @@ -44,7 +44,7 @@ func newEnvCmd(out io.Writer) *cobra.Command { return nil, cobra.ShellCompDirectiveNoFileComp }, - Run: func(cmd *cobra.Command, args []string) { + Run: func(_ *cobra.Command, args []string) { envVars := settings.EnvVars() if len(args) == 0 { diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 4fcd8a0e4..62e9f90fa 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -72,7 +72,7 @@ func bindOutputFlag(cmd *cobra.Command, varRef *output.Format) { cmd.Flags().VarP(newOutputValue(output.Table, varRef), outputFlag, "o", fmt.Sprintf("prints the output in the specified format. Allowed values: %s", strings.Join(output.Formats(), ", "))) - err := cmd.RegisterFlagCompletionFunc(outputFlag, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc(outputFlag, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { var formatNames []string for format, desc := range output.FormatsWithDesc() { formatNames = append(formatNames, fmt.Sprintf("%s\t%s", format, desc)) diff --git a/cmd/helm/get_all.go b/cmd/helm/get_all.go index e51d50536..6def2e0d6 100644 --- a/cmd/helm/get_all.go +++ b/cmd/helm/get_all.go @@ -41,13 +41,13 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download all information for a named release", Long: getAllHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { return err @@ -65,7 +65,7 @@ func newGetAllCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/get_hooks.go b/cmd/helm/get_hooks.go index 913e2c58a..6ba5094e4 100644 --- a/cmd/helm/get_hooks.go +++ b/cmd/helm/get_hooks.go @@ -41,13 +41,13 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download all hooks for a named release", Long: getHooksHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { return err @@ -60,7 +60,7 @@ func newGetHooksCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/get_manifest.go b/cmd/helm/get_manifest.go index baeaf8d72..0df100259 100644 --- a/cmd/helm/get_manifest.go +++ b/cmd/helm/get_manifest.go @@ -43,13 +43,13 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Short: "download the manifest for a named release", Long: getManifestHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { return err @@ -60,7 +60,7 @@ func newGetManifestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command } cmd.Flags().IntVar(&client.Version, "revision", 0, "get the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/get_metadata.go b/cmd/helm/get_metadata.go index adab891bd..084323275 100644 --- a/cmd/helm/get_metadata.go +++ b/cmd/helm/get_metadata.go @@ -40,13 +40,13 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Use: "metadata RELEASE_NAME", Short: "This command fetches metadata for a given release", Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { releaseMetadata, err := client.Run(args[0]) if err != nil { return err @@ -57,7 +57,7 @@ func newGetMetadataCmd(cfg *action.Configuration, out io.Writer) *cobra.Command f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "specify release revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/get_notes.go b/cmd/helm/get_notes.go index b71bcbdf6..6ac2a0450 100644 --- a/cmd/helm/get_notes.go +++ b/cmd/helm/get_notes.go @@ -39,13 +39,13 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download the notes for a named release", Long: getNotesHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { res, err := client.Run(args[0]) if err != nil { return err @@ -59,7 +59,7 @@ func newGetNotesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/get_values.go b/cmd/helm/get_values.go index 6124e1b33..c3fb8c252 100644 --- a/cmd/helm/get_values.go +++ b/cmd/helm/get_values.go @@ -46,13 +46,13 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "download the values file for a named release", Long: getValuesHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { vals, err := client.Run(args[0]) if err != nil { return err @@ -63,7 +63,7 @@ func newGetValuesCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.IntVar(&client.Version, "revision", 0, "get the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/helm_test.go b/cmd/helm/helm_test.go index b20b1a24d..7d0bf5751 100644 --- a/cmd/helm/helm_test.go +++ b/cmd/helm/helm_test.go @@ -94,7 +94,7 @@ func executeActionCommandStdinC(store *storage.Storage, in *os.File, cmd string) Releases: store, KubeClient: &kubefake.PrintingKubeClient{Out: io.Discard}, Capabilities: chartutil.DefaultCapabilities, - Log: func(format string, v ...interface{}) {}, + Log: func(_ string, _ ...interface{}) {}, } root, err := newRootCmd(actionConfig, buf, args) diff --git a/cmd/helm/history.go b/cmd/helm/history.go index de8b13a98..42796fab8 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -60,13 +60,13 @@ func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "fetch release history", Aliases: []string{"hist"}, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { history, err := getHistory(client, args[0]) if err != nil { return err diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 74573ee81..cc57e2908 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -94,7 +94,11 @@ And in the following example, 'foo' is set to '{"key1":"value1","key2":"bar"}': $ helm install --set-json='foo={"key1":"value1","key2":"value2"}' --set-json='foo.key2="bar"' myredis ./redis To check the generated manifests of a release without installing the chart, -the '--debug' and '--dry-run' flags can be combined. +the --debug and --dry-run flags can be combined. + +The --dry-run flag will output all generated chart manifests, including Secrets +which can contain sensitive values. To hide Kubernetes Secrets use the +--hide-secret flag. Please carefully consider how and when these flags are used. If --verify is set, the chart MUST have a provenance file, and the provenance file MUST pass all verification steps. @@ -132,7 +136,7 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "install a chart", Long: installDesc, Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compInstall(args, toComplete, client) }, RunE: func(_ *cobra.Command, args []string) error { @@ -159,6 +163,10 @@ func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } addInstallFlags(cmd, cmd.Flags(), client, valueOpts) + // hide-secret is not available in all places the install flags are used so + // it is added separately + f := cmd.Flags() + f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) @@ -194,7 +202,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal addValueOptionsFlags(f, valueOpts) addChartPathOptionsFlags(f, &client.ChartPathOptions) - err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { requiredArgs := 2 if client.GenerateName { requiredArgs = 1 diff --git a/cmd/helm/install_test.go b/cmd/helm/install_test.go index b34d1455c..a3b527031 100644 --- a/cmd/helm/install_test.go +++ b/cmd/helm/install_test.go @@ -33,7 +33,7 @@ func TestInstall(t *testing.T) { } defer srv.Stop() - srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv.WithMiddleware(http.HandlerFunc(func(_ 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) @@ -252,6 +252,22 @@ func TestInstall(t *testing.T) { cmd: fmt.Sprintf("install aeneas test/reqtest --username username --password password --repository-config %s --repository-cache %s", repoFile, srv.Root()), golden: "output/install.txt", }, + { + name: "dry-run displaying secret", + cmd: "install secrets testdata/testcharts/chart-with-secret --dry-run", + golden: "output/install-dry-run-with-secret.txt", + }, + { + name: "dry-run hiding secret", + cmd: "install secrets testdata/testcharts/chart-with-secret --dry-run --hide-secret", + golden: "output/install-dry-run-with-secret-hidden.txt", + }, + { + name: "hide-secret error without dry-run", + cmd: "install secrets testdata/testcharts/chart-with-secret --hide-secret", + wantError: true, + golden: "output/install-hide-secret.txt", + }, } runTestCmd(t, tests) diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 46f3c0efd..6b54bdd3f 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -51,7 +51,7 @@ func newLintCmd(out io.Writer) *cobra.Command { Use: "lint PATH", Short: "examine a chart for possible issues", Long: longLintHelp, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { paths := []string{"."} if len(args) > 0 { paths = args @@ -67,7 +67,7 @@ func newLintCmd(out io.Writer) *cobra.Command { if client.WithSubcharts { for _, p := range paths { - filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, err error) error { + filepath.Walk(filepath.Join(p, "charts"), func(path string, info os.FileInfo, _ error) error { if info != nil { if info.Name() == "Chart.yaml" { paths = append(paths, filepath.Dir(path)) diff --git a/cmd/helm/list.go b/cmd/helm/list.go index 5ca3de18e..91922a66d 100644 --- a/cmd/helm/list.go +++ b/cmd/helm/list.go @@ -69,7 +69,7 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Aliases: []string{"ls"}, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, _ []string) error { if client.AllNamespaces { if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil { return err diff --git a/cmd/helm/load_plugins.go b/cmd/helm/load_plugins.go index 001a084ed..5d511d6fc 100644 --- a/cmd/helm/load_plugins.go +++ b/cmd/helm/load_plugins.go @@ -301,7 +301,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug // to the dynamic completion script of the plugin. DisableFlagParsing: true, // A Run is required for it to be a valid command without subcommands - Run: func(cmd *cobra.Command, args []string) {}, + Run: func(_ *cobra.Command, _ []string) {}, } baseCmd.AddCommand(subCmd) addPluginCommands(plugin, subCmd, &cmd) diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 822d3d56a..b96110ee8 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -55,7 +55,7 @@ func newPackageCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Use: "package [CHART_PATH] [...]", Short: "package a chart directory into a chart archive", Long: packageDesc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { if len(args) == 0 { return errors.Errorf("need at least one argument, the path to the chart") } diff --git a/cmd/helm/plugin_install.go b/cmd/helm/plugin_install.go index 4e8ee327b..b10a87c94 100644 --- a/cmd/helm/plugin_install.go +++ b/cmd/helm/plugin_install.go @@ -44,7 +44,7 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command { Long: pluginInstallDesc, Aliases: []string{"add"}, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // We do file completion, in case the plugin is local return nil, cobra.ShellCompDirectiveDefault @@ -52,10 +52,10 @@ func newPluginInstallCmd(out io.Writer) *cobra.Command { // No more completion once the plugin path has been specified return nil, cobra.ShellCompDirectiveNoFileComp }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(_ *cobra.Command, args []string) error { return o.complete(args) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return o.run(out) }, } diff --git a/cmd/helm/plugin_list.go b/cmd/helm/plugin_list.go index fcd415191..ed17d9e6b 100644 --- a/cmd/helm/plugin_list.go +++ b/cmd/helm/plugin_list.go @@ -31,7 +31,7 @@ func newPluginListCmd(out io.Writer) *cobra.Command { Aliases: []string{"ls"}, Short: "list installed Helm plugins", ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { debug("pluginDirs: %s", settings.PluginsDirectory) plugins, err := plugin.FindPlugins(settings.PluginsDirectory) if err != nil { diff --git a/cmd/helm/plugin_uninstall.go b/cmd/helm/plugin_uninstall.go index ee4a47beb..607baab2e 100644 --- a/cmd/helm/plugin_uninstall.go +++ b/cmd/helm/plugin_uninstall.go @@ -38,13 +38,13 @@ func newPluginUninstallCmd(out io.Writer) *cobra.Command { Use: "uninstall ...", Aliases: []string{"rm", "remove"}, Short: "uninstall one or more Helm plugins", - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(_ *cobra.Command, args []string) error { return o.complete(args) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return o.run(out) }, } diff --git a/cmd/helm/plugin_update.go b/cmd/helm/plugin_update.go index 4515acdbb..3f6d963fb 100644 --- a/cmd/helm/plugin_update.go +++ b/cmd/helm/plugin_update.go @@ -39,13 +39,13 @@ func newPluginUpdateCmd(out io.Writer) *cobra.Command { Use: "update ...", Aliases: []string{"up"}, Short: "update one or more Helm plugins", - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compListPlugins(toComplete, args), cobra.ShellCompDirectiveNoFileComp }, - PreRunE: func(cmd *cobra.Command, args []string) error { + PreRunE: func(_ *cobra.Command, args []string) error { return o.complete(args) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return o.run(out) }, } diff --git a/cmd/helm/pull.go b/cmd/helm/pull.go index 4ca2c47e8..de4918d72 100644 --- a/cmd/helm/pull.go +++ b/cmd/helm/pull.go @@ -51,13 +51,13 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Aliases: []string{"fetch"}, Long: pullDesc, Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListCharts(toComplete, false) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.Settings = settings if client.Version == "" && client.Devel { debug("setting version to >0.0.0-0") @@ -90,7 +90,7 @@ func newPullCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.StringVarP(&client.DestDir, "destination", "d", ".", "location to write the chart. If this and untardir are specified, untardir is appended to this") addChartPathOptionsFlags(f, &client.ChartPathOptions) - err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 1 { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/pull_test.go b/cmd/helm/pull_test.go index 41ac237f4..ae70595f9 100644 --- a/cmd/helm/pull_test.go +++ b/cmd/helm/pull_test.go @@ -258,7 +258,7 @@ func TestPullWithCredentialsCmd(t *testing.T) { } defer srv.Stop() - srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv.WithMiddleware(http.HandlerFunc(func(_ 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) diff --git a/cmd/helm/push.go b/cmd/helm/push.go index be804661b..84b8a823b 100644 --- a/cmd/helm/push.go +++ b/cmd/helm/push.go @@ -50,7 +50,7 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "push a chart to remote", Long: pushDesc, Args: require.MinimumNArgs(2), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // Do file completion for the chart file to push return nil, cobra.ShellCompDirectiveDefault @@ -67,10 +67,11 @@ func newPushCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { registryClient, err := newRegistryClient( o.certFile, o.keyFile, o.caFile, o.insecureSkipTLSverify, o.plainHTTP, "", "", ) + if err != nil { return fmt.Errorf("missing registry client: %w", err) } diff --git a/cmd/helm/registry_login.go b/cmd/helm/registry_login.go index 112e06a95..bfb163786 100644 --- a/cmd/helm/registry_login.go +++ b/cmd/helm/registry_login.go @@ -54,7 +54,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman Long: registryLoginDesc, Args: require.MinimumNArgs(1), ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { hostname := args[0] username, password, err := getUsernamePassword(o.username, o.password, o.passwordFromStdinOpt) diff --git a/cmd/helm/registry_logout.go b/cmd/helm/registry_logout.go index 0084f8c09..61dae1da3 100644 --- a/cmd/helm/registry_logout.go +++ b/cmd/helm/registry_logout.go @@ -36,7 +36,7 @@ func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Comma Long: registryLogoutDesc, Args: require.MinimumNArgs(1), ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { hostname := args[0] return action.NewRegistryLogout(cfg).Run(out, hostname) }, diff --git a/cmd/helm/release_testing.go b/cmd/helm/release_testing.go index 548ae2b8a..578717378 100644 --- a/cmd/helm/release_testing.go +++ b/cmd/helm/release_testing.go @@ -48,13 +48,13 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command Short: "run tests for a release", Long: releaseTestHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.Namespace = settings.Namespace() notName := regexp.MustCompile(`^!\s?name=`) for _, f := range filter { diff --git a/cmd/helm/repo_add.go b/cmd/helm/repo_add.go index 2deda3f4f..83fcdf50c 100644 --- a/cmd/helm/repo_add.go +++ b/cmd/helm/repo_add.go @@ -72,7 +72,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { Short: "add a chart repository", Args: require.ExactArgs(2), ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.name = args[0] o.url = args[1] o.repoFile = settings.RepositoryConfig diff --git a/cmd/helm/repo_index.go b/cmd/helm/repo_index.go index d947aca60..fb7ec811f 100644 --- a/cmd/helm/repo_index.go +++ b/cmd/helm/repo_index.go @@ -54,7 +54,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command { Short: "generate an index file given a directory containing packaged charts", Long: repoIndexDesc, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // Allow file completion when completing the argument for the directory return nil, cobra.ShellCompDirectiveDefault @@ -62,7 +62,7 @@ func newRepoIndexCmd(out io.Writer) *cobra.Command { // No more completions, so disable file completion return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.dir = args[0] return o.run(out) }, diff --git a/cmd/helm/repo_list.go b/cmd/helm/repo_list.go index 334f859a1..8abd20654 100644 --- a/cmd/helm/repo_list.go +++ b/cmd/helm/repo_list.go @@ -37,7 +37,7 @@ func newRepoListCmd(out io.Writer) *cobra.Command { Short: "list chart repositories", Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { f, _ := repo.LoadFile(settings.RepositoryConfig) if len(f.Repositories) == 0 && !(outfmt == output.JSON || outfmt == output.YAML) { return errors.New("no repositories to show") diff --git a/cmd/helm/repo_remove.go b/cmd/helm/repo_remove.go index 0c1ad2cd5..82a235fec 100644 --- a/cmd/helm/repo_remove.go +++ b/cmd/helm/repo_remove.go @@ -44,10 +44,10 @@ func newRepoRemoveCmd(out io.Writer) *cobra.Command { Aliases: []string{"rm"}, Short: "remove one or more chart repositories", Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache o.names = args diff --git a/cmd/helm/repo_update.go b/cmd/helm/repo_update.go index 27661674c..8d5f532f1 100644 --- a/cmd/helm/repo_update.go +++ b/cmd/helm/repo_update.go @@ -57,10 +57,10 @@ func newRepoUpdateCmd(out io.Writer) *cobra.Command { Short: "update information of available charts locally from chart repositories", Long: updateDesc, Args: require.MinimumNArgs(0), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compListRepos(toComplete, args), cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache o.names = args diff --git a/cmd/helm/repo_update_test.go b/cmd/helm/repo_update_test.go index 645c68cfe..1ddf0f5ed 100644 --- a/cmd/helm/repo_update_test.go +++ b/cmd/helm/repo_update_test.go @@ -34,7 +34,7 @@ func TestUpdateCmd(t *testing.T) { var out bytes.Buffer // 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, failOnRepoUpdateFail bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } @@ -59,7 +59,7 @@ func TestUpdateCmdMultiple(t *testing.T) { var out bytes.Buffer // 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, failOnRepoUpdateFail bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } @@ -85,7 +85,7 @@ func TestUpdateCmdInvalid(t *testing.T) { var out bytes.Buffer // 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, failOnRepoUpdateFail bool) error { + updater := func(repos []*repo.ChartRepository, out io.Writer, _ bool) error { for _, re := range repos { fmt.Fprintln(out, re.Config.Name) } diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index 7de98e404..7e4c721f5 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -46,7 +46,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "roll back a release to a previous revision", Long: rollbackDesc, Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { return compListReleases(toComplete, args, cfg) } @@ -57,7 +57,7 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { if len(args) > 1 { ver, err := strconv.Atoi(args[1]) if err != nil { diff --git a/cmd/helm/root.go b/cmd/helm/root.go index beb62bd39..61e8eb794 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -102,7 +102,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string addKlogFlags(flags) // Setup shell completion for the namespace flag - err := cmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("namespace", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { if client, err := actionConfig.KubernetesClientSet(); err == nil { // Choose a long enough timeout that the user notices something is not working // but short enough that the user is not made to wait very long @@ -125,7 +125,7 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string } // Setup shell completion for the kube-context flag - err = cmd.RegisterFlagCompletionFunc("kube-context", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err = cmd.RegisterFlagCompletionFunc("kube-context", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { cobra.CompDebugln("About to get the different kube-contexts", settings.Debug) loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() diff --git a/cmd/helm/search_hub.go b/cmd/helm/search_hub.go index 1618a4c9f..d9482f67a 100644 --- a/cmd/helm/search_hub.go +++ b/cmd/helm/search_hub.go @@ -64,7 +64,7 @@ func newSearchHubCmd(out io.Writer) *cobra.Command { Use: "hub [KEYWORD]", Short: "search for charts in the Artifact Hub or your own hub instance", Long: searchHubDesc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { return o.run(out, args) }, } diff --git a/cmd/helm/search_hub_test.go b/cmd/helm/search_hub_test.go index 89ce2b3e5..f3730275a 100644 --- a/cmd/helm/search_hub_test.go +++ b/cmd/helm/search_hub_test.go @@ -27,7 +27,7 @@ func TestSearchHubCmd(t *testing.T) { // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) defer ts.Close() @@ -57,7 +57,7 @@ func TestSearchHubListRepoCmd(t *testing.T) { // Setup a mock search service var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"stable","url":"https://charts.helm.sh/stable"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/stable/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T17:57:31.38Z","digest":"119c499251bffd4b06ff0cd5ac98c2ce32231f84899fb4825be6c2d90971c742","urls":["https://charts.helm.sh/stable/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/stable/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/stable/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/stable/phpmyadmin/versions/3.0.0"}}}},{"id":"bitnami/phpmyadmin","type":"chart","attributes":{"name":"phpmyadmin","repo":{"name":"bitnami","url":"https://charts.bitnami.com"},"description":"phpMyAdmin is an mysql administration frontend","home":"https://www.phpmyadmin.net/","keywords":["mariadb","mysql","phpmyadmin"],"maintainers":[{"name":"Bitnami","email":"containers@bitnami.com"}],"sources":["https://github.com/bitnami/bitnami-docker-phpmyadmin"],"icon":""},"links":{"self":"/v1/charts/bitnami/phpmyadmin"},"relationships":{"latestChartVersion":{"data":{"version":"3.0.0","app_version":"4.9.0-1","created":"2019-08-08T18:34:13.341Z","digest":"66d77cf6d8c2b52c488d0a294cd4996bd5bad8dc41d3829c394498fb401c008a","urls":["https://charts.bitnami.com/bitnami/phpmyadmin-3.0.0.tgz"],"readme":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/README.md","values":"/v1/assets/bitnami/phpmyadmin/versions/3.0.0/values.yaml"},"links":{"self":"/v1/charts/bitnami/phpmyadmin/versions/3.0.0"}}}}]}` - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) defer ts.Close() @@ -155,7 +155,7 @@ func TestSearchHubCmd_FailOnNoResponseTests(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Setup a mock search service - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, tt.response) })) defer ts.Close() diff --git a/cmd/helm/search_repo.go b/cmd/helm/search_repo.go index 2c6f17094..f2bbca9e4 100644 --- a/cmd/helm/search_repo.go +++ b/cmd/helm/search_repo.go @@ -81,7 +81,7 @@ func newSearchRepoCmd(out io.Writer) *cobra.Command { Use: "repo [keyword]", Short: "search repositories for a keyword in charts", Long: searchRepoDesc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { o.repoFile = settings.RepositoryConfig o.repoCacheDir = settings.RepositoryCache return o.run(out, args) diff --git a/cmd/helm/show.go b/cmd/helm/show.go index b615ac97b..1387c8617 100644 --- a/cmd/helm/show.go +++ b/cmd/helm/show.go @@ -69,7 +69,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } // Function providing dynamic auto-completion - validArgsFunc := func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + validArgsFunc := func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -82,7 +82,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showAllDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowAll err := addRegistryClient(client) if err != nil { @@ -103,7 +103,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showValuesDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowValues err := addRegistryClient(client) if err != nil { @@ -124,7 +124,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowChart err := addRegistryClient(client) if err != nil { @@ -145,7 +145,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: readmeChartDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowReadme err := addRegistryClient(client) if err != nil { @@ -166,7 +166,7 @@ func newShowCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Long: showCRDsDesc, Args: require.ExactArgs(1), ValidArgsFunction: validArgsFunc, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.OutputFormat = action.ShowCRDs err := addRegistryClient(client) if err != nil { @@ -199,7 +199,7 @@ func addShowFlags(subCmd *cobra.Command, client *action.Show) { } addChartPathOptionsFlags(f, &client.ChartPathOptions) - err := subCmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := subCmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 1 { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/cmd/helm/status.go b/cmd/helm/status.go index 850862cd5..9fa6ec4bc 100644 --- a/cmd/helm/status.go +++ b/cmd/helm/status.go @@ -58,13 +58,13 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "display the status of the named release", Long: statusHelp, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { // When the output format is a table the resources should be fetched // and displayed as a table. When YAML or JSON the resources will be @@ -88,7 +88,7 @@ func newStatusCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.IntVar(&client.Version, "revision", 0, "if set, display the status of the named release with revision") - err := cmd.RegisterFlagCompletionFunc("revision", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("revision", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 1 { return compListRevisions(toComplete, cfg, args[0]) } diff --git a/cmd/helm/template.go b/cmd/helm/template.go index 16895d22d..ff6621a49 100644 --- a/cmd/helm/template.go +++ b/cmd/helm/template.go @@ -61,7 +61,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "locally render templates", Long: templateDesc, Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compInstall(args, toComplete, client) }, RunE: func(_ *cobra.Command, args []string) error { diff --git a/cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt b/cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt new file mode 100644 index 000000000..2f928c3dc --- /dev/null +++ b/cmd/helm/testdata/output/install-dry-run-with-secret-hidden.txt @@ -0,0 +1,20 @@ +NAME: secrets +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: pending-install +REVISION: 1 +TEST SUITE: None +HOOKS: +MANIFEST: +--- +# Source: chart-with-secret/templates/secret.yaml +# HIDDEN: The Secret output has been suppressed +--- +# Source: chart-with-secret/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-configmap +data: + foo: bar + diff --git a/cmd/helm/testdata/output/install-dry-run-with-secret.txt b/cmd/helm/testdata/output/install-dry-run-with-secret.txt new file mode 100644 index 000000000..6c094ef6a --- /dev/null +++ b/cmd/helm/testdata/output/install-dry-run-with-secret.txt @@ -0,0 +1,25 @@ +NAME: secrets +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: pending-install +REVISION: 1 +TEST SUITE: None +HOOKS: +MANIFEST: +--- +# Source: chart-with-secret/templates/secret.yaml +apiVersion: v1 +kind: Secret +metadata: + name: test-secret +stringData: + foo: bar +--- +# Source: chart-with-secret/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-configmap +data: + foo: bar + diff --git a/cmd/helm/testdata/output/install-hide-secret.txt b/cmd/helm/testdata/output/install-hide-secret.txt new file mode 100644 index 000000000..aaf73b478 --- /dev/null +++ b/cmd/helm/testdata/output/install-hide-secret.txt @@ -0,0 +1 @@ +Error: INSTALLATION FAILED: Hiding Kubernetes secrets requires a dry-run mode diff --git a/cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt b/cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt new file mode 100644 index 000000000..f53b8715a --- /dev/null +++ b/cmd/helm/testdata/output/upgrade-uninstalled-with-keep-history.txt @@ -0,0 +1,7 @@ +Release "funny-bunny" does not exist. Installing it now. +NAME: funny-bunny +LAST DEPLOYED: Fri Sep 2 22:04:05 1977 +NAMESPACE: default +STATUS: deployed +REVISION: 3 +TEST SUITE: None diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/Chart.yaml b/cmd/helm/testdata/testcharts/chart-with-secret/Chart.yaml new file mode 100644 index 000000000..46d069e1c --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-secret/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +description: Chart with Kubernetes Secret +name: chart-with-secret +version: 0.0.1 diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/templates/configmap.yaml b/cmd/helm/testdata/testcharts/chart-with-secret/templates/configmap.yaml new file mode 100644 index 000000000..ce9c27d56 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-secret/templates/configmap.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: test-configmap +data: + foo: bar diff --git a/cmd/helm/testdata/testcharts/chart-with-secret/templates/secret.yaml b/cmd/helm/testdata/testcharts/chart-with-secret/templates/secret.yaml new file mode 100644 index 000000000..b1e1cff56 --- /dev/null +++ b/cmd/helm/testdata/testcharts/chart-with-secret/templates/secret.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Secret +metadata: + name: test-secret +stringData: + foo: bar diff --git a/cmd/helm/uninstall.go b/cmd/helm/uninstall.go index 9ced8fef0..3c4517b50 100644 --- a/cmd/helm/uninstall.go +++ b/cmd/helm/uninstall.go @@ -47,10 +47,10 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "uninstall a release", Long: uninstallDesc, Args: require.MinimumNArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return compListReleases(toComplete, args, cfg) }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { validationErr := validateCascadeFlag(client) if validationErr != nil { return validationErr diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 1b640f546..bbb561046 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -36,6 +36,7 @@ import ( "helm.sh/helm/v3/pkg/cli/values" "helm.sh/helm/v3/pkg/downloader" "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/storage/driver" ) @@ -72,6 +73,10 @@ parameters, and existing values will be merged with any values set via '--values or '--set' flags. Priority is given to new values. $ helm upgrade --reuse-values --set foo=bar --set foo=newbar redis ./redis + +The --dry-run flag will output all generated chart manifests, including Secrets +which can contain sensitive values. To hide Kubernetes Secrets use the +--hide-secret flag. Please carefully consider how and when these flags are used. ` func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { @@ -85,7 +90,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Short: "upgrade a release", Long: upgradeDesc, Args: require.ExactArgs(2), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { return compListReleases(toComplete, args, cfg) } @@ -94,7 +99,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { } return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { client.Namespace = settings.Namespace() registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, @@ -111,12 +116,13 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { client.DryRunOption = "none" } // Fixes #7002 - Support reading values from STDIN for `upgrade` command - // Must load values AFTER determining if we have to call install so that values loaded from stdin are are not read twice + // Must load values AFTER determining if we have to call install so that values loaded from stdin are not read twice if client.Install { // If a release does not exist, install it. histClient := action.NewHistory(cfg) histClient.Max = 1 - if _, err := histClient.Run(args[0]); err == driver.ErrReleaseNotFound { + versions, err := histClient.Run(args[0]) + if err == driver.ErrReleaseNotFound || isReleaseUninstalled(versions) { // Only print this to stdout for table output if outfmt == output.Table { fmt.Fprintf(out, "Release %q does not exist. Installing it now.\n", args[0]) @@ -142,6 +148,11 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { instClient.DependencyUpdate = client.DependencyUpdate instClient.Labels = client.Labels instClient.EnableDNS = client.EnableDNS + instClient.HideSecret = client.HideSecret + + if isReleaseUninstalled(versions) { + instClient.Replace = true + } rel, err := runInstall(args, instClient, valueOpts, out) if err != nil { @@ -242,6 +253,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVarP(&client.Install, "install", "i", false, "if a release by this name doesn't already exist, run an install") f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored") f.StringVar(&client.DryRunOption, "dry-run", "", "simulate an install. If --dry-run is set with no option being specified or as '--dry-run=client', it will not attempt cluster connections. Setting '--dry-run=server' allows attempting cluster connections.") + f.BoolVar(&client.HideSecret, "hide-secret", false, "hide Kubernetes Secrets when also using the --dry-run flag") f.Lookup("dry-run").NoOptDefVal = "client" f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") @@ -268,7 +280,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { bindOutputFlag(cmd, &outfmt) bindPostRenderFlag(cmd, &client.PostRenderer) - err := cmd.RegisterFlagCompletionFunc("version", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { if len(args) != 2 { return nil, cobra.ShellCompDirectiveNoFileComp } @@ -281,3 +293,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { return cmd } + +func isReleaseUninstalled(versions []*release.Release) bool { + return len(versions) > 0 && versions[len(versions)-1].Info.Status == release.StatusUninstalled +} diff --git a/cmd/helm/upgrade_test.go b/cmd/helm/upgrade_test.go index 485267d1d..497c78d71 100644 --- a/cmd/helm/upgrade_test.go +++ b/cmd/helm/upgrade_test.go @@ -175,6 +175,12 @@ func TestUpgradeCmd(t *testing.T) { wantError: true, rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusPendingInstall)}, }, + { + name: "install a previously uninstalled release with '--keep-history' using 'upgrade --install'", + cmd: fmt.Sprintf("upgrade funny-bunny -i '%s'", chartPath), + golden: "output/upgrade-uninstalled-with-keep-history.txt", + rels: []*release.Release{relWithStatusMock("funny-bunny", 2, ch, release.StatusUninstalled)}, + }, } runTestCmd(t, tests) } @@ -458,3 +464,104 @@ func TestUpgradeInstallWithLabels(t *testing.T) { t.Errorf("Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels) } } + +func prepareMockReleaseWithSecret(releaseName string, t *testing.T) (func(n string, v int, ch *chart.Chart) *release.Release, *chart.Chart, string) { + tmpChart := t.TempDir() + configmapData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/configmap.yaml") + if err != nil { + t.Fatalf("Error loading template yaml %v", err) + } + secretData, err := os.ReadFile("testdata/testcharts/chart-with-secret/templates/secret.yaml") + if err != nil { + t.Fatalf("Error loading template yaml %v", err) + } + cfile := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV1, + Name: "testUpgradeChart", + Description: "A Helm chart for Kubernetes", + Version: "0.1.0", + }, + Templates: []*chart.File{{Name: "templates/configmap.yaml", Data: configmapData}, {Name: "templates/secret.yaml", Data: secretData}}, + } + chartPath := filepath.Join(tmpChart, cfile.Metadata.Name) + if err := chartutil.SaveDir(cfile, tmpChart); err != nil { + t.Fatalf("Error creating chart for upgrade: %v", err) + } + ch, err := loader.Load(chartPath) + if err != nil { + t.Fatalf("Error loading chart: %v", err) + } + _ = release.Mock(&release.MockReleaseOptions{ + Name: releaseName, + Chart: ch, + }) + + relMock := func(n string, v int, ch *chart.Chart) *release.Release { + return release.Mock(&release.MockReleaseOptions{Name: n, Version: v, Chart: ch}) + } + + return relMock, ch, chartPath +} + +func TestUpgradeWithDryRun(t *testing.T) { + releaseName := "funny-bunny-labels" + _, _, chartPath := prepareMockReleaseWithSecret(releaseName, t) + + defer resetEnv()() + + store := storageFixture() + + // First install a release into the store so that future --dry-run attempts + // have it available. + cmd := fmt.Sprintf("upgrade %s --install '%s'", releaseName, chartPath) + _, _, err := executeActionCommandC(store, cmd) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + _, err = store.Get(releaseName, 1) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + cmd = fmt.Sprintf("upgrade %s --dry-run '%s'", releaseName, chartPath) + _, out, err := executeActionCommandC(store, cmd) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + // No second release should be stored because this is a dry run. + _, err = store.Get(releaseName, 2) + if err == nil { + t.Error("expected error as there should be no new release but got none") + } + + if !strings.Contains(out, "kind: Secret") { + t.Error("expected secret in output from --dry-run but found none") + } + + // Ensure the secret is not in the output + cmd = fmt.Sprintf("upgrade %s --dry-run --hide-secret '%s'", releaseName, chartPath) + _, out, err = executeActionCommandC(store, cmd) + if err != nil { + t.Errorf("unexpected error, got '%v'", err) + } + + // No second release should be stored because this is a dry run. + _, err = store.Get(releaseName, 2) + if err == nil { + t.Error("expected error as there should be no new release but got none") + } + + if strings.Contains(out, "kind: Secret") { + t.Error("expected no secret in output from --dry-run --hide-secret but found one") + } + + // Ensure there is an error when --hide-secret used without dry-run + cmd = fmt.Sprintf("upgrade %s --hide-secret '%s'", releaseName, chartPath) + _, _, err = executeActionCommandC(store, cmd) + if err == nil { + t.Error("expected error when --hide-secret used without --dry-run") + } +} diff --git a/cmd/helm/verify.go b/cmd/helm/verify.go index d126c9ef3..f667a31e9 100644 --- a/cmd/helm/verify.go +++ b/cmd/helm/verify.go @@ -44,7 +44,7 @@ func newVerifyCmd(out io.Writer) *cobra.Command { Short: "verify that a chart at the given path has been signed and is valid", Long: verifyDesc, Args: require.ExactArgs(1), - ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { if len(args) == 0 { // Allow file completion when completing the argument for the path return nil, cobra.ShellCompDirectiveDefault @@ -52,7 +52,7 @@ func newVerifyCmd(out io.Writer) *cobra.Command { // No more completions, so disable file completion return nil, cobra.ShellCompDirectiveNoFileComp }, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { err := client.Run(args[0]) if err != nil { return err diff --git a/cmd/helm/version.go b/cmd/helm/version.go index d62778f7b..409fc6fd9 100644 --- a/cmd/helm/version.go +++ b/cmd/helm/version.go @@ -66,7 +66,7 @@ func newVersionCmd(out io.Writer) *cobra.Command { Long: versionDesc, Args: require.NoArgs, ValidArgsFunction: noCompletions, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { return o.run(out) }, } diff --git a/go.mod b/go.mod index e200d4fcb..21cc3a7bc 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/crypto v0.17.0 - golang.org/x/term v0.15.0 + golang.org/x/term v0.18.0 golang.org/x/text v0.14.0 k8s.io/api v0.29.0 k8s.io/apiextensions-apiserver v0.29.0 @@ -67,7 +67,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v24.0.6+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect - github.com/docker/docker v24.0.7+incompatible // indirect + github.com/docker/docker v24.0.9+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect @@ -150,12 +150,12 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 2799262df..d89cb2208 100644 --- a/go.sum +++ b/go.sum @@ -80,8 +80,8 @@ github.com/docker/cli v24.0.6+incompatible h1:fF+XCQCgJjjQNIMjzaSmiKJSCcfcXb3TWT github.com/docker/cli v24.0.6+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= @@ -483,14 +483,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -540,8 +540,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/monocular/search_test.go b/internal/monocular/search_test.go index 9f6954af7..fc82ef4b4 100644 --- a/internal/monocular/search_test.go +++ b/internal/monocular/search_test.go @@ -28,7 +28,7 @@ var searchResult = `{"data":[{"id":"stable/phpmyadmin","type":"chart","attribute func TestSearch(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, searchResult) })) defer ts.Close() diff --git a/internal/sympath/walk_test.go b/internal/sympath/walk_test.go index 25f737134..04e0738b9 100644 --- a/internal/sympath/walk_test.go +++ b/internal/sympath/walk_test.go @@ -119,7 +119,7 @@ func mark(info os.FileInfo, err error, errors *[]error, clear bool) error { return err } name := info.Name() - walkTree(tree, tree.name, func(path string, n *Node) { + walkTree(tree, tree.name, func(_ string, n *Node) { if n.name == name { n.marks++ } @@ -131,7 +131,7 @@ func TestWalk(t *testing.T) { makeTree(t) errors := make([]error, 0, 10) clear := true - markFn := func(path string, info os.FileInfo, err error) error { + markFn := func(_ string, info os.FileInfo, err error) error { return mark(info, err, &errors, clear) } // Expect no errors. diff --git a/pkg/action/action.go b/pkg/action/action.go index 5693f4838..863c48f07 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -103,7 +103,7 @@ type Configuration struct { // TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed // // This code has to do with writing files to disk. -func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS bool) ([]*release.Hook, *bytes.Buffer, string, error) { +func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) { hs := []*release.Hook{} b := bytes.NewBuffer(nil) @@ -200,7 +200,11 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu for _, m := range manifests { if outputDir == "" { - fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + if hideSecret && m.Head.Kind == "Secret" && m.Head.Version == "v1" { + fmt.Fprintf(b, "---\n# Source: %s\n# HIDDEN: The Secret output has been suppressed\n", m.Name) + } else { + fmt.Fprintf(b, "---\n# Source: %s\n%s\n", m.Name, m.Content) + } } else { newDir := outputDir if useReleaseName { diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index c4ef6c056..fdcfa7558 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -195,6 +195,13 @@ func withSampleTemplates() chartOption { } } +func withSampleSecret() chartOption { + return func(opts *chartOptions) { + sampleSecret := &chart.File{Name: "templates/secret.yaml", Data: []byte("apiVersion: v1\nkind: Secret\n")} + opts.Templates = append(opts.Templates, sampleSecret) + } +} + func withSampleIncludingIncorrectTemplates() chartOption { return func(opts *chartOptions) { sampleTemplates := []*chart.File{ diff --git a/pkg/action/install.go b/pkg/action/install.go index 2c776f352..fb64e0678 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -69,11 +69,14 @@ type Install struct { ChartPathOptions - ClientOnly bool - Force bool - CreateNamespace bool - DryRun bool - DryRunOption string + ClientOnly bool + Force bool + CreateNamespace bool + DryRun bool + DryRunOption string + // HideSecret can be set to true when DryRun is enabled in order to hide + // Kubernetes Secrets in the output. It cannot be used outside of DryRun. + HideSecret bool DisableHooks bool Replace bool Wait bool @@ -230,6 +233,11 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma } } + // HideSecret must be used with dry run. Otherwise, return an error. + if !i.isDryRun() && i.HideSecret { + return nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") + } + if err := i.availableName(); err != nil { return nil, err } @@ -301,7 +309,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma rel := i.createRelease(chrt, vals, i.Labels) var manifestDoc *bytes.Buffer - rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS) + rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS, i.HideSecret) // Even for errors, attach this if available if manifestDoc != nil { rel.Manifest = manifestDoc.String() diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 118e4b366..69b9cbc48 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -255,6 +255,46 @@ func TestInstallRelease_DryRun(t *testing.T) { is.Equal(res.Info.Description, "Dry run complete") } +func TestInstallRelease_DryRunHiddenSecret(t *testing.T) { + is := assert.New(t) + instAction := installAction(t) + + // First perform a normal dry-run with the secret and confirm its presence. + instAction.DryRun = true + vals := map[string]interface{}{} + res, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + is.Contains(res.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") + + _, err = instAction.cfg.Releases.Get(res.Name, res.Version) + is.Error(err) + is.Equal(res.Info.Description, "Dry run complete") + + // Perform a dry-run where the secret should not be present + instAction.HideSecret = true + vals = map[string]interface{}{} + res2, err := instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + if err != nil { + t.Fatalf("Failed install: %s", err) + } + + is.NotContains(res2.Manifest, "---\n# Source: hello/templates/secret.yaml\napiVersion: v1\nkind: Secret") + + _, err = instAction.cfg.Releases.Get(res2.Name, res2.Version) + is.Error(err) + is.Equal(res2.Info.Description, "Dry run complete") + + // Ensure there is an error when HideSecret True but not in a dry-run mode + instAction.DryRun = false + vals = map[string]interface{}{} + _, err = instAction.Run(buildChart(withSampleSecret(), withSampleTemplates()), vals) + if err == nil { + t.Fatalf("Did not get expected an error when dry-run false and hide secret is true") + } +} + // Regression test for #7955 func TestInstallRelease_DryRun_Lookup(t *testing.T) { is := assert.New(t) diff --git a/pkg/action/package.go b/pkg/action/package.go index b79fcb54f..013b32f55 100644 --- a/pkg/action/package.go +++ b/pkg/action/package.go @@ -161,7 +161,7 @@ func passphraseFileFetcher(passphraseFile string, stdin *os.File) (provenance.Pa if err != nil { return nil, err } - return func(name string) ([]byte, error) { + return func(_ string) ([]byte, error) { return passphrase, nil }, nil } diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index ffb7538a6..2bd40a850 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -74,6 +74,9 @@ type Upgrade struct { DryRun bool // DryRunOption controls whether the operation is prepared, but not executed with options on whether or not to interact with the remote cluster. DryRunOption string + // HideSecret can be set to true when DryRun is enabled in order to hide + // Kubernetes Secrets in the output. It cannot be used outside of DryRun. + HideSecret bool // Force will, if set to `true`, ignore certain warnings and perform the upgrade anyway. // // This should be used with caution. @@ -191,6 +194,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin return nil, nil, errMissingChart } + // HideSecret must be used with dry run. Otherwise, return an error. + if !u.isDryRun() && u.HideSecret { + return nil, nil, errors.New("Hiding Kubernetes secrets requires a dry-run mode") + } + // finds the last non-deleted release with the given name lastRelease, err := u.cfg.Releases.Last(name) if err != nil { @@ -259,7 +267,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin interactWithRemote = true } - hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS) + hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS, u.HideSecret) if err != nil { return nil, nil, err } diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index e259605ce..78b4347e3 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -535,3 +535,54 @@ func TestUpgradeRelease_SystemLabels(t *testing.T) { is.Equal(fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err) } + +func TestUpgradeRelease_DryRun(t *testing.T) { + is := assert.New(t) + req := require.New(t) + + upAction := upgradeAction(t) + rel := releaseStub() + rel.Name = "previous-release" + rel.Info.Status = release.StatusDeployed + req.NoError(upAction.cfg.Releases.Create(rel)) + + upAction.DryRun = true + vals := map[string]interface{}{} + + ctx, done := context.WithCancel(context.Background()) + res, err := upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + done() + req.NoError(err) + is.Equal(release.StatusPendingUpgrade, res.Info.Status) + is.Contains(res.Manifest, "kind: Secret") + + lastRelease, err := upAction.cfg.Releases.Last(rel.Name) + req.NoError(err) + is.Equal(lastRelease.Info.Status, release.StatusDeployed) + is.Equal(1, lastRelease.Version) + + // Test the case for hiding the secret to ensure it is not displayed + upAction.HideSecret = true + vals = map[string]interface{}{} + + ctx, done = context.WithCancel(context.Background()) + res, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + done() + req.NoError(err) + is.Equal(release.StatusPendingUpgrade, res.Info.Status) + is.NotContains(res.Manifest, "kind: Secret") + + lastRelease, err = upAction.cfg.Releases.Last(rel.Name) + req.NoError(err) + is.Equal(lastRelease.Info.Status, release.StatusDeployed) + is.Equal(1, lastRelease.Version) + + // Ensure in a dry run mode when using HideSecret + upAction.DryRun = false + vals = map[string]interface{}{} + + ctx, done = context.WithCancel(context.Background()) + _, err = upAction.RunWithContext(ctx, rel.Name, buildChart(withSampleSecret()), vals) + done() + req.Error(err) +} diff --git a/pkg/chart/loader/archive_test.go b/pkg/chart/loader/archive_test.go index 41b0af1aa..4d6db9ed4 100644 --- a/pkg/chart/loader/archive_test.go +++ b/pkg/chart/loader/archive_test.go @@ -31,8 +31,8 @@ func TestLoadArchiveFiles(t *testing.T) { }{ { name: "empty input should return no files", - generate: func(w *tar.Writer) {}, - check: func(t *testing.T, files []*BufferedFile, err error) { + generate: func(_ *tar.Writer) {}, + check: func(t *testing.T, _ []*BufferedFile, err error) { if err.Error() != "no files in chart archive" { t.Fatalf(`expected "no files in chart archive", got [%#v]`, err) } diff --git a/pkg/chart/metadata.go b/pkg/chart/metadata.go index 97bfc2c0c..a08a97cd1 100644 --- a/pkg/chart/metadata.go +++ b/pkg/chart/metadata.go @@ -16,6 +16,7 @@ limitations under the License. package chart import ( + "path/filepath" "strings" "unicode" @@ -110,6 +111,11 @@ func (md *Metadata) Validate() error { if md.Name == "" { return ValidationError("chart.metadata.name is required") } + + if md.Name != filepath.Base(md.Name) { + return ValidationErrorf("chart.metadata.name %q is invalid", md.Name) + } + if md.Version == "" { return ValidationError("chart.metadata.version is required") } diff --git a/pkg/chart/metadata_test.go b/pkg/chart/metadata_test.go index c8b89ae55..62aea7261 100644 --- a/pkg/chart/metadata_test.go +++ b/pkg/chart/metadata_test.go @@ -40,6 +40,11 @@ func TestValidate(t *testing.T) { &Metadata{APIVersion: "v2", Version: "1.0"}, ValidationError("chart.metadata.name is required"), }, + { + "chart without name", + &Metadata{Name: "../../test", APIVersion: "v2", Version: "1.0"}, + ValidationError("chart.metadata.name \"../../test\" is invalid"), + }, { "chart without version", &Metadata{Name: "test", APIVersion: "v2"}, diff --git a/pkg/chartutil/errors.go b/pkg/chartutil/errors.go index fcdcc27ea..0a4046d2e 100644 --- a/pkg/chartutil/errors.go +++ b/pkg/chartutil/errors.go @@ -33,3 +33,11 @@ type ErrNoValue struct { } func (e ErrNoValue) Error() string { return fmt.Sprintf("%q is not a value", e.Key) } + +type ErrInvalidChartName struct { + Name string +} + +func (e ErrInvalidChartName) Error() string { + return fmt.Sprintf("%q is not a valid chart name", e.Name) +} diff --git a/pkg/chartutil/save.go b/pkg/chartutil/save.go index 2ce4eddaf..4ee90709c 100644 --- a/pkg/chartutil/save.go +++ b/pkg/chartutil/save.go @@ -39,6 +39,10 @@ var headerBytes = []byte("+aHR0cHM6Ly95b3V0dS5iZS96OVV6MWljandyTQo=") // directory, writing the chart's contents to that subdirectory. func SaveDir(c *chart.Chart, dest string) error { // Create the chart directory + err := validateName(c.Name()) + if err != nil { + return err + } outdir := filepath.Join(dest, c.Name()) if fi, err := os.Stat(outdir); err == nil && !fi.IsDir() { return errors.Errorf("file %s already exists and is not a directory", outdir) @@ -149,6 +153,10 @@ func Save(c *chart.Chart, outDir string) (string, error) { } func writeTarContents(out *tar.Writer, c *chart.Chart, prefix string) error { + err := validateName(c.Name()) + if err != nil { + return err + } base := filepath.Join(prefix, c.Name()) // Pull out the dependencies of a v1 Chart, since there's no way @@ -242,3 +250,15 @@ func writeToTar(out *tar.Writer, name string, body []byte) error { _, err := out.Write(body) return err } + +// If the name has directory name has characters which would change the location +// they need to be removed. +func validateName(name string) error { + nname := filepath.Base(name) + + if nname != name { + return ErrInvalidChartName{name} + } + + return nil +} diff --git a/pkg/chartutil/save_test.go b/pkg/chartutil/save_test.go index db485c7cb..98b4e641b 100644 --- a/pkg/chartutil/save_test.go +++ b/pkg/chartutil/save_test.go @@ -106,6 +106,24 @@ func TestSave(t *testing.T) { } }) } + + c := &chart.Chart{ + Metadata: &chart.Metadata{ + APIVersion: chart.APIVersionV1, + Name: "../ahab", + Version: "1.2.3", + }, + Lock: &chart.Lock{ + Digest: "testdigest", + }, + Files: []*chart.File{ + {Name: "scheherazade/shahryar.txt", Data: []byte("1,001 Nights")}, + }, + } + _, err := Save(c, tmp) + if err == nil { + t.Fatal("Expected error saving chart with invalid name") + } } // Creates a copy with a different schema; does not modify anything. @@ -232,4 +250,15 @@ func TestSaveDir(t *testing.T) { if len(c2.Files) != 1 || c2.Files[0].Name != c.Files[0].Name { t.Fatal("Files data did not match") } + + tmp2 := t.TempDir() + c.Metadata.Name = "../ahab" + pth := filepath.Join(tmp2, "tmpcharts") + if err := os.MkdirAll(filepath.Join(pth), 0755); err != nil { + t.Fatal(err) + } + + if err := SaveDir(c, pth); err.Error() != "\"../ahab\" is not a valid chart name" { + t.Fatalf("Did not get expected error for chart named %q", c.Name()) + } } diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 4f74f2642..ba103252d 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -44,7 +44,7 @@ const defaultMaxHistory = 10 // defaultBurstLimit sets the default client-side throttling limit const defaultBurstLimit = 100 -// defaultQPS sets the default QPS value to 0 to to use library defaults unless specified +// defaultQPS sets the default QPS value to 0 to use library defaults unless specified const defaultQPS = float32(0) // EnvSettings describes all of the environment settings. @@ -244,6 +244,9 @@ func (s *EnvSettings) Namespace() string { if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { return ns } + if s.namespace != "" { + return s.namespace + } return "default" } diff --git a/pkg/cli/environment_test.go b/pkg/cli/environment_test.go index 1692a89d5..f7709045c 100644 --- a/pkg/cli/environment_test.go +++ b/pkg/cli/environment_test.go @@ -111,6 +111,14 @@ func TestEnvSettings(t *testing.T) { kubeTLSServer: "example.org", kubeInsecure: true, }, + { + name: "invalid kubeconfig", + ns: "testns", + args: "--namespace=testns --kubeconfig=/path/to/fake/file", + maxhistory: defaultMaxHistory, + burstLimit: defaultBurstLimit, + qps: defaultQPS, + }, } for _, tt := range tests { diff --git a/pkg/downloader/manager_test.go b/pkg/downloader/manager_test.go index f7ab1a568..db2487d16 100644 --- a/pkg/downloader/manager_test.go +++ b/pkg/downloader/manager_test.go @@ -262,6 +262,32 @@ func TestDownloadAll(t *testing.T) { if _, err := os.Stat(filepath.Join(chartPath, "charts", "signtest-0.1.0.tgz")); os.IsNotExist(err) { t.Error(err) } + + // A chart with a bad name like this cannot be loaded and saved. Handling in + // the loading and saving will return an error about the invalid name. In + // this case, the chart needs to be created directly. + badchartyaml := `apiVersion: v2 +description: A Helm chart for Kubernetes +name: ../bad-local-subchart +version: 0.1.0` + if err := os.MkdirAll(filepath.Join(chartPath, "testdata", "bad-local-subchart"), 0755); err != nil { + t.Fatal(err) + } + err = os.WriteFile(filepath.Join(chartPath, "testdata", "bad-local-subchart", "Chart.yaml"), []byte(badchartyaml), 0644) + if err != nil { + t.Fatal(err) + } + + badLocalDep := &chart.Dependency{ + Name: "../bad-local-subchart", + Repository: "file://./testdata/bad-local-subchart", + Version: "0.1.0", + } + + err = m.downloadAll([]*chart.Dependency{badLocalDep}) + if err == nil { + t.Fatal("Expected error for bad dependency name") + } } func TestUpdateBeforeBuild(t *testing.T) { diff --git a/pkg/engine/engine.go b/pkg/engine/engine.go index 61c0782fc..058cfa749 100644 --- a/pkg/engine/engine.go +++ b/pkg/engine/engine.go @@ -239,7 +239,7 @@ func (e Engine) initFunMap(t *template.Template) { // When DNS lookups are not enabled override the sprig function and return // an empty string. if !e.EnableDNS { - funcMap["getHostByName"] = func(name string) string { + funcMap["getHostByName"] = func(_ string) string { return "" } } diff --git a/pkg/getter/httpgetter_test.go b/pkg/getter/httpgetter_test.go index c727d0d7c..0ba7e03e8 100644 --- a/pkg/getter/httpgetter_test.go +++ b/pkg/getter/httpgetter_test.go @@ -287,7 +287,7 @@ func TestDownloadTLS(t *testing.T) { ca, pub, priv := filepath.Join(cd, "rootca.crt"), filepath.Join(cd, "crt.pem"), filepath.Join(cd, "key.pem") insecureSkipTLSverify := false - tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecureSkipTLSverify) if err != nil { t.Fatal(errors.Wrap(err, "can't create TLS config for client")) @@ -332,7 +332,7 @@ func TestDownloadTLS(t *testing.T) { } func TestDownloadInsecureSkipTLSVerify(t *testing.T) { - ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) + ts := httptest.NewTLSServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) defer ts.Close() u, _ := url.ParseRequestURI(ts.URL) @@ -364,7 +364,7 @@ func TestDownloadInsecureSkipTLSVerify(t *testing.T) { } func TestHTTPGetterTarDownload(t *testing.T) { - srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { f, _ := os.Open("testdata/empty-0.0.1.tgz") defer f.Close() diff --git a/pkg/getter/ocigetter.go b/pkg/getter/ocigetter.go index 209786bd7..0547cdcbb 100644 --- a/pkg/getter/ocigetter.go +++ b/pkg/getter/ocigetter.go @@ -119,6 +119,7 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) { IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, + Proxy: http.ProxyFromEnvironment, } }) diff --git a/pkg/ignore/rules.go b/pkg/ignore/rules.go index a80923baf..88de407ad 100644 --- a/pkg/ignore/rules.go +++ b/pkg/ignore/rules.go @@ -173,7 +173,7 @@ func (r *Rules) parseRule(rule string) error { if strings.HasPrefix(rule, "/") { // Require path matches the root path. - p.match = func(n string, fi os.FileInfo) bool { + p.match = func(n string, _ os.FileInfo) bool { rule = strings.TrimPrefix(rule, "/") ok, err := filepath.Match(rule, n) if err != nil { @@ -184,7 +184,7 @@ func (r *Rules) parseRule(rule string) error { } } else if strings.Contains(rule, "/") { // require structural match. - p.match = func(n string, fi os.FileInfo) bool { + p.match = func(n string, _ os.FileInfo) bool { ok, err := filepath.Match(rule, n) if err != nil { log.Printf("Failed to compile %q: %s", rule, err) @@ -193,7 +193,7 @@ func (r *Rules) parseRule(rule string) error { return ok } } else { - p.match = func(n string, fi os.FileInfo) bool { + p.match = func(n string, _ os.FileInfo) bool { // When there is no slash in the pattern, we evaluate ONLY the // filename. n = filepath.Base(n) diff --git a/pkg/kube/wait.go b/pkg/kube/wait.go index ecdd38940..36110d0de 100644 --- a/pkg/kube/wait.go +++ b/pkg/kube/wait.go @@ -19,6 +19,7 @@ package kube // import "helm.sh/helm/v3/pkg/kube" import ( "context" "fmt" + "net/http" "time" "github.com/pkg/errors" @@ -32,6 +33,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/resource" "k8s.io/apimachinery/pkg/util/wait" ) @@ -50,10 +52,27 @@ func (w *waiter) waitForResources(created ResourceList) error { ctx, cancel := context.WithTimeout(context.Background(), w.timeout) defer cancel() + numberOfErrors := make([]int, len(created)) + for i := range numberOfErrors { + numberOfErrors[i] = 0 + } + return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { - for _, v := range created { + waitRetries := 30 + for i, v := range created { ready, err := w.c.IsReady(ctx, v) - if !ready || err != nil { + + if waitRetries > 0 && w.isRetryableError(err, v) { + numberOfErrors[i]++ + if numberOfErrors[i] > waitRetries { + w.log("Max number of retries reached") + return false, err + } + w.log("Retrying as current number of retries %d less than max number of retries %d", numberOfErrors[i]-1, waitRetries) + return false, nil + } + numberOfErrors[i] = 0 + if !ready { return false, err } } @@ -61,6 +80,25 @@ func (w *waiter) waitForResources(created ResourceList) error { }) } +func (w *waiter) isRetryableError(err error, resource *resource.Info) bool { + if err == nil { + return false + } + w.log("Error received when checking status of resource %s. Error: '%s', Resource details: '%s'", resource.Name, err, resource) + if ev, ok := err.(*apierrors.StatusError); ok { + statusCode := ev.Status().Code + retryable := w.isRetryableHTTPStatusCode(statusCode) + w.log("Status code received: %d. Retryable error? %t", statusCode, retryable) + return retryable + } + w.log("Retryable error? %t", true) + return true +} + +func (w *waiter) isRetryableHTTPStatusCode(httpStatusCode int32) bool { + return httpStatusCode == 0 || httpStatusCode == http.StatusTooManyRequests || (httpStatusCode >= 500 && httpStatusCode != http.StatusNotImplemented) +} + // waitForDeletedResources polls to check if all the resources are deleted or a timeout is reached func (w *waiter) waitForDeletedResources(deleted ResourceList) error { w.log("beginning wait for %d resources to be deleted with timeout of %v", len(deleted), w.timeout) @@ -68,7 +106,7 @@ func (w *waiter) waitForDeletedResources(deleted ResourceList) error { ctx, cancel := context.WithTimeout(context.Background(), w.timeout) defer cancel() - return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(ctx context.Context) (bool, error) { + return wait.PollUntilContextCancel(ctx, 2*time.Second, true, func(_ context.Context) (bool, error) { for _, v := range deleted { err := v.Get() if err == nil || !apierrors.IsNotFound(err) { diff --git a/pkg/lint/rules/chartfile.go b/pkg/lint/rules/chartfile.go index 70532ad4f..910602b7d 100644 --- a/pkg/lint/rules/chartfile.go +++ b/pkg/lint/rules/chartfile.go @@ -106,6 +106,10 @@ func validateChartName(cf *chart.Metadata) error { if cf.Name == "" { return errors.New("name is required") } + name := filepath.Base(cf.Name) + if name != cf.Name { + return fmt.Errorf("chart name %q is invalid", cf.Name) + } return nil } diff --git a/pkg/lint/rules/chartfile_test.go b/pkg/lint/rules/chartfile_test.go index 087cda047..a06d7dc31 100644 --- a/pkg/lint/rules/chartfile_test.go +++ b/pkg/lint/rules/chartfile_test.go @@ -30,16 +30,19 @@ import ( ) const ( + badCharNametDir = "testdata/badchartname" badChartDir = "testdata/badchartfile" anotherBadChartDir = "testdata/anotherbadchartfile" ) var ( + badChartNamePath = filepath.Join(badCharNametDir, "Chart.yaml") badChartFilePath = filepath.Join(badChartDir, "Chart.yaml") nonExistingChartFilePath = filepath.Join(os.TempDir(), "Chart.yaml") ) var badChart, _ = chartutil.LoadChartfile(badChartFilePath) +var badChartName, _ = chartutil.LoadChartfile(badChartNamePath) // Validation functions Test func TestValidateChartYamlNotDirectory(t *testing.T) { @@ -69,6 +72,11 @@ func TestValidateChartName(t *testing.T) { if err == nil { t.Errorf("validateChartName to return a linter error, got no error") } + + err = validateChartName(badChartName) + if err == nil { + t.Error("expected validateChartName to return a linter error for an invalid name, got no error") + } } func TestValidateChartVersion(t *testing.T) { diff --git a/pkg/lint/rules/template.go b/pkg/lint/rules/template.go index aa1dbb701..661c7f963 100644 --- a/pkg/lint/rules/template.go +++ b/pkg/lint/rules/template.go @@ -275,10 +275,10 @@ func validateMetadataNameFunc(obj *K8sYamlStruct) validation.ValidateNameFunc { case "certificatesigningrequest": // No validation. // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/certificates/validation/validation.go#L137-L140 - return func(name string, prefix bool) []string { return nil } + return func(_ string, _ bool) []string { return nil } case "role", "clusterrole", "rolebinding", "clusterrolebinding": // https://github.com/kubernetes/kubernetes/blob/v1.20.0/pkg/apis/rbac/validation/validation.go#L32-L34 - return func(name string, prefix bool) []string { + return func(name string, _ bool) []string { return apipath.IsValidPathSegmentName(name) } default: diff --git a/pkg/lint/rules/testdata/badchartname/Chart.yaml b/pkg/lint/rules/testdata/badchartname/Chart.yaml new file mode 100644 index 000000000..64f8fb8bf --- /dev/null +++ b/pkg/lint/rules/testdata/badchartname/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +description: A Helm chart for Kubernetes +version: 0.1.0 +name: "../badchartname" +type: application diff --git a/pkg/lint/rules/testdata/badchartname/values.yaml b/pkg/lint/rules/testdata/badchartname/values.yaml new file mode 100644 index 000000000..9f367033b --- /dev/null +++ b/pkg/lint/rules/testdata/badchartname/values.yaml @@ -0,0 +1 @@ +# Default values for badchartfile. diff --git a/pkg/plugin/plugin.go b/pkg/plugin/plugin.go index fb3bc5215..5bb743481 100644 --- a/pkg/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -175,6 +175,10 @@ var validPluginName = regexp.MustCompile("^[A-Za-z0-9_-]+$") // validatePluginData validates a plugin's YAML data. func validatePluginData(plug *Plugin, filepath string) error { + // When metadata section missing, initialize with no data + if plug.Metadata == nil { + plug.Metadata = &Metadata{} + } if !validPluginName.MatchString(plug.Metadata.Name) { return fmt.Errorf("invalid plugin name at %q", filepath) } diff --git a/pkg/plugin/plugin_test.go b/pkg/plugin/plugin_test.go index 1ec2d5304..725052346 100644 --- a/pkg/plugin/plugin_test.go +++ b/pkg/plugin/plugin_test.go @@ -353,6 +353,11 @@ func TestSetupEnvWithSpace(t *testing.T) { } func TestValidatePluginData(t *testing.T) { + // A mock plugin missing any metadata. + mockMissingMeta := &Plugin{ + Dir: "no-such-dir", + } + for i, item := range []struct { pass bool plug *Plugin @@ -363,6 +368,7 @@ func TestValidatePluginData(t *testing.T) { {false, mockPlugin("$foo -bar")}, // Test leading chars {false, mockPlugin("foo -bar ")}, // Test trailing chars {false, mockPlugin("foo\nbar")}, // Test newline + {false, mockMissingMeta}, // Test if the metadata section missing } { err := validatePluginData(item.plug, fmt.Sprintf("test-%d", i)) if item.pass && err != nil { diff --git a/pkg/provenance/sign_test.go b/pkg/provenance/sign_test.go index 17f727ea7..bf6848368 100644 --- a/pkg/provenance/sign_test.go +++ b/pkg/provenance/sign_test.go @@ -196,7 +196,7 @@ func TestDecryptKey(t *testing.T) { } // We give this a simple callback that returns the password. - if err := k.DecryptKey(func(s string) ([]byte, error) { + if err := k.DecryptKey(func(_ string) ([]byte, error) { return []byte("secret"), nil }); err != nil { t.Fatal(err) @@ -208,7 +208,7 @@ func TestDecryptKey(t *testing.T) { t.Fatal(err) } // Now we give it a bogus password. - if err := k.DecryptKey(func(s string) ([]byte, error) { + if err := k.DecryptKey(func(_ string) ([]byte, error) { return []byte("secrets_and_lies"), nil }); err == nil { t.Fatal("Expected an error when giving a bogus passphrase") diff --git a/pkg/pusher/ocipusher.go b/pkg/pusher/ocipusher.go index 94154d389..b37a0c605 100644 --- a/pkg/pusher/ocipusher.go +++ b/pkg/pusher/ocipusher.go @@ -29,6 +29,7 @@ import ( "helm.sh/helm/v3/internal/tlsutil" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/registry" + "helm.sh/helm/v3/pkg/time/ctime" ) // OCIPusher is the default OCI backend handler @@ -89,6 +90,9 @@ func (pusher *OCIPusher) push(chartRef, href string) error { path.Join(strings.TrimPrefix(href, fmt.Sprintf("%s://", registry.OCIScheme)), meta.Metadata.Name), meta.Metadata.Version) + chartCreationTime := ctime.Created(stat) + pushOpts = append(pushOpts, registry.PushOptCreationTime(chartCreationTime.Format(time.RFC3339))) + _, err = client.Push(chartBytes, ref, pushOpts...) return err } diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 0d353e8a2..9e58e1c16 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -57,8 +57,8 @@ type ( enableCache bool // path to repository config file e.g. ~/.docker/config.json credentialsFile string - username string - password string + username string + password string out io.Writer authorizer auth.Client registryAuthorizer *registryauth.Client @@ -140,7 +140,7 @@ func NewClient(options ...ClientOption) (*Client, error) { "User-Agent": {version.GetUserAgent()}, }, Cache: cache, - Credential: func(ctx context.Context, reg string) (registryauth.Credential, error) { + Credential: func(_ context.Context, reg string) (registryauth.Credential, error) { if client.username != "" && client.password != "" { return registryauth.Credential{ Username: client.username, @@ -229,7 +229,7 @@ func ClientOptPlainHTTP() ClientOption { // ClientOptResolver returns a function that sets the resolver setting on a client options set func ClientOptResolver(resolver remotes.Resolver) ClientOption { return func(client *Client) { - client.resolver = func(ref registry.Reference) (remotes.Resolver, error) { + client.resolver = func(_ registry.Reference) (remotes.Resolver, error) { return resolver, nil } } @@ -558,9 +558,9 @@ type ( } pushOperation struct { - provData []byte - strictMode bool - test bool + provData []byte + strictMode bool + creationTime string } ) @@ -614,7 +614,7 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu descriptors = append(descriptors, provDescriptor) } - ociAnnotations := generateOCIAnnotations(meta, operation.test) + ociAnnotations := generateOCIAnnotations(meta, operation.creationTime) manifestData, manifest, err := content.GenerateManifest(&configDescriptor, ociAnnotations, descriptors...) if err != nil { @@ -683,10 +683,10 @@ func PushOptStrictMode(strictMode bool) PushOption { } } -// PushOptTest returns a function that sets whether test setting on push -func PushOptTest(test bool) PushOption { +// PushOptCreationDate returns a function that sets the creation time +func PushOptCreationTime(creationTime string) PushOption { return func(operation *pushOperation) { - operation.test = test + operation.creationTime = creationTime } } diff --git a/pkg/registry/util.go b/pkg/registry/util.go index 8baf0852a..4bff39495 100644 --- a/pkg/registry/util.go +++ b/pkg/registry/util.go @@ -166,10 +166,10 @@ func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, i } // generateOCIAnnotations will generate OCI annotations to include within the OCI manifest -func generateOCIAnnotations(meta *chart.Metadata, test bool) map[string]string { +func generateOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { // Get annotations from Chart attributes - ociAnnotations := generateChartOCIAnnotations(meta, test) + ociAnnotations := generateChartOCIAnnotations(meta, creationTime) // Copy Chart annotations annotations: @@ -190,7 +190,7 @@ annotations: } // getChartOCIAnnotations will generate OCI annotations from the provided chart -func generateChartOCIAnnotations(meta *chart.Metadata, test bool) map[string]string { +func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string { chartOCIAnnotations := map[string]string{} chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, meta.Description) @@ -198,10 +198,12 @@ func generateChartOCIAnnotations(meta *chart.Metadata, test bool) map[string]str chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version) chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home) - if !test { - chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationCreated, helmtime.Now().UTC().Format(time.RFC3339)) + if len(creationTime) == 0 { + creationTime = helmtime.Now().UTC().Format(time.RFC3339) } + chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationCreated, creationTime) + if len(meta.Sources) > 0 { chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0]) } diff --git a/pkg/registry/util_test.go b/pkg/registry/util_test.go index fdf09360b..908ea4950 100644 --- a/pkg/registry/util_test.go +++ b/pkg/registry/util_test.go @@ -29,6 +29,8 @@ import ( func TestGenerateOCIChartAnnotations(t *testing.T) { + nowString := helmtime.Now().Format(time.RFC3339) + tests := []struct { name string chart *chart.Metadata @@ -43,6 +45,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, }, }, { @@ -56,6 +59,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, "org.opencontainers.image.description": "OCI Helm Chart", "org.opencontainers.image.url": "https://helm.sh", }, @@ -76,6 +80,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, "org.opencontainers.image.description": "OCI Helm Chart", "org.opencontainers.image.url": "https://helm.sh", "org.opencontainers.image.authors": "John Snow", @@ -95,6 +100,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, "org.opencontainers.image.description": "OCI Helm Chart", "org.opencontainers.image.url": "https://helm.sh", "org.opencontainers.image.authors": "John Snow (john@winterfell.com)", @@ -115,6 +121,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, "org.opencontainers.image.description": "OCI Helm Chart", "org.opencontainers.image.url": "https://helm.sh", "org.opencontainers.image.authors": "John Snow (john@winterfell.com), Jane Snow", @@ -133,6 +140,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, "org.opencontainers.image.description": "OCI Helm Chart", "org.opencontainers.image.source": "https://github.com/helm/helm", }, @@ -141,7 +149,7 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { for _, tt := range tests { - result := generateChartOCIAnnotations(tt.chart, true) + result := generateChartOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) @@ -152,6 +160,8 @@ func TestGenerateOCIChartAnnotations(t *testing.T) { func TestGenerateOCIAnnotations(t *testing.T) { + nowString := helmtime.Now().Format(time.RFC3339) + tests := []struct { name string chart *chart.Metadata @@ -166,6 +176,7 @@ func TestGenerateOCIAnnotations(t *testing.T) { map[string]string{ "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", + "org.opencontainers.image.created": nowString, }, }, { @@ -183,6 +194,7 @@ func TestGenerateOCIAnnotations(t *testing.T) { "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", "org.opencontainers.image.description": "OCI Helm Chart", + "org.opencontainers.image.created": nowString, "extrakey": "extravlue", "anotherkey": "anothervalue", }, @@ -203,6 +215,7 @@ func TestGenerateOCIAnnotations(t *testing.T) { "org.opencontainers.image.title": "oci", "org.opencontainers.image.version": "0.0.1", "org.opencontainers.image.description": "OCI Helm Chart", + "org.opencontainers.image.created": nowString, "extrakey": "extravlue", }, }, @@ -210,7 +223,7 @@ func TestGenerateOCIAnnotations(t *testing.T) { for _, tt := range tests { - result := generateOCIAnnotations(tt.chart, true) + result := generateOCIAnnotations(tt.chart, nowString) if !reflect.DeepEqual(tt.expect, result) { t.Errorf("%s: expected map %v, got %v", tt.name, tt.expect, result) @@ -220,12 +233,16 @@ func TestGenerateOCIAnnotations(t *testing.T) { } func TestGenerateOCICreatedAnnotations(t *testing.T) { + + nowTime := helmtime.Now() + nowTimeString := nowTime.Format(time.RFC3339) + chart := &chart.Metadata{ Name: "oci", Version: "0.0.1", } - result := generateOCIAnnotations(chart, false) + result := generateOCIAnnotations(chart, nowTimeString) // Check that created annotation exists if _, ok := result[ocispec.AnnotationCreated]; !ok { @@ -237,4 +254,22 @@ func TestGenerateOCICreatedAnnotations(t *testing.T) { t.Errorf("%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) } + // Verify default creation time set + result = generateOCIAnnotations(chart, "") + + // Check that created annotation exists + if _, ok := result[ocispec.AnnotationCreated]; !ok { + t.Errorf("%s annotation not created", ocispec.AnnotationCreated) + } + + if createdTimeAnnotation, err := helmtime.Parse(time.RFC3339, result[ocispec.AnnotationCreated]); err != nil { + t.Errorf("%s annotation with value '%s' not in RFC3339 format", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated]) + + // Verify creation annotation after time test began + if !nowTime.Before(createdTimeAnnotation) { + t.Errorf("%s annotation with value '%s' not configured properly. Annotation value is not after %s", ocispec.AnnotationCreated, result[ocispec.AnnotationCreated], nowTimeString) + } + + } + } diff --git a/pkg/registry/utils_test.go b/pkg/registry/utils_test.go index 74aa0dbc0..d7aba2bb7 100644 --- a/pkg/registry/utils_test.go +++ b/pkg/registry/utils_test.go @@ -216,9 +216,12 @@ func initCompromisedRegistryTestServer() string { } func testPush(suite *TestSuite) { + + testingChartCreationTime := "1977-09-02T22:04:05Z" + // Bad bytes ref := fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost) - _, err := suite.RegistryClient.Push([]byte("hello"), ref, PushOptTest(true)) + _, err := suite.RegistryClient.Push([]byte("hello"), ref, PushOptCreationTime(testingChartCreationTime)) suite.NotNil(err, "error pushing non-chart bytes") // Load a test chart @@ -229,20 +232,20 @@ func testPush(suite *TestSuite) { // non-strict ref (chart name) ref = fmt.Sprintf("%s/testrepo/boop:%s", suite.DockerRegistryHost, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref, PushOptTest(true)) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) suite.NotNil(err, "error pushing non-strict ref (bad basename)") // non-strict ref (chart name), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptTest(true)) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) suite.Nil(err, "no error pushing non-strict ref (bad basename), with strict mode disabled") // non-strict ref (chart version) ref = fmt.Sprintf("%s/testrepo/%s:latest", suite.DockerRegistryHost, meta.Name) - _, err = suite.RegistryClient.Push(chartData, ref, PushOptTest(true)) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) suite.NotNil(err, "error pushing non-strict ref (bad tag)") // non-strict ref (chart version), with strict mode disabled - _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptTest(true)) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptStrictMode(false), PushOptCreationTime(testingChartCreationTime)) suite.Nil(err, "no error pushing non-strict ref (bad tag), with strict mode disabled") // basic push, good ref @@ -251,7 +254,7 @@ func testPush(suite *TestSuite) { meta, err = extractChartMeta(chartData) suite.Nil(err, "no error extracting chart meta") ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - _, err = suite.RegistryClient.Push(chartData, ref, PushOptTest(true)) + _, err = suite.RegistryClient.Push(chartData, ref, PushOptCreationTime(testingChartCreationTime)) suite.Nil(err, "no error pushing good ref") _, err = suite.RegistryClient.Pull(ref) @@ -269,7 +272,7 @@ func testPush(suite *TestSuite) { // push with prov ref = fmt.Sprintf("%s/testrepo/%s:%s", suite.DockerRegistryHost, meta.Name, meta.Version) - result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptTest(true)) + result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime)) suite.Nil(err, "no error pushing good ref with prov") _, err = suite.RegistryClient.Pull(ref) @@ -281,12 +284,12 @@ func testPush(suite *TestSuite) { suite.Equal(ref, result.Ref) suite.Equal(meta.Name, result.Chart.Meta.Name) suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(684), result.Manifest.Size) + suite.Equal(int64(742), result.Manifest.Size) suite.Equal(int64(99), result.Config.Size) suite.Equal(int64(973), result.Chart.Size) suite.Equal(int64(695), result.Prov.Size) suite.Equal( - "sha256:b57e8ffd938c43253f30afedb3c209136288e6b3af3b33473e95ea3b805888e6", + "sha256:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2", result.Manifest.Digest) suite.Equal( "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", @@ -354,12 +357,12 @@ func testPull(suite *TestSuite) { suite.Equal(ref, result.Ref) suite.Equal(meta.Name, result.Chart.Meta.Name) suite.Equal(meta.Version, result.Chart.Meta.Version) - suite.Equal(int64(684), result.Manifest.Size) + suite.Equal(int64(742), result.Manifest.Size) suite.Equal(int64(99), result.Config.Size) suite.Equal(int64(973), result.Chart.Size) suite.Equal(int64(695), result.Prov.Size) suite.Equal( - "sha256:b57e8ffd938c43253f30afedb3c209136288e6b3af3b33473e95ea3b805888e6", + "sha256:fbbade96da6050f68f94f122881e3b80051a18f13ab5f4081868dd494538f5c2", result.Manifest.Digest) suite.Equal( "sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580", @@ -370,7 +373,7 @@ func testPull(suite *TestSuite) { suite.Equal( "sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256", result.Prov.Digest) - suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}", + suite.Equal("{\"schemaVersion\":2,\"config\":{\"mediaType\":\"application/vnd.cncf.helm.config.v1+json\",\"digest\":\"sha256:8d17cb6bf6ccd8c29aace9a658495cbd5e2e87fc267876e86117c7db681c9580\",\"size\":99},\"layers\":[{\"mediaType\":\"application/vnd.cncf.helm.chart.provenance.v1.prov\",\"digest\":\"sha256:b0a02b7412f78ae93324d48df8fcc316d8482e5ad7827b5b238657a29a22f256\",\"size\":695},{\"mediaType\":\"application/vnd.cncf.helm.chart.content.v1.tar+gzip\",\"digest\":\"sha256:e5ef611620fb97704d8751c16bab17fedb68883bfb0edc76f78a70e9173f9b55\",\"size\":973}],\"annotations\":{\"org.opencontainers.image.created\":\"1977-09-02T22:04:05Z\",\"org.opencontainers.image.description\":\"A Helm chart for Kubernetes\",\"org.opencontainers.image.title\":\"signtest\",\"org.opencontainers.image.version\":\"0.1.0\"}}", string(result.Manifest.Data)) suite.Equal("{\"name\":\"signtest\",\"version\":\"0.1.0\",\"description\":\"A Helm chart for Kubernetes\",\"apiVersion\":\"v1\"}", string(result.Config.Data)) diff --git a/pkg/repo/chartrepo.go b/pkg/repo/chartrepo.go index d9022ee6e..970e96da2 100644 --- a/pkg/repo/chartrepo.go +++ b/pkg/repo/chartrepo.go @@ -96,7 +96,7 @@ func (r *ChartRepository) Load() error { // 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 { + filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, _ error) error { if !f.IsDir() { if strings.Contains(f.Name(), "-index.yaml") { i, err := LoadIndexFile(path) diff --git a/pkg/repo/chartrepo_test.go b/pkg/repo/chartrepo_test.go index 343d5852c..4e72731ea 100644 --- a/pkg/repo/chartrepo_test.go +++ b/pkg/repo/chartrepo_test.go @@ -132,7 +132,7 @@ func TestIndexCustomSchemeDownload(t *testing.T) { repoName := "gcs-repo" repoURL := "gs://some-gcs-bucket" myCustomGetter := &CustomGetter{} - customGetterConstructor := func(options ...getter.Option) (getter.Getter, error) { + customGetterConstructor := func(_ ...getter.Option) (getter.Getter, error) { return myCustomGetter, nil } providers := getter.Providers{{ @@ -267,7 +267,7 @@ func startLocalServerForTests(handler http.Handler) (*httptest.Server, error) { if err != nil { return nil, err } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write(fileBytes) }) } @@ -282,7 +282,7 @@ func startLocalTLSServerForTests(handler http.Handler) (*httptest.Server, error) if err != nil { return nil, err } - handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + handler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write(fileBytes) }) } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 8a23ba060..40b11c5cf 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -359,10 +359,14 @@ func loadIndex(data []byte, source string) (*IndexFile, error) { log.Printf("skipping loading invalid entry for chart %q from %s: empty entry", name, source) continue } + // When metadata section missing, initialize with no data + if cvs[idx].Metadata == nil { + cvs[idx].Metadata = &chart.Metadata{} + } if cvs[idx].APIVersion == "" { cvs[idx].APIVersion = chart.APIVersionV1 } - if err := cvs[idx].Validate(); err != nil { + if err := cvs[idx].Validate(); ignoreSkippableChartValidationError(err) != nil { log.Printf("skipping loading invalid entry for chart %q %q from %s: %s", name, cvs[idx].Version, source, err) cvs = append(cvs[:idx], cvs[idx+1:]...) } @@ -388,3 +392,23 @@ func jsonOrYamlUnmarshal(b []byte, i interface{}) error { } return yaml.UnmarshalStrict(b, i) } + +// ignoreSkippableChartValidationError inspect the given error and returns nil if +// the error isn't important for index loading +// +// In particular, charts may introduce validations that don't impact repository indexes +// And repository indexes may be generated by older/non-complient software, which doesn't +// conform to all validations. +func ignoreSkippableChartValidationError(err error) error { + verr, ok := err.(chart.ValidationError) + if !ok { + return err + } + + // https://github.com/helm/helm/issues/12748 (JFrog repository strips alias field) + if strings.HasPrefix(verr.Error(), "validation: more than one dependency with name or alias") { + return nil + } + + return err +} diff --git a/pkg/repo/index_test.go b/pkg/repo/index_test.go index cb9317f7e..91486670b 100644 --- a/pkg/repo/index_test.go +++ b/pkg/repo/index_test.go @@ -20,6 +20,7 @@ import ( "bufio" "bytes" "encoding/json" + "fmt" "net/http" "os" "path/filepath" @@ -69,6 +70,10 @@ entries: name: grafana foo: - + bar: + - digest: "sha256:1234567890abcdef" + urls: + - https://charts.helm.sh/stable/alpine-1.0.0.tgz ` ) @@ -592,3 +597,50 @@ func TestAddFileIndexEntriesNil(t *testing.T) { } } } + +func TestIgnoreSkippableChartValidationError(t *testing.T) { + type TestCase struct { + Input error + ErrorSkipped bool + } + testCases := map[string]TestCase{ + "nil": { + Input: nil, + }, + "generic_error": { + Input: fmt.Errorf("foo"), + }, + "non_skipped_validation_error": { + Input: chart.ValidationError("chart.metadata.type must be application or library"), + }, + "skipped_validation_error": { + Input: chart.ValidationErrorf("more than one dependency with name or alias %q", "foo"), + ErrorSkipped: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + result := ignoreSkippableChartValidationError(tc.Input) + + if tc.Input == nil { + if result != nil { + t.Error("expected nil result for nil input") + } + return + } + + if tc.ErrorSkipped { + if result != nil { + t.Error("expected nil result for skipped error") + } + return + } + + if tc.Input != result { + t.Error("expected the result equal to input") + } + + }) + } +} diff --git a/pkg/repo/repotest/server.go b/pkg/repo/repotest/server.go index d9a5201aa..4a86707cf 100644 --- a/pkg/repo/repotest/server.go +++ b/pkg/repo/repotest/server.go @@ -61,7 +61,7 @@ func NewTempServerWithCleanupAndBasicAuth(t *testing.T, glob string) *Server { if err != nil { t.Fatal(err) } - srv.WithMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + srv.WithMiddleware(http.HandlerFunc(func(_ 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) diff --git a/pkg/storage/driver/mock_test.go b/pkg/storage/driver/mock_test.go index 1c13e1dba..9174a8c71 100644 --- a/pkg/storage/driver/mock_test.go +++ b/pkg/storage/driver/mock_test.go @@ -262,7 +262,7 @@ func newTestFixtureSQL(t *testing.T, _ ...*rspb.Release) (*SQL, sqlmock.Sqlmock) sqlxDB := sqlx.NewDb(sqlDB, "sqlmock") return &SQL{ db: sqlxDB, - Log: func(a string, b ...interface{}) {}, + Log: func(_ string, _ ...interface{}) {}, namespace: "default", statementBuilder: sq.StatementBuilder.PlaceholderFormat(sq.Dollar), }, mock diff --git a/pkg/storage/driver/sql.go b/pkg/storage/driver/sql.go index 8f5714f15..2ef951184 100644 --- a/pkg/storage/driver/sql.go +++ b/pkg/storage/driver/sql.go @@ -98,9 +98,9 @@ func (s *SQL) Name() string { // Check if all migrations al func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { // make map (set) of ids for fast search - migrationsIds := make(map[string]struct{}) + migrationsIDs := make(map[string]struct{}) for _, migration := range migrations { - migrationsIds[migration.Id] = struct{}{} + migrationsIDs[migration.Id] = struct{}{} } // get list of applied migrations @@ -113,15 +113,15 @@ func (s *SQL) checkAlreadyApplied(migrations []*migrate.Migration) bool { } for _, record := range records { - if _, ok := migrationsIds[record.Id]; ok { + if _, ok := migrationsIDs[record.Id]; ok { s.Log("checkAlreadyApplied: found previous migration (Id: %v) applied at %v", record.Id, record.AppliedAt) - delete(migrationsIds, record.Id) + delete(migrationsIDs, record.Id) } } // check if all migrations appliyed - if len(migrationsIds) != 0 { - for id := range migrationsIds { + if len(migrationsIDs) != 0 { + for id := range migrationsIDs { s.Log("checkAlreadyApplied: find unapplied migration (id: %v)", id) } return false diff --git a/pkg/storage/driver/sql_test.go b/pkg/storage/driver/sql_test.go index 0dfa85e38..37dcc8503 100644 --- a/pkg/storage/driver/sql_test.go +++ b/pkg/storage/driver/sql_test.go @@ -546,31 +546,31 @@ func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace stri func TestSqlChechkAppliedMigrations(t *testing.T) { cases := []struct { migrationsToApply []*migrate.Migration - appliedMigrationsIds []string + appliedMigrationsIDs []string expectedResult bool errorExplanation string }{ { migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}}, - appliedMigrationsIds: []string{"1", "2", "init1", "3", "init2", "4", "5"}, + appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "5"}, expectedResult: false, errorExplanation: "Has found one migration id \"init3\" as applied, that was not applied", }, { migrationsToApply: []*migrate.Migration{{Id: "init1"}, {Id: "init2"}, {Id: "init3"}}, - appliedMigrationsIds: []string{"1", "2", "init1", "3", "init2", "4", "init3", "5"}, + appliedMigrationsIDs: []string{"1", "2", "init1", "3", "init2", "4", "init3", "5"}, expectedResult: true, errorExplanation: "Has not found one or more migration ids, that was applied", }, { migrationsToApply: []*migrate.Migration{{Id: "init"}}, - appliedMigrationsIds: []string{"1", "2", "3", "inits", "4", "tinit", "5"}, + appliedMigrationsIDs: []string{"1", "2", "3", "inits", "4", "tinit", "5"}, expectedResult: false, errorExplanation: "Has found single \"init\", that was not applied", }, { migrationsToApply: []*migrate.Migration{{Id: "init"}}, - appliedMigrationsIds: []string{"1", "2", "init", "3", "init2", "4", "init3", "5"}, + appliedMigrationsIDs: []string{"1", "2", "init", "3", "init2", "4", "init3", "5"}, expectedResult: true, errorExplanation: "Has not found single migration id \"init\", that was applied", }, @@ -578,7 +578,7 @@ func TestSqlChechkAppliedMigrations(t *testing.T) { for i, c := range cases { sqlDriver, mock := newTestFixtureSQL(t) rows := sqlmock.NewRows([]string{"id", "applied_at"}) - for _, id := range c.appliedMigrationsIds { + for _, id := range c.appliedMigrationsIDs { rows.AddRow(id, time.Time{}) } mock. diff --git a/pkg/time/ctime/ctime.go b/pkg/time/ctime/ctime.go new file mode 100644 index 000000000..f2998d76d --- /dev/null +++ b/pkg/time/ctime/ctime.go @@ -0,0 +1,25 @@ +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package ctime + +import ( + "os" + "time" +) + +func Created(fi os.FileInfo) time.Time { + return created(fi) +} diff --git a/pkg/time/ctime/ctime_linux.go b/pkg/time/ctime/ctime_linux.go new file mode 100644 index 000000000..c3cea1d78 --- /dev/null +++ b/pkg/time/ctime/ctime_linux.go @@ -0,0 +1,30 @@ +//go:build linux + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package ctime + +import ( + "os" + "syscall" + "time" +) + +func created(fi os.FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + //nolint + return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) +} diff --git a/pkg/time/ctime/ctime_other.go b/pkg/time/ctime/ctime_other.go new file mode 100644 index 000000000..f21ed7347 --- /dev/null +++ b/pkg/time/ctime/ctime_other.go @@ -0,0 +1,27 @@ +//go:build !linux + +/* +Copyright The Helm Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package ctime + +import ( + "os" + "time" +) + +func created(fi os.FileInfo) time.Time { + return fi.ModTime() +}