fix(gitlab): pin commit statuses to the same pipeline via pipeline_id caching#2671
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2671 +/- ##
==========================================
+ Coverage 59.12% 59.18% +0.06%
==========================================
Files 208 208
Lines 20512 20552 +40
==========================================
+ Hits 12127 12164 +37
- Misses 7612 7614 +2
- Partials 773 774 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request introduces a caching mechanism for GitLab pipeline IDs to ensure that multiple commit statuses for the same project and SHA are grouped within the same pipeline. It includes logic to retrieve, store, and apply these IDs during the status creation process, along with comprehensive unit tests. A critical issue was identified in the setOptPipelineID function where a stale pipeline ID from a source project could persist when falling back to a target project, potentially causing API failures.
071640b to
f57af59
Compare
🤖 AI Analysis - pr-complexity-ratingBased on the provided information, this PR appears to be a synchronization/maintenance operation rather than a functional feature or bug fix implementation. 📊 PR Review Complexity
Overall difficulty: Easy SummaryThis PR is a merge commit synchronizing Suggested reviewers focus
Generated by Pipelines-as-Code LLM Analysis |
🤖 AI Analysis - pr-complexity-ratingBased on the provided context, this PR appears to be a maintenance merge (synchronizing the feature branch with 📊 PR Review Complexity
Overall difficulty: Easy SummaryThis PR consists of a merge commit synchronizing a feature branch ( Suggested reviewers focus
Generated by Pipelines-as-Code LLM Analysis |
|
I am not sure I understand this PR, can you explain it to me? 😅 Your change get pipeline_id only remembered inside the current in-memory GitLab provider object. That helps only while that exact object is still being used. In PAC, the later/final status update is sent from a different phase of the system that creates a non shared new GitLab Provider{} instance, so that cached pipeline_id is lost. In practice, this means the change may help for a few status calls in the same immediate flow, but it likely won’t keep all statuses for the PipelineRun attached to the same GitLab pipeline, which I think your goal. Maybe we need to carry the pipeline_id via an annotation and getting read by the reconciler? |
The issue which this PR addresses that when PAC posts commit statuses to GitLab for a merge request, the status updates can get split across two different GitLab pipelines. At some point during the continuous stream of status updates, GitLab stops associating them with the original MR pipeline and starts routing them to a new external pipeline. The original MR pipeline gets permanently stuck with stale intermediate statuses, while subsequent results sit in an orphaned pipeline invisible to the MR. |
yeah that's right, i missed this point, i think, as you suggested, after the first successful status update, we can add the pipeline_id via an annotation and then any subsequent CreateStatus call can reads it. |
faba1a9 to
a36c209
Compare
|
can you make sure this is tested manually properly? without having to rely on the AI words? |
|
@ab-ghosh is your goal to have pipeline_id only for an event or for whole CI lifecycle of the merge request? |
|
@zakisk it should be for whole CI lifecycle of the MR, so that for all subsequent status updates for the same SHA, we can pass the pipeline ID |
Then you're only persistenting pipeline id for an event not whole lifecycle of the MR. |
a36c209 to
e2b2fe2
Compare
|
/retest |
e2b2fe2 to
3915309
Compare
b408279 to
ddbe9f2
Compare
ddbe9f2 to
8c64009
Compare
theakshaypant
left a comment
There was a problem hiding this comment.
Could you share a screenshot of how this would be different from gitlab auto assigning the pipeline id.
|
@ab-ghosh can you please add e2e test confirming that pipeline_id annotation is added on PipelineRuns |
8c64009 to
f921938
Compare
|
@zakisk Added the required E2E test for this feature |
| if v.pipelineIDMu == nil { | ||
| v.pipelineIDMu = &sync.Mutex{} | ||
| } |
There was a problem hiding this comment.
Removed. Moved the initialization to SetClient as suggested.
| if v.pipelineIDMu == nil { | ||
| v.pipelineIDMu = &sync.Mutex{} | ||
| } |
There was a problem hiding this comment.
you can just initialize it in SetClient provider func
| // Clear pipeline ID when falling back to the target project — the cached | ||
| // ID belongs to the source project's pipeline namespace and is invalid on | ||
| // a different project (fork MR scenario). |
There was a problem hiding this comment.
did you check this? if we set the PIpelineID to 0 then it will create new pipeline on subsequent events and issue is still there. and pipelines from commit status api are bound to SHA not project
There was a problem hiding this comment.
commit status api are bound to SHA not project
Yeah, the main confusion was exactly this, I assumed pipelines from the commit status API are bound to the project, so I used a map by project ID and added a mutex. now i have used plain int64 and removed the map and mutex.
| // pipelineIDMu protects pipelineIDCache from concurrent access when | ||
| // multiple PipelineRun goroutines call CreateStatus simultaneously. | ||
| pipelineIDMu *sync.Mutex | ||
| pipelineIDCache map[string]int64 |
There was a problem hiding this comment.
its only per event so it should not be map[string]int64 you can just store it as int
| pipelineIDCache map[string]int64 | |
| pipelineID int64 |
There was a problem hiding this comment.
Ack, changed to pipelineID int64, removed the mutex as well
There was a problem hiding this comment.
you shouldn't remove the mutex, its needed as this func is called in goroutine and multiple threads are accessing the pipelineID, my suggestion was to not use map for storing pipelineID
| if pid := v.getPipelineID(event.SHA); pid != 0 { | ||
| opt.PipelineID = gitlab.Ptr(pid) | ||
| } else 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) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
| if pid := v.getPipelineID(event.SHA); pid != 0 { | |
| opt.PipelineID = gitlab.Ptr(pid) | |
| } else 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) | |
| } | |
| } | |
| } | |
| 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 = gitlab.Ptr(pid) | |
| v.pipelineIDMu.Unlock() | |
| } | |
| } | |
| } else if v.pipelineID != 0 { | |
| v.pipelineIDMu.Lock() | |
| opt.PipelineID = gitlab.Ptr(v.pipelineID) | |
| v.pipelineIDMu.Unlock() | |
| } |
|
|
||
| // 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, cs *gitlab.CommitStatus) { |
There was a problem hiding this comment.
| func (v *Provider) patchPipelineIDAnnotation(ctx context.Context, statusOpts providerstatus.StatusOpts, cs *gitlab.CommitStatus) { | |
| func (v *Provider) patchPipelineIDAnnotation(ctx context.Context, statusOpts providerstatus.StatusOpts, pipelineID int64) { |
There was a problem hiding this comment.
if its only pipelineID then it doesn't make sense to pass whole CommitStatusOption struct
There was a problem hiding this comment.
Done, added pipelineID int64.
| mergePatch := map[string]any{ | ||
| "metadata": map[string]any{ | ||
| "annotations": map[string]string{ | ||
| keys.GitLabPipelineID: strconv.FormatInt(cs.PipelineID, 10), |
There was a problem hiding this comment.
| keys.GitLabPipelineID: strconv.FormatInt(cs.PipelineID, 10), | |
| keys.GitLabPipelineID: strconv.FormatInt(pipelineID, 10), |
| } | ||
| } | ||
|
|
||
| func TestCreateStatusPipelineIDAnnotation(t *testing.T) { |
There was a problem hiding this comment.
there is already TestCreateStatus func can you add cases from this func and from below other two func to that one test func.
There was a problem hiding this comment.
Done, merged into TestCreateStatus table. Only the test which address the CreateStatus call twice to verify in-memory caching across PipelineRuns kept separate
1d40666 to
5cd2740
Compare
| if pr == nil || (pr.GetName() == "" && pr.GetGenerateName() == "") { | ||
| return | ||
| } | ||
| if _, ok := pr.GetAnnotations()[keys.GitLabPipelineID]; ok { |
There was a problem hiding this comment.
if the pipelineID is different than what's in PipelineRun's annotation here and you're just checking that keys.GitLabPipelineID does exist then it would be conflict. I know its very edge case...
There was a problem hiding this comment.
I think despite an existing pipeline in pipeline run annotation, if you find different pipelineID then log a message saying this IMO but still using the old one...
There was a problem hiding this comment.
Since this is a very rare scenario, a log message should be sufficient here
5cd2740 to
35ad374
Compare
35ad374 to
9df8451
Compare
|
/lgtm |
There was a problem hiding this comment.
Congrats @ab-ghosh your PR Has been approved 🎉
✅ Pull Request Approved
Approval Status:
- Required Approvals: 1
- Current Approvals: 1
👥 Reviewers Who Approved:
| Reviewer | Permission Level | Approval Status |
|---|---|---|
| @zakisk | write |
✅ |
📝 Next Steps
- Ensure all required checks pass
- Comply with branch protection rules
- Request a maintainer to merge using the
/mergecommand (or merge it
directly if you have repository permission).
Automated by the PAC Boussole 🧭
|
@ab-ghosh fix the go-testing... |
9df8451 to
ff4db37
Compare
ff4db37 to
e71d69c
Compare
4b4fbaf to
f00a0de
Compare
f00a0de to
5421660
Compare
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>
5421660 to
a302822
Compare
📝 Description of the Change
When PAC posts commit statuses to GitLab for a merge request, the status updates can get split across two different GitLab pipelines. GitLab's auto-assignment logic can abruptly change routing mid-stream, leaving the original MR pipeline permanently stuck with stale intermediate statuses.
This fix caches the
pipeline_idreturned by the firstSetCommitStatusresponse for a given(project, SHA)pair and passes it on all subsequent calls, ensuring all commit statuses land in the same GitLab pipeline.🔗 Linked GitHub Issue
Fixes #
https://redhat.atlassian.net/browse/SRVKP-11437
🧪 Testing Strategy
🤖 AI Assistance
AI assistance can be used for various tasks, such as code generation,
documentation, or testing.
Please indicate whether you have used AI assistance
for this PR and provide details if applicable.
Important
Slop will be simply rejected, if you are using AI assistance you need to make sure you
understand the code generated and that it meets the project's standards. you
need at least know how to run the code and deploy it (if needed). See
startpaac to make it easy
to deploy and test your code changes.
If the majority of the code in this PR was generated by an AI, please add a
Co-authored-bytrailer to your commit message.For example:
Co-authored-by: Claude noreply@anthropic.com
✅ Submitter Checklist
fix:,feat:) matches the "Type of Change" I selected above.make testandmake lintlocally to check for and fix anyissues. For an efficient workflow, I have considered installing
pre-commit and running
pre-commit installtoautomate these checks.