mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
feat(kubernetesDeploy): added valuesMapping config option (#3568)
This commit is contained in:
parent
5f4cd838cf
commit
a67b4ce558
@ -16,6 +16,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/pkg/errors"
|
||||
)
|
||||
|
||||
func kubernetesDeploy(config kubernetesDeployOptions, telemetryData *telemetry.CustomData) {
|
||||
@ -52,7 +53,9 @@ func runHelmDeploy(config kubernetesDeployOptions, utils kubernetes.DeployUtils,
|
||||
log.Entry().WithError(err).Fatalf("Container registry url '%v' incorrect", config.ContainerRegistryURL)
|
||||
}
|
||||
|
||||
helmValues := helmValues{}
|
||||
helmValues := helmValues{
|
||||
mapping: config.ValuesMapping,
|
||||
}
|
||||
|
||||
if len(config.ImageNames) > 0 {
|
||||
if len(config.ImageNames) != len(config.ImageNameTags) {
|
||||
@ -74,7 +77,7 @@ func runHelmDeploy(config kubernetesDeployOptions, utils kubernetes.DeployUtils,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//support either image or containerImageName and containerImageTag
|
||||
// support either image or containerImageName and containerImageTag
|
||||
containerImageName := ""
|
||||
containerImageTag := ""
|
||||
if len(config.Image) > 0 {
|
||||
@ -177,6 +180,11 @@ func runHelmDeploy(config kubernetesDeployOptions, utils kubernetes.DeployUtils,
|
||||
upgradeParams = append(upgradeParams, "--values", v)
|
||||
}
|
||||
|
||||
err = helmValues.mapValues()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to map values using 'valuesMapping' configuration")
|
||||
}
|
||||
|
||||
upgradeParams = append(
|
||||
upgradeParams,
|
||||
"--install",
|
||||
@ -304,7 +312,7 @@ func runKubectlDeploy(config kubernetesDeployOptions, utils kubernetes.DeployUti
|
||||
log.Entry().WithError(err).Fatalf("Error when reading appTemplate '%v'", config.AppTemplate)
|
||||
}
|
||||
|
||||
//support either image or containerImageName and containerImageTag
|
||||
// support either image or containerImageName and containerImageTag
|
||||
fullImage := ""
|
||||
|
||||
if len(config.Image) > 0 {
|
||||
@ -339,8 +347,11 @@ func runKubectlDeploy(config kubernetesDeployOptions, utils kubernetes.DeployUti
|
||||
return nil
|
||||
}
|
||||
|
||||
type helmValues []struct {
|
||||
key, value string
|
||||
type helmValues struct {
|
||||
mapping map[string]interface{}
|
||||
values []struct {
|
||||
key, value string
|
||||
}
|
||||
}
|
||||
|
||||
func joinKey(parts ...string) string {
|
||||
@ -352,8 +363,8 @@ func joinKey(parts ...string) string {
|
||||
return strings.Join(escapedParts, ".")
|
||||
}
|
||||
|
||||
func (values *helmValues) add(key, value string) {
|
||||
*values = append(*values, struct {
|
||||
func (hv *helmValues) add(key, value string) {
|
||||
hv.values = append(hv.values, struct {
|
||||
key string
|
||||
value string
|
||||
}{
|
||||
@ -362,9 +373,35 @@ func (values *helmValues) add(key, value string) {
|
||||
})
|
||||
}
|
||||
|
||||
func (values helmValues) marshal() string {
|
||||
func (hv helmValues) get(key string) string {
|
||||
for _, item := range hv.values {
|
||||
if item.key == key {
|
||||
return item.value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (hv *helmValues) mapValues() error {
|
||||
for dst, src := range hv.mapping {
|
||||
srcString, ok := src.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid path '%#v' is used for valuesMapping, only strings are supported", src)
|
||||
}
|
||||
if val := hv.get(srcString); val != "" {
|
||||
hv.add(dst, val)
|
||||
} else {
|
||||
log.Entry().Warnf("can not map '%s: %s', %s is not set", dst, src, src)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hv helmValues) marshal() string {
|
||||
builder := strings.Builder{}
|
||||
for idx, item := range values {
|
||||
for idx, item := range hv.values {
|
||||
if idx > 0 {
|
||||
builder.WriteString(",")
|
||||
}
|
||||
|
@ -16,36 +16,37 @@ import (
|
||||
)
|
||||
|
||||
type kubernetesDeployOptions struct {
|
||||
AdditionalParameters []string `json:"additionalParameters,omitempty"`
|
||||
APIServer string `json:"apiServer,omitempty"`
|
||||
AppTemplate string `json:"appTemplate,omitempty"`
|
||||
ChartPath string `json:"chartPath,omitempty"`
|
||||
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
|
||||
ContainerImageName string `json:"containerImageName,omitempty"`
|
||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
||||
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
|
||||
ContainerRegistrySecret string `json:"containerRegistrySecret,omitempty"`
|
||||
CreateDockerRegistrySecret bool `json:"createDockerRegistrySecret,omitempty"`
|
||||
DeploymentName string `json:"deploymentName,omitempty"`
|
||||
DeployTool string `json:"deployTool,omitempty" validate:"possible-values=kubectl helm helm3"`
|
||||
ForceUpdates bool `json:"forceUpdates,omitempty"`
|
||||
HelmDeployWaitSeconds int `json:"helmDeployWaitSeconds,omitempty"`
|
||||
HelmValues []string `json:"helmValues,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
ImageNames []string `json:"imageNames,omitempty"`
|
||||
ImageNameTags []string `json:"imageNameTags,omitempty"`
|
||||
IngressHosts []string `json:"ingressHosts,omitempty"`
|
||||
KeepFailedDeployments bool `json:"keepFailedDeployments,omitempty"`
|
||||
RunHelmTests bool `json:"runHelmTests,omitempty"`
|
||||
ShowTestLogs bool `json:"showTestLogs,omitempty"`
|
||||
KubeConfig string `json:"kubeConfig,omitempty"`
|
||||
KubeContext string `json:"kubeContext,omitempty"`
|
||||
KubeToken string `json:"kubeToken,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
TillerNamespace string `json:"tillerNamespace,omitempty"`
|
||||
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
||||
DeployCommand string `json:"deployCommand,omitempty" validate:"possible-values=apply replace"`
|
||||
AdditionalParameters []string `json:"additionalParameters,omitempty"`
|
||||
APIServer string `json:"apiServer,omitempty"`
|
||||
AppTemplate string `json:"appTemplate,omitempty"`
|
||||
ChartPath string `json:"chartPath,omitempty"`
|
||||
ContainerRegistryPassword string `json:"containerRegistryPassword,omitempty"`
|
||||
ContainerImageName string `json:"containerImageName,omitempty"`
|
||||
ContainerImageTag string `json:"containerImageTag,omitempty"`
|
||||
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
|
||||
ContainerRegistryUser string `json:"containerRegistryUser,omitempty"`
|
||||
ContainerRegistrySecret string `json:"containerRegistrySecret,omitempty"`
|
||||
CreateDockerRegistrySecret bool `json:"createDockerRegistrySecret,omitempty"`
|
||||
DeploymentName string `json:"deploymentName,omitempty"`
|
||||
DeployTool string `json:"deployTool,omitempty" validate:"possible-values=kubectl helm helm3"`
|
||||
ForceUpdates bool `json:"forceUpdates,omitempty"`
|
||||
HelmDeployWaitSeconds int `json:"helmDeployWaitSeconds,omitempty"`
|
||||
HelmValues []string `json:"helmValues,omitempty"`
|
||||
ValuesMapping map[string]interface{} `json:"valuesMapping,omitempty"`
|
||||
Image string `json:"image,omitempty"`
|
||||
ImageNames []string `json:"imageNames,omitempty"`
|
||||
ImageNameTags []string `json:"imageNameTags,omitempty"`
|
||||
IngressHosts []string `json:"ingressHosts,omitempty"`
|
||||
KeepFailedDeployments bool `json:"keepFailedDeployments,omitempty"`
|
||||
RunHelmTests bool `json:"runHelmTests,omitempty"`
|
||||
ShowTestLogs bool `json:"showTestLogs,omitempty"`
|
||||
KubeConfig string `json:"kubeConfig,omitempty"`
|
||||
KubeContext string `json:"kubeContext,omitempty"`
|
||||
KubeToken string `json:"kubeToken,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
TillerNamespace string `json:"tillerNamespace,omitempty"`
|
||||
DockerConfigJSON string `json:"dockerConfigJSON,omitempty"`
|
||||
DeployCommand string `json:"deployCommand,omitempty" validate:"possible-values=apply replace"`
|
||||
}
|
||||
|
||||
// KubernetesDeployCommand Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
|
||||
@ -175,6 +176,7 @@ func addKubernetesDeployFlags(cmd *cobra.Command, stepConfig *kubernetesDeployOp
|
||||
cmd.Flags().BoolVar(&stepConfig.ForceUpdates, "forceUpdates", true, "Adds `--force` flag to a helm resource update command or to a kubectl replace command")
|
||||
cmd.Flags().IntVar(&stepConfig.HelmDeployWaitSeconds, "helmDeployWaitSeconds", 300, "Number of seconds before helm deploy returns.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.HelmValues, "helmValues", []string{}, "List of helm values as YAML file reference or URL (as per helm parameter description for `-f` / `--values`)")
|
||||
|
||||
cmd.Flags().StringVar(&stepConfig.Image, "image", os.Getenv("PIPER_image"), "Full name of the image to be deployed.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ImageNames, "imageNames", []string{}, "List of names of the images to be deployed.")
|
||||
cmd.Flags().StringSliceVar(&stepConfig.ImageNameTags, "imageNameTags", []string{}, "List of full names (registry and tag) of the images to be deployed.")
|
||||
@ -391,6 +393,14 @@ func kubernetesDeployMetadata() config.StepData {
|
||||
Aliases: []config.Alias{},
|
||||
Default: []string{},
|
||||
},
|
||||
{
|
||||
Name: "valuesMapping",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "map[string]interface{}",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "image",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
|
@ -799,6 +799,96 @@ func TestRunKubernetesDeploy(t *testing.T) {
|
||||
assert.EqualError(t, err, "number of imageNames and imageNameTags must be equal")
|
||||
})
|
||||
|
||||
t.Run("test helm v3 - with multiple images and valuesMapping", func(t *testing.T) {
|
||||
opts := kubernetesDeployOptions{
|
||||
ContainerRegistryURL: "https://my.registry:55555",
|
||||
ContainerRegistryUser: "registryUser",
|
||||
ContainerRegistryPassword: "dummy",
|
||||
ContainerRegistrySecret: "testSecret",
|
||||
ChartPath: "path/to/chart",
|
||||
DeploymentName: "deploymentName",
|
||||
DeployTool: "helm3",
|
||||
ForceUpdates: true,
|
||||
HelmDeployWaitSeconds: 400,
|
||||
HelmValues: []string{"values1.yaml", "values2.yaml"},
|
||||
ValuesMapping: map[string]interface{}{
|
||||
"subchart.image.registry": "image.myImage.repository",
|
||||
"subchart.image.tag": "image.myImage.tag",
|
||||
},
|
||||
ImageNames: []string{"myImage", "myImage.sub1", "myImage.sub2"},
|
||||
ImageNameTags: []string{"myImage:myTag", "myImage-sub1:myTag", "myImage-sub2:myTag"},
|
||||
AdditionalParameters: []string{"--testParam", "testValue"},
|
||||
KubeContext: "testCluster",
|
||||
Namespace: "deploymentNamespace",
|
||||
DockerConfigJSON: ".pipeline/docker/config.json",
|
||||
}
|
||||
|
||||
dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
|
||||
|
||||
mockUtils := newKubernetesDeployMockUtils()
|
||||
mockUtils.StdoutReturn = map[string]string{
|
||||
`kubectl create secret generic testSecret --from-file=.dockerconfigjson=.pipeline/docker/config.json --type=kubernetes.io/dockerconfigjson --insecure-skip-tls-verify=true --dry-run=client --output=json`: dockerConfigJSON,
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
|
||||
require.NoError(t, runKubernetesDeploy(opts, &telemetry.CustomData{}, mockUtils, &stdout))
|
||||
|
||||
assert.Equal(t, "kubectl", mockUtils.Calls[0].Exec, "Wrong secret creation command")
|
||||
assert.Equal(t, []string{
|
||||
"create",
|
||||
"secret",
|
||||
"generic",
|
||||
"testSecret",
|
||||
"--from-file=.dockerconfigjson=.pipeline/docker/config.json",
|
||||
"--type=kubernetes.io/dockerconfigjson",
|
||||
"--insecure-skip-tls-verify=true",
|
||||
"--dry-run=client",
|
||||
"--output=json"},
|
||||
mockUtils.Calls[0].Params, "Wrong secret creation parameters")
|
||||
|
||||
assert.Equal(t, "helm", mockUtils.Calls[1].Exec, "Wrong upgrade command")
|
||||
|
||||
assert.Contains(t, mockUtils.Calls[1].Params, `image.myImage.repository=my.registry:55555/myImage,image.myImage.tag=myTag,image.myImage\.sub1.repository=my.registry:55555/myImage-sub1,image.myImage\.sub1.tag=myTag,image.myImage\.sub2.repository=my.registry:55555/myImage-sub2,image.myImage\.sub2.tag=myTag,secret.name=testSecret,secret.dockerconfigjson=ThisIsOurBase64EncodedSecret==,imagePullSecrets[0].name=testSecret,subchart.image.registry=my.registry:55555/myImage,subchart.image.tag=myTag`, "Wrong upgrade parameters")
|
||||
|
||||
})
|
||||
|
||||
t.Run("test helm v3 - with multiple images and incorrect valuesMapping", func(t *testing.T) {
|
||||
opts := kubernetesDeployOptions{
|
||||
ContainerRegistryURL: "https://my.registry:55555",
|
||||
ContainerRegistryUser: "registryUser",
|
||||
ContainerRegistryPassword: "dummy",
|
||||
ContainerRegistrySecret: "testSecret",
|
||||
ChartPath: "path/to/chart",
|
||||
DeploymentName: "deploymentName",
|
||||
DeployTool: "helm3",
|
||||
ForceUpdates: true,
|
||||
HelmDeployWaitSeconds: 400,
|
||||
HelmValues: []string{"values1.yaml", "values2.yaml"},
|
||||
ValuesMapping: map[string]interface{}{
|
||||
"subchart.image.registry": false,
|
||||
},
|
||||
ImageNames: []string{"myImage", "myImage.sub1", "myImage.sub2"},
|
||||
ImageNameTags: []string{"myImage:myTag", "myImage-sub1:myTag", "myImage-sub2:myTag"},
|
||||
AdditionalParameters: []string{"--testParam", "testValue"},
|
||||
KubeContext: "testCluster",
|
||||
Namespace: "deploymentNamespace",
|
||||
DockerConfigJSON: ".pipeline/docker/config.json",
|
||||
}
|
||||
|
||||
dockerConfigJSON := `{"kind": "Secret","data":{".dockerconfigjson": "ThisIsOurBase64EncodedSecret=="}}`
|
||||
|
||||
mockUtils := newKubernetesDeployMockUtils()
|
||||
mockUtils.StdoutReturn = map[string]string{
|
||||
`kubectl create secret generic testSecret --from-file=.dockerconfigjson=.pipeline/docker/config.json --type=kubernetes.io/dockerconfigjson --insecure-skip-tls-verify=true --dry-run=client --output=json`: dockerConfigJSON,
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
|
||||
require.Error(t, runKubernetesDeploy(opts, &telemetry.CustomData{}, mockUtils, &stdout), "invalid path 'false' is used for valueMapping, only strings are supported")
|
||||
|
||||
})
|
||||
|
||||
t.Run("test helm3 - fails without image information", func(t *testing.T) {
|
||||
opts := kubernetesDeployOptions{
|
||||
ContainerRegistryURL: "https://my.registry:55555",
|
||||
|
@ -223,6 +223,22 @@ spec:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: valuesMapping
|
||||
type: "map[string]interface{}"
|
||||
longDescription: |
|
||||
Mapping of values provided by Piper onto custom paths in format `[custom-path]: [piper-value]`
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
valuesMapping:
|
||||
subchart.image.tag: image.debug.tag
|
||||
subchart.image.repository: image.debug.repository
|
||||
subchart.image.pullsecret: secret.dockerconfigjson
|
||||
```
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
- name: image
|
||||
aliases:
|
||||
- name: deployImage
|
||||
|
Loading…
Reference in New Issue
Block a user