Skip to content

Commit b661e62

Browse files
yiftahwkrisstern
andauthored
Add merge request labels to environment variables (#1713)
* fix in readme * update readme * collect labels from merge request web hook and fill in to causeData factory * add merge request labels to CauseData * mvn spotless apply * simple test case * mvn spotless apply * simple unit tests * cleanup * mvn spotless apply * unify labels unit tests logic * argument naming fix * merge request labels checker helper function * update readme * update readme * GitLabMergeRequestExistsStepTest * update unit tests for label exists step * 1 more unit test * move unit test resource to pipeline folder * add author comment * Apply suggestions from code review Co-authored-by: Kris Stern <[email protected]> * fix names * mvn spotless apply --------- Co-authored-by: Kris Stern <[email protected]>
1 parent 0ff4703 commit b661e62

File tree

8 files changed

+316
-7
lines changed

8 files changed

+316
-7
lines changed

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
- [Accept merge request after build](#accept-merge-request)
3333
- [Notify specific project by a specific GitLab connection](#notify-specific-project-by-a-specific-gitlab-connection)
3434
- [Cancel pending builds on merge request update](#cancel-pending-builds-on-merge-request-update)
35+
- [Check if a label is applied to a merge request](#check-if-a-label-is-applied-to-a-merge-request)
3536
- [Compatibility](#compatibility)
3637
- [Contributing to the Plugin](#contributing-to-the-plugin)
3738
- [Testing With Docker](src/docker/README.md#quick-test-environment-setup-using-docker-for-linuxamd64)
@@ -96,6 +97,7 @@ gitlabMergedByUser
9697
gitlabMergeRequestAssignee
9798
gitlabMergeRequestLastCommit
9899
gitlabMergeRequestTargetProjectId
100+
gitlabMergeRequestLabels
99101
gitlabTargetBranch
100102
gitlabTargetRepoName
101103
gitlabTargetNamespace
@@ -609,6 +611,22 @@ gitlabCommitStatus(
609611
To cancel pending builds of the same merge request when new commits are pushed, check 'Cancel pending merge request builds on update' from the Advanced-section in the trigger configuration.
610612
This saves time in projects where builds can stay long time in a build queue and you care only about the status of the newest commit.
611613

614+
### Check if a label is applied to a merge request
615+
To handle conditional logic in your pipelines based on merge request labels, use:
616+
```groovy
617+
script {
618+
if (GitLabMergeRequestLabelExists("bugfix"))
619+
{
620+
echo 'bugfix label detected!'
621+
}
622+
}
623+
```
624+
A comma separated string of the labels is also present as an environment variable: `gitlabMergeRequestLabels`.
625+
e.g for a merge request with the labels: [`bugfix`, `review needed`], `env.gitlabMergeRequestLabels="bugfix,review needed"`.
626+
#### *notes:*
627+
- The environment variable will be null if no labels are applied to the merge request.
628+
- This feature is not available for multibranch pipeline jobs or GitLab push hooks.
629+
612630
## Compatibility
613631

614632
Version 1.2.1 of the plugin introduces a backwards-incompatible change

src/docker/README.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ In order to test the plugin on different versions of `GitLab` and `Jenkins` you
55
An example docker-compose file is available at `gitlab-plugin/src/docker` which the user can use to set up instances of the latest `GitLab` version and latest `Jenkins` LTS version for linux/amd64.
66

77
If they don't already exist, create the following directories and make sure the user that Docker is running as owns them:
8-
* /srv/docker/gitlab/postgresql
9-
* /srv/docker/gitlab/gitlab
10-
* /srv/docker/gitlab/redis
11-
* /srv/docker/jenkins
8+
* /srv/docker/gitlab/postgresql
9+
* /srv/docker/gitlab/gitlab
10+
* /srv/docker/gitlab/redis
11+
* /srv/docker/jenkins
12+
1213
To start the containers for Linux, run `docker-compose up -d` from the `src/docker` folder. If you have problems accessing the services in the containers, run `docker-compose up` by itself to see output from the services as they start, and the latter command is the verbose version of the former.
1314

1415
## Quick test environment setup using Docker for MacOS/arm64

src/main/java/com/dabsquared/gitlabjenkins/cause/CauseData.java

+12
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public final class CauseData {
4040
private final String mergedByUser;
4141
private final String mergeRequestAssignee;
4242
private final Integer mergeRequestTargetProjectId;
43+
private final List<String> mergeRequestLabels;
4344
private final String targetBranch;
4445
private final String targetRepoName;
4546
private final String targetNamespace;
@@ -84,6 +85,7 @@ public final class CauseData {
8485
Integer mergeRequestId,
8586
Integer mergeRequestIid,
8687
Integer mergeRequestTargetProjectId,
88+
List<String> mergeRequestLabels,
8789
String targetBranch,
8890
String targetRepoName,
8991
String targetNamespace,
@@ -131,6 +133,7 @@ public final class CauseData {
131133
this.mergedByUser = mergedByUser == null ? "" : mergedByUser;
132134
this.mergeRequestAssignee = mergeRequestAssignee == null ? "" : mergeRequestAssignee;
133135
this.mergeRequestTargetProjectId = mergeRequestTargetProjectId;
136+
this.mergeRequestLabels = mergeRequestLabels == null ? Collections.emptyList() : mergeRequestLabels;
134137
this.targetBranch = Objects.requireNonNull(targetBranch, "targetBranch must not be null.");
135138
this.targetRepoName = Objects.requireNonNull(targetRepoName, "targetRepoName must not be null.");
136139
this.targetNamespace = Objects.requireNonNull(targetNamespace, "targetNamespace must not be null.");
@@ -177,6 +180,7 @@ public Map<String, String> getBuildVariables() {
177180
variables.put(
178181
"gitlabMergeRequestTargetProjectId",
179182
mergeRequestTargetProjectId == null ? "" : mergeRequestTargetProjectId.toString());
183+
variables.put("gitlabMergeRequestLabels", StringUtils.join(mergeRequestLabels, ","));
180184
variables.put("gitlabMergeRequestLastCommit", lastCommit);
181185
variables.putIfNotNull("gitlabMergeRequestState", mergeRequestState);
182186
variables.putIfNotNull("gitlabMergedByUser", mergedByUser);
@@ -302,6 +306,11 @@ public Integer getMergeRequestTargetProjectId() {
302306
return mergeRequestTargetProjectId;
303307
}
304308

309+
@Exported
310+
public List<String> getMergeRequestLabels() {
311+
return mergeRequestLabels;
312+
}
313+
305314
@Exported
306315
public String getTargetBranch() {
307316
return targetBranch;
@@ -473,6 +482,7 @@ public boolean equals(Object o) {
473482
.append(mergedByUser, causeData.mergedByUser)
474483
.append(mergeRequestAssignee, causeData.mergeRequestAssignee)
475484
.append(mergeRequestTargetProjectId, causeData.mergeRequestTargetProjectId)
485+
.append(mergeRequestLabels, causeData.mergeRequestLabels)
476486
.append(targetBranch, causeData.targetBranch)
477487
.append(targetRepoName, causeData.targetRepoName)
478488
.append(targetNamespace, causeData.targetNamespace)
@@ -522,6 +532,7 @@ public int hashCode() {
522532
.append(mergedByUser)
523533
.append(mergeRequestAssignee)
524534
.append(mergeRequestTargetProjectId)
535+
.append(mergeRequestLabels)
525536
.append(targetBranch)
526537
.append(targetRepoName)
527538
.append(targetNamespace)
@@ -571,6 +582,7 @@ public String toString() {
571582
.append("mergedByUser", mergedByUser)
572583
.append("mergeRequestAssignee", mergeRequestAssignee)
573584
.append("mergeRequestTargetProjectId", mergeRequestTargetProjectId)
585+
.append("mergeRequestLabels", mergeRequestLabels)
574586
.append("targetBranch", targetBranch)
575587
.append("targetRepoName", targetRepoName)
576588
.append("targetNamespace", targetNamespace)

src/main/java/com/dabsquared/gitlabjenkins/trigger/handler/merge/MergeRequestHookTriggerHandlerImpl.java

+7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static com.dabsquared.gitlabjenkins.util.LoggerUtil.toArray;
66
import static java.util.Collections.emptyList;
77
import static java.util.Collections.emptySet;
8+
import static java.util.stream.Collectors.toList;
89

910
import com.dabsquared.gitlabjenkins.cause.CauseData;
1011
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
@@ -216,6 +217,12 @@ protected CauseData retrieveCauseData(MergeRequestHook hook) {
216217
.withMergeRequestAssignee(
217218
hook.getAssignee() == null ? null : hook.getAssignee().getUsername())
218219
.withMergeRequestTargetProjectId(hook.getObjectAttributes().getTargetProjectId())
220+
.withMergeRequestLabels(
221+
hook.getLabels() == null
222+
? null
223+
: hook.getLabels().stream()
224+
.map(MergeRequestLabel::getTitle)
225+
.collect(toList()))
219226
.withTargetBranch(hook.getObjectAttributes().getTargetBranch())
220227
.withTargetRepoName(hook.getObjectAttributes().getTarget().getName())
221228
.withTargetNamespace(hook.getObjectAttributes().getTarget().getNamespace())
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.dabsquared.gitlabjenkins.workflow;
2+
3+
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
4+
import hudson.Extension;
5+
import hudson.model.Run;
6+
import hudson.model.TaskListener;
7+
import java.util.Collections;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Set;
11+
import org.apache.commons.lang.StringUtils;
12+
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
13+
import org.jenkinsci.plugins.workflow.steps.Step;
14+
import org.jenkinsci.plugins.workflow.steps.StepContext;
15+
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
16+
import org.jenkinsci.plugins.workflow.steps.StepExecution;
17+
import org.kohsuke.stapler.DataBoundConstructor;
18+
import org.kohsuke.stapler.DataBoundSetter;
19+
import org.kohsuke.stapler.export.ExportedBean;
20+
21+
/**
22+
* @author Yiftah Waisman
23+
*/
24+
@ExportedBean
25+
public class GitLabMergeRequestLabelExistsStep extends Step {
26+
27+
private String label;
28+
29+
@DataBoundConstructor
30+
public GitLabMergeRequestLabelExistsStep(String label) {
31+
this.label = StringUtils.isEmpty(label) ? null : label;
32+
}
33+
34+
@Override
35+
public StepExecution start(StepContext context) throws Exception {
36+
return new GitLabMergeRequestLabelExistsStepExecution(context, this);
37+
}
38+
39+
public String getLabel() {
40+
return label;
41+
}
42+
43+
@DataBoundSetter
44+
public void setLabel(String label) {
45+
this.label = StringUtils.isEmpty(label) ? null : label;
46+
}
47+
48+
@Extension
49+
public static class DescriptorImpl extends StepDescriptor {
50+
51+
@Override
52+
public String getFunctionName() {
53+
return "GitLabMergeRequestLabelExists";
54+
}
55+
56+
@Override
57+
public String getDisplayName() {
58+
return "Check if a GitLab merge request has a specific label";
59+
}
60+
61+
@Override
62+
public Set<? extends Class<?>> getRequiredContext() {
63+
Set<Class<?>> context = new HashSet<>();
64+
Collections.addAll(context, TaskListener.class, Run.class);
65+
return Collections.unmodifiableSet(context);
66+
}
67+
}
68+
69+
public static class GitLabMergeRequestLabelExistsStepExecution extends AbstractSynchronousStepExecution<Boolean> {
70+
private static final long serialVersionUID = 1;
71+
72+
private final transient Run<?, ?> run;
73+
74+
private final transient GitLabMergeRequestLabelExistsStep step;
75+
76+
public GitLabMergeRequestLabelExistsStepExecution(StepContext context, GitLabMergeRequestLabelExistsStep step)
77+
throws Exception {
78+
super(context);
79+
this.step = step;
80+
run = context.get(Run.class);
81+
}
82+
83+
@Override
84+
protected Boolean run() throws Exception {
85+
GitLabWebHookCause cause = run.getCause(GitLabWebHookCause.class);
86+
if (cause == null) {
87+
return false;
88+
}
89+
List<String> labels = cause.getData().getMergeRequestLabels();
90+
if (labels == null) {
91+
return false;
92+
}
93+
return labels.contains(step.getLabel());
94+
}
95+
}
96+
}

src/test/java/com/dabsquared/gitlabjenkins/environment/GitLabEnvironmentContributorTest.java

+52-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static org.junit.Assert.assertNotNull;
66

77
import com.dabsquared.gitlabjenkins.cause.CauseData;
8+
import com.dabsquared.gitlabjenkins.cause.CauseDataBuilder;
89
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
910
import hudson.EnvVars;
1011
import hudson.matrix.AxisList;
@@ -18,6 +19,8 @@
1819
import hudson.model.StreamBuildListener;
1920
import java.io.IOException;
2021
import java.nio.charset.Charset;
22+
import java.util.Arrays;
23+
import java.util.Collections;
2124
import java.util.List;
2225
import java.util.concurrent.ExecutionException;
2326
import org.junit.Before;
@@ -71,7 +74,46 @@ public void matrixProjectTest() throws IOException, InterruptedException, Execut
7174
}
7275
}
7376

74-
private CauseData generateCauseData() {
77+
public void testFreeStyleProjectLabels(CauseData causeData, String expected)
78+
throws IOException, InterruptedException, ExecutionException {
79+
FreeStyleProject p = jenkins.createFreeStyleProject();
80+
GitLabWebHookCause cause = new GitLabWebHookCause(causeData);
81+
FreeStyleBuild b = p.scheduleBuild2(0, cause).get();
82+
EnvVars env = b.getEnvironment(listener);
83+
assertEquals(expected, env.get("gitlabMergeRequestLabels"));
84+
}
85+
86+
@Test
87+
public void freeStyleProjectTestNoLabels() throws IOException, InterruptedException, ExecutionException {
88+
// withMergeRequestLabels() not called on CauseDataBuilder
89+
testFreeStyleProjectLabels(generateCauseData(), null);
90+
}
91+
92+
@Test
93+
public void freeStyleProjectTestNullLabels() throws IOException, InterruptedException, ExecutionException {
94+
// null passed as labels
95+
testFreeStyleProjectLabels(generateCauseDataWithLabels(null), null);
96+
}
97+
98+
@Test
99+
public void freeStyleProjectTestEmptyLabels() throws IOException, InterruptedException, ExecutionException {
100+
// empty list passed as labels
101+
testFreeStyleProjectLabels(generateCauseDataWithLabels(Collections.emptyList()), null);
102+
}
103+
104+
@Test
105+
public void freeStyleProjectTestOneLabel() throws IOException, InterruptedException, ExecutionException {
106+
testFreeStyleProjectLabels(generateCauseDataWithLabels(Arrays.asList("test1")), "test1");
107+
}
108+
109+
@Test
110+
public void freeStyleProjectTestTwoLabels() throws IOException, InterruptedException, ExecutionException {
111+
testFreeStyleProjectLabels(
112+
generateCauseDataWithLabels(Arrays.asList("test1", "test2", "test with spaces")),
113+
"test1,test2,test with spaces");
114+
}
115+
116+
private CauseDataBuilder generateCauseDataBase() {
75117
return causeData()
76118
.withActionType(CauseData.ActionType.MERGE)
77119
.withSourceProjectId(1)
@@ -95,8 +137,15 @@ private CauseData generateCauseData() {
95137
.withTargetRepoHttpUrl("https://gitlab.org/test.git")
96138
.withTriggeredByUser("test")
97139
.withLastCommit("123")
98-
.withTargetProjectUrl("https://gitlab.org/test")
99-
.build();
140+
.withTargetProjectUrl("https://gitlab.org/test");
141+
}
142+
143+
private CauseData generateCauseData() {
144+
return generateCauseDataBase().build();
145+
}
146+
147+
private CauseData generateCauseDataWithLabels(List<String> labels) {
148+
return generateCauseDataBase().withMergeRequestLabels(labels).build();
100149
}
101150

102151
private void assertEnv(EnvVars env) {

0 commit comments

Comments
 (0)