Skip to content

Commit 49df439

Browse files
committed
fix: Improve GitLab commit status handling
This commit addresses confusion around when GitLab comments are posted versus when commit status updates are used. Changes: * Documentation: Added clear explanation that comments are only posted when commit status updates fail, with visual example showing GitLab Pipelines tab where status appears when successful * Code clarity: Refactored commit status logic with better error handling and detailed logging when both source/target project status updates fail * Test reliability: Enhanced GitLab MR test to properly verify pipeline status success and ensure no comments are posted when status updates work * Test accuracy: Removed redundant CreateStatus test cases and improved SHA tracking for more reliable test execution The key fix is clarifying that Pipelines-as-Code tries to set commit status on both source (fork) and target (upstream) projects, and only falls back to posting comments when both attempts fail due to permission issues. Fixes the misleading documentation that didn't properly explain the relationship between GitLab API permissions, commit status updates, and comment posting behavior. Jira: https://issues.redhat.com/browse/SRVKP-8908 Signed-off-by: Chmouel Boudjnah <[email protected]>
1 parent c8644f3 commit 49df439

File tree

6 files changed

+210
-72
lines changed

6 files changed

+210
-72
lines changed

docs/content/docs/guide/repositorycrd.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Repository CR
33
weight: 1
44
---
5+
56
# Repository CR
67

78
The Repository CR serves the following purposes:
@@ -127,7 +128,29 @@ access to the infrastructure.
127128

128129
## Disabling all comments for PipelineRuns on GitLab MR
129130

130-
`comment_strategy` allows you to disable the comments on GitLab MR for a Repository
131+
By default, Pipelines-as-Code attempts to update the commit status through the
132+
GitLab API. It first tries the source project (fork), then falls back to the
133+
target project (upstream repository). The source project update succeeds when
134+
the configured token has access to the source repository and GitLab creates
135+
pipeline entries for external CI systems like Pipelines-as-Code. The target
136+
project fallback may fail if there's no CI pipeline running for that commit
137+
in the target repository, since GitLab only creates pipeline entries for commits
138+
that actually trigger CI in that specific project. If either status update
139+
succeeds, no comment is posted on the Merge Request.
140+
141+
When a status update succeeds, you can see the status in the GitLab UI in the `Pipelines` tab, as
142+
shown in the following example:
143+
144+
![Gitlab Pipelines from Pipelines-as-Code](/images/gitlab-pipelines-tab.png)
145+
146+
Comments are only posted when:
147+
148+
- Both commit status updates fail (typically due to insufficient token permissions)
149+
- The event type and repository settings allow commenting
150+
- The `comment_strategy` is not set to `disable_all`
151+
152+
To explicitly disable all comments on GitLab Merge Requests for a Repository,
153+
use the `comment_strategy` setting:
131154

132155
```yaml
133156
spec:
@@ -136,8 +159,6 @@ spec:
136159
comment_strategy: "disable_all"
137160
```
138161

139-
When you set the value of `comment_strategy` to `disable_all` it will not add any comment on the merge request for the start and the end of PipelineRun
140-
141162
## Disabling all comments for PipelineRuns in GitHub Pull Requests on GitHub Webhook Setup
142163

143164
`comment_strategy` allows you to disable the comments on GitHub PR for a Repository
492 KB
Loading

pkg/provider/gitlab/gitlab.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -280,17 +280,27 @@ func (v *Provider) CreateStatus(_ context.Context, event *info.Event, statusOpts
280280
Context: gitlab.Ptr(contextName),
281281
}
282282

283-
// In case we have access, set the status. Typically, on a Merge Request (MR)
284-
// from a fork in an upstream repository, the token needs to have write access
285-
// to the fork repository in order to create a status. However, the token set on the
286-
// Repository CR usually doesn't have such broad access, preventing from creating
287-
// a status comment on it.
288-
// This would work on a push or an MR from a branch within the same repo.
289-
// Ignoring errors because of the write access issues,
290-
if _, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt); err != nil {
291-
v.eventEmitter.EmitMessage(v.repo, zap.ErrorLevel, "FailedToSetCommitStatus",
292-
"cannot set status with the GitLab token because of: "+err.Error())
283+
// setCommitStatus attempts to set the commit status for a given SHA, handling GitLab's permission model.
284+
// First tries the source project (fork), then falls back to the target project (upstream repo).
285+
// This fallback is necessary because:
286+
// - In fork/MR scenarios, the GitLab token may not have write access to the contributor's fork
287+
// - This ensures commit status can be displayed somewhere useful regardless
288+
// of permission differences
289+
// Returns nil if status is set on either project, logs error if both attempts fail.
290+
_, _, err := v.Client().Commits.SetCommitStatus(event.SourceProjectID, event.SHA, opt)
291+
if err == nil {
292+
v.Logger.Debugf("created commit status on source project ID %d", event.SourceProjectID)
293+
return nil
294+
}
295+
if _, _, err2 := v.Client().Commits.SetCommitStatus(event.TargetProjectID, event.SHA, opt); err2 == nil {
296+
v.Logger.Debugf("created commit status on target project ID %d", event.TargetProjectID)
297+
return nil
293298
}
299+
v.Logger.Debugf(
300+
"Failed to create commit status: source project ID %d, target project ID %d. "+
301+
"If you want Gitlab Pipeline Status update, ensure your GitLab token has access "+
302+
"to the source repository. Error: %v",
303+
event.SourceProjectID, event.TargetProjectID, err)
294304

295305
eventType := triggertype.IsPullRequestType(event.EventType)
296306
// When a GitOps command is sent on a pushed commit, it mistakenly treats it as a pull_request

pkg/provider/gitlab/gitlab_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,66 @@ func TestCreateStatus(t *testing.T) {
201201
postStr: "has completed",
202202
},
203203
},
204+
{
205+
name: "commit status success on source project",
206+
wantClient: true,
207+
wantErr: false,
208+
fields: fields{
209+
targetProjectID: 100,
210+
},
211+
args: args{
212+
statusOpts: provider.StatusOpts{
213+
Conclusion: "success",
214+
},
215+
event: &info.Event{
216+
TriggerTarget: "pull_request",
217+
SourceProjectID: 200,
218+
TargetProjectID: 100,
219+
SHA: "abc123",
220+
},
221+
postStr: "has successfully",
222+
},
223+
},
224+
{
225+
name: "commit status falls back to target project",
226+
wantClient: true,
227+
wantErr: false,
228+
fields: fields{
229+
targetProjectID: 100,
230+
},
231+
args: args{
232+
statusOpts: provider.StatusOpts{
233+
Conclusion: "success",
234+
},
235+
event: &info.Event{
236+
TriggerTarget: "pull_request",
237+
SourceProjectID: 404, // Will fail to find this project
238+
TargetProjectID: 100,
239+
SHA: "abc123",
240+
},
241+
postStr: "has successfully",
242+
},
243+
},
244+
{
245+
name: "commit status fails on both projects but continues",
246+
wantClient: true,
247+
wantErr: false,
248+
fields: fields{
249+
targetProjectID: 100,
250+
},
251+
args: args{
252+
statusOpts: provider.StatusOpts{
253+
Conclusion: "success",
254+
},
255+
event: &info.Event{
256+
TriggerTarget: "pull_request",
257+
SourceProjectID: 404, // Will fail
258+
TargetProjectID: 405, // Will fail
259+
SHA: "abc123",
260+
},
261+
postStr: "has successfully",
262+
},
263+
},
204264
}
205265
for _, tt := range tests {
206266
t.Run(tt.name, func(t *testing.T) {
@@ -216,6 +276,7 @@ func TestCreateStatus(t *testing.T) {
216276
v := &Provider{
217277
targetProjectID: tt.fields.targetProjectID,
218278
run: params.New(),
279+
Logger: logger,
219280
pacInfo: &info.PacOpts{
220281
Settings: settings.Settings{
221282
ApplicationName: settings.PACApplicationNameDefaultValue,
@@ -232,6 +293,40 @@ func TestCreateStatus(t *testing.T) {
232293
client, mux, tearDown := thelp.Setup(t)
233294
v.SetGitLabClient(client)
234295
defer tearDown()
296+
297+
// Mock commit status endpoints for both source and target projects
298+
if tt.args.event.SourceProjectID != 0 {
299+
// Mock source project commit status endpoint
300+
sourceStatusPath := fmt.Sprintf("/projects/%d/statuses/%s", tt.args.event.SourceProjectID, tt.args.event.SHA)
301+
mux.HandleFunc(sourceStatusPath, func(rw http.ResponseWriter, _ *http.Request) {
302+
if tt.args.event.SourceProjectID == 404 {
303+
// Simulate failure on source project
304+
rw.WriteHeader(http.StatusNotFound)
305+
fmt.Fprint(rw, `{"message": "404 Project Not Found"}`)
306+
return
307+
}
308+
// Success on source project
309+
rw.WriteHeader(http.StatusCreated)
310+
fmt.Fprint(rw, `{}`)
311+
})
312+
}
313+
314+
if tt.args.event.TargetProjectID != 0 {
315+
// Mock target project commit status endpoint
316+
targetStatusPath := fmt.Sprintf("/projects/%d/statuses/%s", tt.args.event.TargetProjectID, tt.args.event.SHA)
317+
mux.HandleFunc(targetStatusPath, func(rw http.ResponseWriter, _ *http.Request) {
318+
if tt.args.event.TargetProjectID == 404 {
319+
// Simulate failure on target project
320+
rw.WriteHeader(http.StatusNotFound)
321+
fmt.Fprint(rw, `{"message": "404 Project Not Found"}`)
322+
return
323+
}
324+
// Success on target project
325+
rw.WriteHeader(http.StatusCreated)
326+
fmt.Fprint(rw, `{}`)
327+
})
328+
}
329+
235330
thelp.MuxNotePost(t, mux, v.targetProjectID, tt.args.event.PullRequestNumber, tt.args.postStr)
236331
}
237332

@@ -1044,9 +1139,12 @@ func TestGitLabCreateComment(t *testing.T) {
10441139
t.Run(tt.name, func(t *testing.T) {
10451140
fakeclient, mux, teardown := thelp.Setup(t)
10461141
defer teardown()
1142+
observer, _ := zapobserver.New(zap.InfoLevel)
1143+
logger := zap.New(observer).Sugar()
10471144

10481145
if tt.clientNil {
10491146
p := &Provider{
1147+
Logger: logger,
10501148
sourceProjectID: 666,
10511149
}
10521150
err := p.CreateComment(context.Background(), tt.event, tt.commit, tt.updateMarker)

0 commit comments

Comments
 (0)