diff --git a/README.md b/README.md index b12cda12..26c85a69 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,10 @@ Note that we will try our best to document any backward incompatibility. And in ## Installation -* download one of [releases](https://github.com/helmfile/helmfile/releases) or -* [run as a container](https://helmfile.readthedocs.io/en/latest/#running-as-a-container) or -* Archlinux: install via `pacman -S helmfile` or from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) or -* openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic`, or +* download one of [releases](https://github.com/helmfile/helmfile/releases) +* [run as a container](https://helmfile.readthedocs.io/en/latest/#running-as-a-container) +* Archlinux: install via `pacman -S helmfile` +* openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic` * Windows (using [scoop](https://scoop.sh/)): `scoop install helmfile` * macOS (using [homebrew](https://brew.sh/)): `brew install helmfile` diff --git a/cmd/apply.go b/cmd/apply.go index 0a2f6cf5..0eb2cd01 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -16,7 +16,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "apply", Short: "Apply all resources from state file only when there are changes", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(applyImpl.GlobalImpl) + err := config.NewCLIConfigImpl(applyImpl.GlobalImpl) if err != nil { return err } @@ -31,7 +31,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringSliceVar(&applyImpl.ApplyOptions.Values, "values", []string{}, "additional value files to be merged into the command") + f.StringSliceVar(&applyImpl.ApplyOptions.Values, "values", nil, "additional value files to be merged into the command") f.IntVar(&applyImpl.ApplyOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") f.BoolVar(&applyImpl.ApplyOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the list of available API versions") f.IntVar(&applyImpl.ApplyOptions.Context, "context", 0, "output NUM lines of context around changes") @@ -46,7 +46,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&applyImpl.ApplyOptions.IncludeTransitiveNeeds, "include-transitive-needs", false, `like --include-needs, but also includes transitive needs (needs of needs). Does nothing when when --selector/-l flag is not provided. Overrides exclusions of other selectors and conditions.`) f.BoolVar(&applyImpl.ApplyOptions.SkipDiffOnInstall, "skip-diff-on-install", false, "Skips running helm-diff on releases being newly installed on this apply. Useful when the release manifests are too huge to be reviewed, or it's too time-consuming to diff at all0") f.BoolVar(&applyImpl.ApplyOptions.IncludeTests, "include-tests", false, "enable the diffing of the helm test hooks") - f.StringArrayVar(&applyImpl.ApplyOptions.Suppress, "suppress", []string{}, "suppress specified Kubernetes objects in the diff output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret") + f.StringArrayVar(&applyImpl.ApplyOptions.Suppress, "suppress", nil, "suppress specified Kubernetes objects in the diff output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret") f.BoolVar(&applyImpl.ApplyOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the diff output. highly recommended to specify on CI/CD use-cases") f.BoolVar(&applyImpl.ApplyOptions.ShowSecrets, "show-secrets", false, "do not redact secret values in the diff output. should be used for debug purpose only") f.BoolVar(&applyImpl.ApplyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs") diff --git a/cmd/build.go b/cmd/build.go index 35ec0d12..ac0d756a 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -16,7 +16,7 @@ func NewBuildCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "build", Short: "Build all resources from state file only when there are changes", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(buildImpl.GlobalImpl) + err := config.NewCLIConfigImpl(buildImpl.GlobalImpl) if err != nil { return err } diff --git a/cmd/cache.go b/cmd/cache.go index e85ded6e..3a0a2e30 100644 --- a/cmd/cache.go +++ b/cmd/cache.go @@ -12,7 +12,7 @@ func NewCacheInfoSubcommand(cacheImpl *config.CacheImpl) *cobra.Command { Use: "info", Short: "cache info", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(cacheImpl.GlobalImpl) + err := config.NewCLIConfigImpl(cacheImpl.GlobalImpl) if err != nil { return err } @@ -34,7 +34,7 @@ func NewCacheCleanupSubcommand(cacheImpl *config.CacheImpl) *cobra.Command { Use: "cleanup", Short: "clean up cache directory", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(cacheImpl.GlobalImpl) + err := config.NewCLIConfigImpl(cacheImpl.GlobalImpl) if err != nil { return err } diff --git a/cmd/charts.go b/cmd/charts.go index 2f3c8f0a..7cb27636 100644 --- a/cmd/charts.go +++ b/cmd/charts.go @@ -16,7 +16,7 @@ func NewChartsCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "charts", Short: "DEPRECATED: sync releases from state file (helm upgrade --install)", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(chartsImpl.GlobalImpl) + err := config.NewCLIConfigImpl(chartsImpl.GlobalImpl) if err != nil { return err } @@ -31,9 +31,9 @@ func NewChartsCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&chartsOptions.Args, "args", chartsOptions.Args, "pass args to helm exec") - f.StringArrayVar(&chartsOptions.Set, "set", chartsOptions.Set, "additional values to be merged into the command") - f.StringArrayVar(&chartsOptions.Values, "values", chartsOptions.Values, "additional value files to be merged into the command") + f.StringVar(&chartsOptions.Args, "args", "", "pass args to helm exec") + f.StringArrayVar(&chartsOptions.Set, "set", nil, "additional values to be merged into the command") + f.StringArrayVar(&chartsOptions.Values, "values", nil, "additional value files to be merged into the command") f.IntVar(&chartsOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") return cmd diff --git a/cmd/delete.go b/cmd/delete.go index 08ec2d4b..09d4c105 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -16,7 +16,7 @@ func NewDeleteCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "delete", Short: "DEPRECATED: delete releases from state file (helm delete)", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(deleteImpl.GlobalImpl) + err := config.NewCLIConfigImpl(deleteImpl.GlobalImpl) if err != nil { return err } @@ -31,10 +31,10 @@ func NewDeleteCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&deleteOptions.Args, "args", deleteOptions.Args, "pass args to helm exec") + f.StringVar(&deleteOptions.Args, "args", "", "pass args to helm exec") f.IntVar(&deleteOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") - f.BoolVar(&deleteOptions.Purge, "purge", deleteOptions.Purge, "purge releases i.e. free release names and histories") - f.BoolVar(&deleteOptions.SkipDeps, "skip-deps", deleteOptions.SkipDeps, `skip running "helm repo update" and "helm dependency build"`) + f.BoolVar(&deleteOptions.Purge, "purge", false, "purge releases i.e. free release names and histories") + f.BoolVar(&deleteOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) return cmd } diff --git a/cmd/deps.go b/cmd/deps.go index 01a89fc6..278cc42a 100644 --- a/cmd/deps.go +++ b/cmd/deps.go @@ -16,7 +16,7 @@ func NewDepsCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "deps", Short: "Update charts based on their requirements", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(depsImpl.GlobalImpl) + err := config.NewCLIConfigImpl(depsImpl.GlobalImpl) if err != nil { return err } @@ -31,8 +31,8 @@ func NewDepsCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&depsOptions.Args, "args", depsOptions.Args, "pass args to helm exec") - f.BoolVar(&depsOptions.SkipRepos, "skip-deps", depsOptions.SkipRepos, `skip running "helm repo update" and "helm dependency build"`) + f.StringVar(&depsOptions.Args, "args", "", "pass args to helm exec") + f.BoolVar(&depsOptions.SkipRepos, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) return cmd } diff --git a/cmd/destroy.go b/cmd/destroy.go index 008fab50..c2ba93d7 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -16,7 +16,7 @@ func NewDestroyCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "destroy", Short: "Destroys and then purges releases", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(destroyImpl.GlobalImpl) + err := config.NewCLIConfigImpl(destroyImpl.GlobalImpl) if err != nil { return err } @@ -31,9 +31,9 @@ func NewDestroyCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&destroyOptions.Args, "args", destroyOptions.Args, "pass args to helm exec") + f.StringVar(&destroyOptions.Args, "args", "", "pass args to helm exec") f.IntVar(&destroyOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") - f.BoolVar(&destroyOptions.SkipDeps, "skip-deps", destroyOptions.SkipDeps, `skip running "helm repo update" and "helm dependency build"`) + f.BoolVar(&destroyOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) return cmd } diff --git a/cmd/diff.go b/cmd/diff.go index 32200367..bbb2127f 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -16,7 +16,7 @@ func NewDiffCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "diff", Short: "Diff releases defined in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(diffImpl.GlobalImpl) + err := config.NewCLIConfigImpl(diffImpl.GlobalImpl) if err != nil { return err } @@ -32,8 +32,8 @@ func NewDiffCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.StringVar(&diffOptions.Args, "args", "", "pass args to helm diff") - f.StringArrayVar(&diffOptions.Set, "set", []string{}, "additional values to be merged into the command") - f.StringArrayVar(&diffOptions.Values, "values", []string{}, "additional value files to be merged into the command") + f.StringArrayVar(&diffOptions.Set, "set", nil, "additional values to be merged into the command") + f.StringArrayVar(&diffOptions.Values, "values", nil, "additional value files to be merged into the command") f.IntVar(&diffOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts") f.BoolVar(&diffOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the diff of available API versions") f.BoolVar(&diffOptions.SkipNeeds, "skip-needs", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`) @@ -47,7 +47,7 @@ func NewDiffCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.IntVar(&diffOptions.Context, "context", 0, "output NUM lines of context around changes") f.StringVar(&diffOptions.Output, "output", "", "output format for diff plugin") f.BoolVar(&diffOptions.SuppressSecrets, "suppress-secrets", false, "suppress secrets in the output. highly recommended to specify on CI/CD use-cases") - f.StringArrayVar(&diffOptions.Suppress, "suppress", []string{}, "suppress specified Kubernetes objects in the output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret") + f.StringArrayVar(&diffOptions.Suppress, "suppress", nil, "suppress specified Kubernetes objects in the output. Can be provided multiple times. For example: --suppress KeycloakClient --suppress VaultSecret") return cmd } diff --git a/cmd/fetch.go b/cmd/fetch.go index 653c1924..91ee7157 100644 --- a/cmd/fetch.go +++ b/cmd/fetch.go @@ -16,7 +16,7 @@ func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "fetch", Short: "Fetch charts from state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(fetchImpl.GlobalImpl) + err := config.NewCLIConfigImpl(fetchImpl.GlobalImpl) if err != nil { return err } @@ -32,8 +32,8 @@ func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.IntVar(&fetchOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") - f.BoolVar(&fetchOptions.SkipDeps, "skip-deps", fetchOptions.SkipDeps, `skip running "helm repo update" and "helm dependency build"`) - f.StringVar(&fetchOptions.OutputDir, "output-dir", fetchOptions.OutputDir, "directory to store charts (default: temporary directory which is deleted when the command terminates)") + f.BoolVar(&fetchOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) + f.StringVar(&fetchOptions.OutputDir, "output-dir", "", "directory to store charts (default: temporary directory which is deleted when the command terminates)") return cmd } diff --git a/cmd/lint.go b/cmd/lint.go index 71e6e07c..b8a0fea7 100644 --- a/cmd/lint.go +++ b/cmd/lint.go @@ -16,7 +16,7 @@ func NewLintCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "lint", Short: "Lint charts from state file (helm lint)", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(lintImpl.GlobalImpl) + err := config.NewCLIConfigImpl(lintImpl.GlobalImpl) if err != nil { return err } diff --git a/cmd/list.go b/cmd/list.go index d439e830..e16a8695 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -16,7 +16,7 @@ func NewListCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "list", Short: "List releases defined in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(listImpl.GlobalImpl) + err := config.NewCLIConfigImpl(listImpl.GlobalImpl) if err != nil { return err } @@ -31,8 +31,8 @@ func NewListCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.BoolVar(&listOptions.KeepTempDir, "keep-temp-dir", listOptions.KeepTempDir, "Keep temporary directory") - f.StringVar(&listOptions.Output, "output", listOptions.Output, "output releases list as a json string") + f.BoolVar(&listOptions.KeepTempDir, "keep-temp-dir", false, "Keep temporary directory") + f.StringVar(&listOptions.Output, "output", "", "output releases list as a json string") return cmd } diff --git a/cmd/repos.go b/cmd/repos.go index e4b90285..963c6e84 100644 --- a/cmd/repos.go +++ b/cmd/repos.go @@ -16,7 +16,7 @@ func NewReposCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "repos", Short: "Repos releases defined in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(reposImpl.GlobalImpl) + err := config.NewCLIConfigImpl(reposImpl.GlobalImpl) if err != nil { return err } @@ -31,7 +31,7 @@ func NewReposCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&reposOptions.Args, "args", reposOptions.Args, "pass args to helm exec") + f.StringVar(&reposOptions.Args, "args", "", "pass args to helm exec") return cmd } diff --git a/cmd/root.go b/cmd/root.go index 49503874..c03b1a4b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -104,24 +104,24 @@ func NewRootCmd(globalConfig *config.GlobalOptions, args []string) (*cobra.Comma func setGlobalOptionsForRootCmd(fs *pflag.FlagSet, globalOptions *config.GlobalOptions) { fs.StringVarP(&globalOptions.HelmBinary, "helm-binary", "b", app.DefaultHelmBinary, "Path to the helm binary") - fs.StringVarP(&globalOptions.File, "file", "f", globalOptions.File, "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference") - fs.StringVarP(&globalOptions.Environment, "environment", "e", globalOptions.Environment, `specify the environment name. defaults to "default"`) - fs.StringArrayVarP(&globalOptions.StateValuesSet, "state-values-set", "s", globalOptions.StateValuesSet, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - fs.StringArrayVarP(&globalOptions.StateValuesFile, "state-values-file", "", globalOptions.StateValuesFile, "specify state values in a YAML file") - fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", globalOptions.Quiet, "Silence output. Equivalent to log-level warn") - fs.StringVar(&globalOptions.KubeContext, "kube-context", globalOptions.KubeContext, "Set kubectl context. Uses current context by default") - fs.BoolVar(&globalOptions.Debug, "debug", globalOptions.Debug, "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect") - fs.BoolVar(&globalOptions.Color, "color", globalOptions.Color, "Output with color") - fs.BoolVar(&globalOptions.NoColor, "no-color", globalOptions.NoColor, "Output without color") - fs.StringVar(&globalOptions.LogLevel, "log-level", globalOptions.LogLevel, "Set log level, default info") - fs.StringVar(&globalOptions.Namespace, "namespace", globalOptions.Namespace, "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}") - fs.StringVar(&globalOptions.Chart, "chart", globalOptions.Chart, "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}") - fs.StringArrayVarP(&globalOptions.Selector, "selector", "l", globalOptions.Selector, `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. + fs.StringVarP(&globalOptions.File, "file", "f", "", "load config from file or directory. defaults to `helmfile.yaml` or `helmfile.d`(means `helmfile.d/*.yaml`) in this preference") + fs.StringVarP(&globalOptions.Environment, "environment", "e", "", `specify the environment name. defaults to "default"`) + fs.StringArrayVarP(&globalOptions.StateValuesSet, "state-values-set", "s", nil, "set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") + fs.StringArrayVarP(&globalOptions.StateValuesFile, "state-values-file", "", nil, "specify state values in a YAML file") + fs.BoolVarP(&globalOptions.Quiet, "quiet", "q", false, "Silence output. Equivalent to log-level warn") + fs.StringVar(&globalOptions.KubeContext, "kube-context", "", "Set kubectl context. Uses current context by default") + fs.BoolVar(&globalOptions.Debug, "debug", false, "Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect") + fs.BoolVar(&globalOptions.Color, "color", false, "Output with color") + fs.BoolVar(&globalOptions.NoColor, "no-color", false, "Output without color") + fs.StringVar(&globalOptions.LogLevel, "log-level", "info", "Set log level, default info") + fs.StringVar(&globalOptions.Namespace, "namespace", "", "Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }}") + fs.StringVar(&globalOptions.Chart, "chart", "", "Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }}") + fs.StringArrayVarP(&globalOptions.Selector, "selector", "l", nil, `Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. A release must match all labels in a group in order to be used. Multiple groups can be specified at once. --selector tier=frontend,tier!=proxy --selector tier=backend. Will match all frontend, non-proxy releases AND all backend releases. The name of a release can be used as a label. --selector name=myrelease`) - fs.BoolVar(&globalOptions.AllowNoMatchingRelease, "allow-no-matching-release", globalOptions.AllowNoMatchingRelease, `Do not exit with an error code if the provided selector has no matching releases.`) - fs.BoolVarP(&globalOptions.Interactive, "interactive", "i", globalOptions.Interactive, "Request confirmation before attempting to modify clusters") + fs.BoolVar(&globalOptions.AllowNoMatchingRelease, "allow-no-matching-release", false, `Do not exit with an error code if the provided selector has no matching releases.`) + fs.BoolVarP(&globalOptions.Interactive, "interactive", "i", false, "Request confirmation before attempting to modify clusters") // avoid 'pflag: help requested' error (#251) fs.BoolP("help", "h", false, "help for helmfile") } diff --git a/cmd/status.go b/cmd/status.go index 2f860df9..41c540dc 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -16,7 +16,7 @@ func NewStatusCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "status", Short: "Retrieve status of releases in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(statusImpl.GlobalImpl) + err := config.NewCLIConfigImpl(statusImpl.GlobalImpl) if err != nil { return err } @@ -31,7 +31,7 @@ func NewStatusCmd(globalCfg *config.GlobalImpl) *cobra.Command { } f := cmd.Flags() - f.StringVar(&statusOptions.Args, "args", statusOptions.Args, "pass args to helm exec") + f.StringVar(&statusOptions.Args, "args", "", "pass args to helm exec") return cmd } diff --git a/cmd/sync.go b/cmd/sync.go index 75b1c144..08fa2494 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -16,7 +16,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "sync", Short: "Sync releases defined in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(syncImpl.GlobalImpl) + err := config.NewCLIConfigImpl(syncImpl.GlobalImpl) if err != nil { return err } @@ -32,8 +32,8 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.StringVar(&syncOptions.Args, "args", "", "pass args to helm sync") - f.StringArrayVar(&syncOptions.Set, "set", []string{}, "additional values to be merged into the command") - f.StringArrayVar(&syncOptions.Values, "values", []string{}, "additional value files to be merged into the command") + f.StringArrayVar(&syncOptions.Set, "set", nil, "additional values to be merged into the command") + f.StringArrayVar(&syncOptions.Values, "values", nil, "additional value files to be merged into the command") f.IntVar(&syncOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts") f.BoolVar(&syncOptions.Validate, "validate", false, "validate your manifests against the Kubernetes cluster you are currently pointing at. Note that this requires access to a Kubernetes cluster to obtain information necessary for validating, like the sync of available API versions") f.BoolVar(&syncOptions.SkipNeeds, "skip-needs", false, `do not automatically include releases from the target release's "needs" when --selector/-l flag is provided. Does nothing when when --selector/-l flag is not provided. Defaults to true when --include-needs or --include-transitive-needs is not provided`) diff --git a/cmd/template.go b/cmd/template.go index 709a8f73..dfc16398 100644 --- a/cmd/template.go +++ b/cmd/template.go @@ -16,7 +16,7 @@ func NewTemplateCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "template", Short: "Template releases defined in state file", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(templateImpl.GlobalImpl) + err := config.NewCLIConfigImpl(templateImpl.GlobalImpl) if err != nil { return err } @@ -32,8 +32,8 @@ func NewTemplateCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.StringVar(&templateOptions.Args, "args", "", "pass args to helm template") - f.StringArrayVar(&templateOptions.Set, "set", []string{}, "additional values to be merged into the command") - f.StringArrayVar(&templateOptions.Values, "values", []string{}, "additional value files to be merged into the command") + f.StringArrayVar(&templateOptions.Set, "set", nil, "additional values to be merged into the command") + f.StringArrayVar(&templateOptions.Values, "values", nil, "additional value files to be merged into the command") f.StringVar(&templateOptions.OutputDir, "output-dir", "", "output directory to pass to helm template (helm template --output-dir)") f.StringVar(&templateOptions.OutputDirTemplate, "output-dir-template", "", "go text template for generating the output directory. Default: {{ .OutputDir }}/{{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}-{{ .Release.Name}}") f.IntVar(&templateOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts") diff --git a/cmd/test.go b/cmd/test.go index fa6e6d78..a59b58e7 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -16,7 +16,7 @@ func NewTestCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "test", Short: "Test charts from state file (helm test)", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(testImpl.GlobalImpl) + err := config.NewCLIConfigImpl(testImpl.GlobalImpl) if err != nil { return err } @@ -33,10 +33,10 @@ func NewTestCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.IntVar(&testOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts") - f.BoolVar(&testOptions.SkipDeps, "skip-deps", testOptions.SkipDeps, `skip running "helm repo update" and "helm dependency build"`) - f.BoolVar(&testOptions.Cleanup, "cleanup", testOptions.Cleanup, "delete test pods upon completion") - f.BoolVar(&testOptions.Logs, "logs", testOptions.Logs, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") - f.StringVar(&testOptions.Args, "args", testOptions.Args, "pass args to helm exec") + f.BoolVar(&testOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) + f.BoolVar(&testOptions.Cleanup, "cleanup", false, "delete test pods upon completion") + f.BoolVar(&testOptions.Logs, "logs", false, "Dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") + f.StringVar(&testOptions.Args, "args", "", "pass args to helm exec") f.IntVar(&testOptions.Timeout, "timeout", 300, "maximum time for tests to run before being considered failed") return cmd diff --git a/cmd/write-values.go b/cmd/write-values.go index 762e4fbc..4d89489d 100644 --- a/cmd/write-values.go +++ b/cmd/write-values.go @@ -16,7 +16,7 @@ func NewWriteValuesCmd(globalCfg *config.GlobalImpl) *cobra.Command { Use: "write-values", Short: "Write values files for releases. Similar to `helmfile template`, write values files instead of manifests.", RunE: func(cmd *cobra.Command, args []string) error { - err := config.NewUrfaveCliConfigImplIns(writeValuesImpl.GlobalImpl) + err := config.NewCLIConfigImpl(writeValuesImpl.GlobalImpl) if err != nil { return err } @@ -32,10 +32,10 @@ func NewWriteValuesCmd(globalCfg *config.GlobalImpl) *cobra.Command { f := cmd.Flags() f.IntVar(&writeValuesOptions.Concurrency, "concurrency", 0, "maximum number of concurrent downloads of release charts") - f.BoolVar(&writeValuesOptions.SkipDeps, "skip-deps", writeValuesOptions.SkipDeps, `skip running "helm repo update" and "helm dependency build"`) - f.StringArrayVar(&writeValuesOptions.Set, "set", writeValuesOptions.Set, "additional values to be merged into the command") - f.StringArrayVar(&writeValuesOptions.Values, "values", writeValuesOptions.Values, "additional value files to be merged into the command") - f.StringVar(&writeValuesOptions.OutputFileTemplate, "output-file-template", writeValuesOptions.OutputFileTemplate, "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml") + f.BoolVar(&writeValuesOptions.SkipDeps, "skip-deps", false, `skip running "helm repo update" and "helm dependency build"`) + f.StringArrayVar(&writeValuesOptions.Set, "set", nil, "additional values to be merged into the command") + f.StringArrayVar(&writeValuesOptions.Values, "values", nil, "additional value files to be merged into the command") + f.StringVar(&writeValuesOptions.OutputFileTemplate, "output-file-template", "", "go text template for generating the output file. Default: {{ .State.BaseName }}-{{ .State.AbsPathSHA1 }}/{{ .Release.Name}}.yaml") return cmd } diff --git a/docs/index.md b/docs/index.md index 6426b36d..35172779 100644 --- a/docs/index.md +++ b/docs/index.md @@ -414,10 +414,10 @@ If you wish to treat your enviroment variables as strings always, even if they a ## Installation -- download one of [releases](https://github.com/helmfile/helmfile/tags) or -- [run as a container](#running-as-a-container) or -- Archlinux: install via `pacman -S helmfile` or from [AUR](https://aur.archlinux.org/packages/kubernetes-helmfile-bin/) or -- openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic`, or +- download one of [releases](https://github.com/helmfile/helmfile/releases) +- [run as a container](https://helmfile.readthedocs.io/en/latest/#running-as-a-container) +- Archlinux: install via `pacman -S helmfile` +- openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic` - Windows (using [scoop](https://scoop.sh/)): `scoop install helmfile` - macOS (using [homebrew](https://brew.sh/)): `brew install helmfile` diff --git a/go.mod b/go.mod index 6652c7ce..b7601c04 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/variantdev/dag v1.1.0 github.com/variantdev/vals v0.18.0 go.uber.org/multierr v1.6.0 - go.uber.org/zap v1.21.0 + go.uber.org/zap v1.22.0 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index d1655830..7fdfdba5 100644 --- a/go.sum +++ b/go.sum @@ -1331,15 +1331,14 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.22.0 h1:Zcye5DUgBloQ9BaT4qc9BnjOFog5TvBSAGkJ3Nf70c0= +go.uber.org/zap v1.22.0/go.mod h1:H4siCOZOrAolnUPJEkfaSjDqyP+BDS0DdDWzwcgt3+U= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= diff --git a/pkg/app/app.go b/pkg/app/app.go index 4dd83873..f69c6403 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -987,7 +987,7 @@ func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converg envvals = append(envvals, v) } - if a.Set != nil { + if len(a.Set) > 0 { envvals = append(envvals, a.Set) } diff --git a/pkg/app/app_list_test.go b/pkg/app/app_list_test.go new file mode 100644 index 00000000..9b71c958 --- /dev/null +++ b/pkg/app/app_list_test.go @@ -0,0 +1,249 @@ +package app + +import ( + "bufio" + "bytes" + "io" + "path/filepath" + "sync" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/variantdev/vals" + + "github.com/helmfile/helmfile/pkg/helmexec" + "github.com/helmfile/helmfile/pkg/testhelper" + "github.com/helmfile/helmfile/pkg/testutil" +) + +func TestListWithEnvironment(t *testing.T) { + type testcase struct { + environment string + ns string + error string + selectors []string + expected string + } + + check := func(t *testing.T, tc testcase) { + t.Helper() + + bs := &bytes.Buffer{} + + func() { + t.Helper() + + logReader, logWriter := io.Pipe() + + logFlushed := &sync.WaitGroup{} + // Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()` + logFlushed.Add(1) + go func() { + scanner := bufio.NewScanner(logReader) + for scanner.Scan() { + bs.Write(scanner.Bytes()) + bs.WriteString("\n") + } + logFlushed.Done() + }() + + defer func() { + // This is here to avoid data-trace on bytes buffer `bs` to capture logs + if err := logWriter.Close(); err != nil { + panic(err) + } + logFlushed.Wait() + }() + + logger := helmexec.NewLogger(logWriter, "debug") + + valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) + if err != nil { + t.Errorf("unexpected error creating vals runtime: %v", err) + } + + files := map[string]string{ + "/path/to/helmfile.d/helmfile_1.yaml": ` +environments: + development: {} + shared: {} + +releases: +- name: logging + chart: incubator/raw + namespace: kube-system + +- name: kubernetes-external-secrets + chart: incubator/raw + namespace: kube-system + needs: + - kube-system/logging + +- name: external-secrets + chart: incubator/raw + namespace: default + labels: + app: test + needs: + - kube-system/kubernetes-external-secrets + +- name: my-release + chart: incubator/raw + namespace: default + labels: + app: test + needs: + - default/external-secrets + + +# Disabled releases are treated as missing +- name: disabled + chart: incubator/raw + namespace: kube-system + installed: false + +- name: test2 + chart: incubator/raw + needs: + - kube-system/disabled + +- name: test3 + chart: incubator/raw + needs: + - test2 +`, + "/path/to/helmfile.d/helmfile_2.yaml": ` +environments: + test: {} + shared: {} + +repositories: +- name: bitnami + url: https://charts.bitnami.com/bitnami + +releases: +- name: cache + namespace: my-app + chart: bitnami/redis + version: 17.0.7 + labels: + app: test + +- name: database + namespace: my-app + chart: bitnami/postgres + version: 11.6.22 +`, + "/path/to/helmfile.d/helmfile_3.yaml": ` +releases: +- name: global + chart: incubator/raw + namespace: kube-system +`, + } + + app := appWithFs(&App{ + OverrideHelmBinary: DefaultHelmBinary, + glob: filepath.Glob, + abs: filepath.Abs, + OverrideKubeContext: "default", + Env: tc.environment, + Logger: logger, + valsRuntime: valsRuntime, + }, files) + + expectNoCallsToHelm(app) + + if tc.ns != "" { + app.Namespace = tc.ns + } + + if tc.selectors != nil { + app.Selectors = tc.selectors + } + + var listErr error + out := testutil.CaptureStdout(func() { + listErr = app.ListReleases(configImpl{}) + }) + + var gotErr string + if listErr != nil { + gotErr = listErr.Error() + } + + if d := cmp.Diff(tc.error, gotErr); d != "" { + t.Fatalf("unexpected error: want (-), got (+): %s", d) + } + + assert.Equal(t, tc.expected, out) + }() + + testhelper.RequireLog(t, "app_list_test", bs) + } + + t.Run("default environment includes all releases", func(t *testing.T) { + check(t, testcase{ + environment: "default", + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +logging kube-system true true incubator/raw +kubernetes-external-secrets kube-system true true incubator/raw +external-secrets default true true app:test incubator/raw +my-release default true true app:test incubator/raw +disabled kube-system true false incubator/raw +test2 true true incubator/raw +test3 true true incubator/raw +cache my-app true true app:test bitnami/redis 17.0.7 +database my-app true true bitnami/postgres 11.6.22 +global kube-system true true incubator/raw +`, + }) + }) + + t.Run("fail on unknown environment", func(t *testing.T) { + check(t, testcase{ + environment: "staging", + error: `err: no releases found that matches specified selector() and environment(staging), in any helmfile`, + }) + }) + + t.Run("list releases matching selector and environment", func(t *testing.T) { + check(t, testcase{ + environment: "development", + selectors: []string{"app=test"}, + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +external-secrets default true true app:test,chart:raw,name:external-secrets,namespace:default incubator/raw +my-release default true true app:test,chart:raw,name:my-release,namespace:default incubator/raw +`, + }) + }) + + t.Run("filters releases for environment used in one file only", func(t *testing.T) { + check(t, testcase{ + environment: "test", + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +cache my-app true true app:test bitnami/redis 17.0.7 +database my-app true true bitnami/postgres 11.6.22 +`, + }) + }) + + t.Run("filters releases for environment used in multiple files", func(t *testing.T) { + check(t, testcase{ + environment: "shared", + // 'global' release has no environments, so is still excluded + expected: `NAME NAMESPACE ENABLED INSTALLED LABELS CHART VERSION +logging kube-system true true incubator/raw +kubernetes-external-secrets kube-system true true incubator/raw +external-secrets default true true app:test incubator/raw +my-release default true true app:test incubator/raw +disabled kube-system true false incubator/raw +test2 true true incubator/raw +test3 true true incubator/raw +cache my-app true true app:test bitnami/redis 17.0.7 +database my-app true true bitnami/postgres 11.6.22 +`, + }) + }) +} diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 0e0f1850..069c3c99 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -34,6 +34,12 @@ func appWithFs(app *App, files map[string]string) *App { } func injectFs(app *App, fs *testhelper.TestFs) *App { + if app.Set == nil { + // Consistent behavior with NewGlobalImpl. + // Doesn't really belong here, but simplest place for it until some refactoring happens + app.Set = make(map[string]interface{}) + } + app.readFile = fs.ReadFile app.glob = fs.Glob app.abs = fs.Abs diff --git a/pkg/app/testdata/app_list_test/default_environment_includes_all_releases b/pkg/app/testdata/app_list_test/default_environment_includes_all_releases new file mode 100644 index 00000000..f9682de0 --- /dev/null +++ b/pkg/app/testdata/app_list_test/default_environment_includes_all_releases @@ -0,0 +1,199 @@ +processing file "helmfile_1.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_1.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile_1.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +merged environment: &{default map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_2.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_2.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile_2.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +merged environment: &{default map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_3.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_3.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile_3.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +merged environment: &{default map[] map[]} +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/app_list_test/fail_on_unknown_environment b/pkg/app/testdata/app_list_test/fail_on_unknown_environment new file mode 100644 index 00000000..ca68355a --- /dev/null +++ b/pkg/app/testdata/app_list_test/fail_on_unknown_environment @@ -0,0 +1,196 @@ +processing file "helmfile_1.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_1.yaml.part.0": inherited=&{staging map[] map[]}, overrode= +first-pass uses: &{staging map[] map[]} +first-pass rendering output of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +first-pass produced: &{staging map[] map[]} +first-pass rendering result of "helmfile_1.yaml.part.0": {staging map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +changing working directory back to "/path/to" +processing file "helmfile_2.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_2.yaml.part.0": inherited=&{staging map[] map[]}, overrode= +first-pass uses: &{staging map[] map[]} +first-pass rendering output of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +first-pass produced: &{staging map[] map[]} +first-pass rendering result of "helmfile_2.yaml.part.0": {staging map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +changing working directory back to "/path/to" +processing file "helmfile_3.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_3.yaml.part.0": inherited=&{staging map[] map[]}, overrode= +first-pass uses: &{staging map[] map[]} +first-pass rendering output of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +first-pass produced: &{staging map[] map[]} +first-pass rendering result of "helmfile_3.yaml.part.0": {staging map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_multiple_files b/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_multiple_files new file mode 100644 index 00000000..0202a4b8 --- /dev/null +++ b/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_multiple_files @@ -0,0 +1,198 @@ +processing file "helmfile_1.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_1.yaml.part.0": inherited=&{shared map[] map[]}, overrode= +first-pass uses: &{shared map[] map[]} +first-pass rendering output of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +first-pass produced: &{shared map[] map[]} +first-pass rendering result of "helmfile_1.yaml.part.0": {shared map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +merged environment: &{shared map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_2.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_2.yaml.part.0": inherited=&{shared map[] map[]}, overrode= +first-pass uses: &{shared map[] map[]} +first-pass rendering output of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +first-pass produced: &{shared map[] map[]} +first-pass rendering result of "helmfile_2.yaml.part.0": {shared map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +merged environment: &{shared map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_3.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_3.yaml.part.0": inherited=&{shared map[] map[]}, overrode= +first-pass uses: &{shared map[] map[]} +first-pass rendering output of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +first-pass produced: &{shared map[] map[]} +first-pass rendering result of "helmfile_3.yaml.part.0": {shared map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_one_file_only b/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_one_file_only new file mode 100644 index 00000000..402abbe2 --- /dev/null +++ b/pkg/app/testdata/app_list_test/filters_releases_for_environment_used_in_one_file_only @@ -0,0 +1,197 @@ +processing file "helmfile_1.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_1.yaml.part.0": inherited=&{test map[] map[]}, overrode= +first-pass uses: &{test map[] map[]} +first-pass rendering output of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +first-pass produced: &{test map[] map[]} +first-pass rendering result of "helmfile_1.yaml.part.0": {test map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +changing working directory back to "/path/to" +processing file "helmfile_2.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_2.yaml.part.0": inherited=&{test map[] map[]}, overrode= +first-pass uses: &{test map[] map[]} +first-pass rendering output of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +first-pass produced: &{test map[] map[]} +first-pass rendering result of "helmfile_2.yaml.part.0": {test map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +merged environment: &{test map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_3.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_3.yaml.part.0": inherited=&{test map[] map[]}, overrode= +first-pass uses: &{test map[] map[]} +first-pass rendering output of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +first-pass produced: &{test map[] map[]} +first-pass rendering result of "helmfile_3.yaml.part.0": {test map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +changing working directory back to "/path/to" diff --git a/pkg/app/testdata/app_list_test/list_releases_matching_selector_and_environment b/pkg/app/testdata/app_list_test/list_releases_matching_selector_and_environment new file mode 100644 index 00000000..2d498ce0 --- /dev/null +++ b/pkg/app/testdata/app_list_test/list_releases_matching_selector_and_environment @@ -0,0 +1,197 @@ +processing file "helmfile_1.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_1.yaml.part.0": inherited=&{development map[] map[]}, overrode= +first-pass uses: &{development map[] map[]} +first-pass rendering output of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +first-pass produced: &{development map[] map[]} +first-pass rendering result of "helmfile_1.yaml.part.0": {development map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_1.yaml.part.0": + 0: + 1: environments: + 2: development: {} + 3: shared: {} + 4: + 5: releases: + 6: - name: logging + 7: chart: incubator/raw + 8: namespace: kube-system + 9: +10: - name: kubernetes-external-secrets +11: chart: incubator/raw +12: namespace: kube-system +13: needs: +14: - kube-system/logging +15: +16: - name: external-secrets +17: chart: incubator/raw +18: namespace: default +19: labels: +20: app: test +21: needs: +22: - kube-system/kubernetes-external-secrets +23: +24: - name: my-release +25: chart: incubator/raw +26: namespace: default +27: labels: +28: app: test +29: needs: +30: - default/external-secrets +31: +32: +33: # Disabled releases are treated as missing +34: - name: disabled +35: chart: incubator/raw +36: namespace: kube-system +37: installed: false +38: +39: - name: test2 +40: chart: incubator/raw +41: needs: +42: - kube-system/disabled +43: +44: - name: test3 +45: chart: incubator/raw +46: needs: +47: - test2 +48: + +merged environment: &{development map[] map[]} +changing working directory back to "/path/to" +processing file "helmfile_2.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_2.yaml.part.0": inherited=&{development map[] map[]}, overrode= +first-pass uses: &{development map[] map[]} +first-pass rendering output of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +first-pass produced: &{development map[] map[]} +first-pass rendering result of "helmfile_2.yaml.part.0": {development map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_2.yaml.part.0": + 0: + 1: environments: + 2: test: {} + 3: shared: {} + 4: + 5: repositories: + 6: - name: bitnami + 7: url: https://charts.bitnami.com/bitnami + 8: + 9: releases: +10: - name: cache +11: namespace: my-app +12: chart: bitnami/redis +13: version: 17.0.7 +14: labels: +15: app: test +16: +17: - name: database +18: namespace: my-app +19: chart: bitnami/postgres +20: version: 11.6.22 +21: + +changing working directory back to "/path/to" +processing file "helmfile_3.yaml" in directory "/path/to/helmfile.d" +changing working directory to "/path/to/helmfile.d" +first-pass rendering starting for "helmfile_3.yaml.part.0": inherited=&{development map[] map[]}, overrode= +first-pass uses: &{development map[] map[]} +first-pass rendering output of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +first-pass produced: &{development map[] map[]} +first-pass rendering result of "helmfile_3.yaml.part.0": {development map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile_3.yaml.part.0": + 0: + 1: releases: + 2: - name: global + 3: chart: incubator/raw + 4: namespace: kube-system + 5: + +changing working directory back to "/path/to" diff --git a/pkg/config/config.go b/pkg/config/config.go index 58031f36..83f0492d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,8 +1,6 @@ package config import ( - "fmt" - "os" "strings" "github.com/urfave/cli" @@ -13,46 +11,7 @@ import ( "github.com/helmfile/helmfile/pkg/state" ) -// nolint: golint -type ConfigImpl struct { - c *cli.Context - - set map[string]interface{} -} - -func NewUrfaveCliConfigImpl(c *cli.Context) (ConfigImpl, error) { - if c.NArg() > 0 { - err := cli.ShowAppHelp(c) - if err != nil { - return ConfigImpl{}, err - } - return ConfigImpl{}, fmt.Errorf("err: extraneous arguments: %s", strings.Join(c.Args(), ", ")) - } - - conf := ConfigImpl{ - c: c, - } - - optsSet := c.GlobalStringSlice("state-values-set") - if len(optsSet) > 0 { - set := map[string]interface{}{} - for i := range optsSet { - ops := strings.Split(optsSet[i], ",") - for j := range ops { - op := strings.SplitN(ops[j], "=", 2) - k := maputil.ParseKey(op[0]) - v := op[1] - - maputil.Set(set, k, v) - } - } - conf.set = set - } - - return conf, nil -} - -func NewUrfaveCliConfigImplIns(g *GlobalImpl) error { +func NewCLIConfigImpl(g *GlobalImpl) error { optsSet := g.RawStateValuesSet() if len(optsSet) > 0 { set := map[string]interface{}{} @@ -71,249 +30,3 @@ func NewUrfaveCliConfigImplIns(g *GlobalImpl) error { return nil } - -func (c ConfigImpl) Set() []string { - return c.c.StringSlice("set") -} - -func (c ConfigImpl) SkipRepos() bool { - return c.c.Bool("skip-repos") -} - -func (c ConfigImpl) Wait() bool { - return c.c.Bool("wait") -} - -func (c ConfigImpl) WaitForJobs() bool { - return c.c.Bool("wait-for-jobs") -} - -func (c ConfigImpl) Values() []string { - return c.c.StringSlice("values") -} - -func (c ConfigImpl) Args() string { - args := c.c.String("args") - enableHelmDebug := c.c.GlobalBool("debug") - - if enableHelmDebug { - args = fmt.Sprintf("%s %s", args, "--debug") - } - return args -} - -func (c ConfigImpl) OutputDir() string { - return strings.TrimRight(c.c.String("output-dir"), fmt.Sprintf("%c", os.PathSeparator)) -} - -func (c ConfigImpl) OutputDirTemplate() string { - return c.c.String("output-dir-template") -} - -func (c ConfigImpl) OutputFileTemplate() string { - return c.c.String("output-file-template") -} - -func (c ConfigImpl) Validate() bool { - return c.c.Bool("validate") -} - -func (c ConfigImpl) Concurrency() int { - return c.c.Int("concurrency") -} - -func (c ConfigImpl) HasCommandName(name string) bool { - return c.c.Command.HasName(name) -} - -func (c ConfigImpl) SkipNeeds() bool { - if !c.IncludeNeeds() { - return c.c.Bool("skip-needs") - } - - return false -} - -func (c ConfigImpl) IncludeNeeds() bool { - return c.c.Bool("include-needs") || c.IncludeTransitiveNeeds() -} - -func (c ConfigImpl) IncludeTransitiveNeeds() bool { - return c.c.Bool("include-transitive-needs") -} - -// DiffConfig - -func (c ConfigImpl) SkipDeps() bool { - return c.c.Bool("skip-deps") -} - -func (c ConfigImpl) DetailedExitcode() bool { - return c.c.Bool("detailed-exitcode") -} - -func (c ConfigImpl) RetainValuesFiles() bool { - return c.c.Bool("retain-values-files") -} - -func (c ConfigImpl) IncludeTests() bool { - return c.c.Bool("include-tests") -} - -func (c ConfigImpl) Suppress() []string { - return c.c.StringSlice("suppress") -} - -func (c ConfigImpl) SuppressSecrets() bool { - return c.c.Bool("suppress-secrets") -} - -func (c ConfigImpl) ShowSecrets() bool { - return c.c.Bool("show-secrets") -} - -func (c ConfigImpl) SuppressDiff() bool { - return c.c.Bool("suppress-diff") -} - -// DeleteConfig - -func (c ConfigImpl) Purge() bool { - return c.c.Bool("purge") -} - -// TestConfig - -func (c ConfigImpl) Cleanup() bool { - return c.c.Bool("cleanup") -} - -func (c ConfigImpl) Logs() bool { - return c.c.Bool("logs") -} - -func (c ConfigImpl) Timeout() int { - if !c.c.IsSet("timeout") { - return state.EmptyTimeout - } - return c.c.Int("timeout") -} - -// ListConfig - -func (c ConfigImpl) Output() string { - return c.c.String("output") -} - -func (c ConfigImpl) KeepTempDir() bool { - return c.c.Bool("keep-temp-dir") -} - -// GlobalConfig - -func (c ConfigImpl) HelmBinary() string { - return c.c.GlobalString("helm-binary") -} - -func (c ConfigImpl) KubeContext() string { - return c.c.GlobalString("kube-context") -} - -func (c ConfigImpl) Namespace() string { - return c.c.GlobalString("namespace") -} - -func (c ConfigImpl) Chart() string { - return c.c.GlobalString("chart") -} - -func (c ConfigImpl) FileOrDir() string { - return c.c.GlobalString("file") -} - -func (c ConfigImpl) Selectors() []string { - return c.c.GlobalStringSlice("selector") -} - -func (c ConfigImpl) StateValuesSet() map[string]interface{} { - return c.set -} - -func (c ConfigImpl) StateValuesFiles() []string { - return c.c.GlobalStringSlice("state-values-file") -} - -func (c ConfigImpl) Interactive() bool { - return c.c.GlobalBool("interactive") -} - -func (c ConfigImpl) Color() bool { - if c := c.c.GlobalBool("color"); c { - return c - } - - if c.NoColor() { - return false - } - - // We replicate the helm-diff behavior in helmfile - // because when when helmfile calls helm-diff, helm-diff has no access to term and therefore - // we can't rely on helm-diff's ability to auto-detect term for color output. - // See https://github.com/roboll/helmfile/issues/2043 - - terminal := term.IsTerminal(int(os.Stdout.Fd())) - // https://github.com/databus23/helm-diff/issues/281 - dumb := os.Getenv("TERM") == "dumb" - return terminal && !dumb -} - -func (c ConfigImpl) NoColor() bool { - return c.c.GlobalBool("no-color") -} - -func (c ConfigImpl) Context() int { - return c.c.Int("context") -} - -func (c ConfigImpl) DiffOutput() string { - return c.c.String("output") -} - -func (c ConfigImpl) SkipCleanup() bool { - return c.c.Bool("skip-cleanup") -} - -func (c ConfigImpl) SkipCRDs() bool { - return c.c.Bool("skip-crds") -} - -func (c ConfigImpl) SkipDiffOnInstall() bool { - return c.c.Bool("skip-diff-on-install") -} - -func (c ConfigImpl) EmbedValues() bool { - return c.c.Bool("embed-values") -} - -func (c ConfigImpl) IncludeCRDs() bool { - return c.c.Bool("include-crds") -} - -func (c ConfigImpl) SkipTests() bool { - return c.c.Bool("skip-tests") -} - -func (c ConfigImpl) Logger() *zap.SugaredLogger { - return c.c.App.Metadata["logger"].(*zap.SugaredLogger) -} - -func (c ConfigImpl) Env() string { - env := c.c.GlobalString("environment") - if env == "" { - env = os.Getenv("HELMFILE_ENVIRONMENT") - if env == "" { - env = state.DefaultEnv - } - } - return env -} diff --git a/pkg/state/storage.go b/pkg/state/storage.go index df52b8bd..321ad1d2 100644 --- a/pkg/state/storage.go +++ b/pkg/state/storage.go @@ -38,8 +38,14 @@ func (st *Storage) resolveFile(missingFileHandler *string, tpe, path string) ([] if remote.IsRemote(path) { r := remote.NewRemote(st.logger, "", st.readFile, directoryExistsAt, fileExistsAt) - fetchedDir, _ := r.Fetch(path, "values") - files = []string{fetchedDir} + fetchedFilePath, err := r.Fetch(path, "values") + if err != nil { + return nil, false, err + } + + if fileExistsAt(fetchedFilePath) { + files = []string{fetchedFilePath} + } } else { files, err = st.ExpandPaths(path) } diff --git a/pkg/state/storage_test.go b/pkg/state/storage_test.go new file mode 100644 index 00000000..225e1e54 --- /dev/null +++ b/pkg/state/storage_test.go @@ -0,0 +1,91 @@ +package state + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "testing" + + "github.com/helmfile/helmfile/pkg/helmexec" + "github.com/helmfile/helmfile/pkg/remote" +) + +func TestStorage_resolveFile(t *testing.T) { + type args struct { + missingFileHandler *string + title string + path string + } + + cacheDir := remote.CacheDir() + infoHandler := MissingFileHandlerInfo + errorHandler := MissingFileHandlerError + + tests := []struct { + name string + args args + wantFiles []string + wantSkipped bool + wantErr bool + }{ + { + name: "non existing file in repo produce skip", + args: args{ + path: "git::https://github.com/helmfile/helmfile.git@examples/values/non-existing-file.yaml?ref=v0.145.2", + title: "values", + missingFileHandler: &infoHandler, + }, + wantSkipped: true, + wantErr: false, + }, + { + name: "non existing file in repo produce skip", + args: args{ + path: "git::https://github.com/helmfile/helmfile.git@examples/values/non-existing-file.yaml?ref=v0.145.2", + title: "values", + missingFileHandler: &errorHandler, + }, + wantSkipped: false, + wantErr: true, + }, + { + name: "existing remote value fetched", + args: args{ + path: "git::https://github.com/helmfile/helmfile.git@examples/values/replica-values.yaml?ref=v0.145.2", + title: "values", + missingFileHandler: &infoHandler, + }, + wantFiles: []string{fmt.Sprintf("%s/%s", cacheDir, "values/https_github_com_helmfile_helmfile_git.ref=v0.145.2/examples/values/replica-values.yaml")}, + wantSkipped: false, + wantErr: false, + }, + { + name: "non existing remote repo produce an error", + args: args{ + path: "https://github.com/helmfile/helmfiles.git@examples/values/replica-values.yaml?ref=v0.145.2", + title: "values", + missingFileHandler: &infoHandler, + }, + wantSkipped: false, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := NewStorage(cacheDir, helmexec.NewLogger(os.Stderr, "debug"), filepath.Glob) + + files, skipped, err := st.resolveFile(tt.args.missingFileHandler, tt.args.title, tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("resolveFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(files, tt.wantFiles) { + t.Errorf("resolveFile() files = %v, want %v", files, tt.wantFiles) + } + if skipped != tt.wantSkipped { + t.Errorf("resolveFile() skipped = %v, want %v", skipped, tt.wantSkipped) + } + }) + } +}