diff --git a/cmd/helmExecute.go b/cmd/helmExecute.go index d66999609..00a9b258e 100644 --- a/cmd/helmExecute.go +++ b/cmd/helmExecute.go @@ -17,7 +17,8 @@ func helmExecute(config helmExecuteOptions, telemetryData *telemetry.CustomData) KubeConfig: config.KubeConfig, HelmDeployWaitSeconds: config.HelmDeployWaitSeconds, AppVersion: config.AppVersion, - DependencyUpdate: config.DependencyUpdate, + Dependency: config.Dependency, + PackageDependencyUpdate: config.PackageDependencyUpdate, HelmValues: config.HelmValues, FilterTest: config.FilterTest, DumpLogs: config.DumpLogs, @@ -69,9 +70,9 @@ func runHelmExecute(config helmExecuteOptions, helmExecutor kubernetes.HelmExecu if err := helmExecutor.RunHelmUninstall(); err != nil { return fmt.Errorf("failed to execute helm uninstall: %v", err) } - case "package": - if err := helmExecutor.RunHelmPackage(); err != nil { - return fmt.Errorf("failed to execute helm package: %v", err) + case "dependency": + if err := helmExecutor.RunHelmDependency(); err != nil { + return fmt.Errorf("failed to execute helm dependency: %v", err) } case "publish": if err := helmExecutor.RunHelmPublish(); err != nil { @@ -91,8 +92,10 @@ func runHelmExecuteDefault(config helmExecuteOptions, helmExecutor kubernetes.He return fmt.Errorf("failed to execute helm lint: %v", err) } - if err := helmExecutor.RunHelmPackage(); err != nil { - return fmt.Errorf("failed to execute helm package: %v", err) + if len(config.Dependency) > 0 { + if err := helmExecutor.RunHelmDependency(); err != nil { + return fmt.Errorf("failed to execute helm dependency: %v", err) + } } if config.Publish { diff --git a/cmd/helmExecute_generated.go b/cmd/helmExecute_generated.go index 78fbad947..cebfae5c5 100644 --- a/cmd/helmExecute_generated.go +++ b/cmd/helmExecute_generated.go @@ -30,9 +30,10 @@ type helmExecuteOptions struct { KubeContext string `json:"kubeContext,omitempty"` Namespace string `json:"namespace,omitempty"` DockerConfigJSON string `json:"dockerConfigJSON,omitempty"` - HelmCommand string `json:"helmCommand,omitempty" validate:"possible-values=upgrade install lint test uninstall package publish"` + HelmCommand string `json:"helmCommand,omitempty" validate:"possible-values=upgrade lint install test uninstall dependency publish"` AppVersion string `json:"appVersion,omitempty"` - DependencyUpdate bool `json:"dependencyUpdate,omitempty"` + Dependency string `json:"dependency,omitempty" validate:"possible-values=build list update"` + PackageDependencyUpdate bool `json:"packageDependencyUpdate,omitempty"` DumpLogs bool `json:"dumpLogs,omitempty"` FilterTest string `json:"filterTest,omitempty"` CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"` @@ -62,15 +63,15 @@ Executes helm functionality as the package manager for Kubernetes. * [Helm Charts] (https://artifacthub.io/) ` + "`" + `` + "`" + `` + "`" + ` Available Commands: - install install a chart - lint examine a chart for possible issues - package package a chart directory into a chart archive - repo add, list, remove, update, and index chart repositories - test run tests for a release - uninstall uninstall a release - upgrade upgrade a release - verify verify that a chart at the given path has been signed and is valid - push upload a chart to a registry +` + "`" + `upgrade` + "`" + `, ` + "`" + `lint` + "`" + `, ` + "`" + `install` + "`" + `, ` + "`" + `test` + "`" + `, ` + "`" + `uninstall` + "`" + `, ` + "`" + `dependency` + "`" + `, ` + "`" + `publish` + "`" + ` + + upgrade upgrade a release + lint examine a chart for possible issues + install install a chart + test run tests for a release + uninstall uninstall a release + dependency package a chart directory into a chart archive + publish package and puslish a release ` + "`" + `` + "`" + `` + "`" + ` @@ -167,9 +168,10 @@ func addHelmExecuteFlags(cmd *cobra.Command, stepConfig *helmExecuteOptions) { cmd.Flags().StringVar(&stepConfig.KubeContext, "kubeContext", os.Getenv("PIPER_kubeContext"), "Defines the context to use from the \"kubeconfig\" file.") cmd.Flags().StringVar(&stepConfig.Namespace, "namespace", `default`, "Defines the target Kubernetes namespace for the deployment.") cmd.Flags().StringVar(&stepConfig.DockerConfigJSON, "dockerConfigJSON", os.Getenv("PIPER_dockerConfigJSON"), "Path to the file `.docker/config.json` - this is typically provided by your CI/CD system. You can find more details about the Docker credentials in the [Docker documentation](https://docs.docker.com/engine/reference/commandline/login/).") - cmd.Flags().StringVar(&stepConfig.HelmCommand, "helmCommand", os.Getenv("PIPER_helmCommand"), "Helm: defines the command `install`, `lint`, `package`, `test`, `upgrade` and etc.") + cmd.Flags().StringVar(&stepConfig.HelmCommand, "helmCommand", os.Getenv("PIPER_helmCommand"), "Helm: defines the command `upgrade`, `lint`, `install`, `test`, `uninstall`, `dependency`, `publish`.") cmd.Flags().StringVar(&stepConfig.AppVersion, "appVersion", os.Getenv("PIPER_appVersion"), "set the appVersion on the chart to this version") - cmd.Flags().BoolVar(&stepConfig.DependencyUpdate, "dependencyUpdate", false, "set the appVersion on the chart to this version") + cmd.Flags().StringVar(&stepConfig.Dependency, "dependency", os.Getenv("PIPER_dependency"), "manage a chart's dependencies") + cmd.Flags().BoolVar(&stepConfig.PackageDependencyUpdate, "packageDependencyUpdate", false, "update dependencies from \"Chart.yaml\" to dir \"charts/\" before packaging") cmd.Flags().BoolVar(&stepConfig.DumpLogs, "dumpLogs", false, "dump the logs from test pods (this runs after all tests are complete, but before any cleanup)") cmd.Flags().StringVar(&stepConfig.FilterTest, "filterTest", os.Getenv("PIPER_filterTest"), "specify tests by attribute (currently `name`) using attribute=value syntax or `!attribute=value` to exclude a test (can specify multiple or separate values with commas `name=test1,name=test2`)") cmd.Flags().StringSliceVar(&stepConfig.CustomTLSCertificateLinks, "customTlsCertificateLinks", []string{}, "List of download links to custom TLS certificates. This is required to ensure trusted connections to instances with repositories (like nexus) when publish flag is set to true.") @@ -399,7 +401,16 @@ func helmExecuteMetadata() config.StepData { Default: os.Getenv("PIPER_appVersion"), }, { - Name: "dependencyUpdate", + Name: "dependency", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "string", + Mandatory: false, + Aliases: []config.Alias{}, + Default: os.Getenv("PIPER_dependency"), + }, + { + Name: "packageDependencyUpdate", ResourceRef: []config.ResourceReference{}, Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"}, Type: "bool", diff --git a/cmd/helmExecute_test.go b/cmd/helmExecute_test.go index a475b47e7..012740fe6 100644 --- a/cmd/helmExecute_test.go +++ b/cmd/helmExecute_test.go @@ -196,7 +196,7 @@ func TestRunHelmUninstall(t *testing.T) { } } -func TestRunHelmPackage(t *testing.T) { +func TestRunHelmDependency(t *testing.T) { t.Parallel() testTable := []struct { @@ -206,23 +206,23 @@ func TestRunHelmPackage(t *testing.T) { }{ { config: helmExecuteOptions{ - HelmCommand: "package", + HelmCommand: "dependency", }, methodError: nil, }, { config: helmExecuteOptions{ - HelmCommand: "package", + HelmCommand: "dependency", }, methodError: errors.New("some error"), - expectedErrStr: "failed to execute helm package: some error", + expectedErrStr: "failed to execute helm dependency: some error", }, } for i, testCase := range testTable { t.Run(fmt.Sprint("case ", i), func(t *testing.T) { helmExecute := &mocks.HelmExecutor{} - helmExecute.On("RunHelmPackage").Return(testCase.methodError) + helmExecute.On("RunHelmDependency").Return(testCase.methodError) err := runHelmExecute(testCase.config, helmExecute) if err != nil { @@ -300,7 +300,7 @@ func TestRunHelmDefaultCommand(t *testing.T) { HelmCommand: "", }, methodPackageError: errors.New("some error"), - expectedErrStr: "failed to execute helm package: some error", + expectedErrStr: "failed to execute helm dependency: some error", }, { config: helmExecuteOptions{ @@ -315,7 +315,7 @@ func TestRunHelmDefaultCommand(t *testing.T) { t.Run(fmt.Sprint("case ", i), func(t *testing.T) { helmExecute := &mocks.HelmExecutor{} helmExecute.On("RunHelmLint").Return(testCase.methodLintError) - helmExecute.On("RunHelmPackage").Return(testCase.methodPackageError) + helmExecute.On("RunHelmDependency").Return(testCase.methodPackageError) helmExecute.On("RunHelmPublish").Return(testCase.methodPublishError) err := runHelmExecute(testCase.config, helmExecute) diff --git a/pkg/kubernetes/helm.go b/pkg/kubernetes/helm.go index deae5f1cb..e75a10050 100644 --- a/pkg/kubernetes/helm.go +++ b/pkg/kubernetes/helm.go @@ -16,9 +16,9 @@ type HelmExecutor interface { RunHelmLint() error RunHelmInstall() error RunHelmUninstall() error - RunHelmPackage() error RunHelmTest() error RunHelmPublish() error + RunHelmDependency() error } // HelmExecute struct @@ -45,7 +45,8 @@ type HelmExecuteOptions struct { DockerConfigJSON string `json:"dockerConfigJSON,omitempty"` PackageVersion string `json:"packageVersion,omitempty"` AppVersion string `json:"appVersion,omitempty"` - DependencyUpdate bool `json:"dependencyUpdate,omitempty"` + Dependency string `json:"dependency,omitempty" validate:"possible-values=build list update"` + PackageDependencyUpdate bool `json:"packageDependencyUpdate,omitempty"` DumpLogs bool `json:"dumpLogs,omitempty"` FilterTest string `json:"filterTest,omitempty"` TargetRepositoryURL string `json:"targetRepositoryURL,omitempty"` @@ -265,7 +266,7 @@ func (h *HelmExecute) RunHelmUninstall() error { } // RunHelmPackage is used to package a chart directory into a chart archive -func (h *HelmExecute) RunHelmPackage() error { +func (h *HelmExecute) runHelmPackage() error { err := h.runHelmInit() if err != nil { return fmt.Errorf("failed to execute deployments: %v", err) @@ -278,7 +279,7 @@ func (h *HelmExecute) RunHelmPackage() error { if len(h.config.PackageVersion) > 0 { helmParams = append(helmParams, "--version", h.config.PackageVersion) } - if h.config.DependencyUpdate { + if h.config.PackageDependencyUpdate { helmParams = append(helmParams, "--dependency-update") } if len(h.config.AppVersion) > 0 { @@ -323,6 +324,31 @@ func (h *HelmExecute) RunHelmTest() error { return nil } +// RunHelmDependency is used to manage a chart's dependencies +func (h *HelmExecute) RunHelmDependency() error { + if len(h.config.Dependency) == 0 { + return fmt.Errorf("there is no dependency value. Possible values are build, list, update") + } + + helmParams := []string{ + "dependency", + } + + helmParams = append(helmParams, h.config.Dependency) + + helmParams = append(helmParams, h.config.ChartPath) + + if len(h.config.AdditionalParameters) > 0 { + helmParams = append(helmParams, h.config.AdditionalParameters...) + } + + if err := h.runHelmCommand(helmParams); err != nil { + log.Entry().WithError(err).Fatal("Helm dependency call failed") + } + + return nil +} + //RunHelmPublish is used to upload a chart to a registry func (h *HelmExecute) RunHelmPublish() error { err := h.runHelmInit() @@ -330,6 +356,11 @@ func (h *HelmExecute) RunHelmPublish() error { return fmt.Errorf("failed to execute deployments: %v", err) } + err = h.runHelmPackage() + if err != nil { + return fmt.Errorf("failed to execute deployments: %v", err) + } + if len(h.config.TargetRepositoryURL) == 0 { return fmt.Errorf("there's no target repository for helm chart publishing configured") } diff --git a/pkg/kubernetes/helm_test.go b/pkg/kubernetes/helm_test.go index 2083e691c..6834765f2 100644 --- a/pkg/kubernetes/helm_test.go +++ b/pkg/kubernetes/helm_test.go @@ -241,11 +241,11 @@ func TestRunHelm(t *testing.T) { }, { config: HelmExecuteOptions{ - ChartPath: ".", - DeploymentName: "testPackage", - PackageVersion: "1.2.3", - DependencyUpdate: true, - AppVersion: "9.8.7", + ChartPath: ".", + DeploymentName: "testPackage", + PackageVersion: "1.2.3", + PackageDependencyUpdate: true, + AppVersion: "9.8.7", }, expectedConfig: []string{"package", ".", "--version", "1.2.3", "--dependency-update", "--app-version", "9.8.7"}, }, @@ -258,7 +258,7 @@ func TestRunHelm(t *testing.T) { verbose: false, stdout: log.Writer(), } - err := helmExecute.RunHelmPackage() + err := helmExecute.runHelmPackage() assert.NoError(t, err) assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedConfig}, utils.Calls[i]) } @@ -373,6 +373,50 @@ func TestRunHelm(t *testing.T) { } }) + t.Run("Helm dependency command", func(t *testing.T) { + utils := newHelmMockUtilsBundle() + + testTable := []struct { + config HelmExecuteOptions + expectedError error + expectedResult []string + }{ + { + config: HelmExecuteOptions{ + ChartPath: ".", + }, + expectedError: errors.New("there is no dependency value. Possible values are build, list, update"), + expectedResult: nil, + }, + { + config: HelmExecuteOptions{ + ChartPath: ".", + Dependency: "update", + }, + expectedError: nil, + expectedResult: []string{"dependency", "update", "."}, + }, + } + + for _, testCase := range testTable { + helmExecute := HelmExecute{ + utils: utils, + config: testCase.config, + verbose: false, + stdout: log.Writer(), + } + err := helmExecute.RunHelmDependency() + if testCase.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, testCase.expectedError, err) + } else { + assert.NoError(t, err) + assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedResult}, utils.Calls[0]) + } + + } + }) + t.Run("Helm publish command", func(t *testing.T) { utils := newHelmMockUtilsBundle() diff --git a/pkg/kubernetes/mocks/HelmExecutor.go b/pkg/kubernetes/mocks/HelmExecutor.go index 71e46ae54..5c88b6489 100644 --- a/pkg/kubernetes/mocks/HelmExecutor.go +++ b/pkg/kubernetes/mocks/HelmExecutor.go @@ -9,8 +9,8 @@ type HelmExecutor struct { mock.Mock } -// RunHelmAdd provides a mock function with given fields: -func (_m *HelmExecutor) RunHelmAdd() error { +// RunHelmDependency provides a mock function with given fields: +func (_m *HelmExecutor) RunHelmDependency() error { ret := _m.Called() var r0 error @@ -51,20 +51,6 @@ func (_m *HelmExecutor) RunHelmLint() error { return r0 } -// RunHelmPackage provides a mock function with given fields: -func (_m *HelmExecutor) RunHelmPackage() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - // RunHelmPublish provides a mock function with given fields: func (_m *HelmExecutor) RunHelmPublish() error { ret := _m.Called() diff --git a/resources/metadata/helmExecute.yaml b/resources/metadata/helmExecute.yaml index b98551efc..5b22de03e 100644 --- a/resources/metadata/helmExecute.yaml +++ b/resources/metadata/helmExecute.yaml @@ -11,15 +11,15 @@ metadata: * [Helm Charts] (https://artifacthub.io/) ``` Available Commands: - install install a chart - lint examine a chart for possible issues - package package a chart directory into a chart archive - repo add, list, remove, update, and index chart repositories - test run tests for a release - uninstall uninstall a release - upgrade upgrade a release - verify verify that a chart at the given path has been signed and is valid - push upload a chart to a registry + `upgrade`, `lint`, `install`, `test`, `uninstall`, `dependency`, `publish` + + upgrade upgrade a release + lint examine a chart for possible issues + install install a chart + test run tests for a release + uninstall uninstall a release + dependency package a chart directory into a chart archive + publish package and puslish a release ``` @@ -189,18 +189,18 @@ spec: default: docker-config - name: helmCommand type: string - description: "Helm: defines the command `install`, `lint`, `package`, `test`, `upgrade` and etc." + description: "Helm: defines the command `upgrade`, `lint`, `install`, `test`, `uninstall`, `dependency`, `publish`." scope: - PARAMETERS - STAGES - STEPS possibleValues: - upgrade - - install - lint + - install - test - uninstall - - package + - dependency - publish - name: appVersion type: string @@ -210,9 +210,20 @@ spec: - PARAMETERS - STAGES - STEPS - - name: dependencyUpdate + - name: dependency + type: string + description: "manage a chart's dependencies" + scope: + - PARAMETERS + - STAGES + - STEPS + possibleValues: + - build + - list + - update + - name: packageDependencyUpdate type: bool - description: set the appVersion on the chart to this version + description: update dependencies from "Chart.yaml" to dir "charts/" before packaging default: false scope: - GENERAL