Skip to content

Commit 68eea24

Browse files
croniknfalco79
authored andcommitted
[JENKINS-74970] Could not send notifications to Bitbucket Server when pull request is from fork (#998)
Fix how the API client for Bitbucket Server is built to send build notification status of scm head that comes from a fork. In this case user does not have grants on commits in the forked repository. Build notification status must be always placed in the target repository.
1 parent 1492cd1 commit 68eea24

File tree

2 files changed

+76
-21
lines changed

2 files changed

+76
-21
lines changed

src/main/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotifications.java

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -218,29 +218,36 @@ private static void sendNotifications(BitbucketSCMSource source, Run<?, ?> build
218218
.filters().stream()
219219
.anyMatch(ExcludeOriginPRBranchesSCMHeadFilter.class::isInstance);
220220

221-
String key;
222-
String refName;
223-
BitbucketApi bitbucket;
221+
final String key;
222+
final String refName;
223+
final BitbucketApi bitbucket;
224224
if (rev instanceof PullRequestSCMRevision) {
225225
listener.getLogger().println("[Bitbucket] Notifying pull request build result");
226226
PullRequestSCMHead head = (PullRequestSCMHead) rev.getHead();
227227
key = getBuildKey(build, head.getOriginName(), shareBuildKeyBetweenBranchAndPR);
228-
/*
229-
* Poor documentation for bitbucket cloud at:
230-
* https://community.atlassian.com/t5/Bitbucket-questions/Re-Builds-not-appearing-in-pull-requests/qaq-p/1805991/comment-id/65864#M65864
231-
* that means refName null or valued with only head.getBranchName()
232-
*
233-
* For Bitbucket Server, refName should be "refs/heads/" + the name
234-
* of the source branch of the pull request, and the build status
235-
* should be posted to the repository that contains that branch.
236-
* If refName is null, then Bitbucket Server does not show the
237-
* build status in the list of pull requests, but still shows it
238-
* on the web page of the individual pull request.
239-
*/
240-
bitbucket = source.buildBitbucketClient(head);
241-
if (BitbucketApiUtils.isCloud(bitbucket)) {
228+
if (BitbucketApiUtils.isCloud(source.getServerUrl())) {
229+
/*
230+
* Poor documentation for bitbucket cloud at:
231+
* https://community.atlassian.com/t5/Bitbucket-questions/Re-Builds-not-appearing-in-pull-requests/qaq-p/1805991/comment-id/65864#M65864
232+
* that means refName null or valued with only head.getBranchName()
233+
*/
242234
refName = null;
235+
bitbucket = source.buildBitbucketClient(head);
243236
} else {
237+
/*
238+
* Head may point to a forked repository that the credentials do
239+
* not have access to, resulting in a 401 error. So we need to
240+
* push build status to the target repository
241+
*/
242+
bitbucket = source.buildBitbucketClient();
243+
/*
244+
* For Bitbucket Server, refName should be "refs/heads/" + the
245+
* name of the source branch of the pull request, and the build
246+
* status should be posted to the repository that contains that
247+
* branch. If refName is null, then Bitbucket Server does not
248+
* show the build status in the list of pull requests, but still
249+
* shows it on the web page of the individual pull request.
250+
*/
244251
refName = "refs/heads/" + head.getBranchName();
245252
}
246253
} else {

src/test/java/com/cloudbees/jenkins/plugins/bitbucket/impl/notifier/BitbucketBuildStatusNotificationsJUnit5Test.java

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketMockApiFactory;
2828
import com.cloudbees.jenkins.plugins.bitbucket.BitbucketSCMSource;
2929
import com.cloudbees.jenkins.plugins.bitbucket.BranchSCMHead;
30+
import com.cloudbees.jenkins.plugins.bitbucket.ForkPullRequestDiscoveryTrait;
31+
import com.cloudbees.jenkins.plugins.bitbucket.ForkPullRequestDiscoveryTrait.TrustEveryone;
32+
import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMHead;
33+
import com.cloudbees.jenkins.plugins.bitbucket.PullRequestSCMRevision;
3034
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketApi;
3135
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus;
3236
import com.cloudbees.jenkins.plugins.bitbucket.api.BitbucketBuildStatus.Status;
@@ -54,16 +58,20 @@
5458
import jenkins.model.JenkinsLocationConfiguration;
5559
import jenkins.plugins.git.AbstractGitSCMSource.SCMRevisionImpl;
5660
import jenkins.scm.api.SCMHead;
61+
import jenkins.scm.api.SCMHeadOrigin.Fork;
5762
import jenkins.scm.api.SCMRevision;
5863
import jenkins.scm.api.SCMRevisionAction;
64+
import jenkins.scm.api.mixin.ChangeRequestCheckoutStrategy;
5965
import jenkins.scm.api.trait.SCMSourceTrait;
66+
import org.apache.commons.codec.digest.DigestUtils;
6067
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
6168
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
6269
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
6370
import org.junit.jupiter.api.Test;
6471
import org.junit.jupiter.params.ParameterizedTest;
6572
import org.junit.jupiter.params.provider.Arguments;
6673
import org.junit.jupiter.params.provider.MethodSource;
74+
import org.jvnet.hudson.test.Issue;
6775
import org.jvnet.hudson.test.JenkinsRule;
6876
import org.jvnet.hudson.test.junit.jupiter.WithJenkins;
6977
import org.mockito.ArgumentCaptor;
@@ -107,8 +115,9 @@ void test_status_notification_for_given_build_result(UnaryOperator<BitbucketBuil
107115
assertThat(captor.getValue().getState()).isEqualTo(expectedStatus.name());
108116
}
109117

118+
@Issue("JENKINS-72780")
110119
@Test
111-
void test_status_notification_for_given_build_result(@NonNull JenkinsRule r) throws Exception {
120+
void test_status_notification_name_when_UseReadableNotificationIds_is_true(@NonNull JenkinsRule r) throws Exception {
112121
StreamBuildListener taskListener = new StreamBuildListener(System.out, StandardCharsets.UTF_8);
113122
URL jenkinsURL = new URL("http://example.com:" + r.getURL().getPort() + r.contextPath + "/");
114123
JenkinsLocationConfiguration.get().setUrl(jenkinsURL.toString());
@@ -133,6 +142,41 @@ void test_status_notification_for_given_build_result(@NonNull JenkinsRule r) thr
133142
assertThat(captor.getValue().getKey()).isEqualTo("P/BRANCH-JOB");
134143
}
135144

145+
@Issue("JENKINS-74970")
146+
@Test
147+
void test_status_notification_on_fork(@NonNull JenkinsRule r) throws Exception {
148+
StreamBuildListener taskListener = new StreamBuildListener(System.out, StandardCharsets.UTF_8);
149+
URL jenkinsURL = new URL("http://example.com:" + r.getURL().getPort() + r.contextPath + "/");
150+
JenkinsLocationConfiguration.get().setUrl(jenkinsURL.toString());
151+
152+
String serverURL = "https://acme.bitbucket.org";
153+
154+
ForkPullRequestDiscoveryTrait trait = new ForkPullRequestDiscoveryTrait(2, new TrustEveryone());
155+
BranchSCMHead targetHead = new BranchSCMHead("master");
156+
PullRequestSCMHead scmHead = new PullRequestSCMHead("name", "repoOwner", "repository1", "feature1", "1", "title", targetHead, new Fork("repository1"), ChangeRequestCheckoutStrategy.HEAD);
157+
SCMRevisionImpl prRevision = new SCMRevisionImpl(scmHead, "cff417db");
158+
SCMRevisionImpl targetRevision = new SCMRevisionImpl(targetHead, "c341232342311");
159+
SCMRevision scmRevision = new PullRequestSCMRevision(scmHead, targetRevision, prRevision);
160+
WorkflowRun build = prepareBuildForNotification(r, trait, serverURL, scmRevision);
161+
doReturn(Result.SUCCESS).when(build).getResult();
162+
163+
FilePath workspace = r.jenkins.getWorkspaceFor(build.getParent());
164+
165+
BitbucketApi apiClient = mock(BitbucketServerAPIClient.class);
166+
BitbucketMockApiFactory.add(serverURL, apiClient);
167+
168+
JobCheckoutListener listener = new JobCheckoutListener();
169+
listener.onCheckout(build, null, workspace, taskListener, null, SCMRevisionState.NONE);
170+
171+
ArgumentCaptor<BitbucketBuildStatus> captor = ArgumentCaptor.forClass(BitbucketBuildStatus.class);
172+
verify(apiClient).postBuildStatus(captor.capture());
173+
assertThat(captor.getValue()).satisfies(status -> {
174+
assertThat(status.getHash()).isEqualTo(prRevision.getHash());
175+
assertThat(status.getKey()).isEqualTo(DigestUtils.md5Hex("p/branch-job"));
176+
assertThat(status.getRefname()).isEqualTo("refs/heads/" + scmHead.getBranchName());
177+
});
178+
}
179+
136180
private static Stream<Arguments> buildStatusProvider() {
137181
UnaryOperator<BitbucketBuildStatusNotificationsTrait> notifyAbortAsCancelled = t -> {
138182
t.setSendStoppedNotificationForAbortBuild(true);
@@ -156,6 +200,12 @@ private static Stream<Arguments> buildStatusProvider() {
156200
}
157201

158202
private WorkflowRun prepareBuildForNotification(@NonNull JenkinsRule r, @NonNull SCMSourceTrait trait, @NonNull String serverURL) throws Exception {
203+
SCMHead scmHead = new BranchSCMHead("master");
204+
SCMRevision scmRevision = new SCMRevisionImpl(scmHead, "c341232342311");
205+
return prepareBuildForNotification(r, trait, serverURL, scmRevision);
206+
}
207+
208+
private WorkflowRun prepareBuildForNotification(@NonNull JenkinsRule r, @NonNull SCMSourceTrait trait, @NonNull String serverURL, SCMRevision scmRevision) throws Exception {
159209
BitbucketSCMSource scmSource = new BitbucketSCMSource("repoOwner", "repository");
160210
scmSource.setServerUrl(serverURL);
161211
scmSource.setTraits(List.of(trait));
@@ -166,8 +216,6 @@ private WorkflowRun prepareBuildForNotification(@NonNull JenkinsRule r, @NonNull
166216

167217
WorkflowJob job = new WorkflowJob(project, "branch-job");
168218

169-
SCMHead scmHead = new BranchSCMHead("master");
170-
SCMRevision scmRevision = new SCMRevisionImpl(scmHead, "c341232342311");
171219
SCM scm = mock(SCM.class);
172220

173221
WorkflowRun build = mock(WorkflowRun.class);
@@ -178,7 +226,7 @@ private WorkflowRun prepareBuildForNotification(@NonNull JenkinsRule r, @NonNull
178226
BranchProjectFactory<WorkflowJob, WorkflowRun> projectFactory = mock(BranchProjectFactory.class);
179227
when(projectFactory.isProject(job)).thenReturn(true);
180228
when(projectFactory.asProject(job)).thenReturn(job);
181-
Branch branch = new Branch(scmSource.getId(), scmHead, scm, Collections.emptyList());
229+
Branch branch = new Branch(scmSource.getId(), scmRevision.getHead(), scm, Collections.emptyList());
182230
when(projectFactory.getBranch(job)).thenReturn(branch);
183231
project.setProjectFactory(projectFactory);
184232

0 commit comments

Comments
 (0)