1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

feat(Gitops): Gitops update deployment with helm (#2247)

* makes containerImage not mandatory

* Adds kubectl container

* Adds log statement to debug

* adds general to container image

* removes GENERAL again
Removes condition from Kubectl container

* removes workDir

* marks logs as debug

* adds workingdir again

* Adds author to commits

* Adds commit time now

* remove deprecated and reorder

* adds deprecated again to containerRegistryUrl
Adds GENERAL scope to containerImage

* updates generated file

* Renames containerImageNameTag

* adds else case

* adds debug log

* code cleanup

* adds debug log

* revert

* adds debug logs

* revert

* makes root path not hidden

* revert

* Read container properties

* Removes debug message

* Removes debug message

* Removes general scope again

* Fixes unit test

* Adds helm capabilities to the gitopsUpdateDeployment step

* Adds helm capabilities to gitopsUpdateDeployment step

* Removes condition from input field

* Adds test for invalid deploy tool

* Fixes typo

* Adds tests for git errors and file errors
Simplifies test setup

* Adds test for error on image name extraction

* fixes URL variable name

* adds workind directory to paths

* Refactors too long method

* Reverts refactoring method

* Adds repository name as parameter

* Adds glob method

* Test glob method

* Revert "Test glob method"

This reverts commit ac11b54c14.

* Revert "Adds glob method"

This reverts commit ddf47ddebe.

* Revert "Adds repository name as parameter"

This reverts commit 8fc471c909.

* Removes getWd

* Adds stash deployDescriptor

* removes = from paramters

* Revert "removes = from paramters"

This reverts commit 3ecb3665e2.

* Adds " around parameters

* adds logging of all files

* Updates helm to version 3.3.4

* Clean up debug logs

* Raise error if no branch name provided.
Defaulting should be handled by step configuration.

* clean code

* Updates fields and adds checks for required field for certain deploy tools

* Fixes default commit message

* Update long description

* Removes default parameter

* Update resources/metadata/gitopsUpdateDeployment.yaml

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

* Updates yaml file

* Add error category and removes too much wrapping

* Update generated file

* Checks all parameters before returning the error

* Introduces constant

* Renames constant

* Fixes unit tests

* unexpose constants

* Makes tests thread safe and resilient to failed deletion

* Remove methods that did not work properly with hash containers rather than tags.

Co-authored-by: Stephan Aßmus <stephan.assmus@sap.com>
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
Fabian Reh 2020-11-03 18:29:46 +01:00 committed by GitHub
parent 8007a1a6c6
commit 04599e97da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 830 additions and 153 deletions

View File

@ -2,6 +2,7 @@ package cmd
import (
"bytes"
"fmt"
"github.com/SAP/jenkins-library/pkg/command"
"github.com/SAP/jenkins-library/pkg/docker"
gitUtil "github.com/SAP/jenkins-library/pkg/git"
@ -14,8 +15,12 @@ import (
"io"
"os"
"path/filepath"
"strings"
)
const toolKubectl = "kubectl"
const toolHelm = "helm"
type iGitopsUpdateDeploymentGitUtils interface {
CommitSingleFile(filePath, commitMessage, author string) (plumbing.Hash, error)
PushChangesToRepository(username, password string) error
@ -62,7 +67,7 @@ func (g *gitopsUpdateDeploymentGitUtils) ChangeBranch(branchName string) error {
return gitUtil.ChangeBranch(branchName, g.worktree)
}
func gitopsUpdateDeployment(config gitopsUpdateDeploymentOptions, telemetryData *telemetry.CustomData) {
func gitopsUpdateDeployment(config gitopsUpdateDeploymentOptions, _ *telemetry.CustomData) {
// for command execution use Command
var c gitopsUpdateDeploymentExecRunner = &command.Command{}
// reroute command output to logging framework
@ -81,37 +86,47 @@ func gitopsUpdateDeployment(config gitopsUpdateDeploymentOptions, telemetryData
}
func runGitopsUpdateDeployment(config *gitopsUpdateDeploymentOptions, command gitopsUpdateDeploymentExecRunner, gitUtils iGitopsUpdateDeploymentGitUtils, fileUtils gitopsUpdateDeploymentFileUtils) error {
err := checkRequiredFieldsForDeployTool(config)
if err != nil {
return err
}
temporaryFolder, err := fileUtils.TempDir(".", "temp-")
if err != nil {
return errors.Wrap(err, "failed to create temporary directory")
}
defer fileUtils.RemoveAll(temporaryFolder)
defer func() {
err = fileUtils.RemoveAll(temporaryFolder)
if err != nil {
log.Entry().WithError(err).Error("error during temporary directory deletion")
}
}()
err = gitUtils.PlainClone(config.Username, config.Password, config.ServerURL, temporaryFolder)
err = cloneRepositoryAndChangeBranch(config, gitUtils, temporaryFolder)
if err != nil {
return errors.Wrap(err, "failed to plain clone repository")
return errors.Wrap(err, "repository could not get prepared")
}
err = gitUtils.ChangeBranch(config.BranchName)
if err != nil {
return errors.Wrap(err, "failed to change branch")
}
registryImage, err := buildRegistryPlusImage(config)
if err != nil {
return errors.Wrap(err, "failed to apply kubectl command")
}
patchString := "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"" + config.ContainerName + "\",\"image\":\"" + registryImage + "\"}]}}}}"
filePath := filepath.Join(temporaryFolder, config.FilePath)
kubectlOutputBytes, err := runKubeCtlCommand(command, patchString, filePath)
if err != nil {
return errors.Wrap(err, "failed to apply kubectl command")
var outputBytes []byte
if config.Tool == toolKubectl {
outputBytes, err = executeKubectl(config, command, outputBytes, filePath)
if err != nil {
return errors.Wrap(err, "error on kubectl execution")
}
} else if config.Tool == toolHelm {
outputBytes, err = runHelmCommand(command, config)
if err != nil {
return errors.Wrap(err, "failed to apply helm command")
}
} else {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.New("tool " + config.Tool + " is not supported")
}
err = fileUtils.FileWrite(filePath, kubectlOutputBytes, 0755)
err = fileUtils.FileWrite(filePath, outputBytes, 0755)
if err != nil {
return errors.Wrap(err, "failed to write file")
}
@ -126,22 +141,94 @@ func runGitopsUpdateDeployment(config *gitopsUpdateDeploymentOptions, command gi
return nil
}
func runKubeCtlCommand(command gitopsUpdateDeploymentExecRunner, patchString string, filePath string) ([]byte, error) {
var kubectlOutput = bytes.Buffer{}
command.Stdout(&kubectlOutput)
kubeParams := []string{
"patch",
"--local",
"--output=yaml",
"--patch=" + patchString,
"--filename=" + filePath,
func checkRequiredFieldsForDeployTool(config *gitopsUpdateDeploymentOptions) error {
if config.Tool == toolHelm {
err := checkRequiredFieldsForHelm(config)
if err != nil {
return errors.Wrap(err, "missing required fields for helm")
}
logNotRequiredButFilledFieldForHelm(config)
} else if config.Tool == toolKubectl {
err := checkRequiredFieldsForKubectl(config)
if err != nil {
return errors.Wrap(err, "missing required fields for kubectl")
}
logNotRequiredButFilledFieldForKubectl(config)
}
err := command.RunExecutable("kubectl", kubeParams...)
return nil
}
func checkRequiredFieldsForHelm(config *gitopsUpdateDeploymentOptions) error {
var missingParameters []string
if config.ChartPath == "" {
missingParameters = append(missingParameters, "chartPath")
}
if config.DeploymentName == "" {
missingParameters = append(missingParameters, "deploymentName")
}
if len(missingParameters) > 0 {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Errorf("the following parameters are necessary for helm: %v", missingParameters)
}
return nil
}
func checkRequiredFieldsForKubectl(config *gitopsUpdateDeploymentOptions) error {
var missingParameters []string
if config.ContainerName == "" {
missingParameters = append(missingParameters, "containerName")
}
if len(missingParameters) > 0 {
log.SetErrorCategory(log.ErrorConfiguration)
return errors.Errorf("the following parameters are necessary for kubectl: %v", missingParameters)
}
return nil
}
func logNotRequiredButFilledFieldForHelm(config *gitopsUpdateDeploymentOptions) {
if config.ContainerName != "" {
log.Entry().Info("containerName is not used for helm and can be removed")
}
}
func logNotRequiredButFilledFieldForKubectl(config *gitopsUpdateDeploymentOptions) {
if config.ChartPath != "" {
log.Entry().Info("chartPath is not used for kubectl and can be removed")
}
if len(config.HelmValues) > 0 {
log.Entry().Info("helmValues is not used for kubectl and can be removed")
}
if len(config.DeploymentName) > 0 {
log.Entry().Info("deploymentName is not used for kubectl and can be removed")
}
}
func cloneRepositoryAndChangeBranch(config *gitopsUpdateDeploymentOptions, gitUtils iGitopsUpdateDeploymentGitUtils, temporaryFolder string) error {
err := gitUtils.PlainClone(config.Username, config.Password, config.ServerURL, temporaryFolder)
if err != nil {
return errors.Wrap(err, "failed to plain clone repository")
}
err = gitUtils.ChangeBranch(config.BranchName)
if err != nil {
return errors.Wrap(err, "failed to change branch")
}
return nil
}
func executeKubectl(config *gitopsUpdateDeploymentOptions, command gitopsUpdateDeploymentExecRunner, outputBytes []byte, filePath string) ([]byte, error) {
registryImage, err := buildRegistryPlusImage(config)
if err != nil {
return nil, errors.Wrap(err, "failed to apply kubectl command")
}
return kubectlOutput.Bytes(), nil
patchString := "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"" + config.ContainerName + "\",\"image\":\"" + registryImage + "\"}]}}}}"
outputBytes, err = runKubeCtlCommand(command, patchString, filePath)
if err != nil {
return nil, errors.Wrap(err, "failed to apply kubectl command")
}
return outputBytes, nil
}
func buildRegistryPlusImage(config *gitopsUpdateDeploymentOptions) (string, error) {
@ -160,8 +247,97 @@ func buildRegistryPlusImage(config *gitopsUpdateDeploymentOptions) (string, erro
return url + config.ContainerImageNameTag, nil
}
func runKubeCtlCommand(command gitopsUpdateDeploymentExecRunner, patchString string, filePath string) ([]byte, error) {
var kubectlOutput = bytes.Buffer{}
command.Stdout(&kubectlOutput)
kubeParams := []string{
"patch",
"--local",
"--output=yaml",
"--patch=" + patchString,
"--filename=" + filePath,
}
err := command.RunExecutable(toolKubectl, kubeParams...)
if err != nil {
return nil, errors.Wrap(err, "failed to apply kubectl command")
}
return kubectlOutput.Bytes(), nil
}
func runHelmCommand(runner gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions) ([]byte, error) {
var helmOutput = bytes.Buffer{}
runner.Stdout(&helmOutput)
registryImage, imageTag, err := buildRegistryPlusImageAndTagSeparately(config)
if err != nil {
return nil, errors.Wrap(err, "failed to extract registry URL, image name, and image tag")
}
helmParams := []string{
"template",
config.DeploymentName,
filepath.Join(".", config.ChartPath),
"--set=image.repository=" + registryImage,
"--set=image.tag=" + imageTag,
}
for _, value := range config.HelmValues {
helmParams = append(helmParams, "--values", value)
}
err = runner.RunExecutable(toolHelm, helmParams...)
if err != nil {
return nil, errors.Wrap(err, "failed to execute helm command")
}
return helmOutput.Bytes(), nil
}
// buildRegistryPlusImageAndTagSeparately combines the registry together with the image name. Handles the tag separately.
// Tag is defined by everything on the right hand side of the colon sign. This looks weird for sha container versions but works for helm.
func buildRegistryPlusImageAndTagSeparately(config *gitopsUpdateDeploymentOptions) (string, string, error) {
registryURL := config.ContainerRegistryURL
url := ""
if registryURL != "" {
containerURL, err := docker.ContainerRegistryFromURL(registryURL)
if err != nil {
return "", "", errors.Wrap(err, "registry URL could not be extracted")
}
if containerURL != "" {
containerURL = containerURL + "/"
}
url = containerURL
}
imageNameTag := config.ContainerImageNameTag
var imageName, imageTag string
if strings.Contains(imageNameTag, ":") {
split := strings.Split(imageNameTag, ":")
if split[0] == "" {
log.SetErrorCategory(log.ErrorConfiguration)
return "", "", errors.New("image name could not be extracted")
}
if split[1] == "" {
log.SetErrorCategory(log.ErrorConfiguration)
return "", "", errors.New("tag could not be extracted")
}
imageName = split[0]
imageTag = split[1]
return url + imageName, imageTag, nil
}
log.SetErrorCategory(log.ErrorConfiguration)
return "", "", errors.New("image name and tag could not be extracted")
}
func commitAndPushChanges(config *gitopsUpdateDeploymentOptions, gitUtils iGitopsUpdateDeploymentGitUtils) (plumbing.Hash, error) {
commit, err := gitUtils.CommitSingleFile(config.FilePath, config.CommitMessage, config.Username)
commitMessage := config.CommitMessage
if commitMessage == "" {
commitMessage = defaultCommitMessage(config)
}
commit, err := gitUtils.CommitSingleFile(config.FilePath, commitMessage, config.Username)
if err != nil {
return [20]byte{}, errors.Wrap(err, "committing changes failed")
}
@ -173,3 +349,9 @@ func commitAndPushChanges(config *gitopsUpdateDeploymentOptions, gitUtils iGitop
return commit, nil
}
func defaultCommitMessage(config *gitopsUpdateDeploymentOptions) string {
image, tag, _ := buildRegistryPlusImageAndTagSeparately(config)
commitMessage := fmt.Sprintf("Updated %v to version %v", image, tag)
return commitMessage
}

View File

@ -14,15 +14,19 @@ import (
)
type gitopsUpdateDeploymentOptions struct {
BranchName string `json:"branchName,omitempty"`
CommitMessage string `json:"commitMessage,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
FilePath string `json:"filePath,omitempty"`
ContainerName string `json:"containerName,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
ContainerImageNameTag string `json:"containerImageNameTag,omitempty"`
BranchName string `json:"branchName,omitempty"`
CommitMessage string `json:"commitMessage,omitempty"`
ServerURL string `json:"serverUrl,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
FilePath string `json:"filePath,omitempty"`
ContainerName string `json:"containerName,omitempty"`
ContainerRegistryURL string `json:"containerRegistryUrl,omitempty"`
ContainerImageNameTag string `json:"containerImageNameTag,omitempty"`
ChartPath string `json:"chartPath,omitempty"`
HelmValues []string `json:"helmValues,omitempty"`
DeploymentName string `json:"deploymentName,omitempty"`
Tool string `json:"tool,omitempty"`
}
// GitopsUpdateDeploymentCommand Updates Kubernetes Deployment Manifest in an Infrastructure Git Repository
@ -40,7 +44,9 @@ func GitopsUpdateDeploymentCommand() *cobra.Command {
It can for example be used for GitOps scenarios where the update of the manifests triggers an update of the corresponding deployment in Kubernetes.
As of today, it supports the update of deployment yaml files via kubectl patch. The container inside the yaml must be described within the following hierarchy: {"spec":{"template":{"spec":{"containers":[{...}]}}}}`,
As of today, it supports the update of deployment yaml files via kubectl patch and update a whole helm template.
For kubectl the container inside the yaml must be described within the following hierarchy: ` + "`" + `{"spec":{"template":{"spec":{"containers":[{...}]}}}}` + "`" + `
For helm the whole template is generated into a file and uploaded into the repository.`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
startTime = time.Now()
log.SetStepName(STEP_NAME)
@ -89,7 +95,7 @@ As of today, it supports the update of deployment yaml files via kubectl patch.
func addGitopsUpdateDeploymentFlags(cmd *cobra.Command, stepConfig *gitopsUpdateDeploymentOptions) {
cmd.Flags().StringVar(&stepConfig.BranchName, "branchName", `master`, "The name of the branch where the changes should get pushed into.")
cmd.Flags().StringVar(&stepConfig.CommitMessage, "commitMessage", `Updated {{containerName}} to version {{containerImage}}`, "The commit message of the commit that will be done to do the changes.")
cmd.Flags().StringVar(&stepConfig.CommitMessage, "commitMessage", os.Getenv("PIPER_commitMessage"), "The commit message of the commit that will be done to do the changes.")
cmd.Flags().StringVar(&stepConfig.ServerURL, "serverUrl", `https://github.com`, "GitHub server url to the repository.")
cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User name for git authentication")
cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password/token for git authentication.")
@ -97,13 +103,19 @@ func addGitopsUpdateDeploymentFlags(cmd *cobra.Command, stepConfig *gitopsUpdate
cmd.Flags().StringVar(&stepConfig.ContainerName, "containerName", os.Getenv("PIPER_containerName"), "The name of the container to update")
cmd.Flags().StringVar(&stepConfig.ContainerRegistryURL, "containerRegistryUrl", os.Getenv("PIPER_containerRegistryUrl"), "http(s) url of the Container registry where the image is located")
cmd.Flags().StringVar(&stepConfig.ContainerImageNameTag, "containerImageNameTag", os.Getenv("PIPER_containerImageNameTag"), "Container image name with version tag to annotate in the deployment configuration.")
cmd.Flags().StringVar(&stepConfig.ChartPath, "chartPath", os.Getenv("PIPER_chartPath"), "Defines the chart path for deployments using helm.")
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.DeploymentName, "deploymentName", os.Getenv("PIPER_deploymentName"), "Defines the name of the deployment.")
cmd.Flags().StringVar(&stepConfig.Tool, "tool", `kubectl`, "Defines the tool which should be used to update the deployment description.")
cmd.MarkFlagRequired("commitMessage")
cmd.MarkFlagRequired("branchName")
cmd.MarkFlagRequired("serverUrl")
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
cmd.MarkFlagRequired("filePath")
cmd.MarkFlagRequired("containerName")
cmd.MarkFlagRequired("containerRegistryUrl")
cmd.MarkFlagRequired("containerImageNameTag")
cmd.MarkFlagRequired("tool")
}
// retrieve step metadata
@ -121,7 +133,7 @@ func gitopsUpdateDeploymentMetadata() config.StepData {
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Mandatory: true,
Aliases: []config.Alias{},
},
{
@ -129,7 +141,7 @@ func gitopsUpdateDeploymentMetadata() config.StepData {
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Mandatory: false,
Aliases: []config.Alias{},
},
{
@ -181,7 +193,7 @@ func gitopsUpdateDeploymentMetadata() config.StepData {
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Mandatory: false,
Aliases: []config.Alias{},
},
{
@ -194,7 +206,7 @@ func gitopsUpdateDeploymentMetadata() config.StepData {
},
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Mandatory: true,
Aliases: []config.Alias{{Name: "dockerRegistryUrl"}},
},
{
@ -207,9 +219,41 @@ func gitopsUpdateDeploymentMetadata() config.StepData {
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Mandatory: true,
Aliases: []config.Alias{{Name: "image"}, {Name: "containerImage"}},
},
{
Name: "chartPath",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "helmChartPath"}},
},
{
Name: "helmValues",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "[]string",
Mandatory: false,
Aliases: []config.Alias{},
},
{
Name: "deploymentName",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: false,
Aliases: []config.Alias{{Name: "helmDeploymentName"}},
},
{
Name: "tool",
ResourceRef: []config.ResourceReference{},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
Mandatory: true,
Aliases: []config.Alias{},
},
},
},
},

View File

@ -42,75 +42,227 @@ func TestBuildRegistryPlusImage(t *testing.T) {
})
}
func TestRunGitopsUpdateDeployment(t *testing.T) {
func TestBuildRegistryPlusImageWithoutTag(t *testing.T) {
t.Parallel()
t.Run("successful run", func(t *testing.T) {
var configuration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
ServerURL: "https://github.com",
Username: "admin3",
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerName: "myContainer",
t.Run("build full image", func(t *testing.T) {
registryImage, tag, err := buildRegistryPlusImageAndTagSeparately(&gitopsUpdateDeploymentOptions{
ContainerRegistryURL: "https://myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
}
})
assert.NoError(t, err)
assert.Equal(t, "myregistry.com/myFancyContainer", registryImage)
assert.Equal(t, "1337", tag)
})
gitUtilsMock := &validGitUtilsMock{}
t.Run("without registry", func(t *testing.T) {
registryImage, tag, err := buildRegistryPlusImageAndTagSeparately(&gitopsUpdateDeploymentOptions{
ContainerRegistryURL: "",
ContainerImageNameTag: "myFancyContainer:1337",
})
assert.NoError(t, err)
assert.Equal(t, "myFancyContainer", registryImage)
assert.Equal(t, "1337", tag)
})
t.Run("without faulty URL", func(t *testing.T) {
_, _, err := buildRegistryPlusImageAndTagSeparately(&gitopsUpdateDeploymentOptions{
ContainerRegistryURL: "//myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
})
assert.Error(t, err)
assert.EqualError(t, err, "registry URL could not be extracted: invalid registry url")
})
}
runnerMock := gitOpsExecRunnerMock{}
var c gitopsUpdateDeploymentExecRunner = &runnerMock
func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
var validConfiguration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
ServerURL: "https://github.com",
Username: "admin3",
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerName: "myContainer",
ContainerRegistryURL: "https://myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
Tool: "kubectl",
}
err := runGitopsUpdateDeployment(configuration, c, gitUtilsMock, piperutils.Files{})
t.Parallel()
t.Run("successful run", func(t *testing.T) {
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage)
assert.Equal(t, "kubectl", runnerMock.executable)
assert.Equal(t, "patch", runnerMock.params[0])
assert.Equal(t, "--local", runnerMock.params[1])
assert.Equal(t, "--output=yaml", runnerMock.params[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[3])
assert.True(t, strings.Contains(runnerMock.params[4], filepath.Join("dir1/dir2/depl.yaml")))
})
t.Run("default commit message", func(t *testing.T) {
var configuration = *validConfiguration
configuration.CommitMessage = ""
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "Updated myregistry.com/myFancyContainer to version 1337", gitUtilsMock.commitMessage)
assert.Equal(t, "kubectl", runnerMock.executable)
assert.Equal(t, "patch", runnerMock.params[0])
assert.Equal(t, "--local", runnerMock.params[1])
assert.Equal(t, "--output=yaml", runnerMock.params[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[3])
assert.True(t, strings.Contains(runnerMock.params[4], filepath.Join("dir1/dir2/depl.yaml")))
})
t.Run("ChartPath not used for kubectl", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ChartPath = "chartPath"
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "kubectl", runnerMock.executable)
assert.Equal(t, "patch", runnerMock.kubectlParams[0])
assert.Equal(t, "--local", runnerMock.kubectlParams[1])
assert.Equal(t, "--output=yaml", runnerMock.kubectlParams[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.kubectlParams[3])
assert.True(t, strings.Contains(runnerMock.kubectlParams[4], filepath.Join("dir1/dir2/depl.yaml")))
assert.Equal(t, "patch", runnerMock.params[0])
assert.Equal(t, "--local", runnerMock.params[1])
assert.Equal(t, "--output=yaml", runnerMock.params[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[3])
assert.True(t, strings.Contains(runnerMock.params[4], filepath.Join("dir1/dir2/depl.yaml")))
})
t.Run("HelmValues not used for kubectl", func(t *testing.T) {
var configuration = *validConfiguration
configuration.HelmValues = []string{"HelmValues"}
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "kubectl", runnerMock.executable)
assert.Equal(t, "patch", runnerMock.params[0])
assert.Equal(t, "--local", runnerMock.params[1])
assert.Equal(t, "--output=yaml", runnerMock.params[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[3])
assert.True(t, strings.Contains(runnerMock.params[4], filepath.Join("dir1/dir2/depl.yaml")))
})
t.Run("DeploymentName not used for kubectl", func(t *testing.T) {
var configuration = *validConfiguration
configuration.DeploymentName = "DeploymentName"
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "kubectl", runnerMock.executable)
assert.Equal(t, "patch", runnerMock.params[0])
assert.Equal(t, "--local", runnerMock.params[1])
assert.Equal(t, "--output=yaml", runnerMock.params[2])
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[3])
assert.True(t, strings.Contains(runnerMock.params[4], filepath.Join("dir1/dir2/depl.yaml")))
})
t.Run("missing ContainerName", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ContainerName = ""
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "missing required fields for kubectl: the following parameters are necessary for kubectl: [containerName]")
})
t.Run("error on kubectl execution", func(t *testing.T) {
runner := &gitOpsExecRunnerMock{failOnRunExecutable: true}
err := runGitopsUpdateDeployment(validConfiguration, runner, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "error on kubectl execution: failed to apply kubectl command: failed to apply kubectl command: error happened")
})
t.Run("invalid URL", func(t *testing.T) {
var configuration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
ServerURL: "https://github.com",
Username: "admin3",
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerName: "myContainer",
ContainerRegistryURL: "//myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
}
var configuration = *validConfiguration
configuration.ContainerRegistryURL = "//myregistry.com/registry/containers"
gitUtilsMock := &validGitUtilsMock{}
err := runGitopsUpdateDeployment(configuration, nil, gitUtilsMock, piperutils.Files{})
assert.EqualError(t, err, "failed to apply kubectl command: registry URL could not be extracted: invalid registry url")
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.EqualError(t, err, "error on kubectl execution: failed to apply kubectl command: registry URL could not be extracted: invalid registry url")
})
t.Run("error on plane clone", func(t *testing.T) {
var configuration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
ServerURL: "https://github.com",
Username: "admin3",
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerName: "myContainer",
ContainerRegistryURL: "https://myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
}
t.Run("error on plain clone", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnClone: true}
err := runGitopsUpdateDeployment(configuration, nil, &gitUtilsMockErrorClone{}, piperutils.Files{})
assert.EqualError(t, err, "failed to plain clone repository: error on clone")
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "repository could not get prepared: failed to plain clone repository: error on clone")
})
t.Run("error on change branch", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnChangeBranch: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "repository could not get prepared: failed to change branch: error on change branch")
})
t.Run("error on commit changes", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnCommit: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "failed to commit and push changes: committing changes failed: error on commit")
})
t.Run("error on push commits", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnPush: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "failed to commit and push changes: pushing changes failed: error on push")
})
t.Run("error on temp dir creation", func(t *testing.T) {
fileUtils := &filesMock{failOnCreation: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.EqualError(t, err, "failed to create temporary directory: error appeared")
})
t.Run("error on file write", func(t *testing.T) {
fileUtils := &filesMock{failOnWrite: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.EqualError(t, err, "failed to write file: error appeared")
})
t.Run("error on temp dir deletion", func(t *testing.T) {
fileUtils := &filesMock{failOnDeletion: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.NoError(t, err)
_ = piperutils.Files{}.RemoveAll(fileUtils.path)
})
}
func TestRunGitopsUpdateDeploymentWithInvalid(t *testing.T) {
t.Run("invalid deploy tool is not supported", func(t *testing.T) {
var configuration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
@ -119,19 +271,237 @@ func TestRunGitopsUpdateDeployment(t *testing.T) {
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerName: "myContainer",
ContainerRegistryURL: "https://myregistry.com/registry/containers",
ContainerImageNameTag: "myFancyContainer:1337",
ContainerRegistryURL: "https://myregistry.com",
ContainerImageNameTag: "registry/containers/myFancyContainer:1337",
Tool: "invalid",
ChartPath: "./helm",
DeploymentName: "myFancyDeployment",
HelmValues: []string{"./helm/additionalValues.yaml"},
}
err := runGitopsUpdateDeployment(configuration, nil, &gitopsUpdateDeploymentGitUtils{}, filesMockErrorTempDirCreation{})
err := runGitopsUpdateDeployment(configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "tool invalid is not supported")
})
}
func TestRunGitopsUpdateDeploymentWithHelm(t *testing.T) {
var validConfiguration = &gitopsUpdateDeploymentOptions{
BranchName: "main",
CommitMessage: "This is the commit message",
ServerURL: "https://github.com",
Username: "admin3",
Password: "validAccessToken",
FilePath: "dir1/dir2/depl.yaml",
ContainerRegistryURL: "https://myregistry.com",
ContainerImageNameTag: "registry/containers/myFancyContainer:1337",
Tool: "helm",
ChartPath: "./helm",
DeploymentName: "myFancyDeployment",
HelmValues: []string{"./helm/additionalValues.yaml"},
}
t.Parallel()
t.Run("successful run", func(t *testing.T) {
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage)
assert.Equal(t, "helm", runnerMock.executable)
assert.Equal(t, "template", runnerMock.params[0])
assert.Equal(t, "myFancyDeployment", runnerMock.params[1])
assert.Equal(t, filepath.Join(".", "helm"), runnerMock.params[2])
assert.Equal(t, "--set=image.repository=myregistry.com/registry/containers/myFancyContainer", runnerMock.params[3])
assert.Equal(t, "--set=image.tag=1337", runnerMock.params[4])
assert.Equal(t, "--values", runnerMock.params[5])
assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6])
})
t.Run("default commit message", func(t *testing.T) {
var configuration = *validConfiguration
configuration.CommitMessage = ""
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "Updated myregistry.com/registry/containers/myFancyContainer to version 1337", gitUtilsMock.commitMessage)
assert.Equal(t, "helm", runnerMock.executable)
assert.Equal(t, "template", runnerMock.params[0])
assert.Equal(t, "myFancyDeployment", runnerMock.params[1])
assert.Equal(t, filepath.Join(".", "helm"), runnerMock.params[2])
assert.Equal(t, "--set=image.repository=myregistry.com/registry/containers/myFancyContainer", runnerMock.params[3])
assert.Equal(t, "--set=image.tag=1337", runnerMock.params[4])
assert.Equal(t, "--values", runnerMock.params[5])
assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6])
})
t.Run("ContainerName not used for helm", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ContainerName = "containerName"
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "helm", runnerMock.executable)
assert.Equal(t, "template", runnerMock.params[0])
assert.Equal(t, "myFancyDeployment", runnerMock.params[1])
assert.Equal(t, filepath.Join(".", "helm"), runnerMock.params[2])
assert.Equal(t, "--set=image.repository=myregistry.com/registry/containers/myFancyContainer", runnerMock.params[3])
assert.Equal(t, "--set=image.tag=1337", runnerMock.params[4])
assert.Equal(t, "--values", runnerMock.params[5])
assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6])
})
t.Run("HelmValues is optional", func(t *testing.T) {
var configuration = *validConfiguration
configuration.HelmValues = nil
gitUtilsMock := &gitUtilsMock{}
runnerMock := &gitOpsExecRunnerMock{}
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
assert.NoError(t, err)
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
assert.Equal(t, "helm", runnerMock.executable)
assert.Equal(t, "template", runnerMock.params[0])
assert.Equal(t, "myFancyDeployment", runnerMock.params[1])
assert.Equal(t, filepath.Join(".", "helm"), runnerMock.params[2])
assert.Equal(t, "--set=image.repository=myregistry.com/registry/containers/myFancyContainer", runnerMock.params[3])
assert.Equal(t, "--set=image.tag=1337", runnerMock.params[4])
})
t.Run("erroneous URL", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ContainerRegistryURL = "://myregistry.com"
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, `failed to apply helm command: failed to extract registry URL, image name, and image tag: registry URL could not be extracted: invalid registry url: parse "://myregistry.com": missing protocol scheme`)
})
t.Run("missing ChartPath", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ChartPath = ""
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "missing required fields for helm: the following parameters are necessary for helm: [chartPath]")
})
t.Run("missing DeploymentName", func(t *testing.T) {
var configuration = *validConfiguration
configuration.DeploymentName = ""
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "missing required fields for helm: the following parameters are necessary for helm: [deploymentName]")
})
t.Run("missing DeploymentName and ChartPath", func(t *testing.T) {
var configuration = *validConfiguration
configuration.DeploymentName = ""
configuration.ChartPath = ""
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "missing required fields for helm: the following parameters are necessary for helm: [chartPath deploymentName]")
})
t.Run("erroneous tag", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ContainerImageNameTag = "registry/containers/myFancyContainer:"
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "failed to apply helm command: failed to extract registry URL, image name, and image tag: tag could not be extracted")
})
t.Run("erroneous image name", func(t *testing.T) {
var configuration = *validConfiguration
configuration.ContainerImageNameTag = ":1.0.1"
err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "failed to apply helm command: failed to extract registry URL, image name, and image tag: image name could not be extracted")
})
t.Run("error on helm execution", func(t *testing.T) {
runner := &gitOpsExecRunnerMock{failOnRunExecutable: true}
err := runGitopsUpdateDeployment(validConfiguration, runner, &gitUtilsMock{}, &filesMock{})
assert.Error(t, err)
assert.EqualError(t, err, "failed to apply helm command: failed to execute helm command: error happened")
})
t.Run("error on plain clone", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnClone: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "repository could not get prepared: failed to plain clone repository: error on clone")
})
t.Run("error on change branch", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnChangeBranch: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "repository could not get prepared: failed to change branch: error on change branch")
})
t.Run("error on commit changes", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnCommit: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "failed to commit and push changes: committing changes failed: error on commit")
})
t.Run("error on push commits", func(t *testing.T) {
gitUtils := &gitUtilsMock{failOnPush: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
assert.EqualError(t, err, "failed to commit and push changes: pushing changes failed: error on push")
})
t.Run("error on temp dir creation", func(t *testing.T) {
fileUtils := &filesMock{failOnCreation: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.EqualError(t, err, "failed to create temporary directory: error appeared")
})
t.Run("error on file write", func(t *testing.T) {
fileUtils := &filesMock{failOnWrite: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.EqualError(t, err, "failed to write file: error appeared")
})
t.Run("error on temp dir deletion", func(t *testing.T) {
fileUtils := &filesMock{failOnDeletion: true}
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
assert.NoError(t, err)
_ = piperutils.Files{}.RemoveAll(fileUtils.path)
})
}
type gitOpsExecRunnerMock struct {
out io.Writer
kubectlParams []string
executable string
out io.Writer
params []string
executable string
failOnRunExecutable bool
}
func (e *gitOpsExecRunnerMock) Stdout(out io.Writer) {
@ -143,74 +513,95 @@ func (gitOpsExecRunnerMock) Stderr(io.Writer) {
}
func (e *gitOpsExecRunnerMock) RunExecutable(executable string, params ...string) error {
if e.failOnRunExecutable {
return errors.New("error happened")
}
e.executable = executable
e.kubectlParams = params
e.params = params
_, err := e.out.Write([]byte(expectedYaml))
return err
}
type filesMockErrorTempDirCreation struct{}
func (c filesMockErrorTempDirCreation) FileWrite(string, []byte, os.FileMode) error {
panic("implement me")
type filesMock struct {
failOnCreation bool
failOnDeletion bool
failOnWrite bool
path string
}
func (filesMockErrorTempDirCreation) TempDir(string, string) (name string, err error) {
return "", errors.New("error appeared")
func (f filesMock) FileWrite(path string, content []byte, perm os.FileMode) error {
if f.failOnWrite {
return errors.New("error appeared")
}
return piperutils.Files{}.FileWrite(path, content, perm)
}
func (filesMockErrorTempDirCreation) RemoveAll(string) error {
panic("implement me")
func (f filesMock) TempDir(dir string, pattern string) (name string, err error) {
if f.failOnCreation {
return "", errors.New("error appeared")
}
return piperutils.Files{}.TempDir(dir, pattern)
}
type gitUtilsMockErrorClone struct{}
func (gitUtilsMockErrorClone) CommitSingleFile(string, string, string) (plumbing.Hash, error) {
panic("implement me")
func (f *filesMock) RemoveAll(path string) error {
if f.failOnDeletion {
f.path = path
return errors.New("error appeared")
}
return piperutils.Files{}.RemoveAll(path)
}
func (gitUtilsMockErrorClone) PushChangesToRepository(string, string) error {
panic("implement me")
type gitUtilsMock struct {
savedFile string
changedBranch string
commitMessage string
temporaryDirectory string
failOnClone bool
failOnChangeBranch bool
failOnCommit bool
failOnPush bool
}
func (gitUtilsMockErrorClone) PlainClone(string, string, string, string) error {
return errors.New("error on clone")
}
func (gitUtilsMockErrorClone) ChangeBranch(string) error {
panic("implement me")
}
func (gitUtilsMockErrorClone) GetWorktree() (*git.Worktree, error) {
panic("implement me")
}
type validGitUtilsMock struct {
savedFile string
changedBranch string
}
func (validGitUtilsMock) GetWorktree() (*git.Worktree, error) {
func (gitUtilsMock) GetWorktree() (*git.Worktree, error) {
return nil, nil
}
func (v *validGitUtilsMock) ChangeBranch(branchName string) error {
func (v *gitUtilsMock) ChangeBranch(branchName string) error {
if v.failOnChangeBranch {
return errors.New("error on change branch")
}
v.changedBranch = branchName
return nil
}
func (v *validGitUtilsMock) CommitSingleFile(string, string, string) (plumbing.Hash, error) {
matches, _ := piperutils.Files{}.Glob("*/dir1/dir2/depl.yaml")
func (v *gitUtilsMock) CommitSingleFile(_ string, commitMessage string, _ string) (plumbing.Hash, error) {
if v.failOnCommit {
return [20]byte{}, errors.New("error on commit")
}
v.commitMessage = commitMessage
matches, _ := piperutils.Files{}.Glob(v.temporaryDirectory + "/dir1/dir2/depl.yaml")
if len(matches) < 1 {
return [20]byte{}, errors.New("could not find file")
}
fileRead, _ := piperutils.Files{}.FileRead(matches[0])
v.savedFile = string(fileRead)
return [20]byte{123}, nil
}
func (validGitUtilsMock) PushChangesToRepository(string, string) error {
func (v gitUtilsMock) PushChangesToRepository(string, string) error {
if v.failOnPush {
return errors.New("error on push")
}
return nil
}
func (validGitUtilsMock) PlainClone(_, _, _, directory string) error {
func (v *gitUtilsMock) PlainClone(_, _, _, directory string) error {
if v.failOnClone {
return errors.New("error on clone")
}
v.temporaryDirectory = directory
filePath := filepath.Join(directory, "dir1/dir2/depl.yaml")
err := piperutils.Files{}.MkdirAll(filepath.Join(directory, "dir1/dir2"), 0755)
if err != nil {

View File

@ -86,14 +86,14 @@ func plainClone(username, password, serverURL, directory string, abstractionGit
// ChangeBranch checkout the provided branch.
// It will create a new branch if the branch does not exist yet.
// It will checkout "master" if no branch name if provided
// It will return an error if no branch name if provided
func ChangeBranch(branchName string, worktree *git.Worktree) error {
return changeBranch(branchName, worktree)
}
func changeBranch(branchName string, worktree utilsWorkTree) error {
if branchName == "" {
branchName = "master"
return errors.New("no branch name provided")
}
var checkoutOptions = &git.CheckoutOptions{}

View File

@ -82,12 +82,11 @@ func TestChangeBranch(t *testing.T) {
assert.False(t, worktreeMock.create)
})
t.Run("empty branch defaulted to master", func(t *testing.T) {
t.Run("empty branch raises error", func(t *testing.T) {
worktreeMock := &WorktreeMock{}
err := changeBranch("", worktreeMock)
assert.NoError(t, err)
assert.Equal(t, string(plumbing.NewBranchReferenceName("master")), worktreeMock.checkedOutBranch)
assert.False(t, worktreeMock.create)
assert.Error(t, err)
assert.EqualError(t, err, "no branch name provided")
})
t.Run("create new branch", func(t *testing.T) {

View File

@ -6,13 +6,20 @@ metadata:
It can for example be used for GitOps scenarios where the update of the manifests triggers an update of the corresponding deployment in Kubernetes.
As of today, it supports the update of deployment yaml files via kubectl patch. The container inside the yaml must be described within the following hierarchy: {"spec":{"template":{"spec":{"containers":[{...}]}}}}
As of today, it supports the update of deployment yaml files via kubectl patch and update a whole helm template.
For kubectl the container inside the yaml must be described within the following hierarchy: `{"spec":{"template":{"spec":{"containers":[{...}]}}}}`
For helm the whole template is generated into a file and uploaded into the repository.
spec:
inputs:
secrets:
- name: gitHttpsCredentialsId
description: Jenkins 'Username with password' credentials ID containing username/password for http access to your git repository.
type: jenkins
resources:
- name: deployDescriptor
type: stash
params:
- name: branchName
description: The name of the branch where the changes should get pushed into.
@ -22,15 +29,15 @@ spec:
- STEPS
type: string
default: master
mandatory: true
- name: commitMessage
description: The commit message of the commit that will be done to do the changes.
longDescription: If the commit message is empty a default message in the form "Updated _containerName_ to version _containerImage_" will be used.
scope:
- PARAMETERS
- STAGES
- STEPS
type: string
mandatory: true
default: Updated {{containerName}} to version {{containerImage}}
- name: serverUrl
aliases:
- name: githubServerUrl
@ -84,11 +91,11 @@ spec:
- STAGES
- STEPS
type: string
mandatory: true
- name: containerRegistryUrl
aliases:
- name: dockerRegistryUrl
type: string
mandatory: true
description: http(s) url of the Container registry where the image is located
scope:
- GENERAL
@ -104,6 +111,7 @@ spec:
deprecated: true
- name: containerImage
type: string
mandatory: true
description: Container image name with version tag to annotate in the deployment configuration.
scope:
- PARAMETERS
@ -112,9 +120,62 @@ spec:
resourceRef:
- name: commonPipelineEnvironment
param: container/imageNameTag
- name: chartPath
aliases:
- name: helmChartPath
type: string
description: Defines the chart path for deployments using helm.
scope:
- PARAMETERS
- STAGES
- STEPS
- name: helmValues
type: "[]string"
description: List of helm values as YAML file reference or URL (as per helm parameter description for `-f` / `--values`)
scope:
- PARAMETERS
- STAGES
- STEPS
- name: deploymentName
aliases:
- name: helmDeploymentName
type: string
description: Defines the name of the deployment.
scope:
- PARAMETERS
- STAGES
- STEPS
# default: deployment
- name: tool
type: string
description: Defines the tool which should be used to update the deployment description.
mandatory: true
scope:
- PARAMETERS
- STAGES
- STEPS
default: kubectl
possibleValues:
- kubectl
- helm
containers:
- image: dtzar/helm-kubectl:3.3.4
workingDir: /config
options:
- name: -u
value: "0"
conditions:
- conditionRef: strings-equal
params:
- name: tool
value: helm
- image: dtzar/helm-kubectl:2.12.1
workingDir: /config
options:
- name: -u
value: "0"
conditions:
- conditionRef: strings-equal
params:
- name: tool
value: kubectl