diff --git a/cmd/helm/flags.go b/cmd/helm/flags.go index 62e9f90fa..84f8842dd 100644 --- a/cmd/helm/flags.go +++ b/cmd/helm/flags.go @@ -1,252 +1,254 @@ -/* -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 main - -import ( - "flag" - "fmt" - "log" - "path/filepath" - "sort" - "strings" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "k8s.io/klog/v2" - - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/output" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/postrender" - "helm.sh/helm/v3/pkg/repo" -) - -const ( - outputFlag = "output" - postRenderFlag = "post-renderer" - postRenderArgsFlag = "post-renderer-args" -) - -func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { - f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)") - f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") - f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") - f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") -} - -func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { - f.StringVar(&c.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") - f.BoolVar(&c.Verify, "verify", false, "verify the package before using it") - f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification") - f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart") - f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart") - f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart") - f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") - f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") - f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains") -} - -// bindOutputFlag will add the output flag to the given command and bind the -// value to the given format pointer -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(_ *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)) - } - - // Sort the results to get a deterministic order for the tests - sort.Strings(formatNames) - return formatNames, cobra.ShellCompDirectiveNoFileComp - }) - - if err != nil { - log.Fatal(err) - } -} - -type outputValue output.Format - -func newOutputValue(defaultValue output.Format, p *output.Format) *outputValue { - *p = defaultValue - return (*outputValue)(p) -} - -func (o *outputValue) String() string { - // It is much cleaner looking (and technically less allocations) to just - // convert to a string rather than type asserting to the underlying - // output.Format - return string(*o) -} - -func (o *outputValue) Type() string { - return "format" -} - -func (o *outputValue) Set(s string) error { - outfmt, err := output.ParseFormat(s) - if err != nil { - return err - } - *o = outputValue(outfmt) - return nil -} - -func bindPostRenderFlag(cmd *cobra.Command, varRef *postrender.PostRenderer) { - p := &postRendererOptions{varRef, "", []string{}} - cmd.Flags().Var(&postRendererString{p}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path") - cmd.Flags().Var(&postRendererArgsSlice{p}, postRenderArgsFlag, "an argument to the post-renderer (can specify multiple)") -} - -type postRendererOptions struct { - renderer *postrender.PostRenderer - binaryPath string - args []string -} - -type postRendererString struct { - options *postRendererOptions -} - -func (p *postRendererString) String() string { - return p.options.binaryPath -} - -func (p *postRendererString) Type() string { - return "postRendererString" -} - -func (p *postRendererString) Set(val string) error { - if val == "" { - return nil - } - p.options.binaryPath = val - pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...) - if err != nil { - return err - } - *p.options.renderer = pr - return nil -} - -type postRendererArgsSlice struct { - options *postRendererOptions -} - -func (p *postRendererArgsSlice) String() string { - return "[" + strings.Join(p.options.args, ",") + "]" -} - -func (p *postRendererArgsSlice) Type() string { - return "postRendererArgsSlice" -} - -func (p *postRendererArgsSlice) Set(val string) error { - - // a post-renderer defined by a user may accept empty arguments - p.options.args = append(p.options.args, val) - - if p.options.binaryPath == "" { - return nil - } - // overwrite if already create PostRenderer by `post-renderer` flags - pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...) - if err != nil { - return err - } - *p.options.renderer = pr - return nil -} - -func (p *postRendererArgsSlice) Append(val string) error { - p.options.args = append(p.options.args, val) - return nil -} - -func (p *postRendererArgsSlice) Replace(val []string) error { - p.options.args = val - return nil -} - -func (p *postRendererArgsSlice) GetSlice() []string { - return p.options.args -} - -func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) { - chartInfo := strings.Split(chartRef, "/") - if len(chartInfo) != 2 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - repoName := chartInfo[0] - chartName := chartInfo[1] - - path := filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(repoName)) - - var versions []string - if indexFile, err := repo.LoadIndexFile(path); err == nil { - for _, details := range indexFile.Entries[chartName] { - appVersion := details.Metadata.AppVersion - appVersionDesc := "" - if appVersion != "" { - appVersionDesc = fmt.Sprintf("App: %s, ", appVersion) - } - created := details.Created.Format("January 2, 2006") - createdDesc := "" - if created != "" { - createdDesc = fmt.Sprintf("Created: %s ", created) - } - deprecated := "" - if details.Metadata.Deprecated { - deprecated = "(deprecated)" - } - versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Metadata.Version, appVersionDesc, createdDesc, deprecated)) - } - } - - return versions, cobra.ShellCompDirectiveNoFileComp -} - -// addKlogFlags adds flags from k8s.io/klog -// marks the flags as hidden to avoid polluting the help text -func addKlogFlags(fs *pflag.FlagSet) { - local := flag.NewFlagSet("klog", flag.ExitOnError) - klog.InitFlags(local) - local.VisitAll(func(fl *flag.Flag) { - fl.Name = normalize(fl.Name) - if fs.Lookup(fl.Name) != nil { - return - } - newflag := pflag.PFlagFromGoFlag(fl) - newflag.Hidden = true - fs.AddFlag(newflag) - }) -} - -// normalize replaces underscores with hyphens -func normalize(s string) string { - return strings.ReplaceAll(s, "_", "-") -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "flag" + "fmt" + "log" + "path/filepath" + "sort" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/klog/v2" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli/output" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v3/pkg/postrender" + "helm.sh/helm/v3/pkg/repo" +) + +const ( + outputFlag = "output" + postRenderFlag = "post-renderer" + postRenderArgsFlag = "post-renderer-args" +) + +func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { + f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)") + f.StringArrayVar(&v.Values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&v.StringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + f.StringArrayVar(&v.FileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") + f.StringArrayVar(&v.JSONValues, "set-json", []string{}, "set JSON values on the command line (can specify multiple or separate values with commas: key1=jsonval1,key2=jsonval2)") + f.StringArrayVar(&v.LiteralValues, "set-literal", []string{}, "set a literal STRING value on the command line") + f.StringSliceVarP(&v.PropertyFiles, "property-file", "p", []string{}, "specify property files to load (can specify multiple)") +} + +func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { + f.StringVar(&c.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") + f.BoolVar(&c.Verify, "verify", false, "verify the package before using it") + f.StringVar(&c.Keyring, "keyring", defaultKeyring(), "location of public keys used for verification") + f.StringVar(&c.RepoURL, "repo", "", "chart repository url where to locate the requested chart") + f.StringVar(&c.Username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&c.Password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&c.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&c.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.BoolVar(&c.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&c.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") + f.StringVar(&c.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + f.BoolVar(&c.PassCredentialsAll, "pass-credentials", false, "pass credentials to all domains") +} + +// bindOutputFlag will add the output flag to the given command and bind the +// value to the given format pointer +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(_ *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)) + } + + // Sort the results to get a deterministic order for the tests + sort.Strings(formatNames) + return formatNames, cobra.ShellCompDirectiveNoFileComp + }) + + if err != nil { + log.Fatal(err) + } +} + +type outputValue output.Format + +func newOutputValue(defaultValue output.Format, p *output.Format) *outputValue { + *p = defaultValue + return (*outputValue)(p) +} + +func (o *outputValue) String() string { + // It is much cleaner looking (and technically less allocations) to just + // convert to a string rather than type asserting to the underlying + // output.Format + return string(*o) +} + +func (o *outputValue) Type() string { + return "format" +} + +func (o *outputValue) Set(s string) error { + outfmt, err := output.ParseFormat(s) + if err != nil { + return err + } + *o = outputValue(outfmt) + return nil +} + +func bindPostRenderFlag(cmd *cobra.Command, varRef *postrender.PostRenderer) { + p := &postRendererOptions{varRef, "", []string{}} + cmd.Flags().Var(&postRendererString{p}, postRenderFlag, "the path to an executable to be used for post rendering. If it exists in $PATH, the binary will be used, otherwise it will try to look for the executable at the given path") + cmd.Flags().Var(&postRendererArgsSlice{p}, postRenderArgsFlag, "an argument to the post-renderer (can specify multiple)") +} + +type postRendererOptions struct { + renderer *postrender.PostRenderer + binaryPath string + args []string +} + +type postRendererString struct { + options *postRendererOptions +} + +func (p *postRendererString) String() string { + return p.options.binaryPath +} + +func (p *postRendererString) Type() string { + return "postRendererString" +} + +func (p *postRendererString) Set(val string) error { + if val == "" { + return nil + } + p.options.binaryPath = val + pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...) + if err != nil { + return err + } + *p.options.renderer = pr + return nil +} + +type postRendererArgsSlice struct { + options *postRendererOptions +} + +func (p *postRendererArgsSlice) String() string { + return "[" + strings.Join(p.options.args, ",") + "]" +} + +func (p *postRendererArgsSlice) Type() string { + return "postRendererArgsSlice" +} + +func (p *postRendererArgsSlice) Set(val string) error { + + // a post-renderer defined by a user may accept empty arguments + p.options.args = append(p.options.args, val) + + if p.options.binaryPath == "" { + return nil + } + // overwrite if already create PostRenderer by `post-renderer` flags + pr, err := postrender.NewExec(p.options.binaryPath, p.options.args...) + if err != nil { + return err + } + *p.options.renderer = pr + return nil +} + +func (p *postRendererArgsSlice) Append(val string) error { + p.options.args = append(p.options.args, val) + return nil +} + +func (p *postRendererArgsSlice) Replace(val []string) error { + p.options.args = val + return nil +} + +func (p *postRendererArgsSlice) GetSlice() []string { + return p.options.args +} + +func compVersionFlag(chartRef string, _ string) ([]string, cobra.ShellCompDirective) { + chartInfo := strings.Split(chartRef, "/") + if len(chartInfo) != 2 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + + repoName := chartInfo[0] + chartName := chartInfo[1] + + path := filepath.Join(settings.RepositoryCache, helmpath.CacheIndexFile(repoName)) + + var versions []string + if indexFile, err := repo.LoadIndexFile(path); err == nil { + for _, details := range indexFile.Entries[chartName] { + appVersion := details.Metadata.AppVersion + appVersionDesc := "" + if appVersion != "" { + appVersionDesc = fmt.Sprintf("App: %s, ", appVersion) + } + created := details.Created.Format("January 2, 2006") + createdDesc := "" + if created != "" { + createdDesc = fmt.Sprintf("Created: %s ", created) + } + deprecated := "" + if details.Metadata.Deprecated { + deprecated = "(deprecated)" + } + versions = append(versions, fmt.Sprintf("%s\t%s%s%s", details.Metadata.Version, appVersionDesc, createdDesc, deprecated)) + } + } + + return versions, cobra.ShellCompDirectiveNoFileComp +} + +// addKlogFlags adds flags from k8s.io/klog +// marks the flags as hidden to avoid polluting the help text +func addKlogFlags(fs *pflag.FlagSet) { + local := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(local) + local.VisitAll(func(fl *flag.Flag) { + fl.Name = normalize(fl.Name) + if fs.Lookup(fl.Name) != nil { + return + } + newflag := pflag.PFlagFromGoFlag(fl) + newflag.Hidden = true + fs.AddFlag(newflag) + }) +} + +// normalize replaces underscores with hyphens +func normalize(s string) string { + return strings.ReplaceAll(s, "_", "-") +} diff --git a/cmd/helm/install.go b/cmd/helm/install.go index 7d17ce00a..f340e7e17 100644 --- a/cmd/helm/install.go +++ b/cmd/helm/install.go @@ -1,356 +1,357 @@ -/* -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 main - -import ( - "context" - "fmt" - "io" - "log" - "os" - "os/signal" - "syscall" - "time" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli/output" - "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" -) - -const installDesc = ` -This command installs a chart archive. - -The install argument must be a chart reference, a path to a packaged chart, -a path to an unpacked chart directory or a URL. - -To override values in a chart, use either the '--values' flag and pass in a file -or use the '--set' flag and pass configuration from the command line, to force -a string value use '--set-string'. You can use '--set-file' to set individual -values from a file when the value itself is too long for the command line -or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. - - $ helm install -f myvalues.yaml myredis ./redis - -or - - $ helm install --set name=prod myredis ./redis - -or - - $ helm install --set-string long_int=1234567890 myredis ./redis - -or - - $ helm install --set-file my_script=dothings.sh myredis ./redis - -or - - $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis - - -You can specify the '--values'/'-f' flag multiple times. The priority will be given to the -last (right-most) file specified. For example, if both myvalues.yaml and override.yaml -contained a key called 'Test', the value set in override.yaml would take precedence: - - $ helm install -f myvalues.yaml -f override.yaml myredis ./redis - -You can specify the '--set' flag multiple times. The priority will be given to the -last (right-most) set specified. For example, if both 'bar' and 'newbar' values are -set for a key called 'foo', the 'newbar' value would take precedence: - - $ helm install --set foo=bar --set foo=newbar myredis ./redis - -Similarly, in the following example 'foo' is set to '["four"]': - - $ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis - -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 --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. - -There are six different ways you can express the chart you want to install: - -1. By chart reference: helm install mymaria example/mariadb -2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz -3. By path to an unpacked chart directory: helm install mynginx ./nginx -4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz -5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx -6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx - -CHART REFERENCES - -A chart reference is a convenient way of referencing a chart in a chart repository. - -When you use a chart reference with a repo prefix ('example/mariadb'), Helm will look in the local -configuration for a chart repository named 'example', and will then look for a -chart in that repository whose name is 'mariadb'. It will install the latest stable version of that chart -until you specify '--devel' flag to also include development version (alpha, beta, and release candidate releases), or -supply a version number with the '--version' flag. - -To see the list of chart repositories, use 'helm repo list'. To search for -charts in a repository, use 'helm search'. -` - -func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { - client := action.NewInstall(cfg) - valueOpts := &values.Options{} - var outfmt output.Format - - cmd := &cobra.Command{ - Use: "install [NAME] [CHART]", - Short: "install a chart", - Long: installDesc, - Args: require.MinimumNArgs(1), - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return compInstall(args, toComplete, client) - }, - RunE: func(_ *cobra.Command, args []string) error { - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) - if err != nil { - return fmt.Errorf("missing registry client: %w", err) - } - client.SetRegistryClient(registryClient) - - // This is for the case where "" is specifically passed in as a - // value. When there is no value passed in NoOptDefVal will be used - // and it is set to client. See addInstallFlags. - if client.DryRunOption == "" { - client.DryRunOption = "none" - } - rel, err := runInstall(args, client, valueOpts, out) - if err != nil { - return errors.Wrap(err, "INSTALLATION FAILED") - } - - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) - }, - } - - 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) - - return cmd -} - -func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { - f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") - // --dry-run options with expected outcome: - // - Not set means no dry run and server is contacted. - // - Set with no value, a value of client, or a value of true and the server is not contacted - // - Set with a value of false, none, or false and the server is contacted - // The true/false part is meant to reflect some legacy behavior while none is equal to "". - 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.Lookup("dry-run").NoOptDefVal = "client" - f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") - f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") - f.BoolVar(&client.Replace, "replace", false, "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") - f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") - f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") - f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") - f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") - f.StringVar(&client.Description, "description", "", "add a custom description") - 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.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") - f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") - f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") - f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") - f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") - f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") - f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.") - f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") - f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata") - addValueOptionsFlags(f, valueOpts) - addChartPathOptionsFlags(f, &client.ChartPathOptions) - - err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - requiredArgs := 2 - if client.GenerateName { - requiredArgs = 1 - } - if len(args) != requiredArgs { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return compVersionFlag(args[requiredArgs-1], toComplete) - }) - - if err != nil { - log.Fatal(err) - } -} - -func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { - debug("Original chart version: %q", client.Version) - if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") - client.Version = ">0.0.0-0" - } - - name, chart, err := client.NameAndChart(args) - if err != nil { - return nil, err - } - client.ReleaseName = name - - cp, err := client.ChartPathOptions.LocateChart(chart, settings) - if err != nil { - return nil, err - } - - debug("CHART PATH: %s\n", cp) - - p := getter.All(settings) - vals, err := valueOpts.MergeValues(p) - if err != nil { - return nil, err - } - - // Check chart dependencies to make sure all are present in /charts - chartRequested, err := loader.Load(cp) - if err != nil { - return nil, err - } - - if err := checkIfInstallable(chartRequested); err != nil { - return nil, err - } - - if chartRequested.Metadata.Deprecated { - warning("This chart is deprecated") - } - - if req := chartRequested.Metadata.Dependencies; req != nil { - // If CheckDependencies returns an error, we have unfulfilled dependencies. - // As of Helm 2.4.0, this is treated as a stopping condition: - // https://github.com/helm/helm/issues/2209 - if err := action.CheckDependencies(chartRequested, req); err != nil { - err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies") - if client.DependencyUpdate { - man := &downloader.Manager{ - Out: out, - ChartPath: cp, - Keyring: client.ChartPathOptions.Keyring, - SkipUpdate: false, - Getters: p, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - Debug: settings.Debug, - RegistryClient: client.GetRegistryClient(), - } - if err := man.Update(); err != nil { - return nil, err - } - // Reload the chart with the updated Chart.lock file. - if chartRequested, err = loader.Load(cp); err != nil { - return nil, errors.Wrap(err, "failed reloading chart after repo update") - } - } else { - return nil, err - } - } - } - - client.Namespace = settings.Namespace() - - // Validate DryRunOption member is one of the allowed values - if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { - return nil, err - } - - // Create context and prepare the handle of SIGTERM - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - - // Set up channel on which to send signal notifications. - // We must use a buffered channel or risk missing the signal - // if we're not ready to receive when the signal is sent. - cSignal := make(chan os.Signal, 2) - signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) - go func() { - <-cSignal - fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0]) - cancel() - }() - - return client.RunWithContext(ctx, chartRequested, vals) -} - -// checkIfInstallable validates if a chart can be installed -// -// Application chart type is only installable -func checkIfInstallable(ch *chart.Chart) error { - switch ch.Metadata.Type { - case "", "application": - return nil - } - return errors.Errorf("%s charts are not installable", ch.Metadata.Type) -} - -// Provide dynamic auto-completion for the install and template commands -func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) { - requiredArgs := 1 - if client.GenerateName { - requiredArgs = 0 - } - if len(args) == requiredArgs { - return compListCharts(toComplete, true) - } - return nil, cobra.ShellCompDirectiveNoFileComp -} - -func validateDryRunOptionFlag(dryRunOptionFlagValue string) error { - // Validate dry-run flag value with a set of allowed value - allowedDryRunValues := []string{"false", "true", "none", "client", "server"} - isAllowed := false - for _, v := range allowedDryRunValues { - if dryRunOptionFlagValue == v { - isAllowed = true - break - } - } - if !isAllowed { - return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server") - } - return nil -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io" + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli/output" + "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" +) + +const installDesc = ` +This command installs a chart archive. + +The install argument must be a chart reference, a path to a packaged chart, +a path to an unpacked chart directory or a URL. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force +a string value use '--set-string'. You can use '--set-file' to set individual +values from a file when the value itself is too long for the command line +or is dynamically generated. You can also use '--set-json' to set json values +(scalars/objects/arrays) from the command line. + + $ helm install -f myvalues.yaml myredis ./redis + +or + + $ helm install --set name=prod myredis ./redis + +or + + $ helm install --set-string long_int=1234567890 myredis ./redis + +or + + $ helm install --set-file my_script=dothings.sh myredis ./redis + +or + + $ helm install --set-json 'master.sidecars=[{"name":"sidecar","image":"myImage","imagePullPolicy":"Always","ports":[{"name":"portname","containerPort":1234}]}]' myredis ./redis + + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm install -f myvalues.yaml -f override.yaml myredis ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm install --set foo=bar --set foo=newbar myredis ./redis + +Similarly, in the following example 'foo' is set to '["four"]': + + $ helm install --set-json='foo=["one", "two", "three"]' --set-json='foo=["four"]' myredis ./redis + +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 --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. + +There are six different ways you can express the chart you want to install: + +1. By chart reference: helm install mymaria example/mariadb +2. By path to a packaged chart: helm install mynginx ./nginx-1.2.3.tgz +3. By path to an unpacked chart directory: helm install mynginx ./nginx +4. By absolute URL: helm install mynginx https://example.com/charts/nginx-1.2.3.tgz +5. By chart reference and repo url: helm install --repo https://example.com/charts/ mynginx nginx +6. By OCI registries: helm install mynginx --version 1.2.3 oci://example.com/charts/nginx + +CHART REFERENCES + +A chart reference is a convenient way of referencing a chart in a chart repository. + +When you use a chart reference with a repo prefix ('example/mariadb'), Helm will look in the local +configuration for a chart repository named 'example', and will then look for a +chart in that repository whose name is 'mariadb'. It will install the latest stable version of that chart +until you specify '--devel' flag to also include development version (alpha, beta, and release candidate releases), or +supply a version number with the '--version' flag. + +To see the list of chart repositories, use 'helm repo list'. To search for +charts in a repository, use 'helm search'. +` + +func newInstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { + client := action.NewInstall(cfg) + valueOpts := &values.Options{} + var outfmt output.Format + + cmd := &cobra.Command{ + Use: "install [NAME] [CHART]", + Short: "install a chart", + Long: installDesc, + Args: require.MinimumNArgs(1), + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return compInstall(args, toComplete, client) + }, + RunE: func(_ *cobra.Command, args []string) error { + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + client.SetRegistryClient(registryClient) + + // This is for the case where "" is specifically passed in as a + // value. When there is no value passed in NoOptDefVal will be used + // and it is set to client. See addInstallFlags. + if client.DryRunOption == "" { + client.DryRunOption = "none" + } + rel, err := runInstall(args, client, valueOpts, out) + if err != nil { + return errors.Wrap(err, "INSTALLATION FAILED") + } + + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) + }, + } + + 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) + + return cmd +} + +func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Install, valueOpts *values.Options) { + f.BoolVar(&client.CreateNamespace, "create-namespace", false, "create the release namespace if not present") + // --dry-run options with expected outcome: + // - Not set means no dry run and server is contacted. + // - Set with no value, a value of client, or a value of true and the server is not contacted + // - Set with a value of false, none, or false and the server is contacted + // The true/false part is meant to reflect some legacy behavior while none is equal to "". + 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.Lookup("dry-run").NoOptDefVal = "client" + f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") + f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during install") + f.BoolVar(&client.Replace, "replace", false, "reuse the given name, only if that name is a deleted release which remains in the history. This is unsafe in production") + f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") + f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "generate the name (and omit the NAME parameter)") + f.StringVar(&client.NameTemplate, "name-template", "", "specify template used to name the release") + f.StringVar(&client.Description, "description", "", "add a custom description") + 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.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") + f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema") + f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") + f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") + f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") + f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.") + f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") + f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in install output. Does not affect presence in chart metadata") + addValueOptionsFlags(f, valueOpts) + addChartPathOptionsFlags(f, &client.ChartPathOptions) + + err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + requiredArgs := 2 + if client.GenerateName { + requiredArgs = 1 + } + if len(args) != requiredArgs { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compVersionFlag(args[requiredArgs-1], toComplete) + }) + + if err != nil { + log.Fatal(err) + } +} + +func runInstall(args []string, client *action.Install, valueOpts *values.Options, out io.Writer) (*release.Release, error) { + debug("Original chart version: %q", client.Version) + if client.Version == "" && client.Devel { + debug("setting version to >0.0.0-0") + client.Version = ">0.0.0-0" + } + + name, chart, err := client.NameAndChart(args) + if err != nil { + return nil, err + } + client.ReleaseName = name + + cp, err := client.ChartPathOptions.LocateChart(chart, settings) + if err != nil { + return nil, err + } + + debug("CHART PATH: %s\n", cp) + + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p, settings.VaultAddress, settings.Token) + if err != nil { + return nil, err + } + + // Check chart dependencies to make sure all are present in /charts + chartRequested, err := loader.Load(cp) + if err != nil { + return nil, err + } + + if err := checkIfInstallable(chartRequested); err != nil { + return nil, err + } + + if chartRequested.Metadata.Deprecated { + warning("This chart is deprecated") + } + + if req := chartRequested.Metadata.Dependencies; req != nil { + // If CheckDependencies returns an error, we have unfulfilled dependencies. + // As of Helm 2.4.0, this is treated as a stopping condition: + // https://github.com/helm/helm/issues/2209 + if err := action.CheckDependencies(chartRequested, req); err != nil { + err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies") + if client.DependencyUpdate { + man := &downloader.Manager{ + Out: out, + ChartPath: cp, + Keyring: client.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, + RegistryClient: client.GetRegistryClient(), + } + if err := man.Update(); err != nil { + return nil, err + } + // Reload the chart with the updated Chart.lock file. + if chartRequested, err = loader.Load(cp); err != nil { + return nil, errors.Wrap(err, "failed reloading chart after repo update") + } + } else { + return nil, err + } + } + } + + client.Namespace = settings.Namespace() + + // Validate DryRunOption member is one of the allowed values + if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { + return nil, err + } + + // Create context and prepare the handle of SIGTERM + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + + // Set up channel on which to send signal notifications. + // We must use a buffered channel or risk missing the signal + // if we're not ready to receive when the signal is sent. + cSignal := make(chan os.Signal, 2) + signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) + go func() { + <-cSignal + fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0]) + cancel() + }() + + return client.RunWithContext(ctx, chartRequested, vals) +} + +// checkIfInstallable validates if a chart can be installed +// +// Application chart type is only installable +func checkIfInstallable(ch *chart.Chart) error { + switch ch.Metadata.Type { + case "", "application": + return nil + } + return errors.Errorf("%s charts are not installable", ch.Metadata.Type) +} + +// Provide dynamic auto-completion for the install and template commands +func compInstall(args []string, toComplete string, client *action.Install) ([]string, cobra.ShellCompDirective) { + requiredArgs := 1 + if client.GenerateName { + requiredArgs = 0 + } + if len(args) == requiredArgs { + return compListCharts(toComplete, true) + } + return nil, cobra.ShellCompDirectiveNoFileComp +} + +func validateDryRunOptionFlag(dryRunOptionFlagValue string) error { + // Validate dry-run flag value with a set of allowed value + allowedDryRunValues := []string{"false", "true", "none", "client", "server"} + isAllowed := false + for _, v := range allowedDryRunValues { + if dryRunOptionFlagValue == v { + isAllowed = true + break + } + } + if !isAllowed { + return errors.New("Invalid dry-run flag. Flag must one of the following: false, true, none, client, server") + } + return nil +} diff --git a/cmd/helm/lint.go b/cmd/helm/lint.go index 4c5e24149..a32547998 100644 --- a/cmd/helm/lint.go +++ b/cmd/helm/lint.go @@ -1,156 +1,157 @@ -/* -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 main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/lint/support" -) - -var longLintHelp = ` -This command takes a path to a chart and runs a series of tests to verify that -the chart is well-formed. - -If the linter encounters things that will cause the chart to fail installation, -it will emit [ERROR] messages. If it encounters issues that break with convention -or recommendation, it will emit [WARNING] messages. -` - -func newLintCmd(out io.Writer) *cobra.Command { - client := action.NewLint() - valueOpts := &values.Options{} - var kubeVersion string - - cmd := &cobra.Command{ - Use: "lint PATH", - Short: "examine a chart for possible issues", - Long: longLintHelp, - RunE: func(_ *cobra.Command, args []string) error { - paths := []string{"."} - if len(args) > 0 { - paths = args - } - - if kubeVersion != "" { - parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion) - if err != nil { - return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) - } - client.KubeVersion = parsedKubeVersion - } - - if client.WithSubcharts { - for _, p := range paths { - 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)) - } else if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { - paths = append(paths, path) - } - } - return nil - }) - } - } - - client.Namespace = settings.Namespace() - vals, err := valueOpts.MergeValues(getter.All(settings)) - if err != nil { - return err - } - - var message strings.Builder - failed := 0 - errorsOrWarnings := 0 - - for _, path := range paths { - result := client.Run([]string{path}, vals) - - // If there is no errors/warnings and quiet flag is set - // go to the next chart - hasWarningsOrErrors := action.HasWarningsOrErrors(result) - if hasWarningsOrErrors { - errorsOrWarnings++ - } - if client.Quiet && !hasWarningsOrErrors { - continue - } - - fmt.Fprintf(&message, "==> Linting %s\n", path) - - // All the Errors that are generated by a chart - // that failed a lint will be included in the - // results.Messages so we only need to print - // the Errors if there are no Messages. - if len(result.Messages) == 0 { - for _, err := range result.Errors { - fmt.Fprintf(&message, "Error %s\n", err) - } - } - - for _, msg := range result.Messages { - if !client.Quiet || msg.Severity > support.InfoSev { - fmt.Fprintf(&message, "%s\n", msg) - } - } - - if len(result.Errors) != 0 { - failed++ - } - - // Adding extra new line here to break up the - // results, stops this from being a big wall of - // text and makes it easier to follow. - fmt.Fprint(&message, "\n") - } - - fmt.Fprint(out, message.String()) - - summary := fmt.Sprintf("%d chart(s) linted, %d chart(s) failed", len(paths), failed) - if failed > 0 { - return errors.New(summary) - } - if !client.Quiet || errorsOrWarnings > 0 { - fmt.Fprintln(out, summary) - } - return nil - }, - } - - f := cmd.Flags() - f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") - f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") - f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") - f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") - f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks") - addValueOptionsFlags(f, valueOpts) - - return cmd -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/lint/support" +) + +var longLintHelp = ` +This command takes a path to a chart and runs a series of tests to verify that +the chart is well-formed. + +If the linter encounters things that will cause the chart to fail installation, +it will emit [ERROR] messages. If it encounters issues that break with convention +or recommendation, it will emit [WARNING] messages. +` + +func newLintCmd(out io.Writer) *cobra.Command { + client := action.NewLint() + valueOpts := &values.Options{} + var kubeVersion string + + cmd := &cobra.Command{ + Use: "lint PATH", + Short: "examine a chart for possible issues", + Long: longLintHelp, + RunE: func(_ *cobra.Command, args []string) error { + paths := []string{"."} + if len(args) > 0 { + paths = args + } + + if kubeVersion != "" { + parsedKubeVersion, err := chartutil.ParseKubeVersion(kubeVersion) + if err != nil { + return fmt.Errorf("invalid kube version '%s': %s", kubeVersion, err) + } + client.KubeVersion = parsedKubeVersion + } + + if client.WithSubcharts { + for _, p := range paths { + 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)) + } else if strings.HasSuffix(path, ".tgz") || strings.HasSuffix(path, ".tar.gz") { + paths = append(paths, path) + } + } + return nil + }) + } + } + + client.Namespace = settings.Namespace() + vals, err := valueOpts.MergeValues(getter.All(settings), settings.VaultAddress, settings.Token) + if err != nil { + return err + } + + var message strings.Builder + failed := 0 + errorsOrWarnings := 0 + + for _, path := range paths { + result := client.Run([]string{path}, vals) + + // If there is no errors/warnings and quiet flag is set + // go to the next chart + hasWarningsOrErrors := action.HasWarningsOrErrors(result) + if hasWarningsOrErrors { + errorsOrWarnings++ + } + if client.Quiet && !hasWarningsOrErrors { + continue + } + + fmt.Fprintf(&message, "==> Linting %s\n", path) + + // All the Errors that are generated by a chart + // that failed a lint will be included in the + // results.Messages so we only need to print + // the Errors if there are no Messages. + if len(result.Messages) == 0 { + for _, err := range result.Errors { + fmt.Fprintf(&message, "Error %s\n", err) + } + } + + for _, msg := range result.Messages { + if !client.Quiet || msg.Severity > support.InfoSev { + fmt.Fprintf(&message, "%s\n", msg) + } + } + + if len(result.Errors) != 0 { + failed++ + } + + // Adding extra new line here to break up the + // results, stops this from being a big wall of + // text and makes it easier to follow. + fmt.Fprint(&message, "\n") + } + + fmt.Fprint(out, message.String()) + + summary := fmt.Sprintf("%d chart(s) linted, %d chart(s) failed", len(paths), failed) + if failed > 0 { + return errors.New(summary) + } + if !client.Quiet || errorsOrWarnings > 0 { + fmt.Fprintln(out, summary) + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVar(&client.Strict, "strict", false, "fail on lint warnings") + f.BoolVar(&client.WithSubcharts, "with-subcharts", false, "lint dependent charts") + f.BoolVar(&client.Quiet, "quiet", false, "print only warnings and errors") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") + f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for capabilities and deprecation checks") + addValueOptionsFlags(f, valueOpts) + + return cmd +} diff --git a/cmd/helm/package.go b/cmd/helm/package.go index 19ab3dc7f..fe1b67470 100644 --- a/cmd/helm/package.go +++ b/cmd/helm/package.go @@ -1,137 +1,138 @@ -/* -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 main - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/downloader" - "helm.sh/helm/v3/pkg/getter" -) - -const packageDesc = ` -This command packages a chart into a versioned chart archive file. If a path -is given, this will look at that path for a chart (which must contain a -Chart.yaml file) and then package that directory. - -Versioned chart archives are used by Helm package repositories. - -To sign a chart, use the '--sign' flag. In most cases, you should also -provide '--keyring path/to/secret/keys' and '--key keyname'. - - $ helm package --sign ./mychart --key mykey --keyring ~/.gnupg/secring.gpg - -If '--keyring' is not specified, Helm usually defaults to the public keyring -unless your environment is otherwise configured. -` - -func newPackageCmd(out io.Writer) *cobra.Command { - client := action.NewPackage() - valueOpts := &values.Options{} - - cmd := &cobra.Command{ - Use: "package [CHART_PATH] [...]", - Short: "package a chart directory into a chart archive", - Long: packageDesc, - RunE: func(_ *cobra.Command, args []string) error { - if len(args) == 0 { - return errors.Errorf("need at least one argument, the path to the chart") - } - if client.Sign { - if client.Key == "" { - return errors.New("--key is required for signing a package") - } - if client.Keyring == "" { - return errors.New("--keyring is required for signing a package") - } - } - client.RepositoryConfig = settings.RepositoryConfig - client.RepositoryCache = settings.RepositoryCache - p := getter.All(settings) - vals, err := valueOpts.MergeValues(p) - if err != nil { - return err - } - - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) - if err != nil { - return fmt.Errorf("missing registry client: %w", err) - } - - for i := 0; i < len(args); i++ { - path, err := filepath.Abs(args[i]) - if err != nil { - return err - } - if _, err := os.Stat(args[i]); err != nil { - return err - } - - if client.DependencyUpdate { - downloadManager := &downloader.Manager{ - Out: io.Discard, - ChartPath: path, - Keyring: client.Keyring, - Getters: p, - Debug: settings.Debug, - RegistryClient: registryClient, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - } - - if err := downloadManager.Update(); err != nil { - return err - } - } - p, err := client.Run(path, vals) - if err != nil { - return err - } - fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p) - } - return nil - }, - } - - f := cmd.Flags() - f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package") - f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true") - f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring") - f.StringVar(&client.PassphraseFile, "passphrase-file", "", `location of a file which contains the passphrase for the signing key. Use "-" in order to read from stdin.`) - f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version") - f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") - f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") - f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) - f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart") - f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") - f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") - f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") - f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") - f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") - f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") - - return cmd -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/cli/values" + "helm.sh/helm/v3/pkg/downloader" + "helm.sh/helm/v3/pkg/getter" +) + +const packageDesc = ` +This command packages a chart into a versioned chart archive file. If a path +is given, this will look at that path for a chart (which must contain a +Chart.yaml file) and then package that directory. + +Versioned chart archives are used by Helm package repositories. + +To sign a chart, use the '--sign' flag. In most cases, you should also +provide '--keyring path/to/secret/keys' and '--key keyname'. + + $ helm package --sign ./mychart --key mykey --keyring ~/.gnupg/secring.gpg + +If '--keyring' is not specified, Helm usually defaults to the public keyring +unless your environment is otherwise configured. +` + +func newPackageCmd(out io.Writer) *cobra.Command { + client := action.NewPackage() + valueOpts := &values.Options{} + + cmd := &cobra.Command{ + Use: "package [CHART_PATH] [...]", + Short: "package a chart directory into a chart archive", + Long: packageDesc, + RunE: func(_ *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.Errorf("need at least one argument, the path to the chart") + } + if client.Sign { + if client.Key == "" { + return errors.New("--key is required for signing a package") + } + if client.Keyring == "" { + return errors.New("--keyring is required for signing a package") + } + } + client.RepositoryConfig = settings.RepositoryConfig + client.RepositoryCache = settings.RepositoryCache + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p, settings.VaultAddress, settings.Token) + if err != nil { + return err + } + + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + + for i := 0; i < len(args); i++ { + path, err := filepath.Abs(args[i]) + if err != nil { + return err + } + if _, err := os.Stat(args[i]); err != nil { + return err + } + + if client.DependencyUpdate { + downloadManager := &downloader.Manager{ + Out: io.Discard, + ChartPath: path, + Keyring: client.Keyring, + Getters: p, + Debug: settings.Debug, + RegistryClient: registryClient, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + } + + if err := downloadManager.Update(); err != nil { + return err + } + } + p, err := client.Run(path, vals) + if err != nil { + return err + } + fmt.Fprintf(out, "Successfully packaged chart and saved it to: %s\n", p) + } + return nil + }, + } + + f := cmd.Flags() + f.BoolVar(&client.Sign, "sign", false, "use a PGP private key to sign this package") + f.StringVar(&client.Key, "key", "", "name of the key to use when signing. Used if --sign is true") + f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "location of a public keyring") + f.StringVar(&client.PassphraseFile, "passphrase-file", "", `location of a file which contains the passphrase for the signing key. Use "-" in order to read from stdin.`) + f.StringVar(&client.Version, "version", "", "set the version on the chart to this semver version") + f.StringVar(&client.AppVersion, "app-version", "", "set the appVersion on the chart to this version") + f.StringVarP(&client.Destination, "destination", "d", ".", "location to write the chart.") + f.BoolVarP(&client.DependencyUpdate, "dependency-update", "u", false, `update dependencies from "Chart.yaml" to dir "charts/" before packaging`) + f.StringVar(&client.Username, "username", "", "chart repository username where to locate the requested chart") + f.StringVar(&client.Password, "password", "", "chart repository password where to locate the requested chart") + f.StringVar(&client.CertFile, "cert-file", "", "identify HTTPS client using this SSL certificate file") + f.StringVar(&client.KeyFile, "key-file", "", "identify HTTPS client using this SSL key file") + f.BoolVar(&client.InsecureSkipTLSverify, "insecure-skip-tls-verify", false, "skip tls certificate checks for the chart download") + f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download") + f.StringVar(&client.CaFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") + + return cmd +} diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 1585a78da..c487569b5 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -1,304 +1,305 @@ -/* -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 main - -import ( - "context" - "fmt" - "io" - "log" - "os" - "os/signal" - "syscall" - "time" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "helm.sh/helm/v3/cmd/helm/require" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli/output" - "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" -) - -const upgradeDesc = ` -This command upgrades a release to a new version of a chart. - -The upgrade arguments must be a release and chart. The chart -argument can be either: a chart reference('example/mariadb'), a path to a chart directory, -a packaged chart, or a fully qualified URL. For chart references, the latest -version will be specified unless the '--version' flag is set. - -To override values in a chart, use either the '--values' flag and pass in a file -or use the '--set' flag and pass configuration from the command line, to force string -values, use '--set-string'. You can use '--set-file' to set individual -values from a file when the value itself is too long for the command line -or is dynamically generated. You can also use '--set-json' to set json values -(scalars/objects/arrays) from the command line. - -You can specify the '--values'/'-f' flag multiple times. The priority will be given to the -last (right-most) file specified. For example, if both myvalues.yaml and override.yaml -contained a key called 'Test', the value set in override.yaml would take precedence: - - $ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis - -You can specify the '--set' flag multiple times. The priority will be given to the -last (right-most) set specified. For example, if both 'bar' and 'newbar' values are -set for a key called 'foo', the 'newbar' value would take precedence: - - $ helm upgrade --set foo=bar --set foo=newbar redis ./redis - -You can update the values for an existing release with this command as well via the -'--reuse-values' flag. The 'RELEASE' and 'CHART' arguments should be set to the original -parameters, and existing values will be merged with any values set via '--values'/'-f' -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 { - client := action.NewUpgrade(cfg) - valueOpts := &values.Options{} - var outfmt output.Format - var createNamespace bool - - cmd := &cobra.Command{ - Use: "upgrade [RELEASE] [CHART]", - Short: "upgrade a release", - Long: upgradeDesc, - Args: require.ExactArgs(2), - ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) == 0 { - return compListReleases(toComplete, args, cfg) - } - if len(args) == 1 { - return compListCharts(toComplete, true) - } - return noMoreArgsComp() - }, - RunE: func(_ *cobra.Command, args []string) error { - client.Namespace = settings.Namespace() - - registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, - client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) - if err != nil { - return fmt.Errorf("missing registry client: %w", err) - } - client.SetRegistryClient(registryClient) - - // This is for the case where "" is specifically passed in as a - // value. When there is no value passed in NoOptDefVal will be used - // and it is set to client. See addInstallFlags. - if client.DryRunOption == "" { - 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 not read twice - if client.Install { - // If a release does not exist, install it. - histClient := action.NewHistory(cfg) - histClient.Max = 1 - 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]) - } - instClient := action.NewInstall(cfg) - instClient.CreateNamespace = createNamespace - instClient.ChartPathOptions = client.ChartPathOptions - instClient.Force = client.Force - instClient.DryRun = client.DryRun - instClient.DryRunOption = client.DryRunOption - instClient.DisableHooks = client.DisableHooks - instClient.SkipCRDs = client.SkipCRDs - instClient.Timeout = client.Timeout - instClient.Wait = client.Wait - instClient.WaitForJobs = client.WaitForJobs - instClient.Devel = client.Devel - instClient.Namespace = client.Namespace - instClient.Atomic = client.Atomic - instClient.PostRenderer = client.PostRenderer - instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation - instClient.SubNotes = client.SubNotes - instClient.HideNotes = client.HideNotes - instClient.SkipSchemaValidation = client.SkipSchemaValidation - instClient.Description = client.Description - 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 { - return err - } - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, instClient.HideNotes}) - } else if err != nil { - return err - } - } - - if client.Version == "" && client.Devel { - debug("setting version to >0.0.0-0") - client.Version = ">0.0.0-0" - } - - chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings) - if err != nil { - return err - } - // Validate dry-run flag value is one of the allowed values - if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { - return err - } - - p := getter.All(settings) - vals, err := valueOpts.MergeValues(p) - if err != nil { - return err - } - - // Check chart dependencies to make sure all are present in /charts - ch, err := loader.Load(chartPath) - if err != nil { - return err - } - if req := ch.Metadata.Dependencies; req != nil { - if err := action.CheckDependencies(ch, req); err != nil { - err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies") - if client.DependencyUpdate { - man := &downloader.Manager{ - Out: out, - ChartPath: chartPath, - Keyring: client.ChartPathOptions.Keyring, - SkipUpdate: false, - Getters: p, - RepositoryConfig: settings.RepositoryConfig, - RepositoryCache: settings.RepositoryCache, - Debug: settings.Debug, - } - if err := man.Update(); err != nil { - return err - } - // Reload the chart with the updated Chart.lock file. - if ch, err = loader.Load(chartPath); err != nil { - return errors.Wrap(err, "failed reloading chart after repo update") - } - } else { - return err - } - } - } - - if ch.Metadata.Deprecated { - warning("This chart is deprecated") - } - - // Create context and prepare the handle of SIGTERM - ctx := context.Background() - ctx, cancel := context.WithCancel(ctx) - - // Set up channel on which to send signal notifications. - // We must use a buffered channel or risk missing the signal - // if we're not ready to receive when the signal is sent. - cSignal := make(chan os.Signal, 2) - signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) - go func() { - <-cSignal - fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0]) - cancel() - }() - - rel, err := client.RunWithContext(ctx, args[0], ch, vals) - - if err != nil { - return errors.Wrap(err, "UPGRADE FAILED") - } - - if outfmt == output.Table { - fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0]) - } - - return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) - }, - } - - f := cmd.Flags() - f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") - 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") - f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") - f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") - f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema") - f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled") - f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") - f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") - f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") - f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") - f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") - f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") - f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") - f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") - f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") - f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in upgrade output. Does not affect presence in chart metadata") - f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") - f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be separated by comma. Original release labels will be merged with upgrade labels. You can unset label using null.") - f.StringVar(&client.Description, "description", "", "add a custom description") - f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") - f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") - addChartPathOptionsFlags(f, &client.ChartPathOptions) - addValueOptionsFlags(f, valueOpts) - bindOutputFlag(cmd, &outfmt) - bindPostRenderFlag(cmd, &client.PostRenderer) - - err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - if len(args) != 2 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - return compVersionFlag(args[1], toComplete) - }) - - if err != nil { - log.Fatal(err) - } - - return cmd -} - -func isReleaseUninstalled(versions []*release.Release) bool { - return len(versions) > 0 && versions[len(versions)-1].Info.Status == release.StatusUninstalled -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "context" + "fmt" + "io" + "log" + "os" + "os/signal" + "syscall" + "time" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "helm.sh/helm/v3/cmd/helm/require" + "helm.sh/helm/v3/pkg/action" + "helm.sh/helm/v3/pkg/chart/loader" + "helm.sh/helm/v3/pkg/cli/output" + "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" +) + +const upgradeDesc = ` +This command upgrades a release to a new version of a chart. + +The upgrade arguments must be a release and chart. The chart +argument can be either: a chart reference('example/mariadb'), a path to a chart directory, +a packaged chart, or a fully qualified URL. For chart references, the latest +version will be specified unless the '--version' flag is set. + +To override values in a chart, use either the '--values' flag and pass in a file +or use the '--set' flag and pass configuration from the command line, to force string +values, use '--set-string'. You can use '--set-file' to set individual +values from a file when the value itself is too long for the command line +or is dynamically generated. You can also use '--set-json' to set json values +(scalars/objects/arrays) from the command line. + +You can specify the '--values'/'-f' flag multiple times. The priority will be given to the +last (right-most) file specified. For example, if both myvalues.yaml and override.yaml +contained a key called 'Test', the value set in override.yaml would take precedence: + + $ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis + +You can specify the '--set' flag multiple times. The priority will be given to the +last (right-most) set specified. For example, if both 'bar' and 'newbar' values are +set for a key called 'foo', the 'newbar' value would take precedence: + + $ helm upgrade --set foo=bar --set foo=newbar redis ./redis + +You can update the values for an existing release with this command as well via the +'--reuse-values' flag. The 'RELEASE' and 'CHART' arguments should be set to the original +parameters, and existing values will be merged with any values set via '--values'/'-f' +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 { + client := action.NewUpgrade(cfg) + valueOpts := &values.Options{} + var outfmt output.Format + var createNamespace bool + + cmd := &cobra.Command{ + Use: "upgrade [RELEASE] [CHART]", + Short: "upgrade a release", + Long: upgradeDesc, + Args: require.ExactArgs(2), + ValidArgsFunction: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) == 0 { + return compListReleases(toComplete, args, cfg) + } + if len(args) == 1 { + return compListCharts(toComplete, true) + } + return noMoreArgsComp() + }, + RunE: func(_ *cobra.Command, args []string) error { + client.Namespace = settings.Namespace() + + registryClient, err := newRegistryClient(client.CertFile, client.KeyFile, client.CaFile, + client.InsecureSkipTLSverify, client.PlainHTTP, client.Username, client.Password) + if err != nil { + return fmt.Errorf("missing registry client: %w", err) + } + client.SetRegistryClient(registryClient) + + // This is for the case where "" is specifically passed in as a + // value. When there is no value passed in NoOptDefVal will be used + // and it is set to client. See addInstallFlags. + if client.DryRunOption == "" { + 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 not read twice + if client.Install { + // If a release does not exist, install it. + histClient := action.NewHistory(cfg) + histClient.Max = 1 + 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]) + } + instClient := action.NewInstall(cfg) + instClient.CreateNamespace = createNamespace + instClient.ChartPathOptions = client.ChartPathOptions + instClient.Force = client.Force + instClient.DryRun = client.DryRun + instClient.DryRunOption = client.DryRunOption + instClient.DisableHooks = client.DisableHooks + instClient.SkipCRDs = client.SkipCRDs + instClient.Timeout = client.Timeout + instClient.Wait = client.Wait + instClient.WaitForJobs = client.WaitForJobs + instClient.Devel = client.Devel + instClient.Namespace = client.Namespace + instClient.Atomic = client.Atomic + instClient.PostRenderer = client.PostRenderer + instClient.DisableOpenAPIValidation = client.DisableOpenAPIValidation + instClient.SubNotes = client.SubNotes + instClient.HideNotes = client.HideNotes + instClient.SkipSchemaValidation = client.SkipSchemaValidation + instClient.Description = client.Description + 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 { + return err + } + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, instClient.HideNotes}) + } else if err != nil { + return err + } + } + + if client.Version == "" && client.Devel { + debug("setting version to >0.0.0-0") + client.Version = ">0.0.0-0" + } + + chartPath, err := client.ChartPathOptions.LocateChart(args[1], settings) + if err != nil { + return err + } + // Validate dry-run flag value is one of the allowed values + if err := validateDryRunOptionFlag(client.DryRunOption); err != nil { + return err + } + + p := getter.All(settings) + vals, err := valueOpts.MergeValues(p, settings.VaultAddress, settings.Token) + if err != nil { + return err + } + + // Check chart dependencies to make sure all are present in /charts + ch, err := loader.Load(chartPath) + if err != nil { + return err + } + if req := ch.Metadata.Dependencies; req != nil { + if err := action.CheckDependencies(ch, req); err != nil { + err = errors.Wrap(err, "An error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies") + if client.DependencyUpdate { + man := &downloader.Manager{ + Out: out, + ChartPath: chartPath, + Keyring: client.ChartPathOptions.Keyring, + SkipUpdate: false, + Getters: p, + RepositoryConfig: settings.RepositoryConfig, + RepositoryCache: settings.RepositoryCache, + Debug: settings.Debug, + } + if err := man.Update(); err != nil { + return err + } + // Reload the chart with the updated Chart.lock file. + if ch, err = loader.Load(chartPath); err != nil { + return errors.Wrap(err, "failed reloading chart after repo update") + } + } else { + return err + } + } + } + + if ch.Metadata.Deprecated { + warning("This chart is deprecated") + } + + // Create context and prepare the handle of SIGTERM + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + + // Set up channel on which to send signal notifications. + // We must use a buffered channel or risk missing the signal + // if we're not ready to receive when the signal is sent. + cSignal := make(chan os.Signal, 2) + signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM) + go func() { + <-cSignal + fmt.Fprintf(out, "Release %s has been cancelled.\n", args[0]) + cancel() + }() + + rel, err := client.RunWithContext(ctx, args[0], ch, vals) + + if err != nil { + return errors.Wrap(err, "UPGRADE FAILED") + } + + if outfmt == output.Table { + fmt.Fprintf(out, "Release %q has been upgraded. Happy Helming!\n", args[0]) + } + + return outfmt.Write(out, &statusPrinter{rel, settings.Debug, false, false, false, client.HideNotes}) + }, + } + + f := cmd.Flags() + f.BoolVar(&createNamespace, "create-namespace", false, "if --install is set, create the release namespace if not present") + 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") + f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") + f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") + f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema") + f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled") + f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") + f.BoolVar(&client.ResetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart") + f.BoolVar(&client.ReuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored") + f.BoolVar(&client.ResetThenReuseValues, "reset-then-reuse-values", false, "when upgrading, reset the values to the ones built into the chart, apply the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' or '--reuse-values' is specified, this is ignored") + f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&client.WaitForJobs, "wait-for-jobs", false, "if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout") + f.BoolVar(&client.Atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade. The --wait flag will be set automatically if --atomic is used") + f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") + f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") + f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") + f.BoolVar(&client.HideNotes, "hide-notes", false, "if set, do not show notes in upgrade output. Does not affect presence in chart metadata") + f.BoolVar(&client.SkipSchemaValidation, "skip-schema-validation", false, "if set, disables JSON schema validation") + f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be separated by comma. Original release labels will be merged with upgrade labels. You can unset label using null.") + f.StringVar(&client.Description, "description", "", "add a custom description") + f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") + f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") + addChartPathOptionsFlags(f, &client.ChartPathOptions) + addValueOptionsFlags(f, valueOpts) + bindOutputFlag(cmd, &outfmt) + bindPostRenderFlag(cmd, &client.PostRenderer) + + err := cmd.RegisterFlagCompletionFunc("version", func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 2 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return compVersionFlag(args[1], toComplete) + }) + + if err != nil { + log.Fatal(err) + } + + return cmd +} + +func isReleaseUninstalled(versions []*release.Release) bool { + return len(versions) > 0 && versions[len(versions)-1].Info.Status == release.StatusUninstalled +} diff --git a/go.mod b/go.mod index 7fad1c54f..f90681487 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/gofrs/flock v0.12.1 github.com/gosuri/uitable v0.0.4 github.com/hashicorp/go-multierror v1.1.1 + github.com/hashicorp/vault/api v1.15.0 github.com/jmoiron/sqlx v1.4.0 github.com/lib/pq v1.10.9 github.com/mattn/go-shellwords v1.0.12 @@ -37,6 +38,7 @@ require ( golang.org/x/crypto v0.29.0 golang.org/x/term v0.26.0 golang.org/x/text v0.20.0 + gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.31.2 k8s.io/apiextensions-apiserver v0.31.2 k8s.io/apimachinery v0.31.2 @@ -77,11 +79,12 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/color v1.13.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -101,8 +104,15 @@ require ( github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru/arc/v2 v2.0.5 // indirect github.com/hashicorp/golang-lru/v2 v2.0.5 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -114,10 +124,12 @@ require ( github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.57 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.4.0 // indirect @@ -137,6 +149,7 @@ require ( github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 // indirect github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -179,7 +192,6 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.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 k8s.io/component-base v0.31.2 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect diff --git a/go.sum b/go.sum index 9b20aa5c3..0da965e78 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,7 @@ github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZ github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -38,6 +39,7 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= @@ -114,8 +116,9 @@ github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lSh github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= @@ -128,6 +131,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs= github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -148,6 +153,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= +github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -208,12 +215,32 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1 github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= +github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= @@ -255,14 +282,13 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= @@ -272,10 +298,17 @@ github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxU github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -320,6 +353,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -354,6 +388,9 @@ github.com/rubenv/sql-migrate v1.7.0 h1:HtQq1xyTN2ISmQDggnh0c9U3JlP8apWh8YO2jzlX github.com/rubenv/sql-migrate v1.7.0/go.mod h1:S4wtDEG1CKn+0ShpTtzWhFpHHI5PvCUtiGI+C+Z2THE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= @@ -500,24 +537,23 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/cli/environment.go b/pkg/cli/environment.go index 635806344..6c187452e 100644 --- a/pkg/cli/environment.go +++ b/pkg/cli/environment.go @@ -1,267 +1,275 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -/* -Package cli describes the operating environment for the Helm CLI. - -Helm's environment encapsulates all of the service dependencies Helm has. -These dependencies are expressed as interfaces so that alternate implementations -(mocks, etc.) can be easily generated. -*/ -package cli - -import ( - "fmt" - "net/http" - "os" - "strconv" - "strings" - - "github.com/spf13/pflag" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/rest" - - "helm.sh/helm/v3/internal/version" - "helm.sh/helm/v3/pkg/helmpath" - "helm.sh/helm/v3/pkg/kube" -) - -// defaultMaxHistory sets the maximum number of releases to 0: unlimited -const defaultMaxHistory = 10 - -// defaultBurstLimit sets the default client-side throttling limit -const defaultBurstLimit = 100 - -// 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. -type EnvSettings struct { - namespace string - config *genericclioptions.ConfigFlags - - // KubeConfig is the path to the kubeconfig file - KubeConfig string - // KubeContext is the name of the kubeconfig context. - KubeContext string - // Bearer KubeToken used for authentication - KubeToken string - // Username to impersonate for the operation - KubeAsUser string - // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list - KubeAsGroups []string - // Kubernetes API Server Endpoint for authentication - KubeAPIServer string - // Custom certificate authority file. - KubeCaFile string - // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. - // This makes the HTTPS connections insecure - KubeInsecureSkipTLSVerify bool - // KubeTLSServerName overrides the name to use for server certificate validation. - // If it is not provided, the hostname used to contact the server is used - KubeTLSServerName string - // Debug indicates whether or not Helm is running in Debug mode. - Debug bool - // RegistryConfig is the path to the registry config file. - RegistryConfig string - // RepositoryConfig is the path to the repositories file. - RepositoryConfig string - // RepositoryCache is the path to the repository cache directory. - RepositoryCache string - // PluginsDirectory is the path to the plugins directory. - PluginsDirectory string - // MaxHistory is the max release history maintained. - MaxHistory int - // BurstLimit is the default client-side throttling limit. - BurstLimit int - // QPS is queries per second which may be used to avoid throttling. - QPS float32 -} - -func New() *EnvSettings { - env := &EnvSettings{ - namespace: os.Getenv("HELM_NAMESPACE"), - MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), - KubeContext: os.Getenv("HELM_KUBECONTEXT"), - KubeToken: os.Getenv("HELM_KUBETOKEN"), - KubeAsUser: os.Getenv("HELM_KUBEASUSER"), - KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), - KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), - KubeCaFile: os.Getenv("HELM_KUBECAFILE"), - KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), - KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), - PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), - RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), - RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), - RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), - BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), - QPS: envFloat32Or("HELM_QPS", defaultQPS), - } - env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) - - // bind to kubernetes config flags - config := &genericclioptions.ConfigFlags{ - Namespace: &env.namespace, - Context: &env.KubeContext, - BearerToken: &env.KubeToken, - APIServer: &env.KubeAPIServer, - CAFile: &env.KubeCaFile, - KubeConfig: &env.KubeConfig, - Impersonate: &env.KubeAsUser, - Insecure: &env.KubeInsecureSkipTLSVerify, - TLSServerName: &env.KubeTLSServerName, - ImpersonateGroup: &env.KubeAsGroups, - WrapConfigFn: func(config *rest.Config) *rest.Config { - config.Burst = env.BurstLimit - config.QPS = env.QPS - config.Wrap(func(rt http.RoundTripper) http.RoundTripper { - return &kube.RetryingRoundTripper{Wrapped: rt} - }) - config.UserAgent = version.GetUserAgent() - return config - }, - } - if env.BurstLimit != defaultBurstLimit { - config = config.WithDiscoveryBurst(env.BurstLimit) - } - env.config = config - - return env -} - -// AddFlags binds flags to the given flagset. -func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { - fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") - fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") - fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") - fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") - fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") - fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") - fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") - fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") - fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") - fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") - fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") - fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") - fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") - fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes") - fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") - fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") -} - -func envOr(name, def string) string { - if v, ok := os.LookupEnv(name); ok { - return v - } - return def -} - -func envBoolOr(name string, def bool) bool { - if name == "" { - return def - } - envVal := envOr(name, strconv.FormatBool(def)) - ret, err := strconv.ParseBool(envVal) - if err != nil { - return def - } - return ret -} - -func envIntOr(name string, def int) int { - if name == "" { - return def - } - envVal := envOr(name, strconv.Itoa(def)) - ret, err := strconv.Atoi(envVal) - if err != nil { - return def - } - return ret -} - -func envFloat32Or(name string, def float32) float32 { - if name == "" { - return def - } - envVal := envOr(name, strconv.FormatFloat(float64(def), 'f', 2, 32)) - ret, err := strconv.ParseFloat(envVal, 32) - if err != nil { - return def - } - return float32(ret) -} - -func envCSV(name string) (ls []string) { - trimmed := strings.Trim(os.Getenv(name), ", ") - if trimmed != "" { - ls = strings.Split(trimmed, ",") - } - return -} - -func (s *EnvSettings) EnvVars() map[string]string { - envvars := map[string]string{ - "HELM_BIN": os.Args[0], - "HELM_CACHE_HOME": helmpath.CachePath(""), - "HELM_CONFIG_HOME": helmpath.ConfigPath(""), - "HELM_DATA_HOME": helmpath.DataPath(""), - "HELM_DEBUG": fmt.Sprint(s.Debug), - "HELM_PLUGINS": s.PluginsDirectory, - "HELM_REGISTRY_CONFIG": s.RegistryConfig, - "HELM_REPOSITORY_CACHE": s.RepositoryCache, - "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, - "HELM_NAMESPACE": s.Namespace(), - "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), - "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), - "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), - - // broken, these are populated from helm flags and not kubeconfig. - "HELM_KUBECONTEXT": s.KubeContext, - "HELM_KUBETOKEN": s.KubeToken, - "HELM_KUBEASUSER": s.KubeAsUser, - "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), - "HELM_KUBEAPISERVER": s.KubeAPIServer, - "HELM_KUBECAFILE": s.KubeCaFile, - "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), - "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, - } - if s.KubeConfig != "" { - envvars["KUBECONFIG"] = s.KubeConfig - } - return envvars -} - -// Namespace gets the namespace from the configuration -func (s *EnvSettings) Namespace() string { - if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { - return ns - } - if s.namespace != "" { - return s.namespace - } - return "default" -} - -// SetNamespace sets the namespace in the configuration -func (s *EnvSettings) SetNamespace(namespace string) { - s.namespace = namespace -} - -// RESTClientGetter gets the kubeconfig from EnvSettings -func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { - return s.config -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* +Package cli describes the operating environment for the Helm CLI. + +Helm's environment encapsulates all of the service dependencies Helm has. +These dependencies are expressed as interfaces so that alternate implementations +(mocks, etc.) can be easily generated. +*/ +package cli + +import ( + "fmt" + "net/http" + "os" + "strconv" + "strings" + + "github.com/spf13/pflag" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/rest" + + "helm.sh/helm/v3/internal/version" + "helm.sh/helm/v3/pkg/helmpath" + "helm.sh/helm/v3/pkg/kube" +) + +// defaultMaxHistory sets the maximum number of releases to 0: unlimited +const defaultMaxHistory = 10 + +// defaultBurstLimit sets the default client-side throttling limit +const defaultBurstLimit = 100 + +// 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. +type EnvSettings struct { + namespace string + config *genericclioptions.ConfigFlags + + // KubeConfig is the path to the kubeconfig file + KubeConfig string + // KubeContext is the name of the kubeconfig context. + KubeContext string + // Bearer KubeToken used for authentication + KubeToken string + // Username to impersonate for the operation + KubeAsUser string + // Groups to impersonate for the operation, multiple groups parsed from a comma delimited list + KubeAsGroups []string + // Kubernetes API Server Endpoint for authentication + KubeAPIServer string + // Custom certificate authority file. + KubeCaFile string + // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. + // This makes the HTTPS connections insecure + KubeInsecureSkipTLSVerify bool + // KubeTLSServerName overrides the name to use for server certificate validation. + // If it is not provided, the hostname used to contact the server is used + KubeTLSServerName string + // Debug indicates whether or not Helm is running in Debug mode. + Debug bool + // RegistryConfig is the path to the registry config file. + RegistryConfig string + // RepositoryConfig is the path to the repositories file. + RepositoryConfig string + // RepositoryCache is the path to the repository cache directory. + RepositoryCache string + // PluginsDirectory is the path to the plugins directory. + PluginsDirectory string + // MaxHistory is the max release history maintained. + MaxHistory int + // BurstLimit is the default client-side throttling limit. + BurstLimit int + // QPS is queries per second which may be used to avoid throttling. + QPS float32 + // Add fields to authenticate for Vault or remote repo + Token string + VaultAddress string +} + +func New() *EnvSettings { + env := &EnvSettings{ + namespace: os.Getenv("HELM_NAMESPACE"), + MaxHistory: envIntOr("HELM_MAX_HISTORY", defaultMaxHistory), + KubeContext: os.Getenv("HELM_KUBECONTEXT"), + KubeToken: os.Getenv("HELM_KUBETOKEN"), + KubeAsUser: os.Getenv("HELM_KUBEASUSER"), + KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), + KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), + KubeCaFile: os.Getenv("HELM_KUBECAFILE"), + KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), + KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), + PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")), + RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")), + RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")), + RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")), + BurstLimit: envIntOr("HELM_BURST_LIMIT", defaultBurstLimit), + QPS: envFloat32Or("HELM_QPS", defaultQPS), + VaultAddress: os.Getenv("VAULT_ADDR"), + Token: os.Getenv("VAULT_TOKEN"), + } + env.Debug, _ = strconv.ParseBool(os.Getenv("HELM_DEBUG")) + + // bind to kubernetes config flags + config := &genericclioptions.ConfigFlags{ + Namespace: &env.namespace, + Context: &env.KubeContext, + BearerToken: &env.KubeToken, + APIServer: &env.KubeAPIServer, + CAFile: &env.KubeCaFile, + KubeConfig: &env.KubeConfig, + Impersonate: &env.KubeAsUser, + Insecure: &env.KubeInsecureSkipTLSVerify, + TLSServerName: &env.KubeTLSServerName, + ImpersonateGroup: &env.KubeAsGroups, + WrapConfigFn: func(config *rest.Config) *rest.Config { + config.Burst = env.BurstLimit + config.QPS = env.QPS + config.Wrap(func(rt http.RoundTripper) http.RoundTripper { + return &kube.RetryingRoundTripper{Wrapped: rt} + }) + config.UserAgent = version.GetUserAgent() + return config + }, + } + if env.BurstLimit != defaultBurstLimit { + config = config.WithDiscoveryBurst(env.BurstLimit) + } + env.config = config + + return env +} + +// AddFlags binds flags to the given flagset. +func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) { + fs.StringVarP(&s.namespace, "namespace", "n", s.namespace, "namespace scope for this request") + fs.StringVar(&s.KubeConfig, "kubeconfig", "", "path to the kubeconfig file") + fs.StringVar(&s.KubeContext, "kube-context", s.KubeContext, "name of the kubeconfig context to use") + fs.StringVar(&s.KubeToken, "kube-token", s.KubeToken, "bearer token used for authentication") + fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") + fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") + fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") + fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") + fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") + fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") + fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output") + fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file") + fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs") + fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes") + fs.IntVar(&s.BurstLimit, "burst-limit", s.BurstLimit, "client-side default throttling limit") + fs.Float32Var(&s.QPS, "qps", s.QPS, "queries per second used when communicating with the Kubernetes API, not including bursting") + fs.StringVar(&s.Token, "token", s.Token, "Token to authenticate Vault or remote repo to download values.yaml") + fs.StringVar(&s.VaultAddress, "vault-address", s.VaultAddress, "Host or Address to access Vault server to download Helm values or resources file") +} + +func envOr(name, def string) string { + if v, ok := os.LookupEnv(name); ok { + return v + } + return def +} + +func envBoolOr(name string, def bool) bool { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatBool(def)) + ret, err := strconv.ParseBool(envVal) + if err != nil { + return def + } + return ret +} + +func envIntOr(name string, def int) int { + if name == "" { + return def + } + envVal := envOr(name, strconv.Itoa(def)) + ret, err := strconv.Atoi(envVal) + if err != nil { + return def + } + return ret +} + +func envFloat32Or(name string, def float32) float32 { + if name == "" { + return def + } + envVal := envOr(name, strconv.FormatFloat(float64(def), 'f', 2, 32)) + ret, err := strconv.ParseFloat(envVal, 32) + if err != nil { + return def + } + return float32(ret) +} + +func envCSV(name string) (ls []string) { + trimmed := strings.Trim(os.Getenv(name), ", ") + if trimmed != "" { + ls = strings.Split(trimmed, ",") + } + return +} + +func (s *EnvSettings) EnvVars() map[string]string { + envvars := map[string]string{ + "HELM_BIN": os.Args[0], + "HELM_CACHE_HOME": helmpath.CachePath(""), + "HELM_CONFIG_HOME": helmpath.ConfigPath(""), + "HELM_DATA_HOME": helmpath.DataPath(""), + "HELM_DEBUG": fmt.Sprint(s.Debug), + "HELM_PLUGINS": s.PluginsDirectory, + "HELM_REGISTRY_CONFIG": s.RegistryConfig, + "HELM_REPOSITORY_CACHE": s.RepositoryCache, + "HELM_REPOSITORY_CONFIG": s.RepositoryConfig, + "HELM_NAMESPACE": s.Namespace(), + "HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory), + "HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit), + "HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32), + + // broken, these are populated from helm flags and not kubeconfig. + "HELM_KUBECONTEXT": s.KubeContext, + "HELM_KUBETOKEN": s.KubeToken, + "HELM_KUBEASUSER": s.KubeAsUser, + "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), + "HELM_KUBEAPISERVER": s.KubeAPIServer, + "HELM_KUBECAFILE": s.KubeCaFile, + "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), + "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, + } + if s.KubeConfig != "" { + envvars["KUBECONFIG"] = s.KubeConfig + } + return envvars +} + +// Namespace gets the namespace from the configuration +func (s *EnvSettings) Namespace() string { + if ns, _, err := s.config.ToRawKubeConfigLoader().Namespace(); err == nil { + return ns + } + if s.namespace != "" { + return s.namespace + } + return "default" +} + +// SetNamespace sets the namespace in the configuration +func (s *EnvSettings) SetNamespace(namespace string) { + s.namespace = namespace +} + +// RESTClientGetter gets the kubeconfig from EnvSettings +func (s *EnvSettings) RESTClientGetter() genericclioptions.RESTClientGetter { + return s.config +} diff --git a/pkg/cli/values/options.go b/pkg/cli/values/options.go index 06631cd33..4892bbf91 100644 --- a/pkg/cli/values/options.go +++ b/pkg/cli/values/options.go @@ -1,147 +1,196 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package values - -import ( - "io" - "net/url" - "os" - "strings" - - "github.com/pkg/errors" - "sigs.k8s.io/yaml" - - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/strvals" -) - -// Options captures the different ways to specify values -type Options struct { - ValueFiles []string // -f/--values - StringValues []string // --set-string - Values []string // --set - FileValues []string // --set-file - JSONValues []string // --set-json - LiteralValues []string // --set-literal -} - -// MergeValues merges values from files specified via -f/--values and directly -// via --set-json, --set, --set-string, or --set-file, marshaling them to YAML -func (opts *Options) MergeValues(p getter.Providers) (map[string]interface{}, error) { - base := map[string]interface{}{} - - // User specified a values files via -f/--values - for _, filePath := range opts.ValueFiles { - currentMap := map[string]interface{}{} - - bytes, err := readFile(filePath, p) - if err != nil { - return nil, err - } - - if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { - return nil, errors.Wrapf(err, "failed to parse %s", filePath) - } - // Merge with the previous map - base = mergeMaps(base, currentMap) - } - - // User specified a value via --set-json - for _, value := range opts.JSONValues { - if err := strvals.ParseJSON(value, base); err != nil { - return nil, errors.Errorf("failed parsing --set-json data %s", value) - } - } - - // User specified a value via --set - for _, value := range opts.Values { - if err := strvals.ParseInto(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set data") - } - } - - // User specified a value via --set-string - for _, value := range opts.StringValues { - if err := strvals.ParseIntoString(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-string data") - } - } - - // User specified a value via --set-file - for _, value := range opts.FileValues { - reader := func(rs []rune) (interface{}, error) { - bytes, err := readFile(string(rs), p) - if err != nil { - return nil, err - } - return string(bytes), err - } - if err := strvals.ParseIntoFile(value, base, reader); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-file data") - } - } - - // User specified a value via --set-literal - for _, value := range opts.LiteralValues { - if err := strvals.ParseLiteralInto(value, base); err != nil { - return nil, errors.Wrap(err, "failed parsing --set-literal data") - } - } - - return base, nil -} - -func mergeMaps(a, b map[string]interface{}) map[string]interface{} { - out := make(map[string]interface{}, len(a)) - for k, v := range a { - out[k] = v - } - for k, v := range b { - if v, ok := v.(map[string]interface{}); ok { - if bv, ok := out[k]; ok { - if bv, ok := bv.(map[string]interface{}); ok { - out[k] = mergeMaps(bv, v) - continue - } - } - } - out[k] = v - } - return out -} - -// readFile load a file from stdin, the local directory, or a remote file with a url. -func readFile(filePath string, p getter.Providers) ([]byte, error) { - if strings.TrimSpace(filePath) == "-" { - return io.ReadAll(os.Stdin) - } - u, err := url.Parse(filePath) - if err != nil { - return nil, err - } - - // FIXME: maybe someone handle other protocols like ftp. - g, err := p.ByScheme(u.Scheme) - if err != nil { - return os.ReadFile(filePath) - } - data, err := g.Get(filePath, getter.WithURL(filePath)) - if err != nil { - return nil, err - } - return data.Bytes(), err -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package values + +import ( + "bytes" + "io" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "gopkg.in/yaml.v3" + + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/strvals" +) + +// Options captures the different ways to specify values +type Options struct { + ValueFiles []string // -f/--values + StringValues []string // --set-string + Values []string // --set + FileValues []string // --set-file + JSONValues []string // --set-json + LiteralValues []string // --set-literal + PropertyFiles []string // -p/--property-file +} + +// MergeValues merges values from files specified via -f/--values and directly +// via --set-json, --set, --set-string, or --set-file, marshaling them to YAML +func (opts *Options) MergeValues(p getter.Providers, vaultAddr, vaultToken string) (map[string]interface{}, error) { + base := map[string]interface{}{} + + // User specified a values files via -f/--values + for _, filePath := range opts.ValueFiles { + currentMap := map[string]interface{}{} + + bytes, err := readFile(filePath, p, vaultAddr, vaultToken) + if err != nil { + return nil, err + } + + if err := yaml.Unmarshal(bytes, ¤tMap); err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", filePath) + } + // Merge with the previous map + base = mergeMaps(base, currentMap) + } + + // User specified a value via --set-json + for _, value := range opts.JSONValues { + if err := strvals.ParseJSON(value, base); err != nil { + return nil, errors.Errorf("failed parsing --set-json data %s", value) + } + } + + // User specified a value via --set + for _, value := range opts.Values { + if err := strvals.ParseInto(value, base); err != nil { + return nil, errors.Wrap(err, "failed parsing --set data") + } + } + + // User specified a value via --set-string + for _, value := range opts.StringValues { + if err := strvals.ParseIntoString(value, base); err != nil { + return nil, errors.Wrap(err, "failed parsing --set-string data") + } + } + + // User specified a value via --set-file + for _, value := range opts.FileValues { + reader := func(rs []rune) (interface{}, error) { + bytes, err := readFile(string(rs), p, vaultAddr, vaultToken) + if err != nil { + return nil, err + } + return string(bytes), err + } + if err := strvals.ParseIntoFile(value, base, reader); err != nil { + return nil, errors.Wrap(err, "failed parsing --set-file data") + } + } + + // User specified a value via --set-literal + for _, value := range opts.LiteralValues { + if err := strvals.ParseLiteralInto(value, base); err != nil { + return nil, errors.Wrap(err, "failed parsing --set-literal data") + } + } + + // User specified property files via -p/--property-file + if len(opts.PropertyFiles) > 0 { + propertiesFilesMap, err := opts.MergeProperties(p, vaultAddr, vaultToken) + if err != nil { + return nil, err + } + base["propertiesFiles"] = propertiesFilesMap + } + + return base, nil +} + +// MergeProperties merges properties from files specified via --property-file +func (opts *Options) MergeProperties(p getter.Providers, vaultAddr, vaultToken string) (map[string]interface{}, error) { + propertiesFilesMap := make(map[string]interface{}) + + for _, filePath := range opts.PropertyFiles { + data, err := readFile(filePath, p, vaultAddr, vaultToken) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch properties") + } + + properties := make(map[string]interface{}) + for _, line := range bytes.Split(data, []byte("\n")) { + lineStr := strings.TrimSpace(string(line)) + if lineStr == "" || strings.HasPrefix(lineStr, "#") { + continue + } + parts := strings.SplitN(lineStr, ":", 2) + if len(parts) != 2 { + return nil, errors.Errorf("invalid properties line: %s", lineStr) + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + properties[key] = value + } + + // Use the base file name (or Vault key) as the key in .Values.propertiesFiles + baseName := filepath.Base(filePath) + propertiesFilesMap[baseName] = properties + } + + return propertiesFilesMap, nil +} + +func mergeMaps(a, b map[string]interface{}) map[string]interface{} { + out := make(map[string]interface{}, len(a)) + for k, v := range a { + out[k] = v + } + for k, v := range b { + if v, ok := v.(map[string]interface{}); ok { + if bv, ok := out[k]; ok { + if bv, ok := bv.(map[string]interface{}); ok { + out[k] = mergeMaps(bv, v) + continue + } + } + } + out[k] = v + } + return out +} + +// readFile load a file from stdin, the local directory, or a remote file with a url. +func readFile(filePath string, p getter.Providers, vaultAddr, vaultToken string) ([]byte, error) { + if strings.TrimSpace(filePath) == "-" { + return io.ReadAll(os.Stdin) + } + u, err := url.Parse(filePath) + if err != nil { + return nil, err + } + + // FIXME: maybe someone handle other protocols like ftp. + g, err := p.ByScheme(u.Scheme) + if err != nil { + return os.ReadFile(filePath) + } + + // Fetch data using the provider + data, err := g.Get(filePath, getter.WithURL(filePath), getter.WithAddress(vaultAddr), getter.WithToken(vaultToken)) + if err != nil { + return nil, err + } + + return data.Bytes(), err +} diff --git a/pkg/cli/values/options_test.go b/pkg/cli/values/options_test.go index 54124c0fa..3743b407b 100644 --- a/pkg/cli/values/options_test.go +++ b/pkg/cli/values/options_test.go @@ -1,88 +1,89 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package values - -import ( - "reflect" - "testing" - - "helm.sh/helm/v3/pkg/getter" -) - -func TestMergeValues(t *testing.T) { - nestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "stuff", - }, - } - anotherNestedMap := map[string]interface{}{ - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - flatMap := map[string]interface{}{ - "foo": "bar", - "baz": "stuff", - } - anotherFlatMap := map[string]interface{}{ - "testing": "fun", - } - - testMap := mergeMaps(flatMap, nestedMap) - equal := reflect.DeepEqual(testMap, nestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) - } - - testMap = mergeMaps(nestedMap, flatMap) - equal = reflect.DeepEqual(testMap, flatMap) - if !equal { - t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) - } - - testMap = mergeMaps(nestedMap, anotherNestedMap) - equal = reflect.DeepEqual(testMap, anotherNestedMap) - if !equal { - t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) - } - - testMap = mergeMaps(anotherFlatMap, anotherNestedMap) - expectedMap := map[string]interface{}{ - "testing": "fun", - "foo": "bar", - "baz": map[string]string{ - "cool": "things", - "awesome": "stuff", - }, - } - equal = reflect.DeepEqual(testMap, expectedMap) - if !equal { - t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) - } -} - -func TestReadFile(t *testing.T) { - var p getter.Providers - filePath := "%a.txt" - _, err := readFile(filePath, p) - if err == nil { - t.Errorf("Expected error when has special strings") - } -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package values + +import ( + "reflect" + "testing" + + "helm.sh/helm/v3/pkg/getter" +) + +func TestMergeValues(t *testing.T) { + nestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "stuff", + }, + } + anotherNestedMap := map[string]interface{}{ + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + flatMap := map[string]interface{}{ + "foo": "bar", + "baz": "stuff", + } + anotherFlatMap := map[string]interface{}{ + "testing": "fun", + } + + testMap := mergeMaps(flatMap, nestedMap) + equal := reflect.DeepEqual(testMap, nestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite a flat value. Expected: %v, got %v", nestedMap, testMap) + } + + testMap = mergeMaps(nestedMap, flatMap) + equal = reflect.DeepEqual(testMap, flatMap) + if !equal { + t.Errorf("Expected a flat value to overwrite a map. Expected: %v, got %v", flatMap, testMap) + } + + testMap = mergeMaps(nestedMap, anotherNestedMap) + equal = reflect.DeepEqual(testMap, anotherNestedMap) + if !equal { + t.Errorf("Expected a nested map to overwrite another nested map. Expected: %v, got %v", anotherNestedMap, testMap) + } + + testMap = mergeMaps(anotherFlatMap, anotherNestedMap) + expectedMap := map[string]interface{}{ + "testing": "fun", + "foo": "bar", + "baz": map[string]string{ + "cool": "things", + "awesome": "stuff", + }, + } + equal = reflect.DeepEqual(testMap, expectedMap) + if !equal { + t.Errorf("Expected a map with different keys to merge properly with another map. Expected: %v, got %v", expectedMap, testMap) + } +} + +func TestReadFile(t *testing.T) { + var p getter.Providers + filePath := "%a.txt" + _, err := readFile(filePath, p, "", "") + if err == nil { + t.Errorf("Expected error when has special strings") + } +} diff --git a/pkg/getter/getter.go b/pkg/getter/getter.go index 1acb2093d..f89120e39 100644 --- a/pkg/getter/getter.go +++ b/pkg/getter/getter.go @@ -1,220 +1,246 @@ -/* -Copyright The Helm Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package getter - -import ( - "bytes" - "net/http" - "time" - - "github.com/pkg/errors" - - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/registry" -) - -// options are generic parameters to be provided to the getter during instantiation. -// -// Getters may or may not ignore these parameters as they are passed in. -type options struct { - url string - certFile string - keyFile string - caFile string - unTar bool - insecureSkipVerifyTLS bool - plainHTTP bool - acceptHeader string - username string - password string - passCredentialsAll bool - userAgent string - version string - registryClient *registry.Client - timeout time.Duration - transport *http.Transport -} - -// Option allows specifying various settings configurable by the user for overriding the defaults -// used when performing Get operations with the Getter. -type Option func(*options) - -// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with -// WithTLSClientConfig to set the TLSClientConfig's server name. -func WithURL(url string) Option { - return func(opts *options) { - opts.url = url - } -} - -// WithAcceptHeader sets the request's Accept header as some REST APIs serve multiple content types -func WithAcceptHeader(header string) Option { - return func(opts *options) { - opts.acceptHeader = header - } -} - -// WithBasicAuth sets the request's Authorization header to use the provided credentials -func WithBasicAuth(username, password string) Option { - return func(opts *options) { - opts.username = username - opts.password = password - } -} - -func WithPassCredentialsAll(pass bool) Option { - return func(opts *options) { - opts.passCredentialsAll = pass - } -} - -// WithUserAgent sets the request's User-Agent header to use the provided agent name. -func WithUserAgent(userAgent string) Option { - return func(opts *options) { - opts.userAgent = userAgent - } -} - -// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked -func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { - return func(opts *options) { - opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS - } -} - -// WithTLSClientConfig sets the client auth with the provided credentials. -func WithTLSClientConfig(certFile, keyFile, caFile string) Option { - return func(opts *options) { - opts.certFile = certFile - opts.keyFile = keyFile - opts.caFile = caFile - } -} - -func WithPlainHTTP(plainHTTP bool) Option { - return func(opts *options) { - opts.plainHTTP = plainHTTP - } -} - -// WithTimeout sets the timeout for requests -func WithTimeout(timeout time.Duration) Option { - return func(opts *options) { - opts.timeout = timeout - } -} - -func WithTagName(tagname string) Option { - return func(opts *options) { - opts.version = tagname - } -} - -func WithRegistryClient(client *registry.Client) Option { - return func(opts *options) { - opts.registryClient = client - } -} - -func WithUntar() Option { - return func(opts *options) { - opts.unTar = true - } -} - -// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. -func WithTransport(transport *http.Transport) Option { - return func(opts *options) { - opts.transport = transport - } -} - -// Getter is an interface to support GET to the specified URL. -type Getter interface { - // Get file content by url string - Get(url string, options ...Option) (*bytes.Buffer, error) -} - -// Constructor is the function for every getter which creates a specific instance -// according to the configuration -type Constructor func(options ...Option) (Getter, error) - -// Provider represents any getter and the schemes that it supports. -// -// For example, an HTTP provider may provide one getter that handles both -// 'http' and 'https' schemes. -type Provider struct { - Schemes []string - New Constructor -} - -// Provides returns true if the given scheme is supported by this Provider. -func (p Provider) Provides(scheme string) bool { - for _, i := range p.Schemes { - if i == scheme { - return true - } - } - return false -} - -// Providers is a collection of Provider objects. -type Providers []Provider - -// ByScheme returns a Provider that handles the given scheme. -// -// If no provider handles this scheme, this will return an error. -func (p Providers) ByScheme(scheme string) (Getter, error) { - for _, pp := range p { - if pp.Provides(scheme) { - return pp.New() - } - } - return nil, errors.Errorf("scheme %q not supported", scheme) -} - -const ( - // The cost timeout references curl's default connection timeout. - // https://github.com/curl/curl/blob/master/lib/connect.h#L40C21-L40C21 - // The helm commands are usually executed manually. Considering the acceptable waiting time, we reduced the entire request time to 120s. - DefaultHTTPTimeout = 120 -) - -var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)} - -var httpProvider = Provider{ - Schemes: []string{"http", "https"}, - New: func(options ...Option) (Getter, error) { - options = append(options, defaultOptions...) - return NewHTTPGetter(options...) - }, -} - -var ociProvider = Provider{ - Schemes: []string{registry.OCIScheme}, - New: NewOCIGetter, -} - -// All finds all of the registered getters as a list of Provider instances. -// Currently, the built-in getters and the discovered plugins with downloader -// notations are collected. -func All(settings *cli.EnvSettings) Providers { - result := Providers{httpProvider, ociProvider} - pluginDownloaders, _ := collectPlugins(settings) - result = append(result, pluginDownloaders...) - return result -} +/* +Copyright The Helm Authors. +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "net/http" + "time" + + "github.com/pkg/errors" + + "helm.sh/helm/v3/pkg/cli" + "helm.sh/helm/v3/pkg/registry" +) + +// options are generic parameters to be provided to the getter during instantiation. +// +// Getters may or may not ignore these parameters as they are passed in. +type options struct { + url string + certFile string + keyFile string + caFile string + unTar bool + insecureSkipVerifyTLS bool + plainHTTP bool + acceptHeader string + username string + password string + passCredentialsAll bool + userAgent string + version string + registryClient *registry.Client + timeout time.Duration + transport *http.Transport + + // Added field for Vault integration + address string // Vault address for accessing Vault server + token string // Vault token for authentication +} + +// Option allows specifying various settings configurable by the user for overriding the defaults +// used when performing Get operations with the Getter. +type Option func(*options) + +// WithURL informs the getter the server name that will be used when fetching objects. Used in conjunction with +// WithTLSClientConfig to set the TLSClientConfig's server name. +func WithURL(url string) Option { + return func(opts *options) { + opts.url = url + } +} + +// WithAcceptHeader sets the request's Accept header as some REST APIs serve multiple content types +func WithAcceptHeader(header string) Option { + return func(opts *options) { + opts.acceptHeader = header + } +} + +// WithBasicAuth sets the request's Authorization header to use the provided credentials +func WithBasicAuth(username, password string) Option { + return func(opts *options) { + opts.username = username + opts.password = password + } +} + +func WithPassCredentialsAll(pass bool) Option { + return func(opts *options) { + opts.passCredentialsAll = pass + } +} + +// WithUserAgent sets the request's User-Agent header to use the provided agent name. +func WithUserAgent(userAgent string) Option { + return func(opts *options) { + opts.userAgent = userAgent + } +} + +// WithInsecureSkipVerifyTLS determines if a TLS Certificate will be checked +func WithInsecureSkipVerifyTLS(insecureSkipVerifyTLS bool) Option { + return func(opts *options) { + opts.insecureSkipVerifyTLS = insecureSkipVerifyTLS + } +} + +// WithTLSClientConfig sets the client auth with the provided credentials. +func WithTLSClientConfig(certFile, keyFile, caFile string) Option { + return func(opts *options) { + opts.certFile = certFile + opts.keyFile = keyFile + opts.caFile = caFile + } +} + +func WithPlainHTTP(plainHTTP bool) Option { + return func(opts *options) { + opts.plainHTTP = plainHTTP + } +} + +// WithTimeout sets the timeout for requests +func WithTimeout(timeout time.Duration) Option { + return func(opts *options) { + opts.timeout = timeout + } +} + +func WithTagName(tagname string) Option { + return func(opts *options) { + opts.version = tagname + } +} + +func WithRegistryClient(client *registry.Client) Option { + return func(opts *options) { + opts.registryClient = client + } +} + +func WithUntar() Option { + return func(opts *options) { + opts.unTar = true + } +} + +// WithTransport sets the http.Transport to allow overwriting the HTTPGetter default. +func WithTransport(transport *http.Transport) Option { + return func(opts *options) { + opts.transport = transport + } +} + +// WithAddress sets the Vault address to allow download values.yaml from Vault server. +func WithAddress(address string) Option { + return func(opts *options) { + opts.address = address + } +} + +// WithToken sets the token to allow authentication to Vault server default. +func WithToken(token string) Option { + return func(o *options) { + o.token = token + } +} + +// Getter is an interface to support GET to the specified URL. +type Getter interface { + // Get file content by url string + Get(url string, options ...Option) (*bytes.Buffer, error) +} + +// Constructor is the function for every getter which creates a specific instance +// according to the configuration +type Constructor func(options ...Option) (Getter, error) + +// Provider represents any getter and the schemes that it supports. +// +// For example, an HTTP provider may provide one getter that handles both +// 'http' and 'https' schemes. +type Provider struct { + Schemes []string + New Constructor +} + +// Provides returns true if the given scheme is supported by this Provider. +func (p Provider) Provides(scheme string) bool { + for _, i := range p.Schemes { + if i == scheme { + return true + } + } + return false +} + +// Providers is a collection of Provider objects. +type Providers []Provider + +// ByScheme returns a Provider that handles the given scheme. +// +// If no provider handles this scheme, this will return an error. +func (p Providers) ByScheme(scheme string) (Getter, error) { + for _, pp := range p { + if pp.Provides(scheme) { + return pp.New() + } + } + return nil, errors.Errorf("scheme %q not supported", scheme) +} + +const ( + // The cost timeout references curl's default connection timeout. + // https://github.com/curl/curl/blob/master/lib/connect.h#L40C21-L40C21 + // The helm commands are usually executed manually. Considering the acceptable waiting time, we reduced the entire request time to 120s. + DefaultHTTPTimeout = 120 +) + +var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)} + +var httpProvider = Provider{ + Schemes: []string{"http", "https"}, + New: func(options ...Option) (Getter, error) { + options = append(options, defaultOptions...) + return NewHTTPGetter(options...) + }, +} + +var ociProvider = Provider{ + Schemes: []string{registry.OCIScheme}, + New: NewOCIGetter, +} + +var vaultProvider = Provider{ + Schemes: []string{"vault"}, // Define "vault" as the scheme + New: func(options ...Option) (Getter, error) { + return NewVaultGetter(options...) + }, +} + +// All finds all of the registered getters as a list of Provider instances. +// Currently, the built-in getters and the discovered plugins with downloader +// notations are collected. +func All(settings *cli.EnvSettings) Providers { + result := Providers{httpProvider, ociProvider, vaultProvider} // Including new vaultProvider as well for Vault integration + pluginDownloaders, _ := collectPlugins(settings) + result = append(result, pluginDownloaders...) + return result +} diff --git a/pkg/getter/vaultgetter.go b/pkg/getter/vaultgetter.go new file mode 100755 index 000000000..9459b1935 --- /dev/null +++ b/pkg/getter/vaultgetter.go @@ -0,0 +1,154 @@ +/* +Copyright (c) 2024 Rakuten Symphony India. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package getter + +import ( + "bytes" + "fmt" + "strings" + "sync" + + "github.com/hashicorp/vault/api" + "gopkg.in/yaml.v3" +) + +// VaultGetter is the struct that handles retrieving data from Vault. +type VaultGetter struct { + opts options // Options for Vault (address, token, etc.) + client *api.Client // The Vault client + once sync.Once // Ensure the Vault client is initialized only once +} + +// Get performs a Get from repo.Getter and returns the body. +func (v *VaultGetter) Get(href string, options ...Option) (*bytes.Buffer, error) { + for _, opt := range options { + opt(&v.opts) + } + return v.get(href) +} + +func (v *VaultGetter) get(href string) (*bytes.Buffer, error) { + // Initialize the Vault client + client, err := v.vaultClient() + if err != nil { + return nil, err + } + + // Fetch the values from Vault using the Vault client + valPath := strings.TrimPrefix(href, "vault://") + val, err := client.Logical().Read(valPath) + if err != nil { + return nil, fmt.Errorf("failed to fetch values from Vault: %v", err) + } + + // Ensure the values contains data + if val == nil || val.Data == nil { + return nil, fmt.Errorf("no data found at Vault path: %s", valPath) + } + + // Retrieve the data (assumed to be a string in the Vault response) + data, ok := val.Data["data"].(map[string]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected data format at Vault path: %s", href) + } + + // Unwrap the "values" key if it exists + if values, ok := data["values"].(string); ok { + data = make(map[string]interface{}) + if err := yaml.Unmarshal([]byte(values), &data); err != nil { + return nil, fmt.Errorf("failed to unmarshal values: %v", err) + } + } + + // Check if the data is in properties format + if properties, ok := data["properties"].(string); ok { + data = make(map[string]interface{}) + for _, line := range strings.Split(properties, "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid properties line: %s", line) + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + data[key] = value + } + } + + // Return the data in a byte buffer + buf := bytes.NewBuffer(nil) + if err = yaml.NewEncoder(buf).Encode(data); err != nil { + return nil, fmt.Errorf("failed to encode data to YAML: %v", err) + } + + return buf, nil +} + +// NewVaultGetter creates a new instance of VaultGetter. +func NewVaultGetter(options ...Option) (Getter, error) { + var v VaultGetter + + for _, opt := range options { + opt(&v.opts) + } + + return &v, nil +} + +func (v *VaultGetter) vaultClient() (*api.Client, error) { + if v.client != nil { + return v.client, nil + } + + var config *api.Config + + // Use sync.Once to initialize the Vault client only once + v.once.Do(func() { + config = &api.Config{ + Address: v.opts.address, // Vault URL is set from options + } + }) + + // Configure TLS if needed + if v.opts.caFile != "" || v.opts.insecureSkipVerifyTLS { + tlsConfig := &api.TLSConfig{ + CACert: v.opts.caFile, + Insecure: v.opts.insecureSkipVerifyTLS, + ClientCert: v.opts.certFile, + ClientKey: v.opts.keyFile, + } + err := config.ConfigureTLS(tlsConfig) + if err != nil { + return nil, fmt.Errorf("failed to configure TLS for Vault client: %v", err) + } + } + + // Initialize the Vault client + client, err := api.NewClient(config) + if err != nil { + return nil, fmt.Errorf("failed to create Vault client: %v", err) + } + + // Set the token for authentication + client.SetToken(v.opts.token) + v.client = client + + return v.client, nil +}