1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-02-07 13:42:23 +02:00

kubernetesDeploy - Allow docker config.json file (#2829)

* Introduce docker config.json parameter for kubectl

* Use docker config.json parameter for helm

* Export definition of kube secret parameters

* fix username password existence check

* choose more fitting name for secret spec

* Adopt review suggestions

Co-authored-by: Roland Stengel <r.stengel@sap.com>
This commit is contained in:
Oliver Feldmann 2021-06-11 10:41:03 +02:00 committed by GitHub
parent 84c3cd399d
commit d0f7400122
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 218 additions and 37 deletions

View File

@ -92,29 +92,18 @@ func runHelmDeploy(config kubernetesDeployOptions, command command.ExecRunner, s
}
var secretsData string
if len(config.ContainerRegistryUser) == 0 || len(config.ContainerRegistryPassword) == 0 {
log.Entry().Info("No container registry credentials provided or credentials incomplete: skipping secret creation")
if len(config.DockerConfigJSON) == 0 && (len(config.ContainerRegistryUser) == 0 || len(config.ContainerRegistryPassword) == 0) {
log.Entry().Info("No container registry credentials or docker config.json file provided or credentials incomplete: skipping secret creation")
if len(config.ContainerRegistrySecret) > 0 {
secretsData = fmt.Sprintf(",imagePullSecrets[0].name=%v", config.ContainerRegistrySecret)
}
} else {
var dockerRegistrySecret bytes.Buffer
command.Stdout(&dockerRegistrySecret)
kubeParams := []string{
"--insecure-skip-tls-verify=true",
"create",
"secret",
"docker-registry",
config.ContainerRegistrySecret,
fmt.Sprintf("--docker-server=%v", containerRegistry),
fmt.Sprintf("--docker-username=%v", config.ContainerRegistryUser),
fmt.Sprintf("--docker-password=%v", config.ContainerRegistryPassword),
"--dry-run=true",
"--output=json",
}
kubeSecretParams := defineKubeSecretParams(config, containerRegistry)
log.Entry().Infof("Calling kubectl create secret --dry-run=true ...")
log.Entry().Debugf("kubectl parameters %v", kubeParams)
if err := command.RunExecutable("kubectl", kubeParams...); err != nil {
log.Entry().Debugf("kubectl parameters %v", kubeSecretParams)
if err := command.RunExecutable("kubectl", kubeSecretParams...); err != nil {
log.Entry().WithError(err).Fatal("Retrieving Docker config via kubectl failed")
}
@ -225,23 +214,16 @@ func runKubectlDeploy(config kubernetesDeployOptions, command command.ExecRunner
}
if config.CreateDockerRegistrySecret {
if len(config.ContainerRegistryUser)+len(config.ContainerRegistryPassword) == 0 {
log.Entry().Fatal("Cannot create Container registry secret without proper registry username/password")
if len(config.DockerConfigJSON) == 0 && (len(config.ContainerRegistryUser) == 0 || len(config.ContainerRegistryPassword) == 0) {
log.Entry().Fatal("Cannot create Container registry secret without proper registry username/password or docker config.json file")
}
// first check if secret already exists
kubeCheckParams := append(kubeParams, "get", "secret", config.ContainerRegistrySecret)
if err := command.RunExecutable("kubectl", kubeCheckParams...); err != nil {
log.Entry().Infof("Registry secret '%v' does not exist, let's create it ...", config.ContainerRegistrySecret)
kubeSecretParams := append(
kubeParams,
"create",
"secret",
"docker-registry",
config.ContainerRegistrySecret,
fmt.Sprintf("--docker-server=%v", containerRegistry),
fmt.Sprintf("--docker-username=%v", config.ContainerRegistryUser),
fmt.Sprintf("--docker-password=%v", config.ContainerRegistryPassword),
)
kubeSecretParams := defineKubeSecretParams(config, containerRegistry)
kubeSecretParams = append(kubeParams, kubeSecretParams...)
log.Entry().Infof("Creating container registry secret '%v'", config.ContainerRegistrySecret)
log.Entry().Debugf("Running kubectl with following parameters: %v", kubeSecretParams)
if err := command.RunExecutable("kubectl", kubeSecretParams...); err != nil {
@ -299,3 +281,36 @@ func splitFullImageName(image string) (imageName, tag string, err error) {
}
return "", "", fmt.Errorf("Failed to split image name '%v'", image)
}
func defineKubeSecretParams(config kubernetesDeployOptions, containerRegistry string) []string {
kubeSecretParams := []string{
"create",
"secret",
}
if config.DeployTool == "helm" || config.DeployTool == "helm3" {
kubeSecretParams = append(
kubeSecretParams,
"--insecure-skip-tls-verify=true",
"--dry-run=true",
"--output=json",
)
}
if len(config.DockerConfigJSON) > 0 {
return append(
kubeSecretParams,
"generic",
config.ContainerRegistrySecret,
fmt.Sprintf("--from-file=.dockerconfigjson=%v", config.DockerConfigJSON),
"--type=\"kubernetes.io/dockerconfigjson\"",
)
}
return append(
kubeSecretParams,
"docker-registry",
config.ContainerRegistrySecret,
fmt.Sprintf("--docker-server=%v", containerRegistry),
fmt.Sprintf("--docker-username=%v", config.ContainerRegistryUser),
fmt.Sprintf("--docker-password=%v", config.ContainerRegistryPassword),
)
}

View File

@ -37,6 +37,7 @@ type kubernetesDeployOptions struct {
KubeToken string `json:"kubeToken,omitempty"`
Namespace string `json:"namespace,omitempty"`
TillerNamespace string `json:"tillerNamespace,omitempty"`
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
}
// KubernetesDeployCommand Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
@ -87,6 +88,7 @@ helm upgrade <deploymentName> <chartPath> --install --force --namespace <namespa
log.RegisterSecret(stepConfig.ContainerRegistryUser)
log.RegisterSecret(stepConfig.KubeConfig)
log.RegisterSecret(stepConfig.KubeToken)
log.RegisterSecret(stepConfig.DockerConfigJSON)
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
@ -155,6 +157,7 @@ func addKubernetesDeployFlags(cmd *cobra.Command, stepConfig *kubernetesDeployOp
cmd.Flags().StringVar(&stepConfig.KubeToken, "kubeToken", os.Getenv("PIPER_kubeToken"), "Contains the id_token used by kubectl for authentication. Consider using kubeConfig parameter instead.")
cmd.Flags().StringVar(&stepConfig.Namespace, "namespace", `default`, "Defines the target Kubernetes namespace for the deployment.")
cmd.Flags().StringVar(&stepConfig.TillerNamespace, "tillerNamespace", os.Getenv("PIPER_tillerNamespace"), "Defines optional tiller namespace for deployments using helm.")
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.MarkFlagRequired("containerRegistryUrl")
cmd.MarkFlagRequired("deployTool")
@ -389,6 +392,30 @@ func kubernetesDeployMetadata() config.StepData {
Mandatory: false,
Aliases: []config.Alias{{Name: "helmTillerNamespace"}},
},
{
Name: "dockerConfigJSON",
ResourceRef: []config.ResourceReference{
{
Name: "commonPipelineEnvironment",
Param: "custom/dockerConfigJSON",
},
{
Name: "dockerConfigJsonCredentialsId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/docker-config", "$(vaultBasePath)/$(vaultPipelineName)/docker-config", "$(vaultBasePath)/GROUP-SECRETS/docker-config"},
Type: "vaultSecretFile",
},
},
Scope: []string{"PARAMETERS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{},
},
},
},
Containers: []config.Container{

View File

@ -37,7 +37,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
`kubectl --insecure-skip-tls-verify=true create secret docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\* --dry-run=true --output=json`: dockerConfigJSON,
`kubectl create secret --insecure-skip-tls-verify=true --dry-run=true --output=json docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\*`: dockerConfigJSON,
},
}
@ -49,7 +49,74 @@ func TestRunKubernetesDeploy(t *testing.T) {
assert.Equal(t, []string{"init", "--client-only"}, e.Calls[0].Params, "Wrong init parameters")
assert.Equal(t, "kubectl", e.Calls[1].Exec, "Wrong secret creation command")
assert.Equal(t, []string{"--insecure-skip-tls-verify=true", "create", "secret", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********", "--dry-run=true", "--output=json"}, e.Calls[1].Params, "Wrong secret creation parameters")
assert.Equal(t, []string{"create", "secret", "--insecure-skip-tls-verify=true", "--dry-run=true", "--output=json", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********"}, e.Calls[1].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[2].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
"upgrade",
"deploymentName",
"path/to/chart",
"--install",
"--namespace",
"deploymentNamespace",
"--set",
"image.repository=my.registry:55555/path/to/Image,image.tag=latest,secret.name=testSecret,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,imagePullSecrets[0].name=testSecret,ingress.hosts[0]=ingress.host1,ingress.hosts[1]=ingress.host2",
"--force",
"--wait",
"--timeout",
"400",
"--atomic",
"--kube-context",
"testCluster",
"--testParam",
"testValue",
}, e.Calls[2].Params, "Wrong upgrade parameters")
})
t.Run("test helm - docker config.json path passed as parameter", func(t *testing.T) {
opts := kubernetesDeployOptions{
ContainerRegistryURL: "https://my.registry:55555",
DockerConfigJSON: "/path/to/.docker/config.json",
ContainerRegistrySecret: "testSecret",
ChartPath: "path/to/chart",
DeploymentName: "deploymentName",
DeployTool: "helm",
ForceUpdates: true,
HelmDeployWaitSeconds: 400,
IngressHosts: []string{"ingress.host1", "ingress.host2"},
Image: "path/to/Image:latest",
AdditionalParameters: []string{"--testParam", "testValue"},
KubeContext: "testCluster",
Namespace: "deploymentNamespace",
}
k8sSecretSpec := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
`kubectl create secret --insecure-skip-tls-verify=true --dry-run=true --output=json generic testSecret --from-file=.dockerconfigjson=/path/to/.docker/config.json --type="kubernetes.io/dockerconfigjson"`: k8sSecretSpec,
},
}
var stdout bytes.Buffer
err := runKubernetesDeploy(opts, &e, &stdout)
assert.NoError(t, err)
assert.Equal(t, "helm", e.Calls[0].Exec, "Wrong init command")
assert.Equal(t, []string{"init", "--client-only"}, e.Calls[0].Params, "Wrong init parameters")
assert.Equal(t, "kubectl", e.Calls[1].Exec, "Wrong secret creation command")
assert.Equal(t, []string{
"create",
"secret",
"--insecure-skip-tls-verify=true",
"--dry-run=true",
"--output=json",
"generic",
"testSecret",
"--from-file=.dockerconfigjson=/path/to/.docker/config.json",
`--type="kubernetes.io/dockerconfigjson"`,
}, e.Calls[1].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[2].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
@ -92,11 +159,11 @@ func TestRunKubernetesDeploy(t *testing.T) {
KeepFailedDeployments: true,
}
dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
k8sSecretSpec := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
`kubectl --insecure-skip-tls-verify=true create secret docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\* --dry-run=true --output=json`: dockerConfigJSON,
`kubectl create secret --insecure-skip-tls-verify=true --dry-run=true --output=json docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\*`: k8sSecretSpec,
},
}
@ -108,7 +175,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
assert.Equal(t, []string{"init", "--client-only"}, e.Calls[0].Params, "Wrong init parameters")
assert.Equal(t, "kubectl", e.Calls[1].Exec, "Wrong secret creation command")
assert.Equal(t, []string{"--insecure-skip-tls-verify=true", "create", "secret", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********", "--dry-run=true", "--output=json"}, e.Calls[1].Params, "Wrong secret creation parameters")
assert.Equal(t, []string{"create", "secret", "--insecure-skip-tls-verify=true", "--dry-run=true", "--output=json", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********"}, e.Calls[1].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[2].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
@ -153,7 +220,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
`kubectl --insecure-skip-tls-verify=true create secret docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\* --dry-run=true --output=json`: dockerConfigJSON,
`kubectl create secret --insecure-skip-tls-verify=true --dry-run=true --output=json docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\*`: dockerConfigJSON,
},
}
@ -162,7 +229,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
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", "testSecret", "--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, []string{"create", "secret", "--insecure-skip-tls-verify=true", "--dry-run=true", "--output=json", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********"}, e.Calls[0].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[1].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
@ -213,7 +280,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
e := mock.ExecMockRunner{
StdoutReturn: map[string]string{
`kubectl --insecure-skip-tls-verify=true create secret docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\* --dry-run=true --output=json`: dockerConfigJSON,
`kubectl create secret --insecure-skip-tls-verify=true --dry-run=true --output=json docker-registry testSecret --docker-server=my.registry:55555 --docker-username=registryUser --docker-password=\*\*\*\*\*\*\*\*`: dockerConfigJSON,
},
}
@ -222,7 +289,7 @@ func TestRunKubernetesDeploy(t *testing.T) {
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", "testSecret", "--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, []string{"create", "secret", "--insecure-skip-tls-verify=true", "--dry-run=true", "--output=json", "docker-registry", "testSecret", "--docker-server=my.registry:55555", "--docker-username=registryUser", "--docker-password=********"}, e.Calls[0].Params, "Wrong secret creation parameters")
assert.Equal(t, "helm", e.Calls[1].Exec, "Wrong upgrade command")
assert.Equal(t, []string{
@ -454,6 +521,58 @@ spec:
assert.Contains(t, string(appTemplate), "my.registry:55555/path/to/Image:latest")
})
t.Run("test kubectl - create secret from docker config.json", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
defer os.RemoveAll(dir) // clean up
assert.NoError(t, err, "Error when creating temp dir")
opts := kubernetesDeployOptions{
AppTemplate: filepath.Join(dir, "test.yaml"),
DockerConfigJSON: "/path/to/.docker/config.json",
ContainerRegistryURL: "https://my.registry:55555",
ContainerRegistryUser: "registryUser",
ContainerRegistryPassword: "********",
ContainerRegistrySecret: "regSecret",
CreateDockerRegistrySecret: true,
DeployTool: "kubectl",
Image: "path/to/Image:latest",
AdditionalParameters: []string{"--testParam", "testValue"},
KubeConfig: "This is my kubeconfig",
KubeContext: "testCluster",
Namespace: "deploymentNamespace",
}
kubeYaml := `kind: Deployment
metadata:
spec:
spec:
image: <image-name>`
ioutil.WriteFile(opts.AppTemplate, []byte(kubeYaml), 0755)
e := mock.ExecMockRunner{
ShouldFailOnCommand: map[string]error{
"kubectl --insecure-skip-tls-verify=true --namespace=deploymentNamespace --context=testCluster get secret regSecret": fmt.Errorf("secret not found"),
},
}
var stdout bytes.Buffer
runKubernetesDeploy(opts, &e, &stdout)
assert.Equal(t, e.Env, []string{"KUBECONFIG=This is my kubeconfig"})
assert.Equal(t, []string{
"--insecure-skip-tls-verify=true",
fmt.Sprintf("--namespace=%v", opts.Namespace),
fmt.Sprintf("--context=%v", opts.KubeContext),
"create",
"secret",
"generic",
opts.ContainerRegistrySecret,
fmt.Sprintf("--from-file=.dockerconfigjson=%v", opts.DockerConfigJSON),
`--type="kubernetes.io/dockerconfigjson"`,
}, e.Calls[1].Params, "kubectl parameters incorrect")
})
t.Run("test kubectl - lookup secret/kubeconfig", func(t *testing.T) {
dir, err := ioutil.TempDir("", "")
defer os.RemoveAll(dir) // clean up

View File

@ -40,6 +40,9 @@ spec:
type: jenkins
- name: dockerCredentialsId
type: jenkins
- name: dockerConfigJsonCredentialsId
description: Jenkins 'Secret file' credentials ID containing Docker config.json (with registry credential(s)).
type: jenkins
resources:
- name: deployDescriptor
type: stash
@ -285,6 +288,22 @@ spec:
- PARAMETERS
- STAGES
- STEPS
- name: dockerConfigJSON
type: string
description: 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/).
scope:
- PARAMETERS
secret: true
resourceRef:
- name: commonPipelineEnvironment
param: custom/dockerConfigJSON
- name: dockerConfigJsonCredentialsId
type: secret
- type: vaultSecretFile
paths:
- $(vaultPath)/docker-config
- $(vaultBasePath)/$(vaultPipelineName)/docker-config
- $(vaultBasePath)/GROUP-SECRETS/docker-config
containers:
- image: dtzar/helm-kubectl:3.4.1
workingDir: /config

View File

@ -6,6 +6,7 @@ import groovy.transform.Field
void call(Map parameters = [:]) {
List credentials = [
[type: 'file', id: 'kubeConfigFileCredentialsId', env: ['PIPER_kubeConfig']],
[type: 'file', id: 'dockerConfigJsonCredentialsId', env: ['PIPER_dockerConfigJSON']],
[type: 'token', id: 'kubeTokenCredentialsId', env: ['PIPER_kubeToken']],
[type: 'usernamePassword', id: 'dockerCredentialsId', env: ['PIPER_containerRegistryUser', 'PIPER_containerRegistryPassword']],
]