package cmd import ( "errors" "github.com/SAP/jenkins-library/pkg/piperutils" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/stretchr/testify/assert" "io" "os" "path/filepath" "strings" "testing" ) func TestBuildRegistryPlusImage(t *testing.T) { t.Parallel() t.Run("build full image", func(t *testing.T) { t.Parallel() registryImage, err := buildRegistryPlusImage(&gitopsUpdateDeploymentOptions{ ContainerRegistryURL: "https://myregistry.com/registry/containers", ContainerImageNameTag: "myFancyContainer:1337", }) assert.NoError(t, err) assert.Equal(t, "myregistry.com/myFancyContainer:1337", registryImage) }) t.Run("without registry", func(t *testing.T) { t.Parallel() registryImage, err := buildRegistryPlusImage(&gitopsUpdateDeploymentOptions{ ContainerRegistryURL: "", ContainerImageNameTag: "myFancyContainer:1337", }) assert.NoError(t, err) assert.Equal(t, "myFancyContainer:1337", registryImage) }) t.Run("without faulty URL", func(t *testing.T) { t.Parallel() _, err := buildRegistryPlusImage(&gitopsUpdateDeploymentOptions{ ContainerRegistryURL: "//myregistry.com/registry/containers", ContainerImageNameTag: "myFancyContainer:1337", }) assert.EqualError(t, err, "registry URL could not be extracted: invalid registry url") }) } func TestBuildRegistryPlusImageWithoutTag(t *testing.T) { t.Parallel() t.Run("build full image", func(t *testing.T) { t.Parallel() 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) }) t.Run("without registry", func(t *testing.T) { t.Parallel() 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) { t.Parallel() _, _, err := buildRegistryPlusImageAndTagSeparately(&gitopsUpdateDeploymentOptions{ ContainerRegistryURL: "//myregistry.com/registry/containers", ContainerImageNameTag: "myFancyContainer:1337", }) assert.EqualError(t, err, "registry URL could not be extracted: invalid registry url") }) } 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", } t.Parallel() t.Run("successful run", func(t *testing.T) { t.Parallel() gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch) 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]) 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) { t.Parallel() var configuration = *validConfiguration configuration.CommitMessage = "" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch) 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]) 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) { t.Parallel() var configuration = *validConfiguration configuration.ChartPath = "chartPath" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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]) 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) { t.Parallel() var configuration = *validConfiguration configuration.HelmValues = []string{"HelmValues"} gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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]) 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) { t.Parallel() var configuration = *validConfiguration configuration.DeploymentName = "DeploymentName" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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]) 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("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() var configuration = *validConfiguration configuration.ContainerName = "" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) 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) { t.Parallel() runner := &gitOpsExecRunnerMock{failOnRunExecutable: true} err := runGitopsUpdateDeployment(validConfiguration, runner, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.ContainerRegistryURL = "//myregistry.com/registry/containers" 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 plain clone", func(t *testing.T) { t.Parallel() gitUtils := &gitUtilsMock{failOnClone: true} 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") }) t.Run("error on change branch", func(t *testing.T) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() fileUtils := &filesMock{failOnWrite: true} err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{expectedYaml: expectedYaml}, &gitUtilsMock{}, fileUtils) assert.EqualError(t, err, "failed to write file: error appeared") }) t.Run("error on temp dir deletion", func(t *testing.T) { t.Parallel() 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.Parallel() t.Run("invalid deploy tool is not supported", 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", ContainerImageNameTag: "registry/containers/myFancyContainer:1337", Tool: "invalid", ChartPath: "./helm", DeploymentName: "myFancyDeployment", HelmValues: []string{"./helm/additionalValues.yaml"}, } err := runGitopsUpdateDeployment(configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch) 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(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]) assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6]) }) t.Run("default commit message", func(t *testing.T) { t.Parallel() var configuration = *validConfiguration configuration.CommitMessage = "" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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(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]) assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6]) }) t.Run("ContainerName not used for helm", func(t *testing.T) { t.Parallel() var configuration = *validConfiguration configuration.ContainerName = "containerName" gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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(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]) assert.Equal(t, "./helm/additionalValues.yaml", runnerMock.params[6]) }) t.Run("HelmValues is optional", func(t *testing.T) { t.Parallel() var configuration = *validConfiguration configuration.HelmValues = nil gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} runnerMock.expectedYaml = expectedYaml err := runGitopsUpdateDeployment(&configuration, runnerMock, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, configuration.BranchName, gitUtilsMock.changedBranch) 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(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() var configuration = *validConfiguration configuration.ContainerRegistryURL = "://myregistry.com" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.ChartPath = "" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.DeploymentName = "" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.DeploymentName = "" configuration.ChartPath = "" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.ContainerImageNameTag = "registry/containers/myFancyContainer:" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() var configuration = *validConfiguration configuration.ContainerImageNameTag = ":1.0.1" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() runner := &gitOpsExecRunnerMock{failOnRunExecutable: true} err := runGitopsUpdateDeployment(validConfiguration, runner, &gitUtilsMock{}, &filesMock{}) 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() 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) { t.Parallel() fileUtils := &filesMock{failOnDeletion: true} err := runGitopsUpdateDeployment(validConfiguration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, fileUtils) assert.NoError(t, err) _ = piperutils.Files{}.RemoveAll(fileUtils.path) }) } func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) { var validConfiguration = &gitopsUpdateDeploymentOptions{ BranchName: "main", CommitMessage: "This is the commit message", ServerURL: "https://github.com", Username: "admin3", Password: "validAccessToken", FilePath: "kustomization.yaml", ContainerRegistryURL: "https://myregistry.com", ContainerImageNameTag: "containers/myFancyContainer:1337", Tool: "kustomize", DeploymentName: "myFancyDeployment", } t.Parallel() t.Run("successful run", func(t *testing.T) { t.Parallel() gitUtilsMock := &gitUtilsMock{} runnerMock := &gitOpsExecRunnerMock{} fsMock := &filesMock{} runnerMock.expectedYaml = expectedKustomize err := runGitopsUpdateDeployment(validConfiguration, runnerMock, gitUtilsMock, fsMock) assert.NoError(t, err) assert.Equal(t, validConfiguration.BranchName, gitUtilsMock.changedBranch) 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]) assert.Equal(t, "set", runnerMock.params[1]) assert.Equal(t, "image", runnerMock.params[2]) assert.Equal(t, "myFancyDeployment=myregistry.com/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=myregistry.com/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=myregistry.com/containers/myFancyContainer:1337", runnerMock.params[7]) }) t.Run("with forcePush", func(t *testing.T) { t.Parallel() runner := &gitOpsExecRunnerMock{} validConfiguration.ForcePush = true gitUtilsMock := &gitUtilsMock{forcePush: true} err := runGitopsUpdateDeployment(validConfiguration, runner, gitUtilsMock, &filesMock{}) assert.NoError(t, err) assert.Equal(t, "This is the commit message", gitUtilsMock.commitMessage) }) t.Run("error on kustomize execution", func(t *testing.T) { t.Parallel() runner := &gitOpsExecRunnerMock{failOnRunExecutable: true} err := runGitopsUpdateDeployment(validConfiguration, runner, &gitUtilsMock{}, &filesMock{}) assert.EqualError(t, err, "failed to apply kustomize command: failed to execute kustomize command: error happened") }) t.Run("missing FilePath", func(t *testing.T) { t.Parallel() var configuration = *validConfiguration configuration.FilePath = "" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) assert.EqualError(t, err, "missing required fields for kustomize: the following parameters are necessary for kustomize: [filePath]") }) t.Run("missing DeploymentName", func(t *testing.T) { t.Parallel() var configuration = *validConfiguration configuration.DeploymentName = "" err := runGitopsUpdateDeployment(&configuration, &gitOpsExecRunnerMock{}, &gitUtilsMock{}, &filesMock{}) assert.EqualError(t, err, "missing required fields for kustomize: the following parameters are necessary for kustomize: [deploymentName]") }) } 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 executable string failOnRunExecutable bool dir string expectedYaml string } func (e *gitOpsExecRunnerMock) Stdout(out io.Writer) { e.out = out } func (gitOpsExecRunnerMock) Stderr(io.Writer) { panic("implement me") } func (e *gitOpsExecRunnerMock) SetDir(d string) { e.dir = d } func (e *gitOpsExecRunnerMock) RunExecutable(executable string, params ...string) error { if e.failOnRunExecutable { return errors.New("error happened") } e.executable = executable 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 } 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 (f filesMock) TempDir(dir string, pattern string) (name string, err error) { if f.failOnCreation { return "", errors.New("error appeared") } return piperutils.Files{}.TempDir("", pattern) } func (f *filesMock) RemoveAll(path string) error { if f.failOnDeletion { f.path = path return errors.New("error appeared") } 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 { savedFiles []string changedBranch string commitMessage string temporaryDirectory string failOnClone bool failOnChangeBranch bool failOnCommit bool failOnPush bool skipClone bool forcePush bool } func (gitUtilsMock) GetWorktree() (*git.Worktree, error) { return nil, nil } func (v *gitUtilsMock) ChangeBranch(branchName string) error { if v.failOnChangeBranch { return errors.New("error on change branch") } v.changedBranch = branchName return nil } 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 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)) } return [20]byte{123}, nil } func (v gitUtilsMock) PushChangesToRepository(_ string, _ string, force *bool) error { if v.failOnPush { return errors.New("error on push") } if v.forcePush && !*force { return errors.New("expected forcePush but not defined") } return nil } 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) 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) 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 } var existingYaml = `apiVersion: apps/v1 kind: Deployment metadata: name: myFancyApp labels: tier: application spec: replicas: 4 selector: matchLabels: run: myContainer template: metadata: labels: run: myContainer spec: containers: - image: myregistry.com/myFancyContainer:1336 name: myContainer` var expectedYaml = `apiVersion: apps/v1 kind: Deployment metadata: name: myFancyApp labels: tier: application spec: replicas: 4 selector: matchLabels: run: myContainer template: metadata: labels: run: myContainer spec: containers: - image: myregistry.com/myFancyContainer:1337 name: myContainer` var existingKustomize = `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: myFancyDeployment newTag: "0" ` var expectedKustomize = `apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: myFancyDeployment newName: registry/containers/myFancyContainer newTag: "1337" `