You've already forked sap-jenkins-library
							
							
				mirror of
				https://github.com/SAP/jenkins-library.git
				synced 2025-10-30 23:57:50 +02:00 
			
		
		
		
	feat(gitopsUpdateDeployment) forcePush (#3665)
* feat(gitopsUpdateDeployment) forcePush fix(gitopsUpdateDeployment) include registry The push operation in this step can be forced to bypass branch-protection Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> * add unit test Signed-off-by: Michael Sprauer <Michael.Sprauer@sap.com> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
		| @@ -27,7 +27,7 @@ const toolKustomize = "kustomize" | ||||
|  | ||||
| type iGitopsUpdateDeploymentGitUtils interface { | ||||
| 	CommitFiles(filePaths []string, commitMessage, author string) (plumbing.Hash, error) | ||||
| 	PushChangesToRepository(username, password string) error | ||||
| 	PushChangesToRepository(username, password string, force *bool) error | ||||
| 	PlainClone(username, password, serverURL, directory string) error | ||||
| 	ChangeBranch(branchName string) error | ||||
| } | ||||
| @@ -71,8 +71,8 @@ func (g *gitopsUpdateDeploymentGitUtils) CommitFiles(filePaths []string, commitM | ||||
| 	return commit, nil | ||||
| } | ||||
|  | ||||
| func (g *gitopsUpdateDeploymentGitUtils) PushChangesToRepository(username, password string) error { | ||||
| 	return gitUtil.PushChangesToRepository(username, password, g.repository) | ||||
| func (g *gitopsUpdateDeploymentGitUtils) PushChangesToRepository(username, password string, force *bool) error { | ||||
| 	return gitUtil.PushChangesToRepository(username, password, force, g.repository) | ||||
| } | ||||
|  | ||||
| func (g *gitopsUpdateDeploymentGitUtils) PlainClone(username, password, serverURL, directory string) error { | ||||
| @@ -390,18 +390,19 @@ func runHelmCommand(command gitopsUpdateDeploymentExecRunner, config *gitopsUpda | ||||
| func runKustomizeCommand(command gitopsUpdateDeploymentExecRunner, config *gitopsUpdateDeploymentOptions, filePath string) ([]byte, error) { | ||||
| 	var kustomizeOutput = bytes.Buffer{} | ||||
| 	command.Stdout(&kustomizeOutput) | ||||
| 	registryImage, imageTag, err := buildRegistryPlusImageAndTagSeparately(config) | ||||
|  | ||||
| 	kustomizeParams := []string{ | ||||
| 		"edit", | ||||
| 		"set", | ||||
| 		"image", | ||||
| 		config.DeploymentName + "=" + config.ContainerImageNameTag, | ||||
| 		config.DeploymentName + "=" + registryImage + ":" + imageTag, | ||||
| 	} | ||||
|  | ||||
| 	command.SetDir(filepath.Dir(filePath)) | ||||
|  | ||||
| 	log.Entry().Infof("[kustomize] updating '%s'", filePath) | ||||
| 	err := command.RunExecutable(toolKustomize, kustomizeParams...) | ||||
| 	err = command.RunExecutable(toolKustomize, kustomizeParams...) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to execute kustomize command") | ||||
| 	} | ||||
| @@ -459,7 +460,7 @@ func commitAndPushChanges(config *gitopsUpdateDeploymentOptions, gitUtils iGitop | ||||
| 		return [20]byte{}, errors.Wrap(err, "committing changes failed") | ||||
| 	} | ||||
|  | ||||
| 	err = gitUtils.PushChangesToRepository(config.Username, config.Password) | ||||
| 	err = gitUtils.PushChangesToRepository(config.Username, config.Password, &config.ForcePush) | ||||
| 	if err != nil { | ||||
| 		return [20]byte{}, errors.Wrap(err, "pushing changes failed") | ||||
| 	} | ||||
|   | ||||
| @@ -19,6 +19,7 @@ type gitopsUpdateDeploymentOptions struct { | ||||
| 	BranchName            string   `json:"branchName,omitempty"` | ||||
| 	CommitMessage         string   `json:"commitMessage,omitempty"` | ||||
| 	ServerURL             string   `json:"serverUrl,omitempty"` | ||||
| 	ForcePush             bool     `json:"forcePush,omitempty"` | ||||
| 	Username              string   `json:"username,omitempty"` | ||||
| 	Password              string   `json:"password,omitempty"` | ||||
| 	FilePath              string   `json:"filePath,omitempty"` | ||||
| @@ -133,6 +134,7 @@ func addGitopsUpdateDeploymentFlags(cmd *cobra.Command, stepConfig *gitopsUpdate | ||||
| 	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", 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().BoolVar(&stepConfig.ForcePush, "forcePush", false, "Force push to serverUrl") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Username, "username", os.Getenv("PIPER_username"), "User name for git authentication") | ||||
| 	cmd.Flags().StringVar(&stepConfig.Password, "password", os.Getenv("PIPER_password"), "Password/token for git authentication.") | ||||
| 	cmd.Flags().StringVar(&stepConfig.FilePath, "filePath", os.Getenv("PIPER_filePath"), "Relative path in the git repository to the deployment descriptor file that shall be updated. For different tools this has different semantics:\n\n * `kubectl` - path to the `deployment.yaml` that should be patched. Supports globbing.\n * `helm` - path where the helm chart will be generated into. Here no globbing is supported.\n * `kustomize` - path to the `kustomization.yaml`. Supports globbing.\n") | ||||
| @@ -198,6 +200,15 @@ func gitopsUpdateDeploymentMetadata() config.StepData { | ||||
| 						Aliases:     []config.Alias{{Name: "githubServerUrl"}}, | ||||
| 						Default:     `https://github.com`, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:        "forcePush", | ||||
| 						ResourceRef: []config.ResourceReference{}, | ||||
| 						Scope:       []string{"PARAMETERS", "STAGES", "STEPS"}, | ||||
| 						Type:        "bool", | ||||
| 						Mandatory:   false, | ||||
| 						Aliases:     []config.Alias{}, | ||||
| 						Default:     false, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: "username", | ||||
| 						ResourceRef: []config.ResourceReference{ | ||||
|   | ||||
| @@ -611,7 +611,7 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) { | ||||
| 		Password:              "validAccessToken", | ||||
| 		FilePath:              "kustomization.yaml", | ||||
| 		ContainerRegistryURL:  "https://myregistry.com", | ||||
| 		ContainerImageNameTag: "registry/containers/myFancyContainer:1337", | ||||
| 		ContainerImageNameTag: "containers/myFancyContainer:1337", | ||||
| 		Tool:                  "kustomize", | ||||
| 		DeploymentName:        "myFancyDeployment", | ||||
| 	} | ||||
| @@ -634,7 +634,7 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) { | ||||
| 		assert.Equal(t, "edit", runnerMock.params[0]) | ||||
| 		assert.Equal(t, "set", runnerMock.params[1]) | ||||
| 		assert.Equal(t, "image", runnerMock.params[2]) | ||||
| 		assert.Equal(t, "myFancyDeployment=registry/containers/myFancyContainer:1337", runnerMock.params[3]) | ||||
| 		assert.Equal(t, "myFancyDeployment=myregistry.com/containers/myFancyContainer:1337", runnerMock.params[3]) | ||||
| 	}) | ||||
| 	t.Run("successful run with glob", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| @@ -656,11 +656,21 @@ func TestRunGitopsUpdateDeploymentWithKustomize(t *testing.T) { | ||||
| 		assert.Equal(t, "edit", runnerMock.params[0]) | ||||
| 		assert.Equal(t, "set", runnerMock.params[1]) | ||||
| 		assert.Equal(t, "image", runnerMock.params[2]) | ||||
| 		assert.Equal(t, "myFancyDeployment=registry/containers/myFancyContainer:1337", runnerMock.params[3]) | ||||
| 		assert.Equal(t, "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=registry/containers/myFancyContainer:1337", runnerMock.params[7]) | ||||
| 		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) { | ||||
| @@ -802,6 +812,7 @@ type gitUtilsMock struct { | ||||
| 	failOnCommit       bool | ||||
| 	failOnPush         bool | ||||
| 	skipClone          bool | ||||
| 	forcePush          bool | ||||
| } | ||||
|  | ||||
| func (gitUtilsMock) GetWorktree() (*git.Worktree, error) { | ||||
| @@ -834,10 +845,13 @@ func (v *gitUtilsMock) CommitFiles(newFiles []string, commitMessage string, _ st | ||||
| 	return [20]byte{123}, nil | ||||
| } | ||||
|  | ||||
| func (v gitUtilsMock) PushChangesToRepository(string, string) error { | ||||
| 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 | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -53,14 +53,17 @@ func commitSingleFile(filePath, commitMessage, author string, worktree utilsWork | ||||
| } | ||||
|  | ||||
| // PushChangesToRepository Pushes all committed changes in the repository to the remote repository | ||||
| func PushChangesToRepository(username, password string, repository *git.Repository) error { | ||||
| 	return pushChangesToRepository(username, password, repository) | ||||
| func PushChangesToRepository(username, password string, force *bool, repository *git.Repository) error { | ||||
| 	return pushChangesToRepository(username, password, force, repository) | ||||
| } | ||||
|  | ||||
| func pushChangesToRepository(username, password string, repository utilsRepository) error { | ||||
| func pushChangesToRepository(username, password string, force *bool, repository utilsRepository) error { | ||||
| 	pushOptions := &git.PushOptions{ | ||||
| 		Auth: &http.BasicAuth{Username: username, Password: password}, | ||||
| 	} | ||||
| 	if force != nil { | ||||
| 		pushOptions.Force = *force | ||||
| 	} | ||||
| 	err := repository.Push(pushOptions) | ||||
| 	if err != nil { | ||||
| 		return errors.Wrap(err, "failed to push commit") | ||||
|   | ||||
| @@ -46,7 +46,7 @@ func TestPushChangesToRepository(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	t.Run("successful push", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		err := pushChangesToRepository("user", "password", RepositoryMock{ | ||||
| 		err := pushChangesToRepository("user", "password", nil, RepositoryMock{ | ||||
| 			test: t, | ||||
| 		}) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -54,7 +54,7 @@ func TestPushChangesToRepository(t *testing.T) { | ||||
|  | ||||
| 	t.Run("error pushing", func(t *testing.T) { | ||||
| 		t.Parallel() | ||||
| 		err := pushChangesToRepository("user", "password", RepositoryMockError{}) | ||||
| 		err := pushChangesToRepository("user", "password", nil, RepositoryMockError{}) | ||||
| 		assert.EqualError(t, err, "failed to push commit: error on push commits") | ||||
| 	}) | ||||
| } | ||||
|   | ||||
| @@ -52,6 +52,24 @@ spec: | ||||
|         type: string | ||||
|         default: https://github.com | ||||
|         mandatory: true | ||||
|       - name: forcePush | ||||
|         type: bool | ||||
|         description: Force push to serverUrl | ||||
|         longDescription: | | ||||
|           To bypass branch-protections the git push command can be forced. | ||||
|  | ||||
|           Example: | ||||
|           ```yaml | ||||
|           steps: | ||||
|             gitopsUpdateDeployment: | ||||
|               forcePush: true | ||||
|           ``` | ||||
|         scope: | ||||
|           - PARAMETERS | ||||
|           - STAGES | ||||
|           - STEPS | ||||
|         mandatory: false | ||||
|         default: false | ||||
|       - name: username | ||||
|         type: string | ||||
|         description: User name for git authentication | ||||
|   | ||||
		Reference in New Issue
	
	Block a user