feat(k8s): Add basic support for Helm 3 in kubernetesDeploy step (#1438)

* Extends kubernetesDeploy step to support Helm 3

Currently, the kubernetesDeploy step has no support to Helm 3 due to the fact that:
- the initialization command used works only for Helm 2
- the image used when running the helm CLI is based on Helm 2

The need for Helm 3 support comes from the fact that Helm 3 introduces major architectural changes,
more specifically, the removal of its server-side agent called Tiller - thus, being incompatible with
one another.

This commit adds this support by introducing a new configuration field (helmVersion).
By default, its values is set to 2 (Helm 2) to avoid breaking any existing functionalities.

* Use deployTool field to decide between Helm 2 or 3

* Remove helm init and replace wait for atomic in v3

* Update cmd/kubernetesDeploy.go

Nice catch!

Co-Authored-By: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>

* Add documentation for kubernetesDeploy step

* Add helm3 example for kubernetesDeploy step using mandatory fields

* Add new line at the end of kubernetesDeploy documentation

* Link kubernetesDeploy step with docs generator

* Add possible values for deployTool in kubernetesDeploy

* dummy change

* Revert "dummy change"

Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
Arthur Lenz
2020-04-24 09:37:11 +02:00
committed by GitHub
co-authored by Christopher Fenner Oliver Nocon
parent b942dcd954
commit b335387eac
5 changed files with 106 additions and 13 deletions
+16 -10
View File
@@ -23,7 +23,7 @@ func kubernetesDeploy(config kubernetesDeployOptions, telemetryData *telemetry.C
}
func runKubernetesDeploy(config kubernetesDeployOptions, command execRunner, stdout io.Writer) {
if config.DeployTool == "helm" {
if config.DeployTool == "helm" || config.DeployTool == "helm3" {
runHelmDeploy(config, command, stdout)
} else {
runKubectlDeploy(config, command)
@@ -48,16 +48,18 @@ func runHelmDeploy(config kubernetesDeployOptions, command execRunner, stdout io
log.Entry().WithFields(helmLogFields).Debug("Calling Helm")
helmEnv := []string{fmt.Sprintf("KUBECONFIG=%v", config.KubeConfig)}
if len(config.TillerNamespace) > 0 {
if config.DeployTool == "helm" && len(config.TillerNamespace) > 0 {
helmEnv = append(helmEnv, fmt.Sprintf("TILLER_NAMESPACE=%v", config.TillerNamespace))
}
log.Entry().Debugf("Helm SetEnv: %v", helmEnv)
command.SetEnv(helmEnv)
command.Stdout(stdout)
initParams := []string{"init", "--client-only"}
if err := command.RunExecutable("helm", initParams...); err != nil {
log.Entry().WithError(err).Fatal("Helm init called failed")
if config.DeployTool == "helm" {
initParams := []string{"init", "--client-only"}
if err := command.RunExecutable("helm", initParams...); err != nil {
log.Entry().WithError(err).Fatal("Helm init call failed")
}
}
var dockerRegistrySecret bytes.Buffer
@@ -103,15 +105,19 @@ func runHelmDeploy(config kubernetesDeployOptions, command execRunner, stdout io
config.ChartPath,
"--install",
"--force",
"--namespace",
config.Namespace,
"--wait",
"--timeout",
strconv.Itoa(config.HelmDeployWaitSeconds),
"--namespace", config.Namespace,
"--set",
fmt.Sprintf("image.repository=%v/%v,image.tag=%v,secret.dockerconfigjson=%v%v", containerRegistry, containerImageName, containerImageTag, dockerRegistrySecretData.Data.DockerConfJSON, ingressHosts),
}
if config.DeployTool == "helm" {
upgradeParams = append(upgradeParams, "--wait", "--timeout", strconv.Itoa(config.HelmDeployWaitSeconds))
}
if config.DeployTool == "helm3" {
upgradeParams = append(upgradeParams, "--atomic", "--timeout", fmt.Sprintf("%vs", config.HelmDeployWaitSeconds))
}
if len(config.KubeContext) > 0 {
upgradeParams = append(upgradeParams, "--kube-context", config.KubeContext)
}
+56 -3
View File
@@ -3,12 +3,13 @@ package cmd
import (
"bytes"
"fmt"
"github.com/SAP/jenkins-library/pkg/mock"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
)
@@ -57,11 +58,11 @@ func TestRunKubernetesDeploy(t *testing.T) {
"--force",
"--namespace",
"deploymentNamespace",
"--set",
"image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,ingress.hosts[0]=ingress.host1,ingress.hosts[1]=ingress.host2",
"--wait",
"--timeout",
"400",
"--set",
"image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,ingress.hosts[0]=ingress.host1,ingress.hosts[1]=ingress.host2",
"--kube-context",
"testCluster",
"--testParam",
@@ -69,6 +70,58 @@ func TestRunKubernetesDeploy(t *testing.T) {
}, e.Calls[2].Params, "Wrong upgrade parameters")
})
t.Run("test helm v3", func(t *testing.T) {
opts := kubernetesDeployOptions{
ContainerRegistryURL: "https://my.registry:55555",
ContainerRegistryUser: "registryUser",
ContainerRegistryPassword: "********",
ChartPath: "path/to/chart",
DeploymentName: "deploymentName",
DeployTool: "helm3",
HelmDeployWaitSeconds: 400,
IngressHosts: []string{"ingress.host1", "ingress.host2"},
Image: "path/to/Image:latest",
AdditionalParameters: []string{"--testParam", "testValue"},
KubeContext: "testCluster",
Namespace: "deploymentNamespace",
}
dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
"kubectl --insecure-skip-tls-verify=true create secret docker-registry regsecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=******** --dry-run=true --output=json": dockerConfigJSON,
},
}
var stdout bytes.Buffer
runKubernetesDeploy(opts, &e, &stdout)
assert.Equal(t, "kubectl", e.Calls[0].Exec, "Wrong secret creation command")
assert.Equal(t, []string{"--insecure-skip-tls-verify=true", "create", "secret", "docker-registry", "regsecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********", "--dry-run=true", "--output=json"}, e.Calls[0].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[1].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
"upgrade",
"deploymentName",
"path/to/chart",
"--install",
"--force",
"--namespace",
"deploymentNamespace",
"--set",
"image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,ingress.hosts[0]=ingress.host1,ingress.hosts[1]=ingress.host2",
"--atomic",
"--timeout",
"400s",
"--kube-context",
"testCluster",
"--testParam",
"testValue",
}, e.Calls[1].Params, "Wrong upgrade parameters")
})
t.Run("test kubectl - create secret/kubeconfig", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
defer os.RemoveAll(dir) // clean up
@@ -0,0 +1,22 @@
# ${docGenStepName}
## ${docGenDescription}
## ${docGenParameters}
## ${docGenConfiguration}
## Exceptions
None
## Examples
```groovy
kubernetesDeploy script: this
```
```groovy
// Deploy a helm chart called "myChart" using Helm 3
kubernetesDeploy script: this, deployTool: 'helm3', chartPath: 'myChart', deploymentName: 'myRelease', image: 'nginx', containerRegistryUrl: 'https://docker.io'
```
+1
View File
@@ -63,6 +63,7 @@ nav:
- jenkinsMaterializeLog: steps/jenkinsMaterializeLog.md
- kanikoExecute: steps/kanikoExecute.md
- karmaExecuteTests: steps/karmaExecuteTests.md
- kubernetesDeploy: steps/kubernetesDeploy.md
- mailSendNotification: steps/mailSendNotification.md
- mavenExecute: steps/mavenExecute.md
- mavenExecuteStaticCodeChecks: steps/mavenExecuteStaticCodeChecks.md
+11
View File
@@ -135,6 +135,10 @@ spec:
- STAGES
- STEPS
default: kubectl
possibleValues:
- kubectl
- helm
- helm3
- name: helmDeployWaitSeconds
type: int
description: Number of seconds before helm deploy returns.
@@ -211,6 +215,13 @@ spec:
- STAGES
- STEPS
containers:
- image: dtzar/helm-kubectl:3.1.2
workingDir: /config
conditions:
- conditionRef: strings-equal
params:
- name: deployTool
value: helm3
- image: dtzar/helm-kubectl:2.12.1
workingDir: /config
conditions: