From ff85cbef70191f2b3f1001a58d395e9d53c4a52c Mon Sep 17 00:00:00 2001 From: Luke Kysow Date: Sat, 28 Jul 2018 06:57:49 -0700 Subject: [PATCH] Require approval from someone other than PR author In Bitbucket cloud, the pull request author can approve their own pull request. Ignore this approval. Fixes #201 --- runatlantis.io/docs/apply-requirements.md | 7 +- server/events/vcs/bitbucketcloud/client.go | 4 +- .../events/vcs/bitbucketcloud/client_test.go | 65 ++++++ server/events/vcs/bitbucketcloud/models.go | 3 + .../testdata/pull-approved-by-author.json | 166 +++++++++++++++ .../testdata/pull-approved-multiple.json | 190 ++++++++++++++++++ .../testdata/pull-approved.json | 160 +++++++++++++++ .../testdata/pull-unapproved.json | 135 +++++++++++++ 8 files changed, 728 insertions(+), 2 deletions(-) create mode 100644 server/events/vcs/bitbucketcloud/testdata/pull-approved-by-author.json create mode 100644 server/events/vcs/bitbucketcloud/testdata/pull-approved-multiple.json create mode 100644 server/events/vcs/bitbucketcloud/testdata/pull-approved.json create mode 100644 server/events/vcs/bitbucketcloud/testdata/pull-unapproved.json diff --git a/runatlantis.io/docs/apply-requirements.md b/runatlantis.io/docs/apply-requirements.md index 209124e758..43accb43fb 100644 --- a/runatlantis.io/docs/apply-requirements.md +++ b/runatlantis.io/docs/apply-requirements.md @@ -15,7 +15,12 @@ projects: A pull request approval might not be as secure as you'd expect: * In GitHub **any user with read permissions** to the repo can approve a pull request. * In GitLab, you [can set](https://docs.gitlab.com/ee/user/project/merge_requests/merge_request_approvals.html#editing-approvals) who is allowed to approve. -* In Bitbucket, a user can **approve their own pull request**. +::: + +::: tip Note +In Bitbucket Cloud (bitbucket.org), a user can approve their own pull request. +Atlantis does not count that as an approval and requires at least one user that +is not the author of the pull request to approve it. ::: ## Next Steps diff --git a/server/events/vcs/bitbucketcloud/client.go b/server/events/vcs/bitbucketcloud/client.go index 0c7b23772a..81272f1f11 100644 --- a/server/events/vcs/bitbucketcloud/client.go +++ b/server/events/vcs/bitbucketcloud/client.go @@ -110,7 +110,9 @@ func (b *Client) PullIsApproved(repo models.Repo, pull models.PullRequest) (bool return false, errors.Wrapf(err, "API response %q was missing fields", string(resp)) } for _, participant := range pullResp.Participants { - if *participant.Approved { + // Bitbucket allows the author to approve their own pull request. This + // defeats the purpose of approvals so we don't count that approval. + if *participant.Approved && *participant.User.Username != pull.Author { return true, nil } } diff --git a/server/events/vcs/bitbucketcloud/client_test.go b/server/events/vcs/bitbucketcloud/client_test.go index 3baa512f45..f05ef9bf76 100644 --- a/server/events/vcs/bitbucketcloud/client_test.go +++ b/server/events/vcs/bitbucketcloud/client_test.go @@ -2,8 +2,10 @@ package bitbucketcloud_test import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" + "path/filepath" "testing" "github.com/runatlantis/atlantis/server/events/models" @@ -148,3 +150,66 @@ func TestClient_GetModifiedFilesOldNil(t *testing.T) { Ok(t, err) Equals(t, []string{"parent/child/file1.txt"}, files) } + +func TestClient_PullIsApproved(t *testing.T) { + cases := []struct { + description string + testdata string + exp bool + }{ + { + "no approvers", + "pull-unapproved.json", + false, + }, + { + "approver is the author", + "pull-approved-by-author.json", + false, + }, + { + "single approver", + "pull-approved.json", + true, + }, + { + "two approvers one author", + "pull-approved-multiple.json", + true, + }, + } + + for _, c := range cases { + t.Run(c.description, func(t *testing.T) { + json, err := ioutil.ReadFile(filepath.Join("testdata", c.testdata)) + 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": + w.Write([]byte(json)) // nolint: errcheck + return + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + return + } + })) + defer testServer.Close() + + client := bitbucketcloud.NewClient(http.DefaultClient, "user", "pass", "runatlantis.io") + client.BaseURL = testServer.URL + + repo, err := models.NewRepo(models.BitbucketServer, "owner/repo", "https://bitbucket.org/owner/repo.git", "user", "token") + Ok(t, err) + approved, err := client.PullIsApproved(repo, models.PullRequest{ + Num: 1, + Branch: "branch", + Author: "author", + BaseRepo: repo, + }) + Ok(t, err) + Equals(t, c.exp, approved) + }) + } +} diff --git a/server/events/vcs/bitbucketcloud/models.go b/server/events/vcs/bitbucketcloud/models.go index 270cfca2b4..c40edce778 100644 --- a/server/events/vcs/bitbucketcloud/models.go +++ b/server/events/vcs/bitbucketcloud/models.go @@ -59,6 +59,9 @@ type Link struct { } type Participant struct { Approved *bool `json:"approved,omitempty" validate:"required"` + User *struct { + Username *string `json:"username,omitempty" validate:"required"` + } `json:"user,omitempty" validate:"required"` } type Source struct { Repository *Repository `json:"repository,omitempty" validate:"required"` diff --git a/server/events/vcs/bitbucketcloud/testdata/pull-approved-by-author.json b/server/events/vcs/bitbucketcloud/testdata/pull-approved-by-author.json new file mode 100644 index 0000000000..87e1b13b53 --- /dev/null +++ b/server/events/vcs/bitbucketcloud/testdata/pull-approved-by-author.json @@ -0,0 +1,166 @@ +{ + "type": "pullrequest", + "description": "main.tf edited online with Bitbucket", + "links": { + "decline": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments" + }, + "merge": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5" + }, + "activity": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses" + } + }, + "title": "main.tf edited online with Bitbucket", + "close_source_branch": true, + "reviewers": [], + "id": 5, + "destination": { + "commit": { + "hash": "fe607a7f5172", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "master" + } + }, + "created_on": "2018-07-25T12:23:21.100810+00:00", + "summary": { + "raw": "main.tf edited online with Bitbucket", + "markup": "markdown", + "html": "

main.tf edited online with Bitbucket

", + "type": "rendered" + }, + "source": { + "commit": { + "hash": "3428957ade18", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289" + } + }, + "comment_count": 3, + "state": "OPEN", + "task_count": 0, + "participants": [ + { + "role": "PARTICIPANT", + "participated_on": "2018-07-28T00:06:42.255492+00:00", + "type": "participant", + "approved": true, + "user": { + "username": "author", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + } + } + ], + "reason": "", + "updated_on": "2018-07-28T00:06:42.257659+00:00", + "author": { + "username": "lkysow", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + }, + "merge_commit": null, + "closed_by": null +} \ No newline at end of file diff --git a/server/events/vcs/bitbucketcloud/testdata/pull-approved-multiple.json b/server/events/vcs/bitbucketcloud/testdata/pull-approved-multiple.json new file mode 100644 index 0000000000..32ae46622a --- /dev/null +++ b/server/events/vcs/bitbucketcloud/testdata/pull-approved-multiple.json @@ -0,0 +1,190 @@ +{ + "type": "pullrequest", + "description": "main.tf edited online with Bitbucket", + "links": { + "decline": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments" + }, + "merge": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5" + }, + "activity": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses" + } + }, + "title": "main.tf edited online with Bitbucket", + "close_source_branch": true, + "reviewers": [], + "id": 5, + "destination": { + "commit": { + "hash": "fe607a7f5172", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "master" + } + }, + "created_on": "2018-07-25T12:23:21.100810+00:00", + "summary": { + "raw": "main.tf edited online with Bitbucket", + "markup": "markdown", + "html": "

main.tf edited online with Bitbucket

", + "type": "rendered" + }, + "source": { + "commit": { + "hash": "3428957ade18", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + }, + "avatar": { + "href": "https://bytebucket.org/ravatar/%7B94189367-116b-436a-9f77-2314b97a6067%7D?ts=default" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289" + } + }, + "comment_count": 3, + "state": "OPEN", + "task_count": 0, + "participants": [ + { + "role": "PARTICIPANT", + "participated_on": "2018-07-28T00:06:42.255492+00:00", + "type": "participant", + "approved": true, + "user": { + "username": "author", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + } + }, + { + "role": "PARTICIPANT", + "participated_on": "2018-07-28T00:06:42.255492+00:00", + "type": "participant", + "approved": true, + "user": { + "username": "approver", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + } + } + ], + "reason": "", + "updated_on": "2018-07-28T00:06:42.257659+00:00", + "author": { + "username": "lkysow", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + }, + "merge_commit": null, + "closed_by": null +} \ No newline at end of file diff --git a/server/events/vcs/bitbucketcloud/testdata/pull-approved.json b/server/events/vcs/bitbucketcloud/testdata/pull-approved.json new file mode 100644 index 0000000000..c40340d286 --- /dev/null +++ b/server/events/vcs/bitbucketcloud/testdata/pull-approved.json @@ -0,0 +1,160 @@ +{ + "type": "pullrequest", + "description": "main.tf edited online with Bitbucket", + "links": { + "decline": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments" + }, + "merge": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5" + }, + "activity": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses" + } + }, + "title": "main.tf edited online with Bitbucket", + "close_source_branch": true, + "reviewers": [], + "id": 5, + "destination": { + "commit": { + "hash": "fe607a7f5172", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "master" + } + }, + "created_on": "2018-07-25T12:23:21.100810+00:00", + "summary": { + "raw": "main.tf edited online with Bitbucket", + "markup": "markdown", + "html": "

main.tf edited online with Bitbucket

", + "type": "rendered" + }, + "source": { + "commit": { + "hash": "3428957ade18", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289" + } + }, + "comment_count": 3, + "state": "OPEN", + "task_count": 0, + "participants": [ + { + "role": "PARTICIPANT", + "participated_on": "2018-07-28T00:06:42.255492+00:00", + "type": "participant", + "approved": true, + "user": { + "username": "approver", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + } + } + ], + "reason": "", + "updated_on": "2018-07-28T00:06:42.257659+00:00", + "author": { + "username": "lkysow", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + }, + "merge_commit": null, + "closed_by": null +} \ No newline at end of file diff --git a/server/events/vcs/bitbucketcloud/testdata/pull-unapproved.json b/server/events/vcs/bitbucketcloud/testdata/pull-unapproved.json new file mode 100644 index 0000000000..9d8de84ac8 --- /dev/null +++ b/server/events/vcs/bitbucketcloud/testdata/pull-unapproved.json @@ -0,0 +1,135 @@ +{ + "type": "pullrequest", + "description": "main.tf edited online with Bitbucket", + "links": { + "decline": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/decline" + }, + "commits": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/commits" + }, + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5" + }, + "comments": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/comments" + }, + "merge": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/merge" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/pull-requests/5" + }, + "activity": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/activity" + }, + "diff": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/diff" + }, + "approve": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/approve" + }, + "statuses": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/pullrequests/5/statuses" + } + }, + "title": "main.tf edited online with Bitbucket", + "close_source_branch": true, + "reviewers": [], + "id": 5, + "destination": { + "commit": { + "hash": "fe607a7f5172", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/fe607a7f5172" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/fe607a7f5172" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "master" + } + }, + "created_on": "2018-07-25T12:23:21.100810+00:00", + "summary": { + "raw": "main.tf edited online with Bitbucket", + "markup": "markdown", + "html": "

main.tf edited online with Bitbucket

", + "type": "rendered" + }, + "source": { + "commit": { + "hash": "3428957ade18", + "type": "commit", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example/commit/3428957ade18" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example/commits/3428957ade18" + } + } + }, + "repository": { + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/repositories/lkysow/atlantis-example" + }, + "html": { + "href": "https://bitbucket.org/lkysow/atlantis-example" + } + }, + "type": "repository", + "name": "atlantis-example", + "full_name": "lkysow/atlantis-example", + "uuid": "{94189367-116b-436a-9f77-2314b97a6067}" + }, + "branch": { + "name": "lkysow/maintf-edited-online-with-bitbucket-1532521398289" + } + }, + "comment_count": 3, + "state": "OPEN", + "task_count": 0, + "participants": [], + "reason": "", + "updated_on": "2018-07-28T00:06:42.257659+00:00", + "author": { + "username": "lkysow", + "display_name": "Luke", + "account_id": "557058:dc3817de-68b5-45cd-b81c-5c39d2560090", + "links": { + "self": { + "href": "https://api.bitbucket.org/2.0/users/lkysow" + }, + "html": { + "href": "https://bitbucket.org/lkysow/" + }, + "avatar": { + "href": "https://bitbucket.org/account/lkysow/avatar/" + } + }, + "type": "user", + "uuid": "{bf34a99b-8a11-452c-8fbc-bdffc340e584}" + }, + "merge_commit": null, + "closed_by": null +} \ No newline at end of file