mirror of
https://github.com/SAP/jenkins-library.git
synced 2025-01-18 05:18:24 +02:00
(feat) gitopsUpdateDeployment supports globbing 🌟 (#3533)
* (feat) support for kustomize in gitopsUpdateDeployment step Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * add missing documentation * add another detail in the documentation Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * generate again the update doc Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * (feat) gitopsUpdateDeployment now supports globbing Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * generate and fmt Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * fix tests Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
parent
20f5e955f9
commit
385038e652
@ -11,11 +11,14 @@ import (
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const toolKubectl = "kubectl"
|
||||
@ -23,7 +26,7 @@ const toolHelm = "helm"
|
||||
const toolKustomize = "kustomize"
|
||||
|
||||
type iGitopsUpdateDeploymentGitUtils interface {
|
||||
CommitSingleFile(filePath, commitMessage, author string) (plumbing.Hash, error)
|
||||
CommitFiles(filePaths []string, commitMessage, author string) (plumbing.Hash, error)
|
||||
PushChangesToRepository(username, password string) error
|
||||
PlainClone(username, password, serverURL, directory string) error
|
||||
ChangeBranch(branchName string) error
|
||||
@ -33,6 +36,7 @@ type gitopsUpdateDeploymentFileUtils interface {
|
||||
TempDir(dir, pattern string) (name string, err error)
|
||||
RemoveAll(path string) error
|
||||
FileWrite(path string, content []byte, perm os.FileMode) error
|
||||
Glob(pattern string) ([]string, error)
|
||||
}
|
||||
|
||||
type gitopsUpdateDeploymentExecRunner interface {
|
||||
@ -47,8 +51,24 @@ type gitopsUpdateDeploymentGitUtils struct {
|
||||
repository *git.Repository
|
||||
}
|
||||
|
||||
func (g *gitopsUpdateDeploymentGitUtils) CommitSingleFile(filePath, commitMessage, author string) (plumbing.Hash, error) {
|
||||
return gitUtil.CommitSingleFile(filePath, commitMessage, author, g.worktree)
|
||||
func (g *gitopsUpdateDeploymentGitUtils) CommitFiles(filePaths []string, commitMessage, author string) (plumbing.Hash, error) {
|
||||
for _, path := range filePaths {
|
||||
_, err := g.worktree.Add(path)
|
||||
|
||||
if err != nil {
|
||||
return [20]byte{}, errors.Wrap(err, "failed to add file to git")
|
||||
}
|
||||
}
|
||||
|
||||
commit, err := g.worktree.Commit(commitMessage, &git.CommitOptions{
|
||||
All: true,
|
||||
Author: &object.Signature{Name: author, When: time.Now()},
|
||||
})
|
||||
if err != nil {
|
||||
return [20]byte{}, errors.Wrap(err, "failed to commit file")
|
||||
}
|
||||
|
||||
return commit, nil
|
||||
}
|
||||
|
||||
func (g *gitopsUpdateDeploymentGitUtils) PushChangesToRepository(username, password string) error {
|
||||
@ -94,6 +114,7 @@ func runGitopsUpdateDeployment(config *gitopsUpdateDeploymentOptions, command gi
|
||||
}
|
||||
|
||||
temporaryFolder, err := fileUtils.TempDir(".", "temp-")
|
||||
temporaryFolder = regexp.MustCompile(`^./`).ReplaceAllString(temporaryFolder, "")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create temporary directory")
|
||||
}
|
||||
@ -111,34 +132,70 @@ func runGitopsUpdateDeployment(config *gitopsUpdateDeploymentOptions, command gi
|
||||
}
|
||||
|
||||
filePath := filepath.Join(temporaryFolder, config.FilePath)
|
||||
if config.Tool == toolHelm {
|
||||
filePath = filepath.Join(temporaryFolder, config.ChartPath)
|
||||
}
|
||||
|
||||
allFiles, err := fileUtils.Glob(filePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to expand globbing pattern")
|
||||
} else if len(allFiles) == 0 {
|
||||
return errors.New("no matching files found for provided globbing pattern")
|
||||
}
|
||||
command.SetDir("./")
|
||||
|
||||
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")
|
||||
for _, currentFile := range allFiles {
|
||||
if config.Tool == toolKubectl {
|
||||
outputBytes, err = executeKubectl(config, command, outputBytes, currentFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error on kubectl execution")
|
||||
}
|
||||
} else if config.Tool == toolHelm {
|
||||
|
||||
out, err := runHelmCommand(command, config, currentFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to apply helm command")
|
||||
}
|
||||
// join all helm outputs into the same "FilePath"
|
||||
outputBytes = append(outputBytes, []byte("---\n")...)
|
||||
outputBytes = append(outputBytes, out...)
|
||||
currentFile = filepath.Join(temporaryFolder, config.FilePath)
|
||||
|
||||
} else if config.Tool == toolKustomize {
|
||||
_, err = runKustomizeCommand(command, config, currentFile)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to apply kustomize command")
|
||||
}
|
||||
outputBytes = nil
|
||||
} else if config.Tool == toolKustomize {
|
||||
outputBytes, err = runKustomizeCommand(command, config, filePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to apply kustomize command")
|
||||
}
|
||||
} else {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.New("tool " + config.Tool + " is not supported")
|
||||
}
|
||||
} else if config.Tool == toolHelm {
|
||||
outputBytes, err = runHelmCommand(command, config)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to apply helm command")
|
||||
}
|
||||
} else if config.Tool == toolKustomize {
|
||||
outputBytes, err = runKustomizeCommand(command, config, filePath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to apply kustomize command")
|
||||
|
||||
if outputBytes != nil {
|
||||
err = fileUtils.FileWrite(currentFile, outputBytes, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write file")
|
||||
}
|
||||
}
|
||||
}
|
||||
if config.Tool == toolHelm {
|
||||
// helm only creates one output file.
|
||||
allFiles = []string{config.FilePath}
|
||||
} else {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return errors.New("tool " + config.Tool + " is not supported")
|
||||
// git expects the file path relative to its root:
|
||||
for i := range allFiles {
|
||||
allFiles[i] = strings.ReplaceAll(allFiles[i], temporaryFolder+"/", "")
|
||||
}
|
||||
}
|
||||
|
||||
err = fileUtils.FileWrite(filePath, outputBytes, 0755)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to write file")
|
||||
}
|
||||
|
||||
commit, err := commitAndPushChanges(config, gitUtils)
|
||||
commit, err := commitAndPushChanges(config, gitUtils, allFiles)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to commit and push changes")
|
||||
}
|
||||
@ -260,6 +317,7 @@ func executeKubectl(config *gitopsUpdateDeploymentOptions, command gitopsUpdateD
|
||||
}
|
||||
patchString := "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"" + config.ContainerName + "\",\"image\":\"" + registryImage + "\"}]}}}}"
|
||||
|
||||
log.Entry().Infof("[kubectl] updating '%s'", filePath)
|
||||
outputBytes, err = runKubeCtlCommand(command, patchString, filePath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to apply kubectl command")
|
||||
@ -301,9 +359,9 @@ func runKubeCtlCommand(command gitopsUpdateDeploymentExecRunner, patchString str
|
||||
return kubectlOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func runHelmCommand(runner gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions) ([]byte, error) {
|
||||
func runHelmCommand(command gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions, filePath string) ([]byte, error) {
|
||||
var helmOutput = bytes.Buffer{}
|
||||
runner.Stdout(&helmOutput)
|
||||
command.Stdout(&helmOutput)
|
||||
|
||||
registryImage, imageTag, err := buildRegistryPlusImageAndTagSeparately(config)
|
||||
if err != nil {
|
||||
@ -312,7 +370,7 @@ func runHelmCommand(runner gitopsUpdateDeploymentExecRunner, config *gitopsUpdat
|
||||
helmParams := []string{
|
||||
"template",
|
||||
config.DeploymentName,
|
||||
filepath.Join(".", config.ChartPath),
|
||||
filePath,
|
||||
"--set=image.repository=" + registryImage,
|
||||
"--set=image.tag=" + imageTag,
|
||||
}
|
||||
@ -321,16 +379,17 @@ func runHelmCommand(runner gitopsUpdateDeploymentExecRunner, config *gitopsUpdat
|
||||
helmParams = append(helmParams, "--values", value)
|
||||
}
|
||||
|
||||
err = runner.RunExecutable(toolHelm, helmParams...)
|
||||
log.Entry().Infof("[helmn] updating '%s'", filePath)
|
||||
err = command.RunExecutable(toolHelm, helmParams...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute helm command")
|
||||
}
|
||||
return helmOutput.Bytes(), nil
|
||||
}
|
||||
|
||||
func runKustomizeCommand(runner gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions, filePath string) ([]byte, error) {
|
||||
func runKustomizeCommand(command gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions, filePath string) ([]byte, error) {
|
||||
var kustomizeOutput = bytes.Buffer{}
|
||||
runner.Stdout(&kustomizeOutput)
|
||||
command.Stdout(&kustomizeOutput)
|
||||
|
||||
kustomizeParams := []string{
|
||||
"edit",
|
||||
@ -339,9 +398,10 @@ func runKustomizeCommand(runner gitopsUpdateDeploymentExecRunner, config *gitops
|
||||
config.DeploymentName + "=" + config.ContainerImageNameTag,
|
||||
}
|
||||
|
||||
runner.SetDir(filepath.Dir(filePath))
|
||||
command.SetDir(filepath.Dir(filePath))
|
||||
|
||||
err := runner.RunExecutable(toolKustomize, kustomizeParams...)
|
||||
log.Entry().Infof("[kustomize] updating '%s'", filePath)
|
||||
err := command.RunExecutable(toolKustomize, kustomizeParams...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to execute kustomize command")
|
||||
}
|
||||
@ -387,14 +447,14 @@ func buildRegistryPlusImageAndTagSeparately(config *gitopsUpdateDeploymentOption
|
||||
|
||||
}
|
||||
|
||||
func commitAndPushChanges(config *gitopsUpdateDeploymentOptions, gitUtils iGitopsUpdateDeploymentGitUtils) (plumbing.Hash, error) {
|
||||
func commitAndPushChanges(config *gitopsUpdateDeploymentOptions, gitUtils iGitopsUpdateDeploymentGitUtils, filePaths []string) (plumbing.Hash, error) {
|
||||
commitMessage := config.CommitMessage
|
||||
|
||||
if commitMessage == "" {
|
||||
commitMessage = defaultCommitMessage(config)
|
||||
}
|
||||
|
||||
commit, err := gitUtils.CommitSingleFile(config.FilePath, commitMessage, config.Username)
|
||||
commit, err := gitUtils.CommitFiles(filePaths, commitMessage, config.Username)
|
||||
if err != nil {
|
||||
return [20]byte{}, errors.Wrap(err, "committing changes failed")
|
||||
}
|
||||
|
@ -50,8 +50,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, update a whole helm template and kustomize.
|
||||
|
||||
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.
|
||||
For *helm* the whole template is generated into a single file (` + "`" + `filePath` + "`" + `) and uploaded into the repository.
|
||||
For *kustomize* the ` + "`" + `images` + "`" + ` section will be update with the current image.`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
startTime = time.Now()
|
||||
@ -134,11 +135,11 @@ func addGitopsUpdateDeploymentFlags(cmd *cobra.Command, stepConfig *gitopsUpdate
|
||||
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.")
|
||||
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Relative path in the git repository to the deployment descriptor file that shall be updated. For different tools this has different semantics:\n\n * `kubectl` - path to the `deployment.yaml` that should be patched.\n * `helm` - not used. Please use `chartPath` instead.\n * `kustomize` - path to the `kustomization.yaml`.\n")
|
||||
cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Relative path in the git repository to the deployment descriptor file that shall be updated. For different tools this has different semantics:\n\n * `kubectl` - path to the `deployment.yaml` that should be patched. Supports globbing.\n * `helm` - path where the helm chart will be generated into. Here no globbing is supported.\n * `kustomize` - path to the `kustomization.yaml`. Supports globbing.\n")
|
||||
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().StringVar(&stepConfig.ChartPath, "chartPath", os.Getenv("PIPER_chartPath"), "Defines the chart path for deployments using helm. Globbing is supported to merge multiple charts into one resource.yaml that will be commited.")
|
||||
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. In case of `kustomize` this is the name or alias of the image in the `kustomization.yaml`")
|
||||
cmd.Flags().StringVar(&stepConfig.Tool, "tool", `kubectl`, "Defines the tool which should be used to update the deployment description.")
|
||||
|
@ -101,7 +101,8 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage)
|
||||
assert.Equal(t, "kubectl", runnerMock.executable)
|
||||
assert.Equal(t, "patch", runnerMock.params[0])
|
||||
@ -123,7 +124,8 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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])
|
||||
@ -145,7 +147,8 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, "kubectl", runnerMock.executable)
|
||||
assert.Equal(t, "patch", runnerMock.params[0])
|
||||
assert.Equal(t, "--local", runnerMock.params[1])
|
||||
@ -166,7 +169,8 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, "kubectl", runnerMock.executable)
|
||||
assert.Equal(t, "patch", runnerMock.params[0])
|
||||
assert.Equal(t, "--local", runnerMock.params[1])
|
||||
@ -187,7 +191,8 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, "kubectl", runnerMock.executable)
|
||||
assert.Equal(t, "patch", runnerMock.params[0])
|
||||
assert.Equal(t, "--local", runnerMock.params[1])
|
||||
@ -195,6 +200,35 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
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("successful run with glob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtilsMock := &gitUtilsMock{}
|
||||
runnerMock := &gitOpsExecRunnerMock{}
|
||||
fsMock := &filesMock{}
|
||||
runnerMock.expectedYaml = expectedYaml
|
||||
var configuration = *validConfiguration
|
||||
configuration.FilePath = "glob/kubectl/**/*.yaml"
|
||||
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 2)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFiles[1])
|
||||
|
||||
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("glob/kubectl/dir1/depl.yaml")))
|
||||
|
||||
assert.Equal(t, "patch", runnerMock.params[5])
|
||||
assert.Equal(t, "--local", runnerMock.params[6])
|
||||
assert.Equal(t, "--output=yaml", runnerMock.params[7])
|
||||
assert.Equal(t, `--patch={"spec":{"template":{"spec":{"containers":[{"name":"myContainer","image":"myregistry.com/myFancyContainer:1337"}]}}}}`, runnerMock.params[8])
|
||||
assert.True(t, strings.Contains(runnerMock.params[9], filepath.Join("glob/kubectl/dir2/depl.yaml")))
|
||||
})
|
||||
|
||||
t.Run("missing ContainerName", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -229,7 +263,7 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtils := &gitUtilsMock{failOnClone: true}
|
||||
|
||||
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, gitUtils, &filesMock{})
|
||||
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{expectedYaml: expectedYaml}, gitUtils, &filesMock{})
|
||||
assert.EqualError(t, err, "repository could not get prepared: failed to plain clone repository: error on clone")
|
||||
})
|
||||
|
||||
@ -269,7 +303,7 @@ func TestRunGitopsUpdateDeploymentWithKubectl(t *testing.T) {
|
||||
t.Parallel()
|
||||
fileUtils := &filesMock{failOnWrite: true}
|
||||
|
||||
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils)
|
||||
err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{expectedYaml: expectedYaml}, &gitUtilsMock{}, fileUtils)
|
||||
assert.EqualError(t, err, "failed to write file: error appeared")
|
||||
})
|
||||
|
||||
@ -333,12 +367,13 @@ func TestRunGitopsUpdateDeploymentWithHelm(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, "---\n"+expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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, filepath.Join(gitUtilsMock.temporaryDirectory, "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])
|
||||
@ -357,12 +392,13 @@ func TestRunGitopsUpdateDeploymentWithHelm(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, "---\n"+expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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, filepath.Join(gitUtilsMock.temporaryDirectory, "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])
|
||||
@ -381,11 +417,12 @@ func TestRunGitopsUpdateDeploymentWithHelm(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, "---\n"+expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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, filepath.Join(gitUtilsMock.temporaryDirectory, "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])
|
||||
@ -404,14 +441,45 @@ func TestRunGitopsUpdateDeploymentWithHelm(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedYaml, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, "---\n"+expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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, filepath.Join(gitUtilsMock.temporaryDirectory, "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("successful run with glob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtilsMock := &gitUtilsMock{}
|
||||
runnerMock := &gitOpsExecRunnerMock{}
|
||||
fsMock := &filesMock{}
|
||||
runnerMock.expectedYaml = expectedYaml
|
||||
var configuration = *validConfiguration
|
||||
configuration.ChartPath = "glob/helm/dir*/helm"
|
||||
configuration.HelmValues = nil
|
||||
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, "---\n"+expectedYaml+"---\n"+expectedYaml, gitUtilsMock.savedFiles[0])
|
||||
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(gitUtilsMock.temporaryDirectory, "glob/helm/dir1/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, "template", runnerMock.params[5])
|
||||
assert.Equal(t, "myFancyDeployment", runnerMock.params[6])
|
||||
assert.Equal(t, filepath.Join(gitUtilsMock.temporaryDirectory, "glob/helm/dir2/helm"), runnerMock.params[7])
|
||||
assert.Equal(t, "--set=image.repository=myregistry.com/registry/containers/myFancyContainer", runnerMock.params[8])
|
||||
assert.Equal(t, "--set=image.tag=1337", runnerMock.params[9])
|
||||
})
|
||||
|
||||
t.Run("erroneous URL", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -559,7 +627,8 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) {
|
||||
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Equal(t, expectedKustomize, gitUtilsMock.savedFile)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 1)
|
||||
assert.Equal(t, expectedKustomize, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage)
|
||||
assert.Equal(t, "kustomize", runnerMock.executable)
|
||||
assert.Equal(t, "edit", runnerMock.params[0])
|
||||
@ -567,6 +636,32 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) {
|
||||
assert.Equal(t, "image", runnerMock.params[2])
|
||||
assert.Equal(t, "myFancyDeployment=registry/containers/myFancyContainer:1337", runnerMock.params[3])
|
||||
})
|
||||
t.Run("successful run with glob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtilsMock := &gitUtilsMock{}
|
||||
runnerMock := &gitOpsExecRunnerMock{}
|
||||
fsMock := &filesMock{}
|
||||
runnerMock.expectedYaml = expectedKustomize
|
||||
var configuration = *validConfiguration
|
||||
configuration.FilePath = "glob/kustomize/**/*.yaml"
|
||||
|
||||
err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch)
|
||||
assert.Len(t, gitUtilsMock.savedFiles, 2)
|
||||
assert.Equal(t, expectedKustomize, gitUtilsMock.savedFiles[0])
|
||||
assert.Equal(t, expectedKustomize, gitUtilsMock.savedFiles[1])
|
||||
assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage)
|
||||
assert.Equal(t, "kustomize", runnerMock.executable)
|
||||
assert.Equal(t, "edit", runnerMock.params[0])
|
||||
assert.Equal(t, "set", runnerMock.params[1])
|
||||
assert.Equal(t, "image", runnerMock.params[2])
|
||||
assert.Equal(t, "myFancyDeployment=registry/containers/myFancyContainer:1337", runnerMock.params[3])
|
||||
assert.Equal(t, "edit", runnerMock.params[4])
|
||||
assert.Equal(t, "set", runnerMock.params[5])
|
||||
assert.Equal(t, "image", runnerMock.params[6])
|
||||
assert.Equal(t, "myFancyDeployment=registry/containers/myFancyContainer:1337", runnerMock.params[7])
|
||||
})
|
||||
|
||||
t.Run("error on kustomize execution", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -595,6 +690,35 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunGitopsUpdateDeploymentWithGlobbing(t *testing.T) {
|
||||
var validConfiguration = &gitopsUpdateDeploymentOptions{
|
||||
Tool: toolKubectl,
|
||||
ContainerName: "yes",
|
||||
DeploymentName: "myFancyDeployment",
|
||||
}
|
||||
|
||||
t.Run("globbing fails", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtilsMock := &gitUtilsMock{}
|
||||
runnerMock := &gitOpsExecRunnerMock{}
|
||||
fsMock := &filesMock{failOnGlob: true}
|
||||
|
||||
err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.EqualError(t, err, "unable to expand globbing pattern: error appeared")
|
||||
})
|
||||
t.Run("globbing finds 0 files", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gitUtilsMock := &gitUtilsMock{skipClone: true}
|
||||
runnerMock := &gitOpsExecRunnerMock{}
|
||||
fsMock := &filesMock{}
|
||||
var config = *validConfiguration
|
||||
config.FilePath = "xxx"
|
||||
|
||||
err := runGitopsUpdateDeployment(&config, runnerMock, gitUtilsMock, fsMock)
|
||||
assert.EqualError(t, err, "no matching files found for provided globbing pattern")
|
||||
})
|
||||
}
|
||||
|
||||
type gitOpsExecRunnerMock struct {
|
||||
out io.Writer
|
||||
params []string
|
||||
@ -621,15 +745,21 @@ func (e *gitOpsExecRunnerMock) RunExecutable(executable string, params ...string
|
||||
return errors.New("error happened")
|
||||
}
|
||||
e.executable = executable
|
||||
e.params = params
|
||||
_, err := e.out.Write([]byte(e.expectedYaml))
|
||||
return err
|
||||
e.params = append(e.params, params...)
|
||||
if executable == "kustomize" {
|
||||
return fileUtils.FileWrite(filepath.Join(e.dir, "kustomization.yaml"), []byte(e.expectedYaml), 0755)
|
||||
|
||||
} else {
|
||||
_, err := e.out.Write([]byte(e.expectedYaml))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type filesMock struct {
|
||||
failOnCreation bool
|
||||
failOnDeletion bool
|
||||
failOnWrite bool
|
||||
failOnGlob bool
|
||||
path string
|
||||
}
|
||||
|
||||
@ -655,8 +785,15 @@ func (f *filesMock) RemoveAll(path string) error {
|
||||
return piperutils.Files{}.RemoveAll(path)
|
||||
}
|
||||
|
||||
func (f *filesMock) Glob(pattern string) (matches []string, err error) {
|
||||
if f.failOnGlob {
|
||||
return nil, errors.New("error appeared")
|
||||
}
|
||||
return piperutils.Files{}.Glob(pattern)
|
||||
}
|
||||
|
||||
type gitUtilsMock struct {
|
||||
savedFile string
|
||||
savedFiles []string
|
||||
changedBranch string
|
||||
commitMessage string
|
||||
temporaryDirectory string
|
||||
@ -664,6 +801,7 @@ type gitUtilsMock struct {
|
||||
failOnChangeBranch bool
|
||||
failOnCommit bool
|
||||
failOnPush bool
|
||||
skipClone bool
|
||||
}
|
||||
|
||||
func (gitUtilsMock) GetWorktree() (*git.Worktree, error) {
|
||||
@ -678,19 +816,21 @@ func (v *gitUtilsMock) ChangeBranch(branchName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *gitUtilsMock) CommitSingleFile(newFile string, commitMessage string, _ string) (plumbing.Hash, error) {
|
||||
func (v *gitUtilsMock) CommitFiles(newFiles []string, commitMessage string, _ string) (plumbing.Hash, error) {
|
||||
if v.failOnCommit {
|
||||
return [20]byte{}, errors.New("error on commit")
|
||||
}
|
||||
|
||||
v.commitMessage = commitMessage
|
||||
|
||||
filepath := filepath.Join(v.temporaryDirectory, newFile)
|
||||
fileContent, err := piperutils.Files{}.FileRead(filepath)
|
||||
if err != nil {
|
||||
return [20]byte{}, errors.New("could not find file " + filepath)
|
||||
for _, newFile := range newFiles {
|
||||
filepath := filepath.Join(v.temporaryDirectory, newFile)
|
||||
fileContent, err := piperutils.Files{}.FileRead(filepath)
|
||||
if err != nil {
|
||||
return [20]byte{}, errors.New("could not find file " + filepath)
|
||||
}
|
||||
v.savedFiles = append(v.savedFiles, string(fileContent))
|
||||
}
|
||||
v.savedFile = string(fileContent)
|
||||
return [20]byte{123}, nil
|
||||
}
|
||||
|
||||
@ -702,22 +842,33 @@ func (v gitUtilsMock) PushChangesToRepository(string, string) error {
|
||||
}
|
||||
|
||||
func (v *gitUtilsMock) PlainClone(_, _, _, directory string) error {
|
||||
if v.skipClone {
|
||||
return nil
|
||||
}
|
||||
if v.failOnClone {
|
||||
return errors.New("error on clone")
|
||||
}
|
||||
v.temporaryDirectory = directory
|
||||
|
||||
err := piperutils.Files{}.MkdirAll(filepath.Join(directory, "dir1/dir2"), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "dir1/dir2/depl.yaml"), []byte(existingYaml), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/kubectl/dir1"), 0755)
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/kubectl/dir2"), 0755)
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "glob/kubectl/dir1/depl.yaml"), []byte(existingYaml), 0755)
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "glob/kubectl/dir2/depl.yaml"), []byte(existingYaml), 0755)
|
||||
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "helm"), 0755)
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/helm/dir1/helm"), 0755)
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/helm/dir2/helm"), 0755)
|
||||
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "kustomization.yaml"), []byte(existingKustomize), 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/kustomize/dir1"), 0755)
|
||||
err = piperutils.Files{}.MkdirAll(filepath.Join(directory, "glob/kustomize/dir2"), 0755)
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "glob/kustomize/dir1/kustomization.yaml"), []byte(existingKustomize), 0755)
|
||||
err = piperutils.Files{}.FileWrite(filepath.Join(directory, "glob/kustomize/dir2/kustomization.yaml"), []byte(existingKustomize), 0755)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,9 @@ 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, update a whole helm template and kustomize.
|
||||
|
||||
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.
|
||||
For *helm* the whole template is generated into a single file (`filePath`) and uploaded into the repository.
|
||||
For *kustomize* the `images` section will be update with the current image.
|
||||
|
||||
|
||||
@ -81,9 +82,9 @@ spec:
|
||||
description: |
|
||||
Relative path in the git repository to the deployment descriptor file that shall be updated. For different tools this has different semantics:
|
||||
|
||||
* `kubectl` - path to the `deployment.yaml` that should be patched.
|
||||
* `helm` - not used. Please use `chartPath` instead.
|
||||
* `kustomize` - path to the `kustomization.yaml`.
|
||||
* `kubectl` - path to the `deployment.yaml` that should be patched. Supports globbing.
|
||||
* `helm` - path where the helm chart will be generated into. Here no globbing is supported.
|
||||
* `kustomize` - path to the `kustomization.yaml`. Supports globbing.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
@ -130,7 +131,7 @@ spec:
|
||||
aliases:
|
||||
- name: helmChartPath
|
||||
type: string
|
||||
description: Defines the chart path for deployments using helm.
|
||||
description: Defines the chart path for deployments using helm. Globbing is supported to merge multiple charts into one resource.yaml that will be commited.
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
|
Loading…
x
Reference in New Issue
Block a user