diff --git a/runatlantis.io/docs/server-configuration.md b/runatlantis.io/docs/server-configuration.md index bca4e04da4..ae3eee93e8 100644 --- a/runatlantis.io/docs/server-configuration.md +++ b/runatlantis.io/docs/server-configuration.md @@ -564,11 +564,6 @@ This is useful when you have many projects and want to keep the pull request cle ``` Feature flag to enable ability to use `mergeable` mode with required apply status check. - ::: warning NOTE - If there aren't any required checks set in the Github branch protection settings then this will cause atlantis to fail. - See issue https://github.com/runatlantis/atlantis/issues/2663. - ::: - ### `--gitlab-hostname` ```bash atlantis server --gitlab-hostname="my.gitlab.enterprise.com" diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go index aa64dc8cbc..5008970f96 100644 --- a/server/events/vcs/github_client.go +++ b/server/events/vcs/github_client.go @@ -418,6 +418,10 @@ func (g *GithubClient) GetCombinedStatusMinusApply(repo models.Repo, pull *githu return false, errors.Wrap(err, "getting required status checks") } + if required.RequiredStatusChecks == nil { + return true, nil + } + //check check suite/check run api checksuites, _, err := g.client.Checks.ListCheckSuitesForRef(context.Background(), *pull.Head.Repo.Owner.Login, repo.Name, *pull.Head.Ref, nil) if err != nil { diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go index b84de928df..e42e353027 100644 --- a/server/events/vcs/github_client_test.go +++ b/server/events/vcs/github_client_test.go @@ -737,6 +737,114 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) } } +func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApplyButWithNoBranchProtectionChecks(t *testing.T) { + vcsStatusName := "atlantis" + cases := []struct { + state string + reviewDecision string + expMergeable bool + }{ + { + "blocked", + `"REVIEW_REQUIRED"`, + false, + }, + } + + // Use a real GitHub json response and edit the mergeable_state field. + jsBytes, err := os.ReadFile("testdata/github-pull-request.json") + Ok(t, err) + prJSON := string(jsBytes) + + // Status Check Response + jsBytes, err = os.ReadFile("testdata/github-commit-status-full.json") + Ok(t, err) + commitJSON := string(jsBytes) + + // Branch protection Response + jsBytes, err = os.ReadFile("testdata/github-branch-protection-no-required-checks.json") + Ok(t, err) + branchProtectionJSON := string(jsBytes) + + // List check suites Response + jsBytes, err = os.ReadFile("testdata/github-commit-check-suites-completed.json") + Ok(t, err) + checkSuites := string(jsBytes) + + // List check runs in a check suite + jsBytes, err = os.ReadFile("testdata/github-commit-check-suites-check-runs-completed.json") + Ok(t, err) + checkRuns := string(jsBytes) + + for _, c := range cases { + t.Run(c.state, func(t *testing.T) { + response := strings.Replace(prJSON, + `"mergeable_state": "clean"`, + fmt.Sprintf(`"mergeable_state": "%s"`, c.state), + 1, + ) + + // reviewDecision Response + reviewDecision := fmt.Sprintf(`{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": %s + } + } + } + }`, c.reviewDecision) + + testServer := httptest.NewTLSServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v3/repos/octocat/Hello-World/pulls/1": + w.Write([]byte(response)) // nolint: errcheck + return + case "/api/v3/repos/octocat/Hello-World/pulls/1/reviews?per_page=300": + w.Write([]byte("[]")) // nolint: errcheck + return + case "/api/v3/repos/octocat/Hello-World/commits/new-topic/status": + w.Write([]byte(commitJSON)) // nolint: errcheck + case "/api/graphql": + w.Write([]byte(reviewDecision)) // nolint: errcheck + case "/api/v3/repos/octocat/Hello-World/branches/main/protection": + w.Write([]byte(branchProtectionJSON)) // nolint: errcheck + case "/api/v3/repos/octocat/Hello-World/commits/new-topic/check-suites": + w.Write([]byte(checkSuites)) // nolint: errcheck + case "/api/v3/repos/octocat/Hello-World/check-suites/1234567890/check-runs": + w.Write([]byte(checkRuns)) // nolint: errcheck + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + return + } + })) + testServerURL, err := url.Parse(testServer.URL) + Ok(t, err) + client, err := vcs.NewGithubClient(testServerURL.Host, &vcs.GithubUserCredentials{"user", "pass"}, vcs.GithubConfig{AllowMergeableBypassApply: true}, logging.NewNoopLogger(t)) + Ok(t, err) + defer disableSSLVerification()() + + actMergeable, err := client.PullIsMergeable(models.Repo{ + FullName: "octocat/Hello-World", + Owner: "octocat", + Name: "Hello-World", + CloneURL: "", + SanitizedCloneURL: "", + VCSHost: models.VCSHost{ + Type: models.Github, + Hostname: "github.com", + }, + }, models.PullRequest{ + Num: 1, + }, vcsStatusName) + Ok(t, err) + Equals(t, c.expMergeable, actMergeable) + }) + } +} + func TestGithubClient_MergePullHandlesError(t *testing.T) { cases := []struct { code int diff --git a/server/events/vcs/testdata/github-branch-protection-no-required-checks.json b/server/events/vcs/testdata/github-branch-protection-no-required-checks.json new file mode 100644 index 0000000000..4dd1496b79 --- /dev/null +++ b/server/events/vcs/testdata/github-branch-protection-no-required-checks.json @@ -0,0 +1,10 @@ +{ + "url": "https://api.github.com/repos/octocat/repo/branches/master/protection", + "required_pull_request_reviews": { + "url": "https://api.github.com/repos/octocat/repo/branches/master/protection/required_pull_request_reviews", + "dismiss_stale_reviews": false, + "require_code_owner_reviews": false, + "require_last_push_approval": false, + "required_approving_review_count": 1 + } +} diff --git a/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json b/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json new file mode 100644 index 0000000000..125e4d1ddf --- /dev/null +++ b/server/events/vcs/testdata/github-commit-check-suites-check-runs-completed.json @@ -0,0 +1,95 @@ +{ + "total_count": 1, + "check_runs": [ + { + "id": 4, + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "node_id": "MDg6Q2hlY2tSdW40", + "external_id": "", + "url": "https://api.github.com/repos/github/hello-world/check-runs/4", + "html_url": "https://github.com/github/hello-world/runs/4", + "details_url": "https://example.com", + "status": "completed", + "conclusion": "success", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z", + "output": { + "title": "Mighty Readme report", + "summary": "There are 0 failures, 2 warnings, and 1 notice.", + "text": "You may have some misspelled words on lines 2 and 4. You also may want to add a section in your README about how to install your app.", + "annotations_count": 2, + "annotations_url": "https://api.github.com/repos/github/hello-world/check-runs/4/annotations" + }, + "name": "mighty_readme", + "check_suite": { + "id": 5 + }, + "app": { + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] + } + ] +} diff --git a/server/events/vcs/testdata/github-commit-check-suites-completed.json b/server/events/vcs/testdata/github-commit-check-suites-completed.json new file mode 100644 index 0000000000..b8af9c32a9 --- /dev/null +++ b/server/events/vcs/testdata/github-commit-check-suites-completed.json @@ -0,0 +1,169 @@ +{ + "total_count": 1, + "check_suites": [ + { + "id": 1234567890, + "node_id": "CS_kwDOHE7PYM8AAAAB2iIZfQ", + "head_branch": "atlantis-patch-2", + "head_sha": "4273e07c528292222f119a040079093bf1f11232", + "status": "completed", + "conclusion": null, + "url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890", + "before": "0000000000000000000000000000000000000000", + "after": "4273e07c528292222f119a040079093bf1f11232", + "pull_requests": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/pulls/1", + "id": 1035065545, + "number": 1, + "head": { + "ref": "atlantis-patch-2", + "sha": "4273e07c528292222f119a040079093bf1f11232", + "repo": { + "id": 474926944, + "url": "https://api.github.com/repos/octocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "main", + "sha": "6f5744874b33ceb6a5c91edc91085991dbd1f61a", + "repo": { + "id": 474926944, + "url": "https://api.github.com/repos/octocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "app": { + "id": 184783, + "slug": "atlantis", + "node_id": "A_kwHOBbMkBs4AAtHP", + "owner": { + "login": "octocat", + "id": 95626246, + "node_id": "O_kgDOBbMkBg", + "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "atlantis", + "description": "", + "external_url": "https://atlantis.localhost/", + "html_url": "https://github.com/apps/atlantis", + "created_at": "2022-03-29T08:51:26Z", + "updated_at": "2022-07-01T11:35:37Z", + "permissions": { + "administration": "read", + "checks": "write", + "contents": "write", + "issues": "write", + "metadata": "read", + "pull_requests": "write", + "statuses": "write" + }, + "events": [] + }, + "created_at": "2022-08-24T07:06:21Z", + "updated_at": "2022-08-24T07:06:21Z", + "rerequestable": true, + "runs_rerequestable": true, + "latest_check_runs_count": 0, + "check_runs_url": "https://api.github.com/repos/octocat/Hello-World/check-suites/1234567890/check-runs", + "head_commit": { + "id": "4273e07c528292222f119a040079093bf1f11232", + "tree_id": "56781332464aabdfae51b7f37f72ffc6ce8ce54e", + "message": "test atlantis", + "timestamp": "2022-08-24T07:06:21Z", + "author": { + "name": "octocat", + "email": "octocat@noreply.github.com" + }, + "committer": { + "name": "GitHub", + "email": "noreply@github.com" + } + }, + "repository": { + "id": 474926944, + "node_id": "R_kgDOHE7PYA", + "name": "Hello-World", + "full_name": "octocat/Hello-World", + "private": true, + "owner": { + "login": "octocat", + "id": 95626246, + "node_id": "O_kgDOBbMkBg", + "avatar_url": "https://avatars.githubusercontent.com/u/95626246?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/octocat", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "repos_url": "https://api.github.com/users/octocat/repos", + "events_url": "https://api.github.com/users/octocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/octocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/octocat/Hello-World", + "forks_url": "https://api.github.com/repos/octocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/octocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/octocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/octocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/octocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/octocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments" + } + } + ] +}