Skip to content

Commit

Permalink
Add merge request labels to environment variables (#1713)
Browse files Browse the repository at this point in the history
* 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 <krisstern@outlook.com>

* fix names

* mvn spotless apply

---------

Co-authored-by: Kris Stern <krisstern@outlook.com>
  • Loading branch information
yiftahw and krisstern authored Oct 25, 2024
1 parent 0ff4703 commit b661e62
Show file tree
Hide file tree
Showing 8 changed files with 316 additions and 7 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [Accept merge request after build](#accept-merge-request)
- [Notify specific project by a specific GitLab connection](#notify-specific-project-by-a-specific-gitlab-connection)
- [Cancel pending builds on merge request update](#cancel-pending-builds-on-merge-request-update)
- [Check if a label is applied to a merge request](#check-if-a-label-is-applied-to-a-merge-request)
- [Compatibility](#compatibility)
- [Contributing to the Plugin](#contributing-to-the-plugin)
- [Testing With Docker](src/docker/README.md#quick-test-environment-setup-using-docker-for-linuxamd64)
Expand Down Expand Up @@ -96,6 +97,7 @@ gitlabMergedByUser
gitlabMergeRequestAssignee
gitlabMergeRequestLastCommit
gitlabMergeRequestTargetProjectId
gitlabMergeRequestLabels
gitlabTargetBranch
gitlabTargetRepoName
gitlabTargetNamespace
Expand Down Expand Up @@ -609,6 +611,22 @@ gitlabCommitStatus(
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.
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.

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

## Compatibility

Version 1.2.1 of the plugin introduces a backwards-incompatible change
Expand Down
9 changes: 5 additions & 4 deletions src/docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ In order to test the plugin on different versions of `GitLab` and `Jenkins` you
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.

If they don't already exist, create the following directories and make sure the user that Docker is running as owns them:
* /srv/docker/gitlab/postgresql
* /srv/docker/gitlab/gitlab
* /srv/docker/gitlab/redis
* /srv/docker/jenkins
* /srv/docker/gitlab/postgresql
* /srv/docker/gitlab/gitlab
* /srv/docker/gitlab/redis
* /srv/docker/jenkins

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.

## Quick test environment setup using Docker for MacOS/arm64
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/com/dabsquared/gitlabjenkins/cause/CauseData.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class CauseData {
private final String mergedByUser;
private final String mergeRequestAssignee;
private final Integer mergeRequestTargetProjectId;
private final List<String> mergeRequestLabels;
private final String targetBranch;
private final String targetRepoName;
private final String targetNamespace;
Expand Down Expand Up @@ -84,6 +85,7 @@ public final class CauseData {
Integer mergeRequestId,
Integer mergeRequestIid,
Integer mergeRequestTargetProjectId,
List<String> mergeRequestLabels,
String targetBranch,
String targetRepoName,
String targetNamespace,
Expand Down Expand Up @@ -131,6 +133,7 @@ public final class CauseData {
this.mergedByUser = mergedByUser == null ? "" : mergedByUser;
this.mergeRequestAssignee = mergeRequestAssignee == null ? "" : mergeRequestAssignee;
this.mergeRequestTargetProjectId = mergeRequestTargetProjectId;
this.mergeRequestLabels = mergeRequestLabels == null ? Collections.emptyList() : mergeRequestLabels;
this.targetBranch = Objects.requireNonNull(targetBranch, "targetBranch must not be null.");
this.targetRepoName = Objects.requireNonNull(targetRepoName, "targetRepoName must not be null.");
this.targetNamespace = Objects.requireNonNull(targetNamespace, "targetNamespace must not be null.");
Expand Down Expand Up @@ -177,6 +180,7 @@ public Map<String, String> getBuildVariables() {
variables.put(
"gitlabMergeRequestTargetProjectId",
mergeRequestTargetProjectId == null ? "" : mergeRequestTargetProjectId.toString());
variables.put("gitlabMergeRequestLabels", StringUtils.join(mergeRequestLabels, ","));
variables.put("gitlabMergeRequestLastCommit", lastCommit);
variables.putIfNotNull("gitlabMergeRequestState", mergeRequestState);
variables.putIfNotNull("gitlabMergedByUser", mergedByUser);
Expand Down Expand Up @@ -302,6 +306,11 @@ public Integer getMergeRequestTargetProjectId() {
return mergeRequestTargetProjectId;
}

@Exported
public List<String> getMergeRequestLabels() {
return mergeRequestLabels;
}

@Exported
public String getTargetBranch() {
return targetBranch;
Expand Down Expand Up @@ -473,6 +482,7 @@ public boolean equals(Object o) {
.append(mergedByUser, causeData.mergedByUser)
.append(mergeRequestAssignee, causeData.mergeRequestAssignee)
.append(mergeRequestTargetProjectId, causeData.mergeRequestTargetProjectId)
.append(mergeRequestLabels, causeData.mergeRequestLabels)
.append(targetBranch, causeData.targetBranch)
.append(targetRepoName, causeData.targetRepoName)
.append(targetNamespace, causeData.targetNamespace)
Expand Down Expand Up @@ -522,6 +532,7 @@ public int hashCode() {
.append(mergedByUser)
.append(mergeRequestAssignee)
.append(mergeRequestTargetProjectId)
.append(mergeRequestLabels)
.append(targetBranch)
.append(targetRepoName)
.append(targetNamespace)
Expand Down Expand Up @@ -571,6 +582,7 @@ public String toString() {
.append("mergedByUser", mergedByUser)
.append("mergeRequestAssignee", mergeRequestAssignee)
.append("mergeRequestTargetProjectId", mergeRequestTargetProjectId)
.append("mergeRequestLabels", mergeRequestLabels)
.append("targetBranch", targetBranch)
.append("targetRepoName", targetRepoName)
.append("targetNamespace", targetNamespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static com.dabsquared.gitlabjenkins.util.LoggerUtil.toArray;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.stream.Collectors.toList;

import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
Expand Down Expand Up @@ -216,6 +217,12 @@ protected CauseData retrieveCauseData(MergeRequestHook hook) {
.withMergeRequestAssignee(
hook.getAssignee() == null ? null : hook.getAssignee().getUsername())
.withMergeRequestTargetProjectId(hook.getObjectAttributes().getTargetProjectId())
.withMergeRequestLabels(
hook.getLabels() == null
? null
: hook.getLabels().stream()
.map(MergeRequestLabel::getTitle)
.collect(toList()))
.withTargetBranch(hook.getObjectAttributes().getTargetBranch())
.withTargetRepoName(hook.getObjectAttributes().getTarget().getName())
.withTargetNamespace(hook.getObjectAttributes().getTarget().getNamespace())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.dabsquared.gitlabjenkins.workflow;

import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import hudson.Extension;
import hudson.model.Run;
import hudson.model.TaskListener;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.steps.AbstractSynchronousStepExecution;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.export.ExportedBean;

/**
* @author Yiftah Waisman
*/
@ExportedBean
public class GitLabMergeRequestLabelExistsStep extends Step {

private String label;

@DataBoundConstructor
public GitLabMergeRequestLabelExistsStep(String label) {
this.label = StringUtils.isEmpty(label) ? null : label;
}

@Override
public StepExecution start(StepContext context) throws Exception {
return new GitLabMergeRequestLabelExistsStepExecution(context, this);
}

public String getLabel() {
return label;
}

@DataBoundSetter
public void setLabel(String label) {
this.label = StringUtils.isEmpty(label) ? null : label;
}

@Extension
public static class DescriptorImpl extends StepDescriptor {

@Override
public String getFunctionName() {
return "GitLabMergeRequestLabelExists";
}

@Override
public String getDisplayName() {
return "Check if a GitLab merge request has a specific label";
}

@Override
public Set<? extends Class<?>> getRequiredContext() {
Set<Class<?>> context = new HashSet<>();
Collections.addAll(context, TaskListener.class, Run.class);
return Collections.unmodifiableSet(context);
}
}

public static class GitLabMergeRequestLabelExistsStepExecution extends AbstractSynchronousStepExecution<Boolean> {
private static final long serialVersionUID = 1;

private final transient Run<?, ?> run;

private final transient GitLabMergeRequestLabelExistsStep step;

public GitLabMergeRequestLabelExistsStepExecution(StepContext context, GitLabMergeRequestLabelExistsStep step)
throws Exception {
super(context);
this.step = step;
run = context.get(Run.class);
}

@Override
protected Boolean run() throws Exception {
GitLabWebHookCause cause = run.getCause(GitLabWebHookCause.class);
if (cause == null) {
return false;
}
List<String> labels = cause.getData().getMergeRequestLabels();
if (labels == null) {
return false;
}
return labels.contains(step.getLabel());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.junit.Assert.assertNotNull;

import com.dabsquared.gitlabjenkins.cause.CauseData;
import com.dabsquared.gitlabjenkins.cause.CauseDataBuilder;
import com.dabsquared.gitlabjenkins.cause.GitLabWebHookCause;
import hudson.EnvVars;
import hudson.matrix.AxisList;
Expand All @@ -18,6 +19,8 @@
import hudson.model.StreamBuildListener;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.junit.Before;
Expand Down Expand Up @@ -71,7 +74,46 @@ public void matrixProjectTest() throws IOException, InterruptedException, Execut
}
}

private CauseData generateCauseData() {
public void testFreeStyleProjectLabels(CauseData causeData, String expected)
throws IOException, InterruptedException, ExecutionException {
FreeStyleProject p = jenkins.createFreeStyleProject();
GitLabWebHookCause cause = new GitLabWebHookCause(causeData);
FreeStyleBuild b = p.scheduleBuild2(0, cause).get();
EnvVars env = b.getEnvironment(listener);
assertEquals(expected, env.get("gitlabMergeRequestLabels"));
}

@Test
public void freeStyleProjectTestNoLabels() throws IOException, InterruptedException, ExecutionException {
// withMergeRequestLabels() not called on CauseDataBuilder
testFreeStyleProjectLabels(generateCauseData(), null);
}

@Test
public void freeStyleProjectTestNullLabels() throws IOException, InterruptedException, ExecutionException {
// null passed as labels
testFreeStyleProjectLabels(generateCauseDataWithLabels(null), null);
}

@Test
public void freeStyleProjectTestEmptyLabels() throws IOException, InterruptedException, ExecutionException {
// empty list passed as labels
testFreeStyleProjectLabels(generateCauseDataWithLabels(Collections.emptyList()), null);
}

@Test
public void freeStyleProjectTestOneLabel() throws IOException, InterruptedException, ExecutionException {
testFreeStyleProjectLabels(generateCauseDataWithLabels(Arrays.asList("test1")), "test1");
}

@Test
public void freeStyleProjectTestTwoLabels() throws IOException, InterruptedException, ExecutionException {
testFreeStyleProjectLabels(
generateCauseDataWithLabels(Arrays.asList("test1", "test2", "test with spaces")),
"test1,test2,test with spaces");
}

private CauseDataBuilder generateCauseDataBase() {
return causeData()
.withActionType(CauseData.ActionType.MERGE)
.withSourceProjectId(1)
Expand All @@ -95,8 +137,15 @@ private CauseData generateCauseData() {
.withTargetRepoHttpUrl("https://gitlab.org/test.git")
.withTriggeredByUser("test")
.withLastCommit("123")
.withTargetProjectUrl("https://gitlab.org/test")
.build();
.withTargetProjectUrl("https://gitlab.org/test");
}

private CauseData generateCauseData() {
return generateCauseDataBase().build();
}

private CauseData generateCauseDataWithLabels(List<String> labels) {
return generateCauseDataBase().withMergeRequestLabels(labels).build();
}

private void assertEnv(EnvVars env) {
Expand Down
Loading

0 comments on commit b661e62

Please sign in to comment.