diff --git a/server/events/vcs/gitlab_client.go b/server/events/vcs/gitlab_client.go index ba111c85e9..e0b8abdcf3 100644 --- a/server/events/vcs/gitlab_client.go +++ b/server/events/vcs/gitlab_client.go @@ -416,27 +416,37 @@ func (g *GitlabClient) UpdateStatus(logger logging.SimpleLogging, repo models.Re retries := 1 delay := 2 * time.Second - var commit *gitlab.Commit + var commitStatuses []*gitlab.CommitStatus var resp *gitlab.Response var err error + // get the last commit status with the same ref + getCommitStatusesOptions := &gitlab.GetCommitStatusesOptions{ + ListOptions: gitlab.ListOptions{ + Sort: "desc", + PerPage: 1, + }, + Ref: gitlab.Ptr(pull.HeadBranch), + } + // Try a couple of times to get the pipeline ID for the commit for i := 0; i <= retries; i++ { - commit, resp, err = g.Client.Commits.GetCommit(repo.FullName, pull.HeadCommit, nil) + commitStatuses, resp, err = g.Client.Commits.GetCommitStatuses(repo.FullName, pull.HeadCommit, getCommitStatusesOptions) + if resp != nil { logger.Debug("GET /projects/%s/repository/commits/%d: %d", pull.BaseRepo.ID(), pull.HeadCommit, resp.StatusCode) } if err != nil { return err } - if commit.LastPipeline != nil { - logger.Info("Pipeline found for commit %s, setting pipeline ID to %d", pull.HeadCommit, commit.LastPipeline.ID) + if len(commitStatuses) > 0 { + logger.Info("Pipeline found for commit %s and ref %s, setting pipeline ID to %d", pull.HeadCommit, pull.HeadBranch, commitStatuses[0].PipelineId) // Set the pipeline ID to the last pipeline that ran for the commit - setCommitStatusOptions.PipelineID = gitlab.Ptr(commit.LastPipeline.ID) + setCommitStatusOptions.PipelineID = gitlab.Ptr(commitStatuses[0].PipelineId) break } if i != retries { - logger.Info("No pipeline found for commit %s, retrying in %s", pull.HeadCommit, delay) + logger.Info("No pipeline found for commit %s and ref %s, retrying in %s", pull.HeadCommit, pull.HeadBranch, delay) time.Sleep(delay) } else { // If we've exhausted all retries, set the Ref to the branch name @@ -458,7 +468,7 @@ func (g *GitlabClient) UpdateStatus(logger logging.SimpleLogging, repo models.Re "attempt", i+1, "max_attempts", maxAttempts, "repo", repo.FullName, - "commit", commit.ShortID, + "commit", pull.HeadCommit, "state", state.String(), ) diff --git a/server/events/vcs/gitlab_client_test.go b/server/events/vcs/gitlab_client_test.go index d56bfa2f45..7e88f95a90 100644 --- a/server/events/vcs/gitlab_client_test.go +++ b/server/events/vcs/gitlab_client_test.go @@ -28,6 +28,7 @@ const updateStatusDescription = "description" const updateStatusTargetUrl = "https://google.com" const updateStatusSrc = "src" const updateStatusHeadBranch = "test" +const updateStatusHeadBranchDuplicate = "test_duplicate" /* UpdateStatus request JSON body object */ type UpdateStatusJsonBody struct { @@ -39,15 +40,13 @@ type UpdateStatusJsonBody struct { Ref string `json:"ref"` } -/* GetCommit response last_pipeline JSON object */ -type GetCommitResponseLastPipeline struct { - ID int `json:"id"` +type CommitStatus struct { + Ref string `json:"ref"` + PipelineID int `json:"pipeline_id"` } -/* GetCommit response JSON object */ -type GetCommitResponse struct { - LastPipeline GetCommitResponseLastPipeline `json:"last_pipeline"` -} +/* GetCommitStatuses response JSON object */ +type GetCommitStatusesResponse []CommitStatus /* Empty struct for JSON marshalling */ type EmptyStruct struct{} @@ -346,18 +345,19 @@ func TestGitlabClient_UpdateStatus(t *testing.T) { _, err = w.Write(setStatusJsonResponse) Ok(t, err) - case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha": + case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha/statuses?per_page=1&ref=test&sort=desc": w.WriteHeader(http.StatusOK) - getCommitResponse := GetCommitResponse{ - LastPipeline: GetCommitResponseLastPipeline{ - ID: gitlabPipelineSuccessMrID, + getCommitStatusesResponse := GetCommitStatusesResponse{ + CommitStatus{ + Ref: updateStatusHeadBranch, + PipelineID: gitlabPipelineSuccessMrID, }, } - getCommitJsonResponse, err := json.Marshal(getCommitResponse) + getCommitStatusesJsonResponse, err := json.Marshal(getCommitStatusesResponse) Ok(t, err) - _, err = w.Write(getCommitJsonResponse) + _, err = w.Write(getCommitStatusesJsonResponse) Ok(t, err) case "/api/v4/": @@ -468,24 +468,25 @@ func TestGitlabClient_UpdateStatusGetCommitRetryable(t *testing.T) { _, err = w.Write(getCommitJsonResponse) Ok(t, err) - case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha": + case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha/statuses?per_page=1&ref=test&sort=desc": handledNumberOfRequests++ noCommitLastPipeline := handledNumberOfRequests <= c.commitsWithNoLastPipeline w.WriteHeader(http.StatusOK) if noCommitLastPipeline { - getCommitJsonResponse, err := json.Marshal(EmptyStruct{}) + getCommitStatusesJsonResponse, err := json.Marshal([]EmptyStruct{}) Ok(t, err) - _, err = w.Write(getCommitJsonResponse) + _, err = w.Write(getCommitStatusesJsonResponse) Ok(t, err) } else { - getCommitResponse := GetCommitResponse{ - LastPipeline: GetCommitResponseLastPipeline{ - ID: gitlabPipelineSuccessMrID, + getCommitStatusesResponse := GetCommitStatusesResponse{ + CommitStatus{ + Ref: updateStatusHeadBranch, + PipelineID: gitlabPipelineSuccessMrID, }, } - getCommitJsonResponse, err := json.Marshal(getCommitResponse) + getCommitJsonResponse, err := json.Marshal(getCommitStatusesResponse) Ok(t, err) _, err = w.Write(getCommitJsonResponse) @@ -604,18 +605,19 @@ func TestGitlabClient_UpdateStatusSetCommitStatusConflictRetryable(t *testing.T) _, err = w.Write(getCommitJsonResponse) Ok(t, err) - case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha": + case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha/statuses?per_page=1&ref=test&sort=desc": w.WriteHeader(http.StatusOK) - getCommitResponse := GetCommitResponse{ - LastPipeline: GetCommitResponseLastPipeline{ - ID: gitlabPipelineSuccessMrID, + getCommitStatusesResponse := GetCommitStatusesResponse{ + CommitStatus{ + Ref: updateStatusHeadBranch, + PipelineID: gitlabPipelineSuccessMrID, }, } - getCommitJsonResponse, err := json.Marshal(getCommitResponse) + getCommitStatusesJsonResponse, err := json.Marshal(getCommitStatusesResponse) Ok(t, err) - _, err = w.Write(getCommitJsonResponse) + _, err = w.Write(getCommitStatusesJsonResponse) Ok(t, err) case "/api/v4/": @@ -669,6 +671,104 @@ func TestGitlabClient_UpdateStatusSetCommitStatusConflictRetryable(t *testing.T) } } +func TestGitlabClient_UpdateStatusDifferentRef(t *testing.T) { + logger := logging.NewNoopLogger(t) + + cases := []struct { + status models.CommitStatus + expState string + }{ + { + models.PendingCommitStatus, + "running", + }, + { + models.SuccessCommitStatus, + "success", + }, + { + models.FailedCommitStatus, + "failed", + }, + } + for _, c := range cases { + t.Run(c.expState, func(t *testing.T) { + gotRequest := false + testServer := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.RequestURI { + case "/api/v4/projects/runatlantis%2Fatlantis/statuses/sha": + gotRequest = true + + var updateStatusJsonBody UpdateStatusJsonBody + err := json.NewDecoder(r.Body).Decode(&updateStatusJsonBody) + Ok(t, err) + + Equals(t, c.expState, updateStatusJsonBody.State) + Equals(t, updateStatusSrc, updateStatusJsonBody.Context) + Equals(t, updateStatusTargetUrl, updateStatusJsonBody.TargetUrl) + Equals(t, updateStatusDescription, updateStatusJsonBody.Description) + Equals(t, updateStatusHeadBranch, updateStatusJsonBody.Ref) + + defer r.Body.Close() // nolint: errcheck + + setStatusJsonResponse, err := json.Marshal(EmptyStruct{}) + Ok(t, err) + + _, err = w.Write(setStatusJsonResponse) + Ok(t, err) + + case "/api/v4/projects/runatlantis%2Fatlantis/repository/commits/sha/statuses?per_page=1&ref=test&sort=desc": + w.WriteHeader(http.StatusOK) + + getCommitStatusesJsonResponse, err := json.Marshal([]EmptyStruct{}) + Ok(t, err) + + _, err = w.Write(getCommitStatusesJsonResponse) + Ok(t, err) + + case "/api/v4/": + // Rate limiter requests. + w.WriteHeader(http.StatusOK) + + default: + t.Errorf("got unexpected request at %q", r.RequestURI) + http.Error(w, "not found", http.StatusNotFound) + } + })) + + internalClient, err := gitlab.NewClient("token", gitlab.WithBaseURL(testServer.URL)) + Ok(t, err) + client := &GitlabClient{ + Client: internalClient, + Version: nil, + } + + repo := models.Repo{ + FullName: "runatlantis/atlantis", + Owner: "runatlantis", + Name: "atlantis", + } + err = client.UpdateStatus( + logger, + repo, + models.PullRequest{ + Num: 1, + BaseRepo: repo, + HeadCommit: "sha", + HeadBranch: updateStatusHeadBranch, + }, + c.status, + updateStatusSrc, + updateStatusDescription, + updateStatusTargetUrl, + ) + Ok(t, err) + Assert(t, gotRequest, "expected to get the request") + }) + } +} + func TestGitlabClient_PullIsMergeable(t *testing.T) { logger := logging.NewNoopLogger(t) gitlabClientUnderTest = true