Skip to content

Commit b0bcbac

Browse files
ab-ghoshzakisk
authored andcommitted
fix(gitlab): pin commit statuses to same pipeline
When PAC posts multiple commit statuses for the same SHA, GitLab's auto-assignment logic can route them to different pipelines, leaving the MR pipeline permanently stuck with stale intermediate statuses. Cache the pipeline_id returned by the first SetCommitStatus response and pass it on subsequent calls for the same (project, SHA) pair so all statuses land in the same GitLab pipeline. Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Abhishek Ghosh <abghosh@redhat.com>
1 parent e951778 commit b0bcbac

6 files changed

Lines changed: 542 additions & 74 deletions

File tree

pkg/apis/pipelinesascode/keys/keys.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const (
4848
OriginalPRName = pipelinesascode.GroupName + "/original-prname"
4949
GitAuthSecret = pipelinesascode.GroupName + "/git-auth-secret"
5050
CheckRunID = pipelinesascode.GroupName + "/check-run-id"
51+
GitLabPipelineID = pipelinesascode.GroupName + "/gitlab-pipeline-id"
5152
OnEvent = pipelinesascode.GroupName + "/on-event"
5253
OnComment = pipelinesascode.GroupName + "/on-comment"
5354
OnTargetBranch = pipelinesascode.GroupName + "/on-target-branch"

pkg/provider/gitlab/gitlab.go

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import (
1111
"regexp"
1212
"strconv"
1313
"strings"
14+
"sync"
1415

16+
"github.com/openshift-pipelines/pipelines-as-code/pkg/action"
17+
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
1518
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
1619
"github.com/openshift-pipelines/pipelines-as-code/pkg/changedfiles"
1720
"github.com/openshift-pipelines/pipelines-as-code/pkg/events"
@@ -69,6 +72,8 @@ type Provider struct {
6972
memberCache map[int64]bool
7073
cachedChangedFiles *changedfiles.ChangedFiles
7174
pacUserID int64 // user login used by PAC
75+
pipelineID int64
76+
pipelineIDMu sync.Mutex
7277
}
7378

7479
var defaultGitlabListOptions = gitlab.ListOptions{
@@ -354,22 +359,45 @@ func (v *Provider) CreateStatus(ctx context.Context, event *info.Event, statusOp
354359
Context: gitlab.Ptr(contextName),
355360
}
356361

362+
// Reuse a previously discovered pipeline ID so that all commit statuses
363+
// for the same SHA land in the same GitLab pipeline.
364+
if statusOpts.PipelineRun != nil {
365+
if id, ok := statusOpts.PipelineRun.GetAnnotations()[keys.GitLabPipelineID]; ok {
366+
pid, err := strconv.ParseInt(id, 10, 64)
367+
if err == nil {
368+
opt.PipelineID = gitlab.Ptr(pid)
369+
v.pipelineIDMu.Lock()
370+
v.pipelineID = pid
371+
v.pipelineIDMu.Unlock()
372+
}
373+
}
374+
}
375+
if opt.PipelineID == nil {
376+
v.pipelineIDMu.Lock()
377+
if v.pipelineID != 0 {
378+
opt.PipelineID = gitlab.Ptr(v.pipelineID)
379+
}
380+
v.pipelineIDMu.Unlock()
381+
}
382+
357383
// In case we have access, set the status. Typically, on a Merge Request (MR)
358384
// from a fork in an upstream repository, the token needs to have write access
359385
// to the fork repository in order to create a status. However, the token set on the
360386
// Repository CR usually doesn't have such broad access, preventing from creating
361387
// a status comment on it.
362388
// This would work on a push or an MR from a branch within the same repo.
363389
// Ignoring errors because of the write access issues,
364-
_, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt)
390+
commitStatus, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt)
365391
if err != nil {
366392
v.Logger.Debugf("cannot set status with the GitLab token on the source project: %v", err)
367393
} else {
394+
v.storePipelineID(ctx, statusOpts, commitStatus.PipelineID)
368395
// we managed to set the status on the source repo, all good we are done
369396
v.Logger.Debugf("created commit status on source project ID %d", event.TargetProjectID)
370397
return nil
371398
}
372-
if _, _, err = v.Client().Commits.SetCommitStatus(event.TargetProjectID, event.SHA, opt); err == nil {
399+
if commitStatus, _, err = v.Client().Commits.SetCommitStatus(event.TargetProjectID, event.SHA, opt); err == nil {
400+
v.storePipelineID(ctx, statusOpts, commitStatus.PipelineID)
373401
v.Logger.Debugf("created commit status on target project ID %d", event.TargetProjectID)
374402
// we managed to set the status on the target repo, all good we are done
375403
return nil
@@ -860,3 +888,40 @@ func (v *Provider) formatPipelineComment(sha string, status providerstatus.Statu
860888
return fmt.Sprintf("%s **%s: %s/%s for %s**\n\n%s\n\n<small>Full log available [here](%s)</small>",
861889
emoji, status.Title, v.pacInfo.ApplicationName, status.OriginalPipelineRunName, sha, status.Text, status.DetailsURL)
862890
}
891+
892+
// storePipelineID caches the pipeline ID from a successful SetCommitStatus
893+
// response and patches it onto the PipelineRun annotation for the reconciler.
894+
func (v *Provider) storePipelineID(ctx context.Context, statusOpts providerstatus.StatusOpts, pipelineID int64) {
895+
if pipelineID == 0 {
896+
return
897+
}
898+
v.pipelineIDMu.Lock()
899+
v.pipelineID = pipelineID
900+
v.pipelineIDMu.Unlock()
901+
v.patchPipelineIDAnnotation(ctx, statusOpts, pipelineID)
902+
}
903+
904+
// patchPipelineIDAnnotation stores the GitLab pipeline ID as a PipelineRun
905+
// annotation so the reconciler can read it back across Provider instances.
906+
func (v *Provider) patchPipelineIDAnnotation(ctx context.Context, statusOpts providerstatus.StatusOpts, pipelineID int64) {
907+
pr := statusOpts.PipelineRun
908+
if pr == nil || (pr.GetName() == "" && pr.GetGenerateName() == "") {
909+
return
910+
}
911+
if existing, ok := pr.GetAnnotations()[keys.GitLabPipelineID]; ok {
912+
if existing != strconv.FormatInt(pipelineID, 10) {
913+
v.Logger.Debugf("pipelinerun %s already has gitlab pipeline ID %s, ignoring new ID %d", pr.GetName(), existing, pipelineID)
914+
}
915+
return
916+
}
917+
mergePatch := map[string]any{
918+
"metadata": map[string]any{
919+
"annotations": map[string]string{
920+
keys.GitLabPipelineID: strconv.FormatInt(pipelineID, 10),
921+
},
922+
},
923+
}
924+
if _, err := action.PatchPipelineRun(ctx, v.Logger, "gitlabPipelineID", v.run.Clients.Tekton, pr, mergePatch); err != nil {
925+
v.Logger.Debugf("failed to patch pipelinerun with gitlab pipeline ID: %v", err)
926+
}
927+
}

0 commit comments

Comments
 (0)