You've already forked sap-jenkins-library
mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-09-16 09:26:22 +02:00
Add kubernetesDeploy step (#1073)
* Add kubernetesDeploy step Co-authored-by: Sven Merk <33895725+nevskrem@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,7 @@ RUN go test ./... -cover
|
|||||||
# execute build
|
# execute build
|
||||||
RUN export GIT_COMMIT=$(git rev-parse HEAD) && \
|
RUN export GIT_COMMIT=$(git rev-parse HEAD) && \
|
||||||
export GIT_REPOSITORY=$(git config --get remote.origin.url) && \
|
export GIT_REPOSITORY=$(git config --get remote.origin.url) && \
|
||||||
go build \
|
CGO_ENABLED=0 go build \
|
||||||
-ldflags \
|
-ldflags \
|
||||||
"-X github.com/SAP/jenkins-library/cmd.GitCommit=${GIT_COMMIT} \
|
"-X github.com/SAP/jenkins-library/cmd.GitCommit=${GIT_COMMIT} \
|
||||||
-X github.com/SAP/jenkins-library/pkg/log.LibraryRepository=${GIT_REPOSITORY}" \
|
-X github.com/SAP/jenkins-library/pkg/log.LibraryRepository=${GIT_REPOSITORY}" \
|
||||||
|
@@ -98,6 +98,11 @@ func generateConfig() error {
|
|||||||
return errors.Wrap(err, "getting step config failed")
|
return errors.Wrap(err, "getting step config failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// apply context conditions if context configuration is requested
|
||||||
|
if configOptions.contextConfig {
|
||||||
|
applyContextConditions(metadata, &stepConfig)
|
||||||
|
}
|
||||||
|
|
||||||
myConfigJSON, _ := config.GetJSON(stepConfig.Config)
|
myConfigJSON, _ := config.GetJSON(stepConfig.Config)
|
||||||
|
|
||||||
fmt.Println(myConfigJSON)
|
fmt.Println(myConfigJSON)
|
||||||
@@ -129,3 +134,33 @@ func defaultsAndFilters(metadata *config.StepData, stepName string) ([]io.ReadCl
|
|||||||
//ToDo: retrieve default values from metadata
|
//ToDo: retrieve default values from metadata
|
||||||
return nil, metadata.GetParameterFilters(), nil
|
return nil, metadata.GetParameterFilters(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyContextConditions(metadata config.StepData, stepConfig *config.StepConfig) {
|
||||||
|
//consider conditions for context configuration
|
||||||
|
|
||||||
|
//containers
|
||||||
|
applyContainerConditions(metadata.Spec.Containers, stepConfig)
|
||||||
|
|
||||||
|
//sidecars
|
||||||
|
applyContainerConditions(metadata.Spec.Sidecars, stepConfig)
|
||||||
|
|
||||||
|
//ToDo: remove all unnecessary sub maps?
|
||||||
|
// e.g. extract delete() from applyContainerConditions - loop over all stepConfig.Config[param.Value] and remove ...
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyContainerConditions(containers []config.Container, stepConfig *config.StepConfig) {
|
||||||
|
for _, container := range containers {
|
||||||
|
if len(container.Conditions) > 0 {
|
||||||
|
for _, param := range container.Conditions[0].Params {
|
||||||
|
if container.Conditions[0].ConditionRef == "strings-equal" && stepConfig.Config[param.Name] == param.Value {
|
||||||
|
var containerConf map[string]interface{}
|
||||||
|
containerConf = stepConfig.Config[param.Value].(map[string]interface{})
|
||||||
|
for key, value := range containerConf {
|
||||||
|
stepConfig.Config[key] = value
|
||||||
|
}
|
||||||
|
delete(stepConfig.Config, param.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -86,5 +87,93 @@ func TestDefaultsAndFilters(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(filters.All), "wrong number of filter values")
|
assert.Equal(t, 1, len(filters.All), "wrong number of filter values")
|
||||||
assert.NoError(t, err, "error occured but none expected")
|
assert.NoError(t, err, "error occured but none expected")
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyContextConditions(t *testing.T) {
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
metadata config.StepData
|
||||||
|
conf config.StepConfig
|
||||||
|
expected map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
metadata: config.StepData{Spec: config.StepSpec{Containers: []config.Container{}}},
|
||||||
|
conf: config.StepConfig{Config: map[string]interface{}{}},
|
||||||
|
expected: map[string]interface{}{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: config.StepData{Spec: config.StepSpec{Containers: []config.Container{
|
||||||
|
{
|
||||||
|
Image: "myTestImage:latest",
|
||||||
|
Conditions: []config.Condition{
|
||||||
|
{
|
||||||
|
ConditionRef: "strings-equal",
|
||||||
|
Params: []config.Param{
|
||||||
|
{Name: "param1", Value: "val2"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
conf: config.StepConfig{Config: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"val1": map[string]interface{}{"dockerImage": "myTestImage:latest"},
|
||||||
|
}},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"val1": map[string]interface{}{"dockerImage": "myTestImage:latest"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: config.StepData{Spec: config.StepSpec{Containers: []config.Container{
|
||||||
|
{
|
||||||
|
Image: "myTestImage:latest",
|
||||||
|
Conditions: []config.Condition{
|
||||||
|
{
|
||||||
|
ConditionRef: "strings-equal",
|
||||||
|
Params: []config.Param{
|
||||||
|
{Name: "param1", Value: "val1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
conf: config.StepConfig{Config: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"val1": map[string]interface{}{"dockerImage": "myTestImage:latest"},
|
||||||
|
}},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"dockerImage": "myTestImage:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metadata: config.StepData{Spec: config.StepSpec{Sidecars: []config.Container{
|
||||||
|
{
|
||||||
|
Image: "myTestImage:latest",
|
||||||
|
Conditions: []config.Condition{
|
||||||
|
{
|
||||||
|
ConditionRef: "strings-equal",
|
||||||
|
Params: []config.Param{
|
||||||
|
{Name: "param1", Value: "val1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}},
|
||||||
|
conf: config.StepConfig{Config: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"val1": map[string]interface{}{"dockerImage": "myTestImage:latest"},
|
||||||
|
}},
|
||||||
|
expected: map[string]interface{}{
|
||||||
|
"param1": "val1",
|
||||||
|
"dockerImage": "myTestImage:latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for run, test := range tt {
|
||||||
|
applyContextConditions(test.metadata, &test.conf)
|
||||||
|
assert.Equalf(t, test.expected, test.conf.Config, fmt.Sprintf("Run %v failed", run))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,14 @@ type execRunner interface {
|
|||||||
Stderr(err io.Writer)
|
Stderr(err io.Writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type envExecRunner interface {
|
||||||
|
RunExecutable(e string, p ...string) error
|
||||||
|
Dir(d string)
|
||||||
|
Env(e []string)
|
||||||
|
Stdout(out io.Writer)
|
||||||
|
Stderr(err io.Writer)
|
||||||
|
}
|
||||||
|
|
||||||
type shellRunner interface {
|
type shellRunner interface {
|
||||||
RunShell(s string, c string) error
|
RunShell(s string, c string) error
|
||||||
Dir(d string)
|
Dir(d string)
|
||||||
|
231
cmd/kubernetesDeploy.go
Normal file
231
cmd/kubernetesDeploy.go
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/SAP/jenkins-library/pkg/command"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func kubernetesDeploy(config kubernetesDeployOptions) error {
|
||||||
|
c := command.Command{}
|
||||||
|
// reroute stderr output to logging framework, stdout will be used for command interactions
|
||||||
|
c.Stderr(log.Entry().Writer())
|
||||||
|
runKubernetesDeploy(config, &c, log.Entry().Writer())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func runKubernetesDeploy(config kubernetesDeployOptions, command envExecRunner, stdout io.Writer) {
|
||||||
|
if config.DeployTool == "helm" {
|
||||||
|
runHelmDeploy(config, command, stdout)
|
||||||
|
} else {
|
||||||
|
runKubectlDeploy(config, command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHelmDeploy(config kubernetesDeployOptions, command envExecRunner, stdout io.Writer) {
|
||||||
|
_, containerRegistry, err := splitRegistryURL(config.ContainerRegistryURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Fatalf("Container registry url '%v' incorrect", config.ContainerRegistryURL)
|
||||||
|
}
|
||||||
|
containerImageName, containerImageTag, err := splitFullImageName(config.Image)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Fatalf("Container image '%v' incorrect", config.Image)
|
||||||
|
}
|
||||||
|
helmLogFields := map[string]interface{}{}
|
||||||
|
helmLogFields["Chart Path"] = config.ChartPath
|
||||||
|
helmLogFields["Namespace"] = config.Namespace
|
||||||
|
helmLogFields["Deployment Name"] = config.DeploymentName
|
||||||
|
helmLogFields["Context"] = config.KubeContext
|
||||||
|
helmLogFields["Kubeconfig"] = config.KubeConfig
|
||||||
|
log.Entry().WithFields(helmLogFields).Debug("Calling Helm")
|
||||||
|
|
||||||
|
helmEnv := []string{fmt.Sprintf("KUBECONFIG=%v", config.KubeConfig)}
|
||||||
|
if len(config.TillerNamespace) > 0 {
|
||||||
|
helmEnv = append(helmEnv, fmt.Sprintf("TILLER_NAMESPACE=%v", config.TillerNamespace))
|
||||||
|
}
|
||||||
|
log.Entry().Debugf("Helm Env: %v", helmEnv)
|
||||||
|
command.Env(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")
|
||||||
|
}
|
||||||
|
|
||||||
|
var dockerRegistrySecret bytes.Buffer
|
||||||
|
command.Stdout(&dockerRegistrySecret)
|
||||||
|
kubeParams := []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
"create",
|
||||||
|
"secret",
|
||||||
|
"docker-registry",
|
||||||
|
"regsecret",
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
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().WithError(err).Fatal("Retrieving Docker config via kubectl failed")
|
||||||
|
}
|
||||||
|
log.Entry().Debugf("Secret created: %v", string(dockerRegistrySecret.Bytes()))
|
||||||
|
|
||||||
|
var dockerRegistrySecretData struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Data struct {
|
||||||
|
DockerConfJSON string `json:".dockerconfigjson"`
|
||||||
|
} `json:"data"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(dockerRegistrySecret.Bytes(), &dockerRegistrySecretData); err != nil {
|
||||||
|
log.Entry().WithError(err).Fatal("Reading docker registry secret json failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
ingressHosts := ""
|
||||||
|
for i, h := range config.IngressHosts {
|
||||||
|
ingressHosts += fmt.Sprintf(",ingress.hosts[%v]=%v", i, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeParams := []string{
|
||||||
|
"upgrade",
|
||||||
|
config.DeploymentName,
|
||||||
|
config.ChartPath,
|
||||||
|
"--install",
|
||||||
|
"--force",
|
||||||
|
"--namespace",
|
||||||
|
config.Namespace,
|
||||||
|
"--wait",
|
||||||
|
"--timeout",
|
||||||
|
strconv.Itoa(config.HelmDeployWaitSeconds),
|
||||||
|
"--set",
|
||||||
|
fmt.Sprintf("image.repository=%v/%v,image.tag=%v,secret.dockerconfigjson=%v%v", containerRegistry, containerImageName, containerImageTag, dockerRegistrySecretData.Data.DockerConfJSON, ingressHosts),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.KubeContext) > 0 {
|
||||||
|
upgradeParams = append(upgradeParams, "--kube-context", config.KubeContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.AdditionalParameters) > 0 {
|
||||||
|
upgradeParams = append(upgradeParams, config.AdditionalParameters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Stdout(stdout)
|
||||||
|
log.Entry().Info("Calling helm upgrade ...")
|
||||||
|
log.Entry().Debugf("Helm parameters %v", upgradeParams)
|
||||||
|
command.RunExecutable("helm", upgradeParams...)
|
||||||
|
if err := command.RunExecutable("helm", upgradeParams...); err != nil {
|
||||||
|
log.Entry().WithError(err).Fatal("Helm upgrade call failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func runKubectlDeploy(config kubernetesDeployOptions, command envExecRunner) {
|
||||||
|
_, containerRegistry, err := splitRegistryURL(config.ContainerRegistryURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Fatalf("Container registry url '%v' incorrect", config.ContainerRegistryURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeParams := []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", config.Namespace),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.KubeConfig) > 0 {
|
||||||
|
log.Entry().Info("Using KUBECONFIG environment for authentication.")
|
||||||
|
kubeEnv := []string{fmt.Sprintf("KUBECONFIG=%v", config.KubeConfig)}
|
||||||
|
command.Env(kubeEnv)
|
||||||
|
if len(config.KubeContext) > 0 {
|
||||||
|
kubeParams = append(kubeParams, fmt.Sprintf("--context=%v", config.KubeContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
log.Entry().Info("Using --token parameter for authentication.")
|
||||||
|
kubeParams = append(kubeParams, fmt.Sprintf("--server=%v", config.APIServer))
|
||||||
|
kubeParams = append(kubeParams, fmt.Sprintf("--token=%v", config.KubeToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.CreateDockerRegistrySecret {
|
||||||
|
if len(config.ContainerRegistryUser)+len(config.ContainerRegistryPassword) == 0 {
|
||||||
|
log.Entry().Fatal("Cannot create Container registry secret without proper registry username/password")
|
||||||
|
}
|
||||||
|
// 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),
|
||||||
|
)
|
||||||
|
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 {
|
||||||
|
log.Entry().WithError(err).Fatal("Creating container registry secret failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
appTemplate, err := ioutil.ReadFile(config.AppTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Fatalf("Error when reading appTemplate '%v'", config.AppTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update image name in deployment yaml, expects placeholder like 'image: <image-name>'
|
||||||
|
re := regexp.MustCompile(`image:[ ]*<image-name>`)
|
||||||
|
appTemplate = []byte(re.ReplaceAllString(string(appTemplate), fmt.Sprintf("image: %v/%v", containerRegistry, config.Image)))
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(config.AppTemplate, appTemplate, 0700)
|
||||||
|
if err != nil {
|
||||||
|
log.Entry().WithError(err).Fatalf("Error when updating appTemplate '%v'", config.AppTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
kubeApplyParams := append(kubeParams, "apply", "--filename", config.AppTemplate)
|
||||||
|
if len(config.AdditionalParameters) > 0 {
|
||||||
|
kubeApplyParams = append(kubeApplyParams, config.AdditionalParameters...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := command.RunExecutable("kubectl", kubeApplyParams...); err != nil {
|
||||||
|
log.Entry().Debugf("Running kubectl with following parameters: %v", kubeApplyParams)
|
||||||
|
log.Entry().WithError(err).Fatal("Deployment with kubectl failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitRegistryURL(registryURL string) (protocol, registry string, err error) {
|
||||||
|
parts := strings.Split(registryURL, "://")
|
||||||
|
if len(parts) != 2 || len(parts[1]) == 0 {
|
||||||
|
return "", "", fmt.Errorf("Failed to split registry url '%v'", registryURL)
|
||||||
|
}
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitFullImageName(image string) (imageName, tag string, err error) {
|
||||||
|
parts := strings.Split(image, ":")
|
||||||
|
switch len(parts) {
|
||||||
|
case 0:
|
||||||
|
return "", "", fmt.Errorf("Failed to split image name '%v'", image)
|
||||||
|
case 1:
|
||||||
|
if len(parts[0]) > 0 {
|
||||||
|
return parts[0], "", nil
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf("Failed to split image name '%v'", image)
|
||||||
|
case 2:
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf("Failed to split image name '%v'", image)
|
||||||
|
}
|
267
cmd/kubernetesDeploy_generated.go
Normal file
267
cmd/kubernetesDeploy_generated.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/SAP/jenkins-library/pkg/config"
|
||||||
|
"github.com/SAP/jenkins-library/pkg/log"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
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"`
|
||||||
|
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"`
|
||||||
|
HelmDeployWaitSeconds int `json:"helmDeployWaitSeconds,omitempty"`
|
||||||
|
Image string `json:"image,omitempty"`
|
||||||
|
IngressHosts []string `json:"ingressHosts,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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var myKubernetesDeployOptions kubernetesDeployOptions
|
||||||
|
|
||||||
|
// KubernetesDeployCommand Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
|
||||||
|
func KubernetesDeployCommand() *cobra.Command {
|
||||||
|
metadata := kubernetesDeployMetadata()
|
||||||
|
|
||||||
|
var createKubernetesDeployCmd = &cobra.Command{
|
||||||
|
Use: "kubernetesDeploy",
|
||||||
|
Short: "Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.",
|
||||||
|
Long: `Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
|
||||||
|
|
||||||
|
!!! note "Deployment supports multiple deployment tools"
|
||||||
|
Currently the following are supported:
|
||||||
|
|
||||||
|
* [Helm](https://helm.sh/) command line tool and [Helm Charts](https://docs.helm.sh/developing_charts/#charts).
|
||||||
|
* [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) and ` + "`" + `kubectl apply` + "`" + ` command.
|
||||||
|
|
||||||
|
## Helm
|
||||||
|
Following helm command will be executed by default:
|
||||||
|
|
||||||
|
` + "`" + `` + "`" + `` + "`" + `
|
||||||
|
helm upgrade <deploymentName> <chartPath> --install --force --namespace <namespace> --wait --timeout <helmDeployWaitSeconds> --set "image.repository=<yourRegistry>/<yourImageName>,image.tag=<yourImageTag>,secret.dockerconfigjson=<dockerSecret>,ingress.hosts[0]=<ingressHosts[0]>,,ingress.hosts[1]=<ingressHosts[1]>,...
|
||||||
|
` + "`" + `` + "`" + `` + "`" + `
|
||||||
|
|
||||||
|
* ` + "`" + `yourRegistry` + "`" + ` will be retrieved from ` + "`" + `containerRegistryUrl` + "`" + `
|
||||||
|
* ` + "`" + `yourImageName` + "`" + `, ` + "`" + `yourImageTag` + "`" + ` will be retrieved from ` + "`" + `image` + "`" + `
|
||||||
|
* ` + "`" + `dockerSecret` + "`" + ` will be calculated with a call to ` + "`" + `kubectl create secret docker-registry regsecret --docker-server=<yourRegistry> --docker-username=<containerRegistryUser> --docker-password=<containerRegistryPassword> --dry-run=true --output=json'` + "`" + ``,
|
||||||
|
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
log.SetStepName("kubernetesDeploy")
|
||||||
|
log.SetVerbose(GeneralConfig.Verbose)
|
||||||
|
return PrepareConfig(cmd, &metadata, "kubernetesDeploy", &myKubernetesDeployOptions, config.OpenPiperFile)
|
||||||
|
},
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
return kubernetesDeploy(myKubernetesDeployOptions)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
addKubernetesDeployFlags(createKubernetesDeployCmd)
|
||||||
|
return createKubernetesDeployCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKubernetesDeployFlags(cmd *cobra.Command) {
|
||||||
|
cmd.Flags().StringSliceVar(&myKubernetesDeployOptions.AdditionalParameters, "additionalParameters", []string{}, "Defines additional parameters for \"helm install\" or \"kubectl apply\" command.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.APIServer, "apiServer", os.Getenv("PIPER_apiServer"), "Defines the Url of the API Server of the Kubernetes cluster.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.AppTemplate, "appTemplate", os.Getenv("PIPER_appTemplate"), "Defines the filename for the kubernetes app template (e.g. k8s_apptemplate.yaml)")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.ChartPath, "chartPath", os.Getenv("PIPER_chartPath"), "Defines the chart path for deployments using helm.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.ContainerRegistryPassword, "containerRegistryPassword", os.Getenv("PIPER_containerRegistryPassword"), "Password for container registry access - typically provided by the CI/CD environment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "http(s) url of the Container registry.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.ContainerRegistryUser, "containerRegistryUser", os.Getenv("PIPER_containerRegistryUser"), "Username for container registry access - typically provided by the CI/CD environment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.ContainerRegistrySecret, "containerRegistrySecret", "regsecret", "Name of the container registry secret used for pulling containers from the registry.")
|
||||||
|
cmd.Flags().BoolVar(&myKubernetesDeployOptions.CreateDockerRegistrySecret, "createDockerRegistrySecret", false, "Toggle to turn on Regsecret creation with a \"deployTool:kubectl\" deployment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.DeploymentName, "deploymentName", os.Getenv("PIPER_deploymentName"), "Defines the name of the deployment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.DeployTool, "deployTool", "kubectl", "Defines the tool which should be used for deployment.")
|
||||||
|
cmd.Flags().IntVar(&myKubernetesDeployOptions.HelmDeployWaitSeconds, "helmDeployWaitSeconds", 300, "Number of seconds before helm deploy returns.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.Image, "image", os.Getenv("PIPER_image"), "Full name of the image to be deployed.")
|
||||||
|
cmd.Flags().StringSliceVar(&myKubernetesDeployOptions.IngressHosts, "ingressHosts", []string{}, "List of ingress hosts to be exposed via helm deployment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.KubeConfig, "kubeConfig", os.Getenv("PIPER_kubeConfig"), "Defines the path to the \"kubeconfig\" file.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.KubeContext, "kubeContext", os.Getenv("PIPER_kubeContext"), "Defines the context to use from the \"kubeconfig\" file.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.KubeToken, "kubeToken", os.Getenv("PIPER_kubeToken"), "Contains the id_token used by kubectl for authentication. Consider using kubeConfig parameter instead.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.Namespace, "namespace", "default", "Defines the target Kubernetes namespace for the deployment.")
|
||||||
|
cmd.Flags().StringVar(&myKubernetesDeployOptions.TillerNamespace, "tillerNamespace", os.Getenv("PIPER_tillerNamespace"), "Defines optional tiller namespace for deployments using helm.")
|
||||||
|
|
||||||
|
cmd.MarkFlagRequired("chartPath")
|
||||||
|
cmd.MarkFlagRequired("containerRegistryUrl")
|
||||||
|
cmd.MarkFlagRequired("deploymentName")
|
||||||
|
cmd.MarkFlagRequired("deployTool")
|
||||||
|
cmd.MarkFlagRequired("image")
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieve step metadata
|
||||||
|
func kubernetesDeployMetadata() config.StepData {
|
||||||
|
var theMetaData = config.StepData{
|
||||||
|
Spec: config.StepSpec{
|
||||||
|
Inputs: config.StepInputs{
|
||||||
|
Parameters: []config.StepParameters{
|
||||||
|
{
|
||||||
|
Name: "additionalParameters",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "[]string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{{Name: "helmDeploymentParameters"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "apiServer",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{{Name: "k8sAPIServer"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "appTemplate",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{{Name: "k8sAppTemplate"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "chartPath",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: true,
|
||||||
|
Aliases: []config.Alias{{Name: "helmChartPath"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "containerRegistryPassword",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "containerRegistryUrl",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: true,
|
||||||
|
Aliases: []config.Alias{{Name: "dockerRegistryUrl"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "containerRegistryUser",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "containerRegistrySecret",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "createDockerRegistrySecret",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "bool",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "deploymentName",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: true,
|
||||||
|
Aliases: []config.Alias{{Name: "helmDeploymentName"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "deployTool",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: true,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "helmDeployWaitSeconds",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "int",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "image",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: true,
|
||||||
|
Aliases: []config.Alias{{Name: "deployImage"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ingressHosts",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "[]string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "kubeConfig",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "kubeContext",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "kubeToken",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "namespace",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{{Name: "helmDeploymentNamespace"}, {Name: "k8sDeploymentNamespace"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tillerNamespace",
|
||||||
|
ResourceRef: []config.ResourceReference{},
|
||||||
|
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||||
|
Type: "string",
|
||||||
|
Mandatory: false,
|
||||||
|
Aliases: []config.Alias{{Name: "helmTillerNamespace"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return theMetaData
|
||||||
|
}
|
16
cmd/kubernetesDeploy_generated_test.go
Normal file
16
cmd/kubernetesDeploy_generated_test.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKubernetesDeployCommand(t *testing.T) {
|
||||||
|
|
||||||
|
testCmd := KubernetesDeployCommand()
|
||||||
|
|
||||||
|
// only high level testing performed - details are tested in step generation procudure
|
||||||
|
assert.Equal(t, "kubernetesDeploy", testCmd.Use, "command name incorrect")
|
||||||
|
|
||||||
|
}
|
273
cmd/kubernetesDeploy_test.go
Normal file
273
cmd/kubernetesDeploy_test.go
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRunKubernetesDeploy(t *testing.T) {
|
||||||
|
|
||||||
|
t.Run("test helm", func(t *testing.T) {
|
||||||
|
opts := kubernetesDeployOptions{
|
||||||
|
ContainerRegistryURL: "https://my.registry:55555",
|
||||||
|
ContainerRegistryUser: "registryUser",
|
||||||
|
ContainerRegistryPassword: "********",
|
||||||
|
ChartPath: "path/to/chart",
|
||||||
|
DeploymentName: "deploymentName",
|
||||||
|
DeployTool: "helm",
|
||||||
|
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 := 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, "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{"--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[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",
|
||||||
|
"--force",
|
||||||
|
"--namespace",
|
||||||
|
"deploymentNamespace",
|
||||||
|
"--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",
|
||||||
|
"testValue",
|
||||||
|
}, e.calls[2].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
|
||||||
|
assert.NoError(t, err, "Error when creating temp dir")
|
||||||
|
|
||||||
|
opts := kubernetesDeployOptions{
|
||||||
|
AppTemplate: filepath.Join(dir, "test.yaml"),
|
||||||
|
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 := 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[0], []string{"KUBECONFIG=This is my kubeconfig"})
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[0].exec, "Wrong secret lookup command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
fmt.Sprintf("--context=%v", opts.KubeContext),
|
||||||
|
"get",
|
||||||
|
"secret",
|
||||||
|
opts.ContainerRegistrySecret,
|
||||||
|
}, e.calls[0].params, "kubectl parameters incorrect")
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[1].exec, "Wrong secret create command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
fmt.Sprintf("--context=%v", opts.KubeContext),
|
||||||
|
"create",
|
||||||
|
"secret",
|
||||||
|
"docker-registry",
|
||||||
|
opts.ContainerRegistrySecret,
|
||||||
|
"--docker-server=my.registry:55555",
|
||||||
|
fmt.Sprintf("--docker-username=%v", opts.ContainerRegistryUser),
|
||||||
|
fmt.Sprintf("--docker-password=%v", opts.ContainerRegistryPassword),
|
||||||
|
}, e.calls[1].params, "kubectl parameters incorrect")
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[2].exec, "Wrong apply command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
fmt.Sprintf("--context=%v", opts.KubeContext),
|
||||||
|
"apply",
|
||||||
|
"--filename",
|
||||||
|
opts.AppTemplate,
|
||||||
|
"--testParam",
|
||||||
|
"testValue",
|
||||||
|
}, e.calls[2].params, "kubectl parameters incorrect")
|
||||||
|
|
||||||
|
appTemplate, err := ioutil.ReadFile(opts.AppTemplate)
|
||||||
|
assert.Contains(t, string(appTemplate), "my.registry:55555/path/to/Image:latest")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test kubectl - lookup secret/kubeconfig", 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"),
|
||||||
|
ContainerRegistryURL: "https://my.registry:55555",
|
||||||
|
ContainerRegistryUser: "registryUser",
|
||||||
|
ContainerRegistryPassword: "********",
|
||||||
|
ContainerRegistrySecret: "regSecret",
|
||||||
|
CreateDockerRegistrySecret: true,
|
||||||
|
DeployTool: "kubectl",
|
||||||
|
Image: "path/to/Image:latest",
|
||||||
|
KubeConfig: "This is my kubeconfig",
|
||||||
|
Namespace: "deploymentNamespace",
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(opts.AppTemplate, []byte("testYaml"), 0755)
|
||||||
|
|
||||||
|
e := execMockRunner{}
|
||||||
|
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
runKubernetesDeploy(opts, &e, &stdout)
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[0].exec, "Wrong secret lookup command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
"get",
|
||||||
|
"secret",
|
||||||
|
opts.ContainerRegistrySecret,
|
||||||
|
}, e.calls[0].params, "kubectl parameters incorrect")
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[1].exec, "Wrong apply command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
"apply",
|
||||||
|
"--filename",
|
||||||
|
opts.AppTemplate,
|
||||||
|
}, e.calls[1].params, "kubectl parameters incorrect")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("test kubectl - token only", 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{
|
||||||
|
APIServer: "https://my.api.server",
|
||||||
|
AppTemplate: filepath.Join(dir, "test.yaml"),
|
||||||
|
ContainerRegistryURL: "https://my.registry:55555",
|
||||||
|
ContainerRegistryUser: "registryUser",
|
||||||
|
ContainerRegistryPassword: "********",
|
||||||
|
ContainerRegistrySecret: "regSecret",
|
||||||
|
DeployTool: "kubectl",
|
||||||
|
Image: "path/to/Image:latest",
|
||||||
|
KubeToken: "testToken",
|
||||||
|
Namespace: "deploymentNamespace",
|
||||||
|
}
|
||||||
|
|
||||||
|
ioutil.WriteFile(opts.AppTemplate, []byte("testYaml"), 0755)
|
||||||
|
|
||||||
|
e := execMockRunner{
|
||||||
|
shouldFailOnCommand: map[string]error{},
|
||||||
|
}
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
runKubernetesDeploy(opts, &e, &stdout)
|
||||||
|
|
||||||
|
assert.Equal(t, "kubectl", e.calls[0].exec, "Wrong apply command")
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"--insecure-skip-tls-verify=true",
|
||||||
|
fmt.Sprintf("--namespace=%v", opts.Namespace),
|
||||||
|
fmt.Sprintf("--server=%v", opts.APIServer),
|
||||||
|
fmt.Sprintf("--token=%v", opts.KubeToken),
|
||||||
|
"apply",
|
||||||
|
"--filename",
|
||||||
|
opts.AppTemplate,
|
||||||
|
}, e.calls[0].params, "kubectl parameters incorrect")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitRegistryURL(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
in string
|
||||||
|
outProtocol string
|
||||||
|
outRegistry string
|
||||||
|
outError error
|
||||||
|
}{
|
||||||
|
{in: "https://my.registry.com", outProtocol: "https", outRegistry: "my.registry.com", outError: nil},
|
||||||
|
{in: "https://", outProtocol: "", outRegistry: "", outError: fmt.Errorf("Failed to split registry url 'https://'")},
|
||||||
|
{in: "my.registry.com", outProtocol: "", outRegistry: "", outError: fmt.Errorf("Failed to split registry url 'my.registry.com'")},
|
||||||
|
{in: "", outProtocol: "", outRegistry: "", outError: fmt.Errorf("Failed to split registry url ''")},
|
||||||
|
{in: "https://https://my.registry.com", outProtocol: "", outRegistry: "", outError: fmt.Errorf("Failed to split registry url 'https://https://my.registry.com'")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tt {
|
||||||
|
p, r, err := splitRegistryURL(test.in)
|
||||||
|
assert.Equal(t, test.outProtocol, p, "Protocol value unexpected")
|
||||||
|
assert.Equal(t, test.outRegistry, r, "Registry value unexpected")
|
||||||
|
assert.Equal(t, test.outError, err, "Error value not as expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitImageName(t *testing.T) {
|
||||||
|
tt := []struct {
|
||||||
|
in string
|
||||||
|
outImage string
|
||||||
|
outTag string
|
||||||
|
outError error
|
||||||
|
}{
|
||||||
|
{in: "", outImage: "", outTag: "", outError: fmt.Errorf("Failed to split image name ''")},
|
||||||
|
{in: "path/to/image", outImage: "path/to/image", outTag: "", outError: nil},
|
||||||
|
{in: "path/to/image:tag", outImage: "path/to/image", outTag: "tag", outError: nil},
|
||||||
|
{in: "https://my.registry.com/path/to/image:tag", outImage: "", outTag: "", outError: fmt.Errorf("Failed to split image name 'https://my.registry.com/path/to/image:tag'")},
|
||||||
|
}
|
||||||
|
for _, test := range tt {
|
||||||
|
i, tag, err := splitFullImageName(test.in)
|
||||||
|
assert.Equal(t, test.outImage, i, "Image value unexpected")
|
||||||
|
assert.Equal(t, test.outTag, tag, "Tag value unexpected")
|
||||||
|
assert.Equal(t, test.outError, err, "Error value not as expected")
|
||||||
|
}
|
||||||
|
}
|
@@ -46,6 +46,7 @@ func Execute() {
|
|||||||
rootCmd.AddCommand(VersionCommand())
|
rootCmd.AddCommand(VersionCommand())
|
||||||
rootCmd.AddCommand(DetectExecuteScanCommand())
|
rootCmd.AddCommand(DetectExecuteScanCommand())
|
||||||
rootCmd.AddCommand(KarmaExecuteTestsCommand())
|
rootCmd.AddCommand(KarmaExecuteTestsCommand())
|
||||||
|
rootCmd.AddCommand(KubernetesDeployCommand())
|
||||||
rootCmd.AddCommand(XsDeployCommand())
|
rootCmd.AddCommand(XsDeployCommand())
|
||||||
rootCmd.AddCommand(GithubPublishReleaseCommand())
|
rootCmd.AddCommand(GithubPublishReleaseCommand())
|
||||||
rootCmd.AddCommand(GithubCreatePullRequestCommand())
|
rootCmd.AddCommand(GithubCreatePullRequestCommand())
|
||||||
|
@@ -14,10 +14,13 @@ import (
|
|||||||
|
|
||||||
type execMockRunner struct {
|
type execMockRunner struct {
|
||||||
dir []string
|
dir []string
|
||||||
|
env [][]string
|
||||||
calls []execCall
|
calls []execCall
|
||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
stderr io.Writer
|
stderr io.Writer
|
||||||
|
stdoutReturn map[string]string
|
||||||
shouldFailWith error
|
shouldFailWith error
|
||||||
|
shouldFailOnCommand map[string]error
|
||||||
}
|
}
|
||||||
|
|
||||||
type execCall struct {
|
type execCall struct {
|
||||||
@@ -27,6 +30,7 @@ type execCall struct {
|
|||||||
|
|
||||||
type shellMockRunner struct {
|
type shellMockRunner struct {
|
||||||
dir string
|
dir string
|
||||||
|
env [][]string
|
||||||
calls []string
|
calls []string
|
||||||
shell []string
|
shell []string
|
||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
@@ -38,12 +42,26 @@ func (m *execMockRunner) Dir(d string) {
|
|||||||
m.dir = append(m.dir, d)
|
m.dir = append(m.dir, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *execMockRunner) Env(e []string) {
|
||||||
|
m.env = append(m.env, e)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *execMockRunner) RunExecutable(e string, p ...string) error {
|
func (m *execMockRunner) RunExecutable(e string, p ...string) error {
|
||||||
if m.shouldFailWith != nil {
|
if m.shouldFailWith != nil {
|
||||||
return m.shouldFailWith
|
return m.shouldFailWith
|
||||||
}
|
}
|
||||||
|
|
||||||
exec := execCall{exec: e, params: p}
|
exec := execCall{exec: e, params: p}
|
||||||
m.calls = append(m.calls, exec)
|
m.calls = append(m.calls, exec)
|
||||||
|
|
||||||
|
if c := strings.Join(append([]string{e}, p...), " "); m.shouldFailOnCommand != nil && m.shouldFailOnCommand[c] != nil {
|
||||||
|
return m.shouldFailOnCommand[c]
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := strings.Join(append([]string{e}, p...), " "); m.stdoutReturn != nil && len(m.stdoutReturn[c]) > 0 {
|
||||||
|
m.stdout.Write([]byte(m.stdoutReturn[c]))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +77,10 @@ func (m *shellMockRunner) Dir(d string) {
|
|||||||
m.dir = d
|
m.dir = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *shellMockRunner) Env(e []string) {
|
||||||
|
m.env = append(m.env, e)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *shellMockRunner) RunShell(s string, c string) error {
|
func (m *shellMockRunner) RunShell(s string, c string) error {
|
||||||
|
|
||||||
if m.shouldFailWith != nil {
|
if m.shouldFailWith != nil {
|
||||||
|
@@ -16,6 +16,7 @@ type Command struct {
|
|||||||
dir string
|
dir string
|
||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
stderr io.Writer
|
stderr io.Writer
|
||||||
|
env []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dir sets the working directory for the execution
|
// Dir sets the working directory for the execution
|
||||||
@@ -23,6 +24,11 @@ func (c *Command) Dir(d string) {
|
|||||||
c.dir = d
|
c.dir = d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Env sets explicit environment variables to be used for execution
|
||||||
|
func (c *Command) Env(e []string) {
|
||||||
|
c.env = e
|
||||||
|
}
|
||||||
|
|
||||||
// Stdout ..
|
// Stdout ..
|
||||||
func (c *Command) Stdout(stdout io.Writer) {
|
func (c *Command) Stdout(stdout io.Writer) {
|
||||||
c.stdout = stdout
|
c.stdout = stdout
|
||||||
@@ -43,7 +49,14 @@ func (c *Command) RunShell(shell, script string) error {
|
|||||||
|
|
||||||
cmd := ExecCommand(shell)
|
cmd := ExecCommand(shell)
|
||||||
|
|
||||||
|
if len(c.dir) > 0 {
|
||||||
cmd.Dir = c.dir
|
cmd.Dir = c.dir
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.env) > 0 {
|
||||||
|
cmd.Env = c.env
|
||||||
|
}
|
||||||
|
|
||||||
in := bytes.Buffer{}
|
in := bytes.Buffer{}
|
||||||
in.Write([]byte(script))
|
in.Write([]byte(script))
|
||||||
cmd.Stdin = &in
|
cmd.Stdin = &in
|
||||||
@@ -65,6 +78,10 @@ func (c *Command) RunExecutable(executable string, params ...string) error {
|
|||||||
cmd.Dir = c.dir
|
cmd.Dir = c.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(c.env) > 0 {
|
||||||
|
cmd.Env = c.env
|
||||||
|
}
|
||||||
|
|
||||||
if err := runCmd(cmd, _out, _err); err != nil {
|
if err := runCmd(cmd, _out, _err); err != nil {
|
||||||
return errors.Wrapf(err, "running command '%v' failed", executable)
|
return errors.Wrapf(err, "running command '%v' failed", executable)
|
||||||
}
|
}
|
||||||
|
@@ -208,6 +208,7 @@ func (m *StepData) GetContextParameterFilters() StepFilters {
|
|||||||
for _, condition := range container.Conditions {
|
for _, condition := range container.Conditions {
|
||||||
for _, dependentParam := range condition.Params {
|
for _, dependentParam := range condition.Params {
|
||||||
parameterKeys = append(parameterKeys, dependentParam.Value)
|
parameterKeys = append(parameterKeys, dependentParam.Value)
|
||||||
|
parameterKeys = append(parameterKeys, dependentParam.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,6 +217,7 @@ func (m *StepData) GetContextParameterFilters() StepFilters {
|
|||||||
if len(m.Spec.Sidecars) > 0 {
|
if len(m.Spec.Sidecars) > 0 {
|
||||||
//ToDo: support fallback for "dockerName" configuration property -> via aliasing?
|
//ToDo: support fallback for "dockerName" configuration property -> via aliasing?
|
||||||
containerFilters = append(containerFilters, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}...)
|
containerFilters = append(containerFilters, []string{"containerName", "containerPortMappings", "dockerName", "sidecarEnvVars", "sidecarImage", "sidecarName", "sidecarOptions", "sidecarPullImage", "sidecarReadyCommand", "sidecarVolumeBind", "sidecarWorkspace"}...)
|
||||||
|
//ToDo: add condition param.Value and param.Name to filter as for Containers
|
||||||
}
|
}
|
||||||
if len(containerFilters) > 0 {
|
if len(containerFilters) > 0 {
|
||||||
filters.All = append(filters.All, containerFilters...)
|
filters.All = append(filters.All, containerFilters...)
|
||||||
|
@@ -266,12 +266,12 @@ func TestGetContextParameterFilters(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("Containers", func(t *testing.T) {
|
t.Run("Containers", func(t *testing.T) {
|
||||||
filters := metadata2.GetContextParameterFilters()
|
filters := metadata2.GetContextParameterFilters()
|
||||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.All, "incorrect filter All")
|
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.All, "incorrect filter All")
|
||||||
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.General, "incorrect filter General")
|
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.General, "incorrect filter General")
|
||||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.Steps, "incorrect filter Steps")
|
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.Steps, "incorrect filter Steps")
|
||||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.Stages, "incorrect filter Stages")
|
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.Stages, "incorrect filter Stages")
|
||||||
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.Parameters, "incorrect filter Parameters")
|
assert.Equal(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.Parameters, "incorrect filter Parameters")
|
||||||
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip"}, filters.Env, "incorrect filter Env")
|
assert.NotEqual(t, []string{"containerCommand", "containerShell", "dockerEnvVars", "dockerImage", "dockerOptions", "dockerPullImage", "dockerVolumeBind", "dockerWorkspace", "pip", "scanType"}, filters.Env, "incorrect filter Env")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Sidecars", func(t *testing.T) {
|
t.Run("Sidecars", func(t *testing.T) {
|
||||||
|
@@ -68,12 +68,16 @@ func setDefaultStepParameters(stepData *config.StepData) {
|
|||||||
switch param.Type {
|
switch param.Type {
|
||||||
case "bool":
|
case "bool":
|
||||||
param.Default = "false"
|
param.Default = "false"
|
||||||
|
case "int":
|
||||||
|
param.Default = "0"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch param.Type {
|
switch param.Type {
|
||||||
case "string":
|
case "string":
|
||||||
case "bool":
|
case "bool":
|
||||||
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
||||||
|
case "int":
|
||||||
|
param.Default = fmt.Sprintf("%v", param.Default)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -214,12 +214,14 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) {
|
|||||||
|
|
||||||
if param.Default == nil {
|
if param.Default == nil {
|
||||||
switch param.Type {
|
switch param.Type {
|
||||||
case "string":
|
|
||||||
param.Default = fmt.Sprintf("os.Getenv(\"PIPER_%v\")", param.Name)
|
|
||||||
osImportRequired = true
|
|
||||||
case "bool":
|
case "bool":
|
||||||
// ToDo: Check if default should be read from env
|
// ToDo: Check if default should be read from env
|
||||||
param.Default = "false"
|
param.Default = "false"
|
||||||
|
case "int":
|
||||||
|
param.Default = "0"
|
||||||
|
case "string":
|
||||||
|
param.Default = fmt.Sprintf("os.Getenv(\"PIPER_%v\")", param.Name)
|
||||||
|
osImportRequired = true
|
||||||
case "[]string":
|
case "[]string":
|
||||||
// ToDo: Check if default should be read from env
|
// ToDo: Check if default should be read from env
|
||||||
param.Default = "[]string{}"
|
param.Default = "[]string{}"
|
||||||
@@ -228,14 +230,16 @@ func setDefaultParameters(stepData *config.StepData) (bool, error) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
switch param.Type {
|
switch param.Type {
|
||||||
case "string":
|
|
||||||
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
|
||||||
case "bool":
|
case "bool":
|
||||||
boolVal := "false"
|
boolVal := "false"
|
||||||
if param.Default.(bool) == true {
|
if param.Default.(bool) == true {
|
||||||
boolVal = "true"
|
boolVal = "true"
|
||||||
}
|
}
|
||||||
param.Default = boolVal
|
param.Default = boolVal
|
||||||
|
case "int":
|
||||||
|
param.Default = fmt.Sprintf("%v", param.Default)
|
||||||
|
case "string":
|
||||||
|
param.Default = fmt.Sprintf("\"%v\"", param.Default)
|
||||||
case "[]string":
|
case "[]string":
|
||||||
param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(getStringSliceFromInterface(param.Default), "\", \""))
|
param.Default = fmt.Sprintf("[]string{\"%v\"}", strings.Join(getStringSliceFromInterface(param.Default), "\", \""))
|
||||||
default:
|
default:
|
||||||
@@ -430,6 +434,8 @@ func flagType(paramType string) string {
|
|||||||
switch paramType {
|
switch paramType {
|
||||||
case "bool":
|
case "bool":
|
||||||
theFlagType = "BoolVar"
|
theFlagType = "BoolVar"
|
||||||
|
case "int":
|
||||||
|
theFlagType = "IntVar"
|
||||||
case "string":
|
case "string":
|
||||||
theFlagType = "StringVar"
|
theFlagType = "StringVar"
|
||||||
case "[]string":
|
case "[]string":
|
||||||
|
@@ -115,12 +115,14 @@ func TestSetDefaultParameters(t *testing.T) {
|
|||||||
Spec: config.StepSpec{
|
Spec: config.StepSpec{
|
||||||
Inputs: config.StepInputs{
|
Inputs: config.StepInputs{
|
||||||
Parameters: []config.StepParameters{
|
Parameters: []config.StepParameters{
|
||||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "string", Default: "val0"},
|
{Name: "param0", Type: "string", Default: "val0"},
|
||||||
{Name: "param1", Scope: []string{"STEPS"}, Type: "string"},
|
{Name: "param1", Type: "string"},
|
||||||
{Name: "param2", Scope: []string{"STAGES"}, Type: "bool", Default: true},
|
{Name: "param2", Type: "bool", Default: true},
|
||||||
{Name: "param3", Scope: []string{"PARAMETERS"}, Type: "bool"},
|
{Name: "param3", Type: "bool"},
|
||||||
{Name: "param4", Scope: []string{"ENV"}, Type: "[]string", Default: stringSliceDefault},
|
{Name: "param4", Type: "[]string", Default: stringSliceDefault},
|
||||||
{Name: "param5", Scope: []string{"ENV"}, Type: "[]string"},
|
{Name: "param5", Type: "[]string"},
|
||||||
|
{Name: "param6", Type: "int"},
|
||||||
|
{Name: "param7", Type: "int", Default: 1},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -133,6 +135,8 @@ func TestSetDefaultParameters(t *testing.T) {
|
|||||||
"false",
|
"false",
|
||||||
"[]string{\"val4_1\", \"val4_2\"}",
|
"[]string{\"val4_1\", \"val4_2\"}",
|
||||||
"[]string{}",
|
"[]string{}",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
}
|
}
|
||||||
|
|
||||||
osImport, err := setDefaultParameters(&stepData)
|
osImport, err := setDefaultParameters(&stepData)
|
||||||
@@ -152,8 +156,8 @@ func TestSetDefaultParameters(t *testing.T) {
|
|||||||
Spec: config.StepSpec{
|
Spec: config.StepSpec{
|
||||||
Inputs: config.StepInputs{
|
Inputs: config.StepInputs{
|
||||||
Parameters: []config.StepParameters{
|
Parameters: []config.StepParameters{
|
||||||
{Name: "param0", Scope: []string{"GENERAL"}, Type: "int", Default: 10},
|
{Name: "param0", Type: "n/a", Default: 10},
|
||||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
{Name: "param1", Type: "n/a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -162,7 +166,7 @@ func TestSetDefaultParameters(t *testing.T) {
|
|||||||
Spec: config.StepSpec{
|
Spec: config.StepSpec{
|
||||||
Inputs: config.StepInputs{
|
Inputs: config.StepInputs{
|
||||||
Parameters: []config.StepParameters{
|
Parameters: []config.StepParameters{
|
||||||
{Name: "param1", Scope: []string{"GENERAL"}, Type: "int"},
|
{Name: "param1", Type: "n/a"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -246,6 +250,7 @@ func TestFlagType(t *testing.T) {
|
|||||||
expected string
|
expected string
|
||||||
}{
|
}{
|
||||||
{input: "bool", expected: "BoolVar"},
|
{input: "bool", expected: "BoolVar"},
|
||||||
|
{input: "int", expected: "IntVar"},
|
||||||
{input: "string", expected: "StringVar"},
|
{input: "string", expected: "StringVar"},
|
||||||
{input: "[]string", expected: "StringSliceVar"},
|
{input: "[]string", expected: "StringSliceVar"},
|
||||||
}
|
}
|
||||||
|
220
resources/metadata/kubernetesdeploy.yaml
Normal file
220
resources/metadata/kubernetesdeploy.yaml
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
metadata:
|
||||||
|
name: kubernetesDeploy
|
||||||
|
description: Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
|
||||||
|
longDescription: |-
|
||||||
|
Deployment to Kubernetes test or production namespace within the specified Kubernetes cluster.
|
||||||
|
|
||||||
|
!!! note "Deployment supports multiple deployment tools"
|
||||||
|
Currently the following are supported:
|
||||||
|
|
||||||
|
* [Helm](https://helm.sh/) command line tool and [Helm Charts](https://docs.helm.sh/developing_charts/#charts).
|
||||||
|
* [kubectl](https://kubernetes.io/docs/reference/kubectl/overview/) and `kubectl apply` command.
|
||||||
|
|
||||||
|
## Helm
|
||||||
|
Following helm command will be executed by default:
|
||||||
|
|
||||||
|
```
|
||||||
|
helm upgrade <deploymentName> <chartPath> --install --force --namespace <namespace> --wait --timeout <helmDeployWaitSeconds> --set "image.repository=<yourRegistry>/<yourImageName>,image.tag=<yourImageTag>,secret.dockerconfigjson=<dockerSecret>,ingress.hosts[0]=<ingressHosts[0]>,,ingress.hosts[1]=<ingressHosts[1]>,...
|
||||||
|
```
|
||||||
|
|
||||||
|
* `yourRegistry` will be retrieved from `containerRegistryUrl`
|
||||||
|
* `yourImageName`, `yourImageTag` will be retrieved from `image`
|
||||||
|
* `dockerSecret` will be calculated with a call to `kubectl create secret docker-registry regsecret --docker-server=<yourRegistry> --docker-username=<containerRegistryUser> --docker-password=<containerRegistryPassword> --dry-run=true --output=json'`
|
||||||
|
spec:
|
||||||
|
inputs:
|
||||||
|
secrets:
|
||||||
|
- name: kubeConfigFileCredentialsId
|
||||||
|
type: jenkins
|
||||||
|
- name: kubeTokenCredentialsId
|
||||||
|
type: jenkins
|
||||||
|
- name: dockerCredentialsId
|
||||||
|
type: jenkins
|
||||||
|
resources:
|
||||||
|
- name: deployDescriptor
|
||||||
|
type: stash
|
||||||
|
params:
|
||||||
|
- name: additionalParameters
|
||||||
|
aliases:
|
||||||
|
- name: helmDeploymentParameters
|
||||||
|
type: '[]string'
|
||||||
|
description: Defines additional parameters for \"helm install\" or \"kubectl apply\" command.
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: apiServer
|
||||||
|
aliases:
|
||||||
|
- name: k8sAPIServer
|
||||||
|
type: string
|
||||||
|
description: Defines the Url of the API Server of the Kubernetes cluster.
|
||||||
|
scope:
|
||||||
|
- GENERAL
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: appTemplate
|
||||||
|
aliases:
|
||||||
|
- name: k8sAppTemplate
|
||||||
|
type: string
|
||||||
|
description: Defines the filename for the kubernetes app template (e.g. k8s_apptemplate.yaml)
|
||||||
|
mandatory: false
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: chartPath
|
||||||
|
aliases:
|
||||||
|
- name: helmChartPath
|
||||||
|
type: string
|
||||||
|
description: Defines the chart path for deployments using helm.
|
||||||
|
mandatory: true
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: containerRegistryPassword
|
||||||
|
description: Password for container registry access - typically provided by the CI/CD environment.
|
||||||
|
type: string
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: containerRegistryUrl
|
||||||
|
aliases:
|
||||||
|
- name: dockerRegistryUrl
|
||||||
|
type: string
|
||||||
|
description: http(s) url of the Container registry.
|
||||||
|
scope:
|
||||||
|
- GENERAL
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
mandatory: true
|
||||||
|
- name: containerRegistryUser
|
||||||
|
description: Username for container registry access - typically provided by the CI/CD environment.
|
||||||
|
type: string
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: containerRegistrySecret
|
||||||
|
description: Name of the container registry secret used for pulling containers from the registry.
|
||||||
|
type: string
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
default: regsecret
|
||||||
|
- name: createDockerRegistrySecret
|
||||||
|
type: bool
|
||||||
|
description: Toggle to turn on Regsecret creation with a \"deployTool:kubectl\" deployment.
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
default: false
|
||||||
|
- name: deploymentName
|
||||||
|
aliases:
|
||||||
|
- name: helmDeploymentName
|
||||||
|
type: string
|
||||||
|
description: Defines the name of the deployment.
|
||||||
|
mandatory: true
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: deployTool
|
||||||
|
type: string
|
||||||
|
description: Defines the tool which should be used for deployment.
|
||||||
|
mandatory: true
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
default: kubectl
|
||||||
|
- name: helmDeployWaitSeconds
|
||||||
|
type: int
|
||||||
|
description: Number of seconds before helm deploy returns.
|
||||||
|
mandatory: false
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
default: 300
|
||||||
|
- name: image
|
||||||
|
aliases:
|
||||||
|
- name: deployImage
|
||||||
|
type: string
|
||||||
|
description: Full name of the image to be deployed.
|
||||||
|
mandatory: true
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: ingressHosts
|
||||||
|
type: '[]string'
|
||||||
|
description: List of ingress hosts to be exposed via helm deployment.
|
||||||
|
mandatory: false
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: kubeConfig
|
||||||
|
type: string
|
||||||
|
description: Defines the path to the \"kubeconfig\" file.
|
||||||
|
scope:
|
||||||
|
- GENERAL
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: kubeContext
|
||||||
|
type: string
|
||||||
|
description: Defines the context to use from the \"kubeconfig\" file.
|
||||||
|
mandatory: false
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: kubeToken
|
||||||
|
type: string
|
||||||
|
description: Contains the id_token used by kubectl for authentication. Consider using kubeConfig parameter instead.
|
||||||
|
scope:
|
||||||
|
- GENERAL
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
- name: namespace
|
||||||
|
aliases:
|
||||||
|
- name: helmDeploymentNamespace
|
||||||
|
- name: k8sDeploymentNamespace
|
||||||
|
type: string
|
||||||
|
description: Defines the target Kubernetes namespace for the deployment.
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
default: default
|
||||||
|
- name: tillerNamespace
|
||||||
|
aliases:
|
||||||
|
- name: helmTillerNamespace
|
||||||
|
type: string
|
||||||
|
description: Defines optional tiller namespace for deployments using helm.
|
||||||
|
scope:
|
||||||
|
- PARAMETERS
|
||||||
|
- STAGES
|
||||||
|
- STEPS
|
||||||
|
containers:
|
||||||
|
- image: dtzar/helm-kubectl:2.12.1
|
||||||
|
workingDir: /config
|
||||||
|
conditions:
|
||||||
|
- conditionRef: strings-equal
|
||||||
|
params:
|
||||||
|
- name: deployTool
|
||||||
|
value: helm
|
||||||
|
- image: dtzar/helm-kubectl:2.12.1
|
||||||
|
workingDir: /config
|
||||||
|
conditions:
|
||||||
|
- conditionRef: strings-equal
|
||||||
|
params:
|
||||||
|
- name: deployTool
|
||||||
|
value: kubectl
|
@@ -116,6 +116,7 @@ public class CommonStepsTest extends BasePiperTest{
|
|||||||
'piperStageWrapper', //intended to be called from within stages
|
'piperStageWrapper', //intended to be called from within stages
|
||||||
'buildSetResult',
|
'buildSetResult',
|
||||||
'githubPublishRelease', //implementing new golang pattern without fields
|
'githubPublishRelease', //implementing new golang pattern without fields
|
||||||
|
'kubernetesDeploy', //implementing new golang pattern without fields
|
||||||
'xsDeploy', //implementing new golang pattern without fields
|
'xsDeploy', //implementing new golang pattern without fields
|
||||||
]
|
]
|
||||||
|
|
||||||
|
96
test/groovy/KubernetesDeployTest.groovy
Normal file
96
test/groovy/KubernetesDeployTest.groovy
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.RuleChain
|
||||||
|
import util.*
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*
|
||||||
|
import static org.junit.Assert.assertThat
|
||||||
|
|
||||||
|
class KubernetesDeployTest extends BasePiperTest {
|
||||||
|
|
||||||
|
private JenkinsReadJsonRule readJsonRule = new JenkinsReadJsonRule(this)
|
||||||
|
private JenkinsShellCallRule shellCallRule = new JenkinsShellCallRule(this)
|
||||||
|
private JenkinsStepRule stepRule = new JenkinsStepRule(this)
|
||||||
|
private JenkinsWriteFileRule writeFileRule = new JenkinsWriteFileRule(this)
|
||||||
|
private JenkinsDockerExecuteRule dockerExecuteRule = new JenkinsDockerExecuteRule(this)
|
||||||
|
|
||||||
|
private List withEnvArgs = []
|
||||||
|
private List credentials = []
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public RuleChain rules = Rules
|
||||||
|
.getCommonRules(this)
|
||||||
|
.around(new JenkinsReadYamlRule(this))
|
||||||
|
.around(readJsonRule)
|
||||||
|
.around(shellCallRule)
|
||||||
|
.around(stepRule)
|
||||||
|
.around(writeFileRule)
|
||||||
|
.around(dockerExecuteRule)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void init() {
|
||||||
|
credentials = []
|
||||||
|
helper.registerAllowedMethod("withEnv", [List.class, Closure.class], {arguments, closure ->
|
||||||
|
arguments.each {arg ->
|
||||||
|
withEnvArgs.add(arg.toString())
|
||||||
|
}
|
||||||
|
return closure()
|
||||||
|
})
|
||||||
|
|
||||||
|
helper.registerAllowedMethod('file', [Map], { m -> return m })
|
||||||
|
helper.registerAllowedMethod('string', [Map], { m -> return m })
|
||||||
|
helper.registerAllowedMethod('usernamePassword', [Map], { m -> return m })
|
||||||
|
helper.registerAllowedMethod('withCredentials', [List, Closure], { l, c ->
|
||||||
|
l.each {m ->
|
||||||
|
credentials.add(m)
|
||||||
|
if (m.credentialsId == 'kubeConfig') {
|
||||||
|
binding.setProperty('PIPER_kubeConfig', 'myKubeConfig')
|
||||||
|
} else if (m.credentialsId == 'kubeToken') {
|
||||||
|
binding.setProperty('PIPER_kubeToken','myKubeToken')
|
||||||
|
} else if (m.credentialsId == 'dockerCredentials') {
|
||||||
|
binding.setProperty('PIPER_containerRegistryUser', 'registryUser')
|
||||||
|
binding.setProperty('PIPER_containerRegistryPassword', '********')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
c()
|
||||||
|
} finally {
|
||||||
|
binding.setProperty('PIPER_kubeConfig', null)
|
||||||
|
binding.setProperty('PIPER_kubeToken', null)
|
||||||
|
binding.setProperty('PIPER_containerRegistryUser', null)
|
||||||
|
binding.setProperty('PIPER_containerRegistryPassword', null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testKubernetesDeployAllCreds() {
|
||||||
|
shellCallRule.setReturnValue('./piper getConfig --contextConfig --stepMetadata \'metadata/kubernetesdeploy.yaml\'', '{"kubeConfigFileCredentialsId":"kubeConfig", "kubeTokenCredentialsId":"kubeToken", "dockerCredentialsId":"dockerCredentials", "dockerImage":"my.Registry/K8S:latest"}')
|
||||||
|
|
||||||
|
stepRule.step.kubernetesDeploy(
|
||||||
|
juStabUtils: utils,
|
||||||
|
testParam: "This is test content",
|
||||||
|
script: nullScript
|
||||||
|
)
|
||||||
|
// asserts
|
||||||
|
assertThat(writeFileRule.files['metadata/kubernetesdeploy.yaml'], containsString('name: kubernetesDeploy'))
|
||||||
|
assertThat(withEnvArgs[0], allOf(startsWith('PIPER_parametersJSON'), containsString('"testParam":"This is test content"')))
|
||||||
|
assertThat(shellCallRule.shell[1], is('./piper kubernetesDeploy'))
|
||||||
|
assertThat(credentials.size(), is(3))
|
||||||
|
|
||||||
|
assertThat(dockerExecuteRule.dockerParams.dockerImage, is('my.Registry/K8S:latest'))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testKubernetesDeploySomeCreds() {
|
||||||
|
shellCallRule.setReturnValue('./piper getConfig --contextConfig --stepMetadata \'metadata/kubernetesdeploy.yaml\'', '{"kubeTokenCredentialsId":"kubeToken", "dockerCredentialsId":"dockerCredentials"}')
|
||||||
|
stepRule.step.kubernetesDeploy(
|
||||||
|
juStabUtils: utils,
|
||||||
|
script: nullScript
|
||||||
|
)
|
||||||
|
// asserts
|
||||||
|
assertThat(shellCallRule.shell[1], is('./piper kubernetesDeploy'))
|
||||||
|
assertThat(credentials.size(), is(2))
|
||||||
|
}
|
||||||
|
}
|
48
vars/kubernetesDeploy.groovy
Normal file
48
vars/kubernetesDeploy.groovy
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import com.sap.piper.PiperGoUtils
|
||||||
|
import com.sap.piper.Utils
|
||||||
|
import groovy.transform.Field
|
||||||
|
|
||||||
|
import static com.sap.piper.Prerequisites.checkScript
|
||||||
|
|
||||||
|
@Field String STEP_NAME = getClass().getName()
|
||||||
|
@Field String METADATA_FILE = 'metadata/kubernetesdeploy.yaml'
|
||||||
|
|
||||||
|
void call(Map parameters = [:]) {
|
||||||
|
handlePipelineStepErrors(stepName: STEP_NAME, stepParameters: parameters) {
|
||||||
|
|
||||||
|
def script = checkScript(this, parameters) ?: this
|
||||||
|
|
||||||
|
def utils = parameters.juStabUtils ?: new Utils()
|
||||||
|
parameters.juStabUtils = null
|
||||||
|
|
||||||
|
new PiperGoUtils(this, utils).unstashPiperBin()
|
||||||
|
utils.unstash('pipelineConfigAndTests')
|
||||||
|
script.commonPipelineEnvironment.writeToDisk(script)
|
||||||
|
|
||||||
|
writeFile(file: METADATA_FILE, text: libraryResource(METADATA_FILE))
|
||||||
|
|
||||||
|
withEnv([
|
||||||
|
"PIPER_parametersJSON=${groovy.json.JsonOutput.toJson(parameters)}",
|
||||||
|
]) {
|
||||||
|
// get context configuration
|
||||||
|
Map config = readJSON (text: sh(returnStdout: true, script: "./piper getConfig --contextConfig --stepMetadata '${METADATA_FILE}'"))
|
||||||
|
echo "Config: ${config}"
|
||||||
|
|
||||||
|
dockerExecute(
|
||||||
|
script: script,
|
||||||
|
dockerImage: config.dockerImage,
|
||||||
|
dockerWorkspace: config.dockerWorkspace,
|
||||||
|
) {
|
||||||
|
def creds = []
|
||||||
|
if (config.kubeConfigFileCredentialsId) creds.add(file(credentialsId: config.kubeConfigFileCredentialsId, variable: 'PIPER_kubeConfig'))
|
||||||
|
if (config.kubeTokenCredentialsId) creds.add(string(credentialsId: config.kubeTokenCredentialsId, variable: 'PIPER_kubeToken'))
|
||||||
|
if (config.dockerCredentialsId) creds.add(usernamePassword(credentialsId: config.dockerCredentialsId, passwordVariable: 'PIPER_containerRegistryPassword', usernameVariable: 'PIPER_containerRegistryUser'))
|
||||||
|
|
||||||
|
// execute step
|
||||||
|
withCredentials(creds) {
|
||||||
|
sh "./piper kubernetesDeploy"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user