diff --git a/server/events/vcs/github_client.go b/server/events/vcs/github_client.go index 826cff8b09..234c001ee9 100644 --- a/server/events/vcs/github_client.go +++ b/server/events/vcs/github_client.go @@ -719,15 +719,19 @@ func StatusContextPassed(statusContext StatusContext, vcsstatusname string) bool } func ExpectedCheckPassed(expectedContext githubv4.String, checkRuns []CheckRun, statusContexts []StatusContext, vcsstatusname string) bool { - for _, checkRun := range checkRuns { - if checkRun.Name == expectedContext { - return CheckRunPassed(checkRun) + // Iterate through checkRuns from the end, as GitHub returns them in chronological order + // The last checkRun in the array represents the current status + for i := len(checkRuns) - 1; i >= 0; i-- { + if checkRuns[i].Name == expectedContext { + return CheckRunPassed(checkRuns[i]) } } - for _, statusContext := range statusContexts { - if statusContext.Context == expectedContext { - return StatusContextPassed(statusContext, vcsstatusname) + // Iterate through statusContexts from the end, as GitHub returns them in chronological order + // The last statusContext in the array represents the current status + for i := len(statusContexts) - 1; i >= 0; i-- { + if statusContexts[i].Context == expectedContext { + return StatusContextPassed(statusContexts[i], vcsstatusname) } } @@ -735,16 +739,18 @@ func ExpectedCheckPassed(expectedContext githubv4.String, checkRuns []CheckRun, } func (g *GithubClient) ExpectedWorkflowPassed(expectedWorkflow WorkflowFileReference, checkRuns []CheckRun) (bool, error) { - for _, checkRun := range checkRuns { - if checkRun.CheckSuite.WorkflowRun == nil { + // Iterate through checkRuns from the end, as GitHub returns them in chronological order + // The last checkRun in the array represents the current status + for i := len(checkRuns) - 1; i >= 0; i-- { + if checkRuns[i].CheckSuite.WorkflowRun == nil { continue } - match, err := g.WorkflowRunMatchesWorkflowFileReference(*checkRun.CheckSuite.WorkflowRun, expectedWorkflow) + match, err := g.WorkflowRunMatchesWorkflowFileReference(*checkRuns[i].CheckSuite.WorkflowRun, expectedWorkflow) if err != nil { return false, err } if match { - return CheckRunPassed(checkRun), nil + return CheckRunPassed(checkRuns[i]), nil } } diff --git a/server/events/vcs/github_client_test.go b/server/events/vcs/github_client_test.go index 632fe6bca6..71afffa911 100644 --- a/server/events/vcs/github_client_test.go +++ b/server/events/vcs/github_client_test.go @@ -745,6 +745,12 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) `"APPROVED"`, true, }, + { + "blocked", + "branch-protection-passed-with-retry.json", + `"APPROVED"`, + true, + }, { "blocked", "ruleset-check-expected.json", @@ -763,6 +769,12 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) `"APPROVED"`, true, }, + { + "blocked", + "ruleset-check-passed-with-retry.json", + `"APPROVED"`, + true, + }, { "blocked", "ruleset-workflow-expected.json", @@ -793,6 +805,12 @@ func TestGithubClient_PullIsMergeableWithAllowMergeableBypassApply(t *testing.T) `"APPROVED"`, false, }, + { + "blocked", + "ruleset-workflow-passed-with-retry.json", + `"APPROVED"`, + true, + }, } // Use a real GitHub json response and edit the mergeable_state field. diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed-with-retry.json b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed-with-retry.json new file mode 100644 index 0000000000..4a0d96f75d --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/branch-protection-passed-with-retry.json @@ -0,0 +1,70 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "FAILURE", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "SUCCESS", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed-with-retry.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed-with-retry.json new file mode 100644 index 0000000000..6670e33ea8 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-check-passed-with-retry.json @@ -0,0 +1,80 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + }, + { + "context": "my-required-expected-check" + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "FAILURE", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "my-required-expected-check", + "state": "SUCCESS", + "isRequired": true + } + ] + } + } + } + } + ] + } + } + } + } +} diff --git a/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-with-retry.json b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-with-retry.json new file mode 100644 index 0000000000..a62e000d30 --- /dev/null +++ b/server/events/vcs/testdata/github-pull-request-mergeability/ruleset-workflow-passed-with-retry.json @@ -0,0 +1,110 @@ +{ + "data": { + "repository": { + "pullRequest": { + "reviewDecision": null, + "baseRef": { + "branchProtectionRule": { + "requiredStatusChecks": [] + }, + "rules": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "type": "REQUIRED_STATUS_CHECKS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "requiredStatusChecks": [ + { + "context": "atlantis/apply" + } + ] + } + }, + { + "type": "WORKFLOWS", + "repositoryRuleset": { + "enforcement": "ACTIVE" + }, + "parameters": { + "workflows": [ + { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryId": 120519269, + "sha": null + } + ] + } + } + ] + } + }, + "commits": { + "nodes": [ + { + "commit": { + "statusCheckRollup": { + "contexts": { + "pageInfo": { + "endCursor": "QWERTY", + "hasNextPage": false + }, + "nodes": [ + { + "__typename": "StatusContext", + "context": "atlantis/apply", + "state": "PENDING", + "isRequired": true + }, + { + "__typename": "StatusContext", + "context": "atlantis/plan", + "state": "SUCCESS", + "isRequired": false + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "FAILURE", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + }, + { + "__typename": "CheckRun", + "name": "my required check", + "conclusion": "SUCCESS", + "isRequired": true, + "checkSuite": { + "workflowRun": { + "file": { + "path": ".github/workflows/my-required-workflow.yaml", + "repositoryFileUrl": "https://github.com/runatlantis/atlantis/blob/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/.github/workflows/my-required-workflow.yaml", + "repositoryName": "runatlantis/atlantis" + } + } + } + } + ] + } + } + } + } + ] + } + } + } + } +}