From 8e1357230fca1684a2c7002cec72709ad8b68015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 6 Jul 2026 18:19:11 +0200 Subject: [PATCH 1/4] Add environment variables in builds for pull request labels --- README.md | 27 +++++ docs/de/index.md | 27 +++++ .../scmmanager/scm/api/PullRequest.java | 18 ++- .../scm/api/ScmManagerPullRequestHead.java | 21 ++++ .../env/PullRequestLabelsEnvContributor.java | 76 ++++++++++++ .../scm/DescriptorEndpointSecurityTest.java | 89 ++++++++++++++ .../scmmanager/scm/api/ScmManagerApiTest.java | 13 +++ .../PullRequestLabelsEnvContributorTest.java | 110 ++++++++++++++++++ src/test/resources/mappings/pullRequest.json | 2 +- src/test/resources/mappings/pullRequests.json | 2 +- 10 files changed, 381 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java create mode 100644 src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java create mode 100644 src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java diff --git a/README.md b/README.md index 31525a9..76f93b7 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,33 @@ It starts with the prefix `SCMM_CUSTOM_PROP_` and will end with the key of the c For example the custom property with the key `lang`, will be injected as the environment variable `SCMM_CUSTOM_PROP_lang`. The value of the environment variable, will be the value of the custom property. +#### Pull Request Labels + +For pull request builds, SCM-Manager pull request labels are injected as environment variables. +`SCMM_PR_LABELS` contains all labels as a JSON array, preserving the exact label values and order from SCM-Manager. +For simple access, the plugin also injects `SCMM_PR_LABEL_COUNT` and numbered variables `SCMM_PR_LABEL_1`, `SCMM_PR_LABEL_2`, and so on. + +Example: + +```groovy +pipeline { + agent any + + stages { + stage('Labels') { + steps { + script { + def labels = new groovy.json.JsonSlurperClassic().parseText(env.SCMM_PR_LABELS ?: '[]') + if (labels.contains('backend')) { + echo 'Backend label is set' + } + } + } + } + } +} +``` + ### Organization Folders – Namespaces If you want to have build jobs for every repository in a namespace, you can create "SCM-Manager Namespace" jobs. These will scan all repositories in the given namespace and create multibranch pipelines for each repository where a diff --git a/docs/de/index.md b/docs/de/index.md index fbf2ed5..0c83e39 100644 --- a/docs/de/index.md +++ b/docs/de/index.md @@ -102,6 +102,33 @@ Das "Custom Properties" Plugin unterstützt auch, dass mehrere Werte für eine E Hierbei werden die Werte mittels Tab bzw. einem `\t` separiert. Die Werte einer Eigenschaft werden in der jeweiligen Umgebungsvariable ebenfalls per Tab separiert gesetzt. +#### Pull-Request-Labels + +Bei Pull-Request-Builds werden Labels des SCM-Manager Pull Requests als Umgebungsvariablen gesetzt. +`SCMM_PR_LABELS` enthält alle Labels als JSON-Array und erhält damit die exakten Werte und die Reihenfolge aus dem SCM-Manager. +Für einfachen Zugriff werden zusätzlich `SCMM_PR_LABEL_COUNT` und durchnummerierte Variablen `SCMM_PR_LABEL_1`, `SCMM_PR_LABEL_2` usw. gesetzt. + +Beispiel: + +```groovy +pipeline { + agent any + + stages { + stage('Labels') { + steps { + script { + def labels = new groovy.json.JsonSlurperClassic().parseText(env.SCMM_PR_LABELS ?: '[]') + if (labels.contains('backend')) { + echo 'Backend-Label ist gesetzt' + } + } + } + } + } +} +``` + ### Organization Folders – Namespaces Sollen für alle Repositorys eines **kompletten Namespaces** im SCM-Manager Jobs erzeugt werden, kann ein **Organization Folder**-Job mit einem SCM-Manager-Namespace als Quelle genutzt werden. Dieser prüft alle Repositorys in einem gegebenen Namespace und erzeugt entsprechende Multibranch-Pipelines, wenn im Wurzelverzeichnis des Repositorys eine `Jenkinsfile` gefunden wurde. diff --git a/src/main/java/com/cloudogu/scmmanager/scm/api/PullRequest.java b/src/main/java/com/cloudogu/scmmanager/scm/api/PullRequest.java index 62c2dc6..5c0e41f 100644 --- a/src/main/java/com/cloudogu/scmmanager/scm/api/PullRequest.java +++ b/src/main/java/com/cloudogu/scmmanager/scm/api/PullRequest.java @@ -1,6 +1,8 @@ package com.cloudogu.scmmanager.scm.api; import de.otto.edison.hal.HalRepresentation; +import java.util.Collections; +import java.util.List; import java.util.Objects; public class PullRequest extends HalRepresentation implements ScmManagerObservable { @@ -11,6 +13,8 @@ public class PullRequest extends HalRepresentation implements ScmManagerObservab private String target; + private List labels = Collections.emptyList(); + private CloneInformation cloneInformation; private Branch sourceBranch; @@ -53,6 +57,13 @@ public String getTarget() { return target; } + public List getLabels() { + if (labels == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(labels); + } + @Override public ScmManagerPullRequestHead head() { if (head == null) { @@ -60,7 +71,8 @@ public ScmManagerPullRequestHead head() { cloneInformation, id, new ScmManagerHead(cloneInformation, target), - new ScmManagerHead(cloneInformation, source)); + new ScmManagerHead(cloneInformation, source), + getLabels()); } return head; } @@ -79,6 +91,7 @@ public boolean equals(Object o) { return Objects.equals(id, that.id) && Objects.equals(source, that.source) && Objects.equals(target, that.target) + && Objects.equals(getLabels(), that.getLabels()) && Objects.equals(cloneInformation, that.cloneInformation) && Objects.equals(sourceBranch, that.sourceBranch) && Objects.equals(targetBranch, that.targetBranch) @@ -87,6 +100,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), id, source, target, cloneInformation, sourceBranch, targetBranch, head); + return Objects.hash( + super.hashCode(), id, source, target, getLabels(), cloneInformation, sourceBranch, targetBranch, head); } } diff --git a/src/main/java/com/cloudogu/scmmanager/scm/api/ScmManagerPullRequestHead.java b/src/main/java/com/cloudogu/scmmanager/scm/api/ScmManagerPullRequestHead.java index 7616b4e..b288873 100644 --- a/src/main/java/com/cloudogu/scmmanager/scm/api/ScmManagerPullRequestHead.java +++ b/src/main/java/com/cloudogu/scmmanager/scm/api/ScmManagerPullRequestHead.java @@ -1,6 +1,9 @@ package com.cloudogu.scmmanager.scm.api; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import jenkins.scm.api.SCMHeadOrigin; import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy; import jenkins.scm.api.mixin.ChangeRequestSCMHead2; @@ -14,17 +17,28 @@ public class ScmManagerPullRequestHead extends ScmManagerHead implements ChangeR private final ScmManagerHead target; private final ScmManagerHead source; + private final List labels; public ScmManagerPullRequestHead( @NonNull CloneInformation cloneInformation, @NonNull String id, @NonNull ScmManagerHead target, ScmManagerHead source) { + this(cloneInformation, id, target, source, Collections.emptyList()); + } + + public ScmManagerPullRequestHead( + @NonNull CloneInformation cloneInformation, + @NonNull String id, + @NonNull ScmManagerHead target, + ScmManagerHead source, + List labels) { // ?? why PullRequest/... super(cloneInformation, "PR-" + id); this.id = id; this.target = target; this.source = source; + this.labels = labels == null ? Collections.emptyList() : new ArrayList<>(labels); } @NonNull @@ -43,6 +57,13 @@ public ScmManagerHead getSource() { return source; } + public List getLabels() { + if (labels == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(labels); + } + @NonNull @Override public ChangeRequestCheckoutStrategy getCheckoutStrategy() { diff --git a/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java new file mode 100644 index 0000000..655627f --- /dev/null +++ b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java @@ -0,0 +1,76 @@ +package com.cloudogu.scmmanager.scm.env; + +import com.cloudogu.scmmanager.scm.api.ScmManagerPullRequestHead; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import hudson.EnvVars; +import hudson.Extension; +import hudson.model.EnvironmentContributor; +import hudson.model.Job; +import hudson.model.Run; +import hudson.model.TaskListener; +import java.util.List; +import javax.annotation.Nonnull; +import jenkins.branch.Branch; +import jenkins.scm.api.SCMHead; +import lombok.extern.slf4j.Slf4j; +import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty; + +@Extension(optional = true) +@Slf4j +public class PullRequestLabelsEnvContributor extends EnvironmentContributor { + + private static final String ENV_LABELS = "SCMM_PR_LABELS"; + private static final String ENV_LABEL_COUNT = "SCMM_PR_LABEL_COUNT"; + private static final String ENV_LABEL_PREFIX = "SCMM_PR_LABEL_"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + @Override + public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnull TaskListener listener) { + ScmManagerPullRequestHead pullRequestHead = getPullRequestHead(run); + if (pullRequestHead == null) { + return; + } + + List labels = pullRequestHead.getLabels(); + envs.put(ENV_LABELS, toJson(labels)); + envs.put(ENV_LABEL_COUNT, String.valueOf(labels.size())); + + for (int i = 0; i < labels.size(); i++) { + envs.put(ENV_LABEL_PREFIX + (i + 1), labels.get(i)); + } + } + + private ScmManagerPullRequestHead getPullRequestHead(Run run) { + Object parent = run.getParent(); + if (!(parent instanceof Job)) { + return null; + } + + BranchJobProperty branchJobProperty = ((Job) parent).getProperty(BranchJobProperty.class); + if (branchJobProperty == null) { + return null; + } + + Branch branch = branchJobProperty.getBranch(); + if (branch == null) { + return null; + } + + SCMHead head = branch.getHead(); + if (head instanceof ScmManagerPullRequestHead) { + return (ScmManagerPullRequestHead) head; + } + + return null; + } + + private String toJson(List labels) { + try { + return OBJECT_MAPPER.writeValueAsString(labels); + } catch (JsonProcessingException e) { + log.warn("could not serialize SCM-Manager pull request labels", e); + return "[]"; + } + } +} diff --git a/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java b/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java new file mode 100644 index 0000000..3ce2111 --- /dev/null +++ b/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java @@ -0,0 +1,89 @@ +package com.cloudogu.scmmanager.scm; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.net.URL; +import java.util.List; +import jenkins.model.Jenkins; +import okhttp3.mockwebserver.MockWebServer; +import org.htmlunit.HttpMethod; +import org.htmlunit.Page; +import org.htmlunit.WebRequest; +import org.htmlunit.util.NameValuePair; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +@WithJenkins +class DescriptorEndpointSecurityTest { + + private static final String LOW_PRIVILEGE_USER = "lowpriv"; + private static final String NAVIGATOR_DESCRIPTOR = + "descriptorByName/com.cloudogu.scmmanager.scm.ScmManagerNavigator"; + private static final String SOURCE_DESCRIPTOR = + "descriptorByName/com.cloudogu.scmmanager.scm.ScmManagerSource"; + + private JenkinsRule j; + + @BeforeEach + void beforeEach(JenkinsRule rule) { + j = rule; + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() + .grant(Jenkins.READ) + .everywhere() + .to(LOW_PRIVILEGE_USER) + .grant(Jenkins.ADMINISTER) + .everywhere() + .to("admin")); + } + + @Test + void shouldRequirePostForServerUrlChecks() throws Exception { + assertGetIsRejected(NAVIGATOR_DESCRIPTOR + "/checkServerUrl?value=http://example.com"); + assertGetIsRejected(SOURCE_DESCRIPTOR + "/checkServerUrl?value=http://example.com"); + } + + @Test + void shouldSkipLowPrivilegeCredentialChecksBeforeConnectingToServerUrl() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.start(); + String serverUrl = server.url("/").toString(); + + assertPostIsOk( + NAVIGATOR_DESCRIPTOR + "/checkCredentialsId", + List.of(new NameValuePair("serverUrl", serverUrl), new NameValuePair("value", "scm-creds"))); + assertPostIsOk( + SOURCE_DESCRIPTOR + "/checkCredentialsId", + List.of(new NameValuePair("serverUrl", serverUrl), new NameValuePair("value", "scm-creds"))); + + assertThat(server.getRequestCount()).isZero(); + } + } + + private void assertGetIsRejected(String path) throws Exception { + JenkinsRule.WebClient webClient = lowPrivilegeWebClient(); + + Page page = webClient.goTo(path, "text/html"); + + assertThat(page.getWebResponse().getStatusCode()).isEqualTo(405); + } + + private void assertPostIsOk(String path, List parameters) throws Exception { + JenkinsRule.WebClient webClient = lowPrivilegeWebClient(); + WebRequest request = new WebRequest(new URL(j.getURL(), path), HttpMethod.POST); + request.setRequestParameters(parameters); + + Page page = webClient.getPage(webClient.addCrumb(request)); + + assertThat(page.getWebResponse().getStatusCode()).isEqualTo(200); + } + + private JenkinsRule.WebClient lowPrivilegeWebClient() { + return j.createWebClient() + .withBasicCredentials(LOW_PRIVILEGE_USER, LOW_PRIVILEGE_USER) + .withThrowExceptionOnFailingStatusCode(false); + } +} diff --git a/src/test/java/com/cloudogu/scmmanager/scm/api/ScmManagerApiTest.java b/src/test/java/com/cloudogu/scmmanager/scm/api/ScmManagerApiTest.java index 64c00d6..384917b 100644 --- a/src/test/java/com/cloudogu/scmmanager/scm/api/ScmManagerApiTest.java +++ b/src/test/java/com/cloudogu/scmmanager/scm/api/ScmManagerApiTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; +import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; import jenkins.scm.api.SCMFile; import org.junit.jupiter.api.Test; @@ -207,6 +208,8 @@ void shouldLoadPullRequests() throws Exception { assertThat(pullRequest.getId()).isEqualTo("1"); assertThat(pullRequest.getSource()).isEqualTo("develop"); assertThat(pullRequest.getTarget()).isEqualTo("master"); + assertThat(pullRequest.getLabels()).containsExactly("backend", "needs-review"); + assertThat(pullRequest.head().getLabels()).containsExactly("backend", "needs-review"); } @Test @@ -225,6 +228,16 @@ void shouldLoadSinglePullRequest() throws Exception { assertThat(pullRequest.getId()).isEqualTo("1"); assertThat(pullRequest.getSource()).isEqualTo("develop"); assertThat(pullRequest.getTarget()).isEqualTo("master"); + assertThat(pullRequest.getLabels()).containsExactly("backend", "needs-review"); + assertThat(pullRequest.head().getLabels()).containsExactly("backend", "needs-review"); + } + + @Test + void shouldUseEmptyLabelListWhenLabelsAreMissing() throws Exception { + PullRequest pullRequest = new ObjectMapper() + .readValue("{\"id\":\"1\",\"source\":\"develop\",\"target\":\"master\"}", PullRequest.class); + + assertThat(pullRequest.getLabels()).isEmpty(); } @Test diff --git a/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java b/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java new file mode 100644 index 0000000..9f5e5d1 --- /dev/null +++ b/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java @@ -0,0 +1,110 @@ +package com.cloudogu.scmmanager.scm.env; + +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.cloudogu.scmmanager.scm.api.CloneInformation; +import com.cloudogu.scmmanager.scm.api.ScmManagerHead; +import com.cloudogu.scmmanager.scm.api.ScmManagerPullRequestHead; +import hudson.EnvVars; +import hudson.model.TaskListener; +import hudson.scm.SCM; +import java.util.List; +import jenkins.branch.Branch; +import jenkins.scm.api.SCMHead; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.jenkinsci.plugins.workflow.multibranch.BranchJobProperty; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PullRequestLabelsEnvContributorTest { + + private static final CloneInformation CLONE_INFORMATION = new CloneInformation("git", "http://example.com/repo"); + + @Mock + private WorkflowJob job; + + @Mock + private WorkflowRun run; + + @Mock + private EnvVars envVars; + + @Mock + private TaskListener listener; + + @Mock + private BranchJobProperty branchJobProperty; + + private PullRequestLabelsEnvContributor envContributor; + + @BeforeEach + void beforeEach() { + lenient().when(run.getParent()).thenReturn(job); + envContributor = new PullRequestLabelsEnvContributor(); + } + + @Test + void shouldInjectPullRequestLabels() { + useBranch(new ScmManagerPullRequestHead( + CLONE_INFORMATION, + "42", + new ScmManagerHead(CLONE_INFORMATION, "main"), + new ScmManagerHead(CLONE_INFORMATION, "feature"), + List.of("backend", "needs review", "comma,label"))); + + envContributor.buildEnvironmentFor(run, envVars, listener); + + verify(envVars).put("SCMM_PR_LABELS", "[\"backend\",\"needs review\",\"comma,label\"]"); + verify(envVars).put("SCMM_PR_LABEL_COUNT", "3"); + verify(envVars).put("SCMM_PR_LABEL_1", "backend"); + verify(envVars).put("SCMM_PR_LABEL_2", "needs review"); + verify(envVars).put("SCMM_PR_LABEL_3", "comma,label"); + verifyNoMoreInteractions(envVars); + } + + @Test + void shouldInjectEmptyLabelListForPullRequestWithoutLabels() { + useBranch(new ScmManagerPullRequestHead( + CLONE_INFORMATION, + "42", + new ScmManagerHead(CLONE_INFORMATION, "main"), + new ScmManagerHead(CLONE_INFORMATION, "feature"))); + + envContributor.buildEnvironmentFor(run, envVars, listener); + + verify(envVars).put("SCMM_PR_LABELS", "[]"); + verify(envVars).put("SCMM_PR_LABEL_COUNT", "0"); + verifyNoMoreInteractions(envVars); + } + + @Test + void shouldNotInjectLabelsForNonPullRequestBuild() { + useBranch(new ScmManagerHead(CLONE_INFORMATION, "main")); + + envContributor.buildEnvironmentFor(run, envVars, listener); + + verifyNoInteractions(envVars); + } + + @Test + void shouldNotInjectLabelsWithoutBranchJobProperty() { + envContributor.buildEnvironmentFor(run, envVars, listener); + + verifyNoInteractions(envVars); + } + + private void useBranch(SCMHead head) { + when(job.getProperty(BranchJobProperty.class)).thenReturn(branchJobProperty); + when(branchJobProperty.getBranch()).thenReturn(new Branch("source-id", head, mock(SCM.class), List.of())); + } +} diff --git a/src/test/resources/mappings/pullRequest.json b/src/test/resources/mappings/pullRequest.json index 3acb19c..dbf807c 100644 --- a/src/test/resources/mappings/pullRequest.json +++ b/src/test/resources/mappings/pullRequest.json @@ -10,7 +10,7 @@ }, "response": { "status": 200, - "body": "{\"id\":\"1\",\"author\":{\"id\":\"sdorra\",\"displayName\":\"Sebastian Sdorra\",\"mail\":\"sebastian.sdorra@cloudogu.com\"},\"source\":\"develop\",\"target\":\"master\",\"title\":\"develop => master\",\"description\":null,\"creationDate\":\"2020-06-24T05:24:24.340Z\",\"lastModified\":null,\"status\":\"OPEN\",\"reviewer\":[],\"tasks\":{\"todo\":0,\"done\":0},\"sourceRevision\":null,\"targetRevision\":null,\"markedAsReviewed\":[],\"emergencyMerged\":false,\"ignoredMergeObstacles\":null,\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"comments\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/comments/\"},\"events\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/events\"},\"approve\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/approve\"},\"subscription\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/subscription\"},\"update\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"reject\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/reject\"},\"mergeCheck\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/merge-check\"},\"mergeConflicts\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/conflicts\"},\"defaultCommitMessage\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/commit-message\"},\"merge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"emergencyMerge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"workflowResult\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/workflow/\"},\"reviewMark\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/review-mark/{path}\",\"templated\":true},\"sourceBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/develop\"},\"targetBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/master\"},\"ciStatus\":{\"href\":\"/scm/api/v2/ci/jenkins-plugin/hello-shell/pullrequest/1\"},\"teamscaleFindings\":{\"href\":\"/scm/api/v2/teamscale/pull-request/jenkins-plugin/hello-shell/1/findings\"}}}", + "body": "{\"id\":\"1\",\"author\":{\"id\":\"sdorra\",\"displayName\":\"Sebastian Sdorra\",\"mail\":\"sebastian.sdorra@cloudogu.com\"},\"source\":\"develop\",\"target\":\"master\",\"title\":\"develop => master\",\"description\":null,\"creationDate\":\"2020-06-24T05:24:24.340Z\",\"lastModified\":null,\"status\":\"OPEN\",\"reviewer\":[],\"tasks\":{\"todo\":0,\"done\":0},\"sourceRevision\":null,\"targetRevision\":null,\"markedAsReviewed\":[],\"emergencyMerged\":false,\"ignoredMergeObstacles\":null,\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"comments\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/comments/\"},\"events\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/events\"},\"approve\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/approve\"},\"subscription\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/subscription\"},\"update\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"reject\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/reject\"},\"mergeCheck\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/merge-check\"},\"mergeConflicts\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/conflicts\"},\"defaultCommitMessage\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/commit-message\"},\"merge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"emergencyMerge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"workflowResult\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/workflow/\"},\"reviewMark\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/review-mark/{path}\",\"templated\":true},\"sourceBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/develop\"},\"targetBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/master\"},\"ciStatus\":{\"href\":\"/scm/api/v2/ci/jenkins-plugin/hello-shell/pullrequest/1\"},\"teamscaleFindings\":{\"href\":\"/scm/api/v2/teamscale/pull-request/jenkins-plugin/hello-shell/1/findings\"}},\"labels\":[\"backend\",\"needs-review\"]}", "headers": { "Content-Type": "application/vnd.scmm-pullRequest+json;v=2" } diff --git a/src/test/resources/mappings/pullRequests.json b/src/test/resources/mappings/pullRequests.json index 3c5971a..6ce8857 100644 --- a/src/test/resources/mappings/pullRequests.json +++ b/src/test/resources/mappings/pullRequests.json @@ -10,7 +10,7 @@ }, "response": { "status": 200, - "body": "{\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell\"},\"create\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell\"}},\"_embedded\":{\"pullRequests\":[{\"id\":\"1\",\"author\":{\"id\":\"sdorra\",\"displayName\":\"Sebastian Sdorra\",\"mail\":\"sebastian.sdorra@cloudogu.com\"},\"source\":\"develop\",\"target\":\"master\",\"title\":\"develop => master\",\"description\":null,\"creationDate\":\"2020-06-24T05:24:24.340Z\",\"lastModified\":null,\"status\":\"OPEN\",\"reviewer\":[],\"tasks\":{\"todo\":0,\"done\":0},\"sourceRevision\":null,\"targetRevision\":null,\"markedAsReviewed\":[],\"emergencyMerged\":false,\"ignoredMergeObstacles\":null,\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"comments\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/comments/\"},\"events\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/events\"},\"approve\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/approve\"},\"subscription\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/subscription\"},\"update\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"reject\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/reject\"},\"mergeCheck\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/merge-check\"},\"mergeConflicts\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/conflicts\"},\"defaultCommitMessage\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/commit-message\"},\"merge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"emergencyMerge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"workflowResult\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/workflow/\"},\"reviewMark\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/review-mark/{path}\",\"templated\":true},\"sourceBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/develop\"},\"targetBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/master\"},\"ciStatus\":{\"href\":\"/scm/api/v2/ci/jenkins-plugin/hello-shell/pullrequest/1\"},\"teamscaleFindings\":{\"href\":\"/scm/api/v2/teamscale/pull-request/jenkins-plugin/hello-shell/1/findings\"}}}]}}", + "body": "{\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell\"},\"create\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell\"}},\"_embedded\":{\"pullRequests\":[{\"id\":\"1\",\"author\":{\"id\":\"sdorra\",\"displayName\":\"Sebastian Sdorra\",\"mail\":\"sebastian.sdorra@cloudogu.com\"},\"source\":\"develop\",\"target\":\"master\",\"title\":\"develop => master\",\"description\":null,\"creationDate\":\"2020-06-24T05:24:24.340Z\",\"lastModified\":null,\"status\":\"OPEN\",\"reviewer\":[],\"tasks\":{\"todo\":0,\"done\":0},\"sourceRevision\":null,\"targetRevision\":null,\"markedAsReviewed\":[],\"emergencyMerged\":false,\"ignoredMergeObstacles\":null,\"_links\":{\"self\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"comments\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/comments/\"},\"events\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/events\"},\"approve\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/approve\"},\"subscription\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/subscription\"},\"update\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1\"},\"reject\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/reject\"},\"mergeCheck\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/merge-check\"},\"mergeConflicts\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/conflicts\"},\"defaultCommitMessage\":{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/commit-message\"},\"merge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"emergencyMerge\":[{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=MERGE_COMMIT\",\"name\":\"MERGE_COMMIT\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=FAST_FORWARD_IF_POSSIBLE\",\"name\":\"FAST_FORWARD_IF_POSSIBLE\"},{\"href\":\"/scm/api/v2/merge/jenkins-plugin/hello-shell/1/emergency?strategy=SQUASH\",\"name\":\"SQUASH\"}],\"workflowResult\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/workflow/\"},\"reviewMark\":{\"href\":\"/scm/api/v2/pull-requests/jenkins-plugin/hello-shell/1/review-mark/{path}\",\"templated\":true},\"sourceBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/develop\"},\"targetBranch\":{\"href\":\"/scm/api/v2/repositories/jenkins-plugin/hello-shell/branches/master\"},\"ciStatus\":{\"href\":\"/scm/api/v2/ci/jenkins-plugin/hello-shell/pullrequest/1\"},\"teamscaleFindings\":{\"href\":\"/scm/api/v2/teamscale/pull-request/jenkins-plugin/hello-shell/1/findings\"}},\"labels\":[\"backend\",\"needs-review\"]}]}}", "headers": { "Content-Type": "application/vnd.scmm-pullRequest+json;v=2" } From 669381f7de6cf820d5d71fd58812dcce5bb2e2d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 6 Jul 2026 18:26:40 +0200 Subject: [PATCH 2/4] Fix error by spotbugs --- .../scm/env/PullRequestLabelsEnvContributor.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java index 655627f..5a98269 100644 --- a/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java +++ b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java @@ -42,21 +42,13 @@ public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnul } private ScmManagerPullRequestHead getPullRequestHead(Run run) { - Object parent = run.getParent(); - if (!(parent instanceof Job)) { - return null; - } - - BranchJobProperty branchJobProperty = ((Job) parent).getProperty(BranchJobProperty.class); + Job parent = run.getParent(); + BranchJobProperty branchJobProperty = parent.getProperty(BranchJobProperty.class); if (branchJobProperty == null) { return null; } Branch branch = branchJobProperty.getBranch(); - if (branch == null) { - return null; - } - SCMHead head = branch.getHead(); if (head instanceof ScmManagerPullRequestHead) { return (ScmManagerPullRequestHead) head; From 50ecde5a75f769dba5327a71e4580e586249709b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 6 Jul 2026 18:27:45 +0200 Subject: [PATCH 3/4] Fix spotless error --- .../scmmanager/scm/DescriptorEndpointSecurityTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java b/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java index 3ce2111..7c746c3 100644 --- a/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java +++ b/src/test/java/com/cloudogu/scmmanager/scm/DescriptorEndpointSecurityTest.java @@ -22,8 +22,7 @@ class DescriptorEndpointSecurityTest { private static final String LOW_PRIVILEGE_USER = "lowpriv"; private static final String NAVIGATOR_DESCRIPTOR = "descriptorByName/com.cloudogu.scmmanager.scm.ScmManagerNavigator"; - private static final String SOURCE_DESCRIPTOR = - "descriptorByName/com.cloudogu.scmmanager.scm.ScmManagerSource"; + private static final String SOURCE_DESCRIPTOR = "descriptorByName/com.cloudogu.scmmanager.scm.ScmManagerSource"; private JenkinsRule j; From 7d1adc93372c90e61e1a8ad7fd7f3be64cb1e9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Pfeuffer?= Date: Mon, 6 Jul 2026 18:39:34 +0200 Subject: [PATCH 4/4] Fetch labels for build time --- .../env/PullRequestLabelsEnvContributor.java | 47 ++++++++++- .../PullRequestLabelsEnvContributorTest.java | 81 ++++++++++++++++--- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java index 5a98269..68a8fa3 100644 --- a/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java +++ b/src/main/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributor.java @@ -1,15 +1,23 @@ package com.cloudogu.scmmanager.scm.env; +import com.cloudogu.scmmanager.scm.ScmManagerApiData; +import com.cloudogu.scmmanager.scm.api.PullRequest; +import com.cloudogu.scmmanager.scm.api.Repository; +import com.cloudogu.scmmanager.scm.api.ScmManagerApi; +import com.cloudogu.scmmanager.scm.api.ScmManagerApiFactory; import com.cloudogu.scmmanager.scm.api.ScmManagerPullRequestHead; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import hudson.EnvVars; import hudson.Extension; import hudson.model.EnvironmentContributor; +import hudson.model.ItemGroup; import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import javax.annotation.Nonnull; import jenkins.branch.Branch; import jenkins.scm.api.SCMHead; @@ -25,6 +33,16 @@ public class PullRequestLabelsEnvContributor extends EnvironmentContributor { private static final String ENV_LABEL_PREFIX = "SCMM_PR_LABEL_"; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private final ScmManagerApiFactory apiFactory; + + public PullRequestLabelsEnvContributor() { + this(new ScmManagerApiFactory()); + } + + PullRequestLabelsEnvContributor(ScmManagerApiFactory apiFactory) { + this.apiFactory = apiFactory; + } + @Override public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnull TaskListener listener) { ScmManagerPullRequestHead pullRequestHead = getPullRequestHead(run); @@ -32,7 +50,7 @@ public void buildEnvironmentFor(@Nonnull Run run, @Nonnull EnvVars envs, @Nonnul return; } - List labels = pullRequestHead.getLabels(); + List labels = fetchLabels(run, pullRequestHead); envs.put(ENV_LABELS, toJson(labels)); envs.put(ENV_LABEL_COUNT, String.valueOf(labels.size())); @@ -57,6 +75,33 @@ private ScmManagerPullRequestHead getPullRequestHead(Run run) { return null; } + private List fetchLabels(Run run, ScmManagerPullRequestHead pullRequestHead) { + Job job = run.getParent(); + ScmManagerApiData apiData = job.getAction(ScmManagerApiData.class); + if (apiData == null) { + return pullRequestHead.getLabels(); + } + + try { + ScmManagerApi client = apiFactory.create( + (ItemGroup) job.getParent(), apiData.getServerUrl(), apiData.getCredentialsId()); + Repository repository = client.getRepository(apiData.getNamespace(), apiData.getName()) + .get(); + CompletableFuture pullRequest = client.getPullRequest(repository, pullRequestHead.getId()); + if (pullRequest == null) { + return pullRequestHead.getLabels(); + } + return pullRequest.get().getLabels(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("could not fetch labels for pull request {}. Error: {}", pullRequestHead.getId(), e.getMessage()); + } catch (ExecutionException e) { + log.error("could not fetch labels for pull request {}. Error: {}", pullRequestHead.getId(), e.getMessage()); + } + + return pullRequestHead.getLabels(); + } + private String toJson(List labels) { try { return OBJECT_MAPPER.writeValueAsString(labels); diff --git a/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java b/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java index 9f5e5d1..1fde3f2 100644 --- a/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java +++ b/src/test/java/com/cloudogu/scmmanager/scm/env/PullRequestLabelsEnvContributorTest.java @@ -1,5 +1,6 @@ package com.cloudogu.scmmanager.scm.env; +import static java.util.concurrent.CompletableFuture.completedFuture; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -7,7 +8,12 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import com.cloudogu.scmmanager.scm.ScmManagerApiData; import com.cloudogu.scmmanager.scm.api.CloneInformation; +import com.cloudogu.scmmanager.scm.api.PullRequest; +import com.cloudogu.scmmanager.scm.api.Repository; +import com.cloudogu.scmmanager.scm.api.ScmManagerApi; +import com.cloudogu.scmmanager.scm.api.ScmManagerApiFactory; import com.cloudogu.scmmanager.scm.api.ScmManagerHead; import com.cloudogu.scmmanager.scm.api.ScmManagerPullRequestHead; import hudson.EnvVars; @@ -15,6 +21,7 @@ import hudson.scm.SCM; import java.util.List; import jenkins.branch.Branch; +import jenkins.model.Jenkins; import jenkins.scm.api.SCMHead; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -28,8 +35,21 @@ @ExtendWith(MockitoExtension.class) class PullRequestLabelsEnvContributorTest { + private static final String SERVER_URL = "http://localhost:8080"; + private static final String CREDENTIALS_ID = "CREDS_ID"; + private static final String NAMESPACE = "NAMESPACE"; + private static final String NAME = "NAME"; private static final CloneInformation CLONE_INFORMATION = new CloneInformation("git", "http://example.com/repo"); + @Mock + private ScmManagerApiFactory apiFactory; + + @Mock + private ScmManagerApi api; + + @Mock + private Jenkins owner; + @Mock private WorkflowJob job; @@ -49,21 +69,34 @@ class PullRequestLabelsEnvContributorTest { @BeforeEach void beforeEach() { + lenient().when(job.getParent()).thenReturn(owner); lenient().when(run.getParent()).thenReturn(job); - envContributor = new PullRequestLabelsEnvContributor(); + envContributor = new PullRequestLabelsEnvContributor(apiFactory); } @Test - void shouldInjectPullRequestLabels() { - useBranch(new ScmManagerPullRequestHead( - CLONE_INFORMATION, - "42", - new ScmManagerHead(CLONE_INFORMATION, "main"), - new ScmManagerHead(CLONE_INFORMATION, "feature"), - List.of("backend", "needs review", "comma,label"))); + void shouldInjectCurrentPullRequestLabelsFromApi() { + useBranch(pullRequestHead(List.of("old-label"))); + Repository repository = setupApiCall(pullRequestWithLabels("backend", "needs review", "comma,label")); envContributor.buildEnvironmentFor(run, envVars, listener); + verify(api).getPullRequest(repository, "42"); + verify(envVars).put("SCMM_PR_LABELS", "[\"backend\",\"needs review\",\"comma,label\"]"); + verify(envVars).put("SCMM_PR_LABEL_COUNT", "3"); + verify(envVars).put("SCMM_PR_LABEL_1", "backend"); + verify(envVars).put("SCMM_PR_LABEL_2", "needs review"); + verify(envVars).put("SCMM_PR_LABEL_3", "comma,label"); + verifyNoMoreInteractions(envVars); + } + + @Test + void shouldFallBackToPullRequestHeadLabelsWhenApiDataIsMissing() { + useBranch(pullRequestHead(List.of("backend", "needs review", "comma,label"))); + + envContributor.buildEnvironmentFor(run, envVars, listener); + + verifyNoInteractions(apiFactory); verify(envVars).put("SCMM_PR_LABELS", "[\"backend\",\"needs review\",\"comma,label\"]"); verify(envVars).put("SCMM_PR_LABEL_COUNT", "3"); verify(envVars).put("SCMM_PR_LABEL_1", "backend"); @@ -74,14 +107,11 @@ void shouldInjectPullRequestLabels() { @Test void shouldInjectEmptyLabelListForPullRequestWithoutLabels() { - useBranch(new ScmManagerPullRequestHead( - CLONE_INFORMATION, - "42", - new ScmManagerHead(CLONE_INFORMATION, "main"), - new ScmManagerHead(CLONE_INFORMATION, "feature"))); + useBranch(pullRequestHead(List.of())); envContributor.buildEnvironmentFor(run, envVars, listener); + verifyNoInteractions(apiFactory); verify(envVars).put("SCMM_PR_LABELS", "[]"); verify(envVars).put("SCMM_PR_LABEL_COUNT", "0"); verifyNoMoreInteractions(envVars); @@ -103,6 +133,31 @@ void shouldNotInjectLabelsWithoutBranchJobProperty() { verifyNoInteractions(envVars); } + private Repository setupApiCall(PullRequest pullRequest) { + Repository repository = new Repository(NAMESPACE, NAME, "git"); + when(job.getAction(ScmManagerApiData.class)) + .thenReturn(new ScmManagerApiData(SERVER_URL, CREDENTIALS_ID, NAMESPACE, NAME)); + when(apiFactory.create(owner, SERVER_URL, CREDENTIALS_ID)).thenReturn(api); + when(api.getRepository(NAMESPACE, NAME)).thenReturn(completedFuture(repository)); + when(api.getPullRequest(repository, "42")).thenReturn(completedFuture(pullRequest)); + return repository; + } + + private ScmManagerPullRequestHead pullRequestHead(List labels) { + return new ScmManagerPullRequestHead( + CLONE_INFORMATION, + "42", + new ScmManagerHead(CLONE_INFORMATION, "main"), + new ScmManagerHead(CLONE_INFORMATION, "feature"), + labels); + } + + private PullRequest pullRequestWithLabels(String... labels) { + PullRequest pullRequest = mock(PullRequest.class); + when(pullRequest.getLabels()).thenReturn(List.of(labels)); + return pullRequest; + } + private void useBranch(SCMHead head) { when(job.getProperty(BranchJobProperty.class)).thenReturn(branchJobProperty); when(branchJobProperty.getBranch()).thenReturn(new Branch("source-id", head, mock(SCM.class), List.of()));