From bebbcb8a4d98b595a08b4507f971e56f0e916138 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Tue, 19 Feb 2019 17:30:28 -0500 Subject: [PATCH] Fix automerge bug in Bitbucket Server BB Server requires we send a 'version' parameter along with our call to /merge. --- server/events/command_runner.go | 2 +- server/events/vcs/bitbucketserver/client.go | 16 ++- .../events/vcs/bitbucketserver/client_test.go | 75 ++++------ server/events/vcs/bitbucketserver/models.go | 1 + .../testdata/pull-request.json | 134 ++++++++++++++++++ 5 files changed, 182 insertions(+), 46 deletions(-) create mode 100644 server/events/vcs/bitbucketserver/testdata/pull-request.json diff --git a/server/events/command_runner.go b/server/events/command_runner.go index 380d14f70..27e806c2d 100644 --- a/server/events/command_runner.go +++ b/server/events/command_runner.go @@ -247,7 +247,7 @@ func (c *DefaultCommandRunner) automerge(ctx *CommandContext, pullStatus *models if err != nil { ctx.Log.Err("automerging failed: %s", err) - failureComment := fmt.Sprintf("Automerging failed: %s", err) + failureComment := fmt.Sprintf("Automerging failed:\n```\n%s\n```", err) if commentErr := c.VCSClient.CreateComment(ctx.BaseRepo, ctx.Pull.Num, failureComment); commentErr != nil { ctx.Log.Err("failed to comment about automerge failing: %s", err) } diff --git a/server/events/vcs/bitbucketserver/client.go b/server/events/vcs/bitbucketserver/client.go index 8748edc53..a8a1848b8 100644 --- a/server/events/vcs/bitbucketserver/client.go +++ b/server/events/vcs/bitbucketserver/client.go @@ -241,7 +241,21 @@ func (b *Client) MergePull(pull models.PullRequest) error { if err != nil { return err } - path := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/merge", b.BaseURL, projectKey, pull.BaseRepo.Name, pull.Num) + + // We need to make a get pull request API call to get the correct "version". + path := fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/pull-requests/%d", b.BaseURL, projectKey, pull.BaseRepo.Name, pull.Num) + resp, err := b.makeRequest("GET", path, nil) + if err != nil { + return err + } + var pullResp PullRequest + if err := json.Unmarshal(resp, &pullResp); err != nil { + return errors.Wrapf(err, "Could not parse response %q", string(resp)) + } + if err := validator.New().Struct(pullResp); err != nil { + return errors.Wrapf(err, "API response %q was missing fields", string(resp)) + } + path = fmt.Sprintf("%s/rest/api/1.0/projects/%s/repos/%s/pull-requests/%d/merge?version=%d", b.BaseURL, projectKey, pull.BaseRepo.Name, pull.Num, *pullResp.Version) _, err = b.makeRequest("POST", path, nil) return err } diff --git a/server/events/vcs/bitbucketserver/client_test.go b/server/events/vcs/bitbucketserver/client_test.go index 47b57c071..f2651eb95 100644 --- a/server/events/vcs/bitbucketserver/client_test.go +++ b/server/events/vcs/bitbucketserver/client_test.go @@ -2,13 +2,14 @@ package bitbucketserver_test import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" + "path/filepath" "strings" "testing" "github.com/runatlantis/atlantis/server/events/models" - "github.com/runatlantis/atlantis/server/events/vcs/bitbucketcloud" "github.com/runatlantis/atlantis/server/events/vcs/bitbucketserver" . "github.com/runatlantis/atlantis/testing" ) @@ -79,39 +80,20 @@ func TestClient_GetModifiedFilesPagination(t *testing.T) { Equals(t, []string{"file1.txt", "file2.txt", "file3.txt"}, files) } -// If the "old" key in the list of files is nil we shouldn't error. -func TestClient_GetModifiedFilesOldNil(t *testing.T) { - resp := ` -{ - "pagelen": 500, - "values": [ - { - "status": "added", - "old": null, - "lines_removed": 0, - "lines_added": 2, - "new": { - "path": "parent/child/file1.txt", - "type": "commit_file", - "links": { - "self": { - "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/src/1ed8205eec00dab4f1c0a8c486a4492c98c51f8e/main.tf" - } - } - }, - "type": "diffstat" - } - ], - "page": 1, - "size": 1 -}` - +// Test that we use the correct version parameter in our call to merge the pull +// request. +func TestClient_MergePull(t *testing.T) { + pullRequest, err := ioutil.ReadFile(filepath.Join("testdata", "pull-request.json")) + Ok(t, err) testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.RequestURI { // The first request should hit this URL. - case "/2.0/repositories/owner/repo/pullrequests/1/diffstat": - w.Write([]byte(resp)) // nolint: errcheck + case "/rest/api/1.0/projects/ow/repos/repo/pull-requests/1": + w.Write(pullRequest) // nolint: errcheck return + case "/rest/api/1.0/projects/ow/repos/repo/pull-requests/1/merge?version=3": + Equals(t, "POST", r.Method) + w.Write(pullRequest) // nolint: errcheck default: t.Errorf("got unexpected request at %q", r.RequestURI) http.Error(w, "not found", http.StatusNotFound) @@ -120,22 +102,27 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) { })) defer testServer.Close() - client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io") - client.BaseURL = testServer.URL + client, err := bitbucketserver.NewClient(http.DefaultClient, "user", "pass", testServer.URL, "runatlantis.io") + Ok(t, err) - files, err := client.GetModifiedFiles(models.Repo{ - FullName: "owner/repo", - Owner: "owner", - Name: "repo", - CloneURL: "", - SanitizedCloneURL: "", - VCSHost: models.VCSHost{ - Type: models.BitbucketCloud, - Hostname: "bitbucket.org", + err = client.MergePull(models.PullRequest{ + Num: 1, + HeadCommit: "", + URL: "", + HeadBranch: "", + BaseBranch: "", + Author: "", + State: 0, + BaseRepo: models.Repo{ + FullName: "owner/repo", + Owner: "owner", + Name: "repo", + SanitizedCloneURL: fmt.Sprintf("%s/scm/ow/repo.git", testServer.URL), + VCSHost: models.VCSHost{ + Type: models.BitbucketCloud, + Hostname: "bitbucket.org", + }, }, - }, models.PullRequest{ - Num: 1, }) Ok(t, err) - Equals(t, []string{"parent/child/file1.txt"}, files) } diff --git a/server/events/vcs/bitbucketserver/models.go b/server/events/vcs/bitbucketserver/models.go index ff12fffb1..67bd48744 100644 --- a/server/events/vcs/bitbucketserver/models.go +++ b/server/events/vcs/bitbucketserver/models.go @@ -23,6 +23,7 @@ type CommonEventData struct { } type PullRequest struct { + Version *int `json:"version,omitempty" validate:"required"` ID *int `json:"id,omitempty" validate:"required"` FromRef *Ref `json:"fromRef,omitempty" validate:"required"` ToRef *Ref `json:"toRef,omitempty" validate:"required"` diff --git a/server/events/vcs/bitbucketserver/testdata/pull-request.json b/server/events/vcs/bitbucketserver/testdata/pull-request.json new file mode 100644 index 000000000..2f816b7ee --- /dev/null +++ b/server/events/vcs/bitbucketserver/testdata/pull-request.json @@ -0,0 +1,134 @@ +{ + "id": 2, + "version": 3, + "title": "hi", + "state": "MERGED", + "open": false, + "closed": true, + "createdDate": 1550611116280, + "updatedDate": 1550611904547, + "closedDate": 1550611904547, + "fromRef": { + "id": "refs/heads/hi", + "displayId": "hi", + "latestCommit": "bdcaa224f4b65edb853a689404ef79cf47d8cdda", + "repository": { + "slug": "example", + "id": 1, + "name": "example", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "AT", + "id": 1, + "name": "atlantis", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/AT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "ssh://git@localhost:7999/at/example.git", + "name": "ssh" + }, + { + "href": "http://localhost:7990/scm/at/example.git", + "name": "http" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/AT/repos/example/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/master", + "displayId": "master", + "latestCommit": "59e03b9cc44e16e20741e328faaac26e377c07bf", + "repository": { + "slug": "example", + "id": 1, + "name": "example", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "AT", + "id": 1, + "name": "atlantis", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/AT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "ssh://git@localhost:7999/at/example.git", + "name": "ssh" + }, + { + "href": "http://localhost:7990/scm/at/example.git", + "name": "http" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/AT/repos/example/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "admin", + "emailAddress": "luke@hashicorp.com", + "id": 1, + "displayName": "admin", + "active": true, + "slug": "admin", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/admin" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/AT/repos/example/pull-requests/2" + } + ] + } +}