@@ -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
7479var 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