1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-02-19 19:44:27 +02:00

Fix helm execute command (#3701)

* Add small fix

* fix unit-tests

* Add deploymentName and packageVersion as flags

* small fix

* Change getting version of helm chart

* small fix

Co-authored-by: “Vitalii <“vitalii.sidorov@sap.com”>
Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Vitalii Sidorov 2022-04-14 17:43:47 +04:00 committed by GitHub
parent d519966fe5
commit 63cdfc0e68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 35 deletions

View File

@ -6,6 +6,7 @@ import (
"github.com/SAP/jenkins-library/pkg/kubernetes"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/SAP/jenkins-library/pkg/telemetry"
"github.com/SAP/jenkins-library/pkg/versioning"
)
func helmExecute(config helmExecuteOptions, telemetryData *telemetry.CustomData) {
@ -28,17 +29,27 @@ func helmExecute(config helmExecuteOptions, telemetryData *telemetry.CustomData)
TargetRepositoryPassword: config.TargetRepositoryPassword,
HelmCommand: config.HelmCommand,
CustomTLSCertificateLinks: config.CustomTLSCertificateLinks,
// ArtifactVersion: config.Version,
Version: config.Version,
}
utils := kubernetes.NewDeployUtilsBundle(helmConfig.CustomTLSCertificateLinks)
helmChart := config.ChartPath + "Chart.yaml"
nameChart, packageVersion, err := kubernetes.GetChartInfo(helmChart, utils)
if err != nil {
log.Entry().WithError(err).Fatalf("failed to get version in Chart.yaml: %v", err)
artifactOpts := versioning.Options{
VersioningScheme: "library",
}
artifact, err := versioning.GetArtifact("helm", "", &artifactOpts, utils)
if err != nil {
log.Entry().WithError(err).Fatalf("getting artifact information failed: %v", err)
}
artifactInfo, err := artifact.GetCoordinates()
helmConfig.DeploymentName = artifactInfo.ArtifactID
if len(config.Version) == 0 {
helmConfig.Version = artifactInfo.Version
}
helmConfig.DeploymentName = nameChart
helmConfig.PackageVersion = packageVersion
helmExecutor := kubernetes.NewHelmExecutor(helmConfig, utils, GeneralConfig.Verbose, log.Writer())

View File

@ -19,7 +19,7 @@ type helmExecuteOptions struct {
AdditionalParameters []string `json:"additionalParameters,omitempty"`
ChartPath string `json:"chartPath,omitempty"`
TargetRepositoryURL string `json:"targetRepositoryURL,omitempty"`
TargetRepositoryName string `json:"targetRepositoryName,omitempty" validate:"required_if=HelmCommand install"`
TargetRepositoryName string `json:"targetRepositoryName,omitempty"`
TargetRepositoryUser string `json:"targetRepositoryUser,omitempty"`
TargetRepositoryPassword string `json:"targetRepositoryPassword,omitempty"`
HelmDeployWaitSeconds int `json:"helmDeployWaitSeconds,omitempty"`
@ -38,6 +38,7 @@ type helmExecuteOptions struct {
FilterTest string `json:"filterTest,omitempty"`
CustomTLSCertificateLinks []string `json:"customTlsCertificateLinks,omitempty"`
Publish bool `json:"publish,omitempty"`
Version string `json:"version,omitempty"`
}
// HelmExecuteCommand Executes helm3 functionality as the package manager for Kubernetes.
@ -155,9 +156,9 @@ Note: piper supports only helm3 version, since helm2 is deprecated.`,
func addHelmExecuteFlags(cmd *cobra.Command, stepConfig *helmExecuteOptions) {
cmd.Flags().StringSliceVar(&stepConfig.AdditionalParameters, "additionalParameters", []string{}, "Defines additional parameters for Helm like \"helm install [NAME] [CHART] [flags]\".")
cmd.Flags().StringVar(&stepConfig.ChartPath, "chartPath", os.Getenv("PIPER_chartPath"), "Defines the chart path for helm.")
cmd.Flags().StringVar(&stepConfig.ChartPath, "chartPath", os.Getenv("PIPER_chartPath"), "Defines the chart path for helm. chartPath is mandatory for install/upgrade/publish commands.")
cmd.Flags().StringVar(&stepConfig.TargetRepositoryURL, "targetRepositoryURL", os.Getenv("PIPER_targetRepositoryURL"), "URL of the target repository where the compiled helm .tgz archive shall be uploaded - typically provided by the CI/CD environment.")
cmd.Flags().StringVar(&stepConfig.TargetRepositoryName, "targetRepositoryName", os.Getenv("PIPER_targetRepositoryName"), "set the chart repository")
cmd.Flags().StringVar(&stepConfig.TargetRepositoryName, "targetRepositoryName", os.Getenv("PIPER_targetRepositoryName"), "set the chart repository. The value is required for install/upgrade/uninstall commands.")
cmd.Flags().StringVar(&stepConfig.TargetRepositoryUser, "targetRepositoryUser", os.Getenv("PIPER_targetRepositoryUser"), "Username for the char repository where the compiled helm .tgz archive shall be uploaded - typically provided by the CI/CD environment.")
cmd.Flags().StringVar(&stepConfig.TargetRepositoryPassword, "targetRepositoryPassword", os.Getenv("PIPER_targetRepositoryPassword"), "Password for the target repository where the compiled helm .tgz archive shall be uploaded - typically provided by the CI/CD environment.")
cmd.Flags().IntVar(&stepConfig.HelmDeployWaitSeconds, "helmDeployWaitSeconds", 300, "Number of seconds before helm deploy returns.")
@ -176,8 +177,8 @@ func addHelmExecuteFlags(cmd *cobra.Command, stepConfig *helmExecuteOptions) {
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.")
cmd.Flags().BoolVar(&stepConfig.Publish, "publish", false, "Configures helm to run the deploy command to publish artifacts to a repository.")
cmd.Flags().StringVar(&stepConfig.Version, "version", os.Getenv("PIPER_version"), "Defines the artifact version to use from helm package/publish commands.")
cmd.MarkFlagRequired("chartPath")
cmd.MarkFlagRequired("image")
}
@ -213,7 +214,7 @@ func helmExecuteMetadata() config.StepData {
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Mandatory: false,
Aliases: []config.Alias{{Name: "helmChartPath"}},
Default: os.Getenv("PIPER_chartPath"),
},
@ -454,6 +455,20 @@ func helmExecuteMetadata() config.StepData {
Aliases: []config.Alias{},
Default: false,
},
{
Name: "version",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "artifactVersion",
},
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
Default: os.Getenv("PIPER_version"),
},
},
},
Containers: []config.Container{

View File

@ -43,7 +43,7 @@ type HelmExecuteOptions struct {
KubeContext string `json:"kubeContext,omitempty"`
Namespace string `json:"namespace,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
PackageVersion string `json:"packageVersion,omitempty"`
Version string `json:"version,omitempty"`
AppVersion string `json:"appVersion,omitempty"`
Dependency string `json:"dependency,omitempty" validate:"possible-values=build list update"`
PackageDependencyUpdate bool `json:"packageDependencyUpdate,omitempty"`
@ -110,11 +110,19 @@ func (h *HelmExecute) runHelmAdd() error {
// RunHelmUpgrade is used to upgrade a release
func (h *HelmExecute) RunHelmUpgrade() error {
if len(h.config.ChartPath) == 0 {
return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
}
err := h.runHelmInit()
if err != nil {
return fmt.Errorf("failed to execute deployments: %v", err)
}
if err := h.runHelmAdd(); err != nil {
return fmt.Errorf("failed to execute deployments: %v", err)
}
helmParams := []string{
"upgrade",
h.config.DeploymentName,
@ -184,6 +192,10 @@ func (h *HelmExecute) RunHelmLint() error {
// RunHelmInstall is used to install a chart
func (h *HelmExecute) RunHelmInstall() error {
if len(h.config.ChartPath) == 0 {
return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
}
if err := h.runHelmInit(); err != nil {
return fmt.Errorf("failed to execute deployments: %v", err)
}
@ -235,6 +247,10 @@ func (h *HelmExecute) RunHelmUninstall() error {
return fmt.Errorf("failed to execute deployments: %v", err)
}
if err := h.runHelmAdd(); err != nil {
return fmt.Errorf("failed to execute deployments: %v", err)
}
helmParams := []string{
"uninstall",
h.config.DeploymentName,
@ -267,6 +283,10 @@ func (h *HelmExecute) RunHelmUninstall() error {
// RunHelmPackage is used to package a chart directory into a chart archive
func (h *HelmExecute) runHelmPackage() error {
if len(h.config.ChartPath) == 0 {
return fmt.Errorf("there is no ChartPath value. The chartPath value is mandatory")
}
err := h.runHelmInit()
if err != nil {
return fmt.Errorf("failed to execute deployments: %v", err)
@ -276,8 +296,8 @@ func (h *HelmExecute) runHelmPackage() error {
"package",
h.config.ChartPath,
}
if len(h.config.PackageVersion) > 0 {
helmParams = append(helmParams, "--version", h.config.PackageVersion)
if len(h.config.Version) > 0 {
helmParams = append(helmParams, "--version", h.config.Version)
}
if h.config.PackageDependencyUpdate {
helmParams = append(helmParams, "--dependency-update")
@ -373,7 +393,7 @@ func (h *HelmExecute) RunHelmPublish() error {
h.utils.SetOptions(repoClientOptions)
binary := fmt.Sprintf("%v", h.config.DeploymentName+"-"+h.config.PackageVersion+".tgz")
binary := fmt.Sprintf("%v", h.config.DeploymentName+"-"+h.config.Version+".tgz")
targetPath := fmt.Sprintf("%v/%s", h.config.DeploymentName, binary)

View File

@ -71,8 +71,10 @@ func TestRunHelm(t *testing.T) {
utils := newHelmMockUtilsBundle()
testTable := []struct {
config HelmExecuteOptions
expectedConfig []string
config HelmExecuteOptions
generalVerbose bool
expectedAddConfig []string
expectedUpgradeConfig []string
}{
{
config: HelmExecuteOptions{
@ -83,21 +85,26 @@ func TestRunHelm(t *testing.T) {
HelmDeployWaitSeconds: 3456,
AdditionalParameters: []string{"additional parameter"},
Image: "dtzar/helm-kubectl:3.4.1",
TargetRepositoryName: "test",
TargetRepositoryURL: "https://charts.helm.sh/stable",
},
expectedConfig: []string{"upgrade", "test_deployment", ".", "--install", "--namespace", "test_namespace", "--force", "--wait", "--timeout", "3456s", "--atomic", "additional parameter"},
generalVerbose: true,
expectedAddConfig: []string{"repo", "add", "test", "https://charts.helm.sh/stable", "--debug"},
expectedUpgradeConfig: []string{"upgrade", "test_deployment", ".", "--debug", "--install", "--namespace", "test_namespace", "--force", "--wait", "--timeout", "3456s", "--atomic", "additional parameter"},
},
}
for i, testCase := range testTable {
for _, testCase := range testTable {
helmExecute := HelmExecute{
utils: utils,
config: testCase.config,
verbose: false,
verbose: testCase.generalVerbose,
stdout: log.Writer(),
}
err := helmExecute.RunHelmUpgrade()
assert.NoError(t, err)
assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedConfig}, utils.Calls[i])
assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedAddConfig}, utils.Calls[0])
assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedUpgradeConfig}, utils.Calls[1])
}
})
@ -185,7 +192,6 @@ func TestRunHelm(t *testing.T) {
t.Run("Helm uninstal command", func(t *testing.T) {
t.Parallel()
utils := newHelmMockUtilsBundle()
testTable := []struct {
config HelmExecuteOptions
@ -194,9 +200,10 @@ func TestRunHelm(t *testing.T) {
}{
{
config: HelmExecuteOptions{
ChartPath: ".",
DeploymentName: "testPackage",
Namespace: "test-namespace",
ChartPath: ".",
DeploymentName: "testPackage",
Namespace: "test-namespace",
TargetRepositoryName: "test",
},
expectedConfig: []string{"uninstall", "testPackage", "--namespace", "test-namespace"},
},
@ -206,13 +213,15 @@ func TestRunHelm(t *testing.T) {
DeploymentName: "testPackage",
Namespace: "test-namespace",
HelmDeployWaitSeconds: 524,
TargetRepositoryName: "test",
},
generalVerbose: true,
expectedConfig: []string{"uninstall", "testPackage", "--namespace", "test-namespace", "--wait", "--timeout", "524s", "--debug", "--dry-run"},
},
}
for i, testCase := range testTable {
for _, testCase := range testTable {
utils := newHelmMockUtilsBundle()
helmExecute := HelmExecute{
utils: utils,
config: testCase.config,
@ -221,7 +230,7 @@ func TestRunHelm(t *testing.T) {
}
err := helmExecute.RunHelmUninstall()
assert.NoError(t, err)
assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedConfig}, utils.Calls[i])
assert.Equal(t, mock.ExecCall{Exec: "helm", Params: testCase.expectedConfig}, utils.Calls[1])
}
})
@ -243,7 +252,7 @@ func TestRunHelm(t *testing.T) {
config: HelmExecuteOptions{
ChartPath: ".",
DeploymentName: "testPackage",
PackageVersion: "1.2.3",
Version: "1.2.3",
PackageDependencyUpdate: true,
AppVersion: "9.8.7",
},
@ -316,6 +325,14 @@ func TestRunHelm(t *testing.T) {
ChartPath: ".",
DeploymentName: "testPackage",
},
expectedError: errors.New("failed to execute deployments: there is no TargetRepositoryName value. 'helm repo add' command requires 2 arguments"),
},
{
config: HelmExecuteOptions{
ChartPath: ".",
DeploymentName: "testPackage",
TargetRepositoryName: "test",
},
expectedError: errors.New("namespace has not been set, please configure namespace parameter"),
},
}
@ -424,8 +441,9 @@ func TestRunHelm(t *testing.T) {
TargetRepositoryURL: "https://my.target.repository.local/",
TargetRepositoryUser: "testUser",
TargetRepositoryPassword: "testPWD",
PackageVersion: "1.2.3",
Version: "1.2.3",
DeploymentName: "test_helm_chart",
ChartPath: ".",
}
utils.ReturnFileUploadStatus = 200

View File

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
@ -19,6 +20,7 @@ type DeployUtils interface {
Stdout(out io.Writer)
Stderr(err io.Writer)
RunExecutable(e string, p ...string) error
DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
piperutils.FileUtils
piperhttp.Uploader
@ -95,3 +97,7 @@ func GetChartInfo(chartYamlFile string, utils DeployUtils) (string, string, erro
return name, version, nil
}
func (d *deployUtilsBundle) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
return fmt.Errorf("not implemented")
}

View File

@ -49,3 +49,8 @@ func (utils *HttpClientMock) UploadRequest(method, url, file, fieldName string,
func (utils *HttpClientMock) UploadFile(url, file, fieldName string, header http.Header, cookies []*http.Cookie, uploadType string) (*http.Response, error) {
return utils.UploadRequest(http.MethodPut, url, file, fieldName, header, cookies, uploadType)
}
// DownloadFile mock
func (utils *HttpClientMock) DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error {
return fmt.Errorf("not implemented")
}

View File

@ -28,6 +28,20 @@ func TestSendRequest(t *testing.T) {
})
}
func TestDownloadFile(t *testing.T) {
t.Parallel()
t.Run("DownloadFile", func(t *testing.T) {
utils := HttpClientMock{}
url := "https://localhost"
filename := "testFile"
var header http.Header
var cookies []*http.Cookie
err := utils.DownloadFile(url, filename, header, cookies)
assert.Error(t, err)
})
}
func TestSetOption(t *testing.T) {
t.Parallel()
t.Run("SetOption", func(t *testing.T) {

View File

@ -49,8 +49,7 @@ spec:
aliases:
- name: helmChartPath
type: string
mandatory: true
description: Defines the chart path for helm.
description: Defines the chart path for helm. chartPath is mandatory for install/upgrade/publish commands.
scope:
- PARAMETERS
- STAGES
@ -69,10 +68,7 @@ spec:
param: custom/repositoryUrl
- name: targetRepositoryName
type: string
description: set the chart repository
mandatoryIf:
- name: helmCommand
value: install
description: set the chart repository. The value is required for install/upgrade/uninstall commands.
scope:
- GENERAL
- PARAMETERS
@ -264,6 +260,17 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: version
type: string
description: Defines the artifact version to use from helm package/publish commands.
scope:
- GENERAL
- PARAMETERS
- STAGES
- STEPS
resourceRef:
- name: commonPipelineEnvironment
param: artifactVersion
containers:
- image: dtzar/helm-kubectl:3.8.0
workingDir: /config