Add GitLab CI integration.
Review Request #14459 — Created June 13, 2025 and updated
This change adds support for GitLab CI integration. This works similarly
to how many of our integrations do, where the integration can trigger a
new pipeline.Because of how pipeline configurations work, we can't completely replace
the configuration when starting the pipeline. We therefore send
necessary information as variables, and it's up to the user to add what
they need to their .gitlab-ci.yml file. Documentation for how to do all
of that will be in a separate change for the Review Board manual.This does have one additional feature over other CI integrations, where
the individual job state can be posted as a review.Based on work by André Klitzing at /r/14211. There are a few differences
in functionality from that change:
- Rather than asking the user to put the API token into the GitLab
WebHook configuration's secret field, we allow (optionally) setting a
specific secret key for both the webhook and the integration. - Instead of posting job results in a review and then deleting it
whenever there's an update, those results are put into a general
comment (without an issue) and then the text of that comment gets
updated. We theoretically like to treat comment text as immutable once
published, but in this case I think it's fine. - I've gotten rid of the
gitlab_old_pipeline
setting entirely. In the
case that pipelines are getting rerun, I think it makes sense to
always get those updates on Review Board. In the case where a child
pipeline is sending updates, I've just added notes in the
documentation that triggers really should setstrategy: depend
so
that parent pipelines will wait for their children to complete. - I've added a new dependency on
pydantic
for parsing the WebHook
payload structure. - Added unit tests for everything.
- Various things have been renamed and cleaned up to match our style.
- Created a variety of GitLab CI setups, including simple jobs and
parent/child pipelines. Verified that pipelines were triggered
successfully and results posted back. - Ran unit tests.
Summary | ID |
---|---|
d25add94f949105de3b4ad82dae5b1a63ec39c51 |
Description | From | Last Updated |
---|---|---|
Looks like this won't fit to our workflow anymore. Is it possible to derive from this in a separate custom … |
|
|
'djblets.forms.widgets.CopyableTextInput' imported but unused Column: 1 Error code: F401 |
![]() |
|
line too long (81 > 79 characters) Column: 80 Error code: E501 |
![]() |
|
continuation line under-indented for visual indent Column: 25 Error code: E128 |
![]() |
|
'djblets.util.typing.JSONDict' imported but unused Column: 5 Error code: F401 |
![]() |
|
line too long (90 > 79 characters) Column: 80 Error code: E501 |
![]() |
|
This should be in the multi-line form. |
|
|
It's probably worth using Django's TextChoices enum for these going forward. That gives us stable constants to refer to while … |
|
|
This help text doen't give a lot of useful information. We should at least give an example. |
|
|
This is missing a trailing period. |
|
|
"Git" |
|
|
"GitLab" |
|
|
We normally have required before help_text (including in the field above), so we should make this match. |
|
|
We never return a value from this function. in the success case. |
|
|
"GitLab" |
|
|
Missing localization. |
|
|
Missing localization. |
|
|
Instead of casting, we should pull out the variable and check its type, raising an exception if it's not a … |
|
|
"GitLab" |
|
|
Can you place this one alphabetically? All the rest seem fine. |
|
|
Same note as above regarding type safety. |
|
|
Can we make these keyword-only? |
|
|
If we're inlining this, we should probably prefix with a _ so it's clear it's not a public function. Ideally, … |
|
|
This is a little hard to read. Can we move name to the next line so it doesn't appear to … |
|
|
This is in the wrong import group. |
|
|
Can you make these keyword-only? |
|
|
These should all be , optional. |
|
|
Can you make this multi-line so it's easier to read/maintain? Also, we should pass save=False since we're setting things and … |
|
|
Typo: kwargrs -> kwargs |
|
|
Nit: You could just decorate the above with @spy_for. Same below. |
|
|
Can you update these for keyword-only arguments? |
|
|
You can just pass base_commit_id= in as a parameter and avoid the double-save. |
|
|
Let's keep one per line. |
|
|
Can we test the exact error? Here and below. |
|
|
Blank line between statements and comments. |
|
|
We access self.review_request a lot, so let's initially set it locally, set self.review_request, and then access the local copy. |
|
|
Can we just test the entire dictionary in one go? Avoid surprises later? |
|
|
we normally do one per line. |
|
|
line too long (90 > 79 characters) Column: 80 Error code: E501 |
![]() |
|
Why 202? It's really unclear how this is used here and below. Can we document this? I think more of … |
|
|
The error isn't going to be very useful by itself. Can we add to it? |
|
|
We're not consistent here and with the above or below logs. We should stick to a very consistent format for … |
|
|
While this should never fail, it'd be nice to do this check early before all the validation. |
|
|
We shouldn't assert, as that doesn't survive optimized builds. Good for internal usage, but for type-checking user-controllable state, we need … |
|
|
No need for the blank line here. |
|
|
I know we're using Pydantic to verify, but then why do we need to assert here? Don't we get the … |
|
|
This is a bit vague. Can we say "Unknown build status for <something>"? |
|
|
"URL" |
|
|
Something we need to do better at is building of URLs involving user-supplied input. Going to be commenting about this … |
|
|
This has user-modifiable input, so we need to handle ValueErrors here. |
|
|
id is a reserved function. build_id? |
|
|
To be consistent with above logs, we should capitalize "Unknown". |
|
|
You can just use GeneralComment.objects.create(...). Also, one keyword arg per line. |
|
|
So where I think this is going to bite us is that by having such a specific compatibility requirement here … |
|
|
I changed it to GitlabError because "error 404" is not an authentication error. |
|
|
This is called REVIEWBOARD_REVIEW_ID in jenkins integration. That should be unified. |
|
|
line too long (90 > 79 characters) Column: 80 Error code: E501 |
![]() |
|
never use "DONE_FAILURE" for gitlab-ci. There will always be a possibility that something is temporaly broken. If you use "DONE_FAILURE" … |
|
|
see above |
|
|
see above |
|
|
So you will post the "summary" just for the initial pipeline and ignores all of child-pipeline? Well, most times the … |
|
|
I used detailed_status for this. Otherwise you cannot have "success with warnings" or something like this. |
|
|
Why? The child pipeline is a more interesting part. So the direct link to that pipeline should have higher priority. … |
|
|
why not stage anymore? |
|
|
this will use a lot of lines here... we have 51 jobs. |
|
- Commits:
-
Summary ID 930d3a5f6192cf264f1bf8d1ebf3f342bb9db8f0 d98877b862d2c5366966b4b0665cc7d1c449b0e8
-
-
-
It's probably worth using Django's
TextChoices
enum for these going forward. That gives us stable constants to refer to while also giving us achoices
attribute for fields. -
-
-
-
-
We normally have
required
beforehelp_text
(including in the field above), so we should make this match. -
-
-
-
-
Instead of casting, we should pull out the variable and check its type, raising an exception if it's not a string. That'd avoid any surprises, since we're technically dealing with user input.
-
-
-
-
-
If we're inlining this, we should probably prefix with a
_
so it's clear it's not a public function.Ideally, we'd avoid inlining if we don't have to, though. Might be worth moving this out.
-
This is a little hard to read. Can we move
name
to the next line so it doesn't appear to be part of the lambda? -
-
-
-
Can you make this multi-line so it's easier to read/maintain?
Also, we should pass
save=False
since we're setting things and then saving. -
-
-
-
-
-
-
-
We access
self.review_request
a lot, so let's initially set it locally, setself.review_request
, and then access the local copy. -
-
-
Why 202? It's really unclear how this is used here and below. Can we document this?
I think more of the hook could really use some documentation to make the requirements and responses more clear, especially if 202 is correct.
-
-
We're not consistent here and with the above or below logs. We should stick to a very consistent format for these.
-
-
We shouldn't assert, as that doesn't survive optimized builds. Good for internal usage, but for type-checking user-controllable state, we need to do an explicit check and a
raise
. -
-
I know we're using Pydantic to verify, but then why do we need to assert here? Don't we get the right type and the guarantee?
-
-
-
Something we need to do better at is building of URLs involving user-supplied input. Going to be commenting about this on newer code more. Rather than plugging things into a path, we should be safely feeding values into URLs using
quote()
, so we don't risk a case where some stray/
or?
risks a vulnerability.There may be more places in this code that need to do this.
-
-
-
-
-
So where I think this is going to bite us is that by having such a specific compatibility requirement here in rbintegrations, we're going to collide with Review Board or eventually some other third-party dependency, and with the wider rbintegrations compatibility range for Review Board, this will ultimately affect us elsewhere.
How wide a range can we safely apply here?
I feel like if we're going to support pydantic, it makes sense to have that dependency as part of the Review Board platform, and keep the dependency there. Of course, that doesn't help rbintegrations on older versions, but that's where we can work around that with a wider range.
- Commits:
-
Summary ID d98877b862d2c5366966b4b0665cc7d1c449b0e8 d25add94f949105de3b4ad82dae5b1a63ec39c51
-
-
Looks like this won't fit to our workflow anymore. Is it possible to derive from this in a separate custom extension and use this to rbintegrations?
-
-
never use "DONE_FAILURE" for gitlab-ci. There will always be a possibility that something is temporaly broken. If you use "DONE_FAILURE" you cannot RETRY the pipeline from RB.
DONE_FAILURE is a nice concept from RB, but it doesn’t reflect the reality in production.
-
-
-
So you will post the "summary" just for the initial pipeline and ignores all of child-pipeline?
Well, most times the "initial" pipeline should be just one job to spawn a child-pipeline with all important jobs because the gitlab-ci.yaml needs to be patched first with the diff from review request.
So this summary is pretty useless and cannot be configured anymore.
-
I used
detailed_status
for this. Otherwise you cannot have "success with warnings" or something like this. -
Why? The child pipeline is a more interesting part. So the direct link to that pipeline should have higher priority. Otherwise the developer need to click into parent-pipeline and then select child-pipeline. That is really annonying.
-
-