Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/apis/pipelinesascode/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const (
OriginalPRName = pipelinesascode.GroupName + "/original-prname"
GitAuthSecret = pipelinesascode.GroupName + "/git-auth-secret"
CheckRunID = pipelinesascode.GroupName + "/check-run-id"
GitLabPipelineID = pipelinesascode.GroupName + "/gitlab-pipeline-id"
OnEvent = pipelinesascode.GroupName + "/on-event"
OnComment = pipelinesascode.GroupName + "/on-comment"
OnTargetBranch = pipelinesascode.GroupName + "/on-target-branch"
Expand Down
69 changes: 67 additions & 2 deletions pkg/provider/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import (
"regexp"
"strconv"
"strings"
"sync"

"github.com/openshift-pipelines/pipelines-as-code/pkg/action"
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/keys"
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
"github.com/openshift-pipelines/pipelines-as-code/pkg/changedfiles"
"github.com/openshift-pipelines/pipelines-as-code/pkg/events"
Expand Down Expand Up @@ -69,6 +72,8 @@ type Provider struct {
memberCache map[int64]bool
cachedChangedFiles *changedfiles.ChangedFiles
pacUserID int64 // user login used by PAC
pipelineID int64
pipelineIDMu sync.Mutex
}

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

// Reuse a previously discovered pipeline ID so that all commit statuses
// for the same SHA land in the same GitLab pipeline.
if statusOpts.PipelineRun != nil {
if id, ok := statusOpts.PipelineRun.GetAnnotations()[keys.GitLabPipelineID]; ok {
pid, err := strconv.ParseInt(id, 10, 64)
if err == nil {
opt.PipelineID = gitlab.Ptr(pid)
v.pipelineIDMu.Lock()
v.pipelineID = pid
v.pipelineIDMu.Unlock()
}
}
}
if opt.PipelineID == nil {
v.pipelineIDMu.Lock()
if v.pipelineID != 0 {
opt.PipelineID = gitlab.Ptr(v.pipelineID)
}
v.pipelineIDMu.Unlock()
}

// In case we have access, set the status. Typically, on a Merge Request (MR)
// from a fork in an upstream repository, the token needs to have write access
// to the fork repository in order to create a status. However, the token set on the
// Repository CR usually doesn't have such broad access, preventing from creating
// a status comment on it.
// This would work on a push or an MR from a branch within the same repo.
// Ignoring errors because of the write access issues,
_, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt)
commitStatus, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt)
if err != nil {
v.Logger.Debugf("cannot set status with the GitLab token on the source project: %v", err)
} else {
v.storePipelineID(ctx, statusOpts, commitStatus.PipelineID)
// we managed to set the status on the source repo, all good we are done
v.Logger.Debugf("created commit status on source project ID %d", event.TargetProjectID)
return nil
}
if _, _, err = v.Client().Commits.SetCommitStatus(event.TargetProjectID, event.SHA, opt); err == nil {
if commitStatus, _, err = v.Client().Commits.SetCommitStatus(event.TargetProjectID, event.SHA, opt); err == nil {
v.storePipelineID(ctx, statusOpts, commitStatus.PipelineID)
v.Logger.Debugf("created commit status on target project ID %d", event.TargetProjectID)
// we managed to set the status on the target repo, all good we are done
return nil
Expand Down Expand Up @@ -860,3 +888,40 @@ func (v *Provider) formatPipelineComment(sha string, status providerstatus.Statu
return fmt.Sprintf("%s **%s: %s/%s for %s**\n\n%s\n\n<small>Full log available [here](%s)</small>",
emoji, status.Title, v.pacInfo.ApplicationName, status.OriginalPipelineRunName, sha, status.Text, status.DetailsURL)
}

// storePipelineID caches the pipeline ID from a successful SetCommitStatus
// response and patches it onto the PipelineRun annotation for the reconciler.
func (v *Provider) storePipelineID(ctx context.Context, statusOpts providerstatus.StatusOpts, pipelineID int64) {
if pipelineID == 0 {
return
}
v.pipelineIDMu.Lock()
v.pipelineID = pipelineID
v.pipelineIDMu.Unlock()
v.patchPipelineIDAnnotation(ctx, statusOpts, pipelineID)
}

// patchPipelineIDAnnotation stores the GitLab pipeline ID as a PipelineRun
// annotation so the reconciler can read it back across Provider instances.
func (v *Provider) patchPipelineIDAnnotation(ctx context.Context, statusOpts providerstatus.StatusOpts, pipelineID int64) {
pr := statusOpts.PipelineRun
if pr == nil || (pr.GetName() == "" && pr.GetGenerateName() == "") {
return
}
if existing, ok := pr.GetAnnotations()[keys.GitLabPipelineID]; ok {
if existing != strconv.FormatInt(pipelineID, 10) {
v.Logger.Debugf("pipelinerun %s already has gitlab pipeline ID %s, ignoring new ID %d", pr.GetName(), existing, pipelineID)
}
return
}
mergePatch := map[string]any{
"metadata": map[string]any{
"annotations": map[string]string{
keys.GitLabPipelineID: strconv.FormatInt(pipelineID, 10),
},
},
}
if _, err := action.PatchPipelineRun(ctx, v.Logger, "gitlabPipelineID", v.run.Clients.Tekton, pr, mergePatch); err != nil {
v.Logger.Debugf("failed to patch pipelinerun with gitlab pipeline ID: %v", err)
}
}
Loading
Loading