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 commitac11b54c14
. * Revert "Adds glob method" This reverts commitddf47ddebe
. * Revert "Adds repository name as parameter" This reverts commit8fc471c909
. * Removes getWd * Adds stash deployDescriptor * removes = from paramters * Revert "removes = from paramters" This reverts commit3ecb3665e2
. * 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:
parent
8007a1a6c6
commit
04599e97da
@ -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
|
||||
}
|
||||
|
@ -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{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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{}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user