Skip to content

Commit

Permalink
Send replaced branch for pr (#53)
Browse files Browse the repository at this point in the history
This fixes an issue with CI status in SCM-Manager for
Jenkins multibranch pipelines.
If such a pipeline fails for a branch for whatever
reason (eg. due to a flappy test), this result is stored
in SCM-Manager for the revision. If later on a pull
request is created for this branch, this failed build
is considered as a failed status for the pull request,
even though the pull request build itself might be
successful.

To fix this, this plugin computes the name of the
branch build for a pull request build and sends this
name as 'replaces' within the status request for
SCM-Manager. This 'replaces' field will be evaluated
with the CI plugin version 2.6.0 or later in SCM-Manager.
  • Loading branch information
pfeuffer authored May 16, 2023
1 parent e3023ec commit 5f77ff1
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 28 deletions.
21 changes: 20 additions & 1 deletion src/main/java/com/cloudogu/scmmanager/ScmV2Notifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ public class ScmV2Notifier implements Notifier {
private final NamespaceAndName namespaceAndName;
private final HttpAuthentication httpAuthentication;
private final boolean pullRequest;
private final String sourceBranch;

private AsyncHttpClient client;

private Consumer<Response> completionListener = response -> {
};

ScmV2Notifier(URL instance, NamespaceAndName namespaceAndName, HttpAuthentication httpAuthentication, boolean pullRequest) {
ScmV2Notifier(URL instance, NamespaceAndName namespaceAndName, HttpAuthentication httpAuthentication, boolean pullRequest, String sourceBranch) {
this.instance = instance;
this.namespaceAndName = namespaceAndName;
this.httpAuthentication = httpAuthentication;
this.pullRequest = pullRequest;
this.sourceBranch = sourceBranch;
}

@VisibleForTesting
Expand All @@ -55,6 +57,10 @@ HttpAuthentication getHttpAuthentication() {
return httpAuthentication;
}

public String getSourceBranch() {
return sourceBranch;
}

@VisibleForTesting
void setClient(AsyncHttpClient client) {
this.client = client;
Expand Down Expand Up @@ -104,9 +110,22 @@ public Object onCompleted(Response response) {

private byte[] createRequestBody(BuildStatus buildStatus) {
JSONObject jsonObject = JSONObject.fromObject(buildStatus);
if (pullRequest && sourceBranch != null) {
setReplacedBuild(buildStatus, jsonObject);
}
return jsonObject.toString().getBytes(StandardCharsets.UTF_8);
}

private void setReplacedBuild(BuildStatus buildStatus, JSONObject jsonObject) {
try {
String[] path = buildStatus.getName().split("/");
path[path.length - 1] = URLEncoder.encode(sourceBranch, "UTF-8");
jsonObject.put("replaces", String.join("/", path));
} catch (Exception e) {
LOG.warn("Failed to compute replaced branch '{}' with path '{}'", sourceBranch, buildStatus.getName(), e);
}
}

private String createUrl(String revision, BuildStatus buildStatus) throws UnsupportedEncodingException {
return String.format(getUrl(),
instance.toExternalForm(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ private ScmV2Notifier createNotifier(Run<?, ?> run, JobInformation information,
NamespaceAndName namespaceAndName = createNamespaceAndName(matcher);

HttpAuthentication httpAuthentication = authenticationFactory.createHttp(run, information.getCredentialsId());
return new ScmV2Notifier(instance, namespaceAndName, httpAuthentication, information.isPullRequest());
return new ScmV2Notifier(instance, namespaceAndName, httpAuthentication, information.isPullRequest(), information.getSourceBranch());
}

private NamespaceAndName createNamespaceAndName(Matcher matcher) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/cloudogu/scmmanager/info/JobInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ public class JobInformation {
private final String revision;
private final String credentialsId;
private final boolean pullRequest;
private final String sourceBranch;

public JobInformation(String type, String url, String revision, String credentialsId, boolean pullRequest) {
this(type, url, revision, credentialsId, pullRequest, null);
}

public JobInformation(String type, String url, String revision, String credentialsId, boolean pullRequest, String sourceBranch) {
this.type = type;
this.url = url;
this.revision = revision;
this.credentialsId = credentialsId;
this.pullRequest = pullRequest;
this.sourceBranch = sourceBranch;
}

public String getType() {
Expand All @@ -35,4 +41,8 @@ public String getCredentialsId() {
public boolean isPullRequest() {
return pullRequest;
}

public String getSourceBranch() {
return sourceBranch;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ private Collection<JobInformation> resolve(BranchJobProperty branchJobProperty)
((ScmManagerPullRequestHead) scmHead).getCloneInformation().getUrl(),
((ScmManagerPullRequestHead) scmHead).getId(),
urc.getCredentialsId(),
true
true,
((ScmManagerPullRequestHead) scmHead).getSource().getName()
)).collect(toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,21 @@ public void testGetWithPort() throws MalformedURLException {
assertEquals("http://localhost:8080/scm", notifier.getInstance().toExternalForm());
}

@Test
public void testGetWithSourceBranch() throws MalformedURLException {
applyAuthentication();

JobInformation information = new JobInformation("sample", "https://scm.scm-manager.org/repo/ns/one", "pr-1", "one", true, "simple/branch");
ScmV2Notifier notifier = provider.get(run, information).get();

assertEquals("simple/branch", notifier.getSourceBranch());
}

private JobInformation createInformation(String s) {
return new JobInformation("sample", s, "abc", "one", false);
}

private void applyAuthentication() {
when(authenticationFactory.createHttp(run, "one")).thenReturn(AuthenticationFactory.NOOP_HTTP_AUTHENTICATION);
}

}
69 changes: 45 additions & 24 deletions src/test/java/com/cloudogu/scmmanager/ScmV2NotifierTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,54 @@ public class ScmV2NotifierTest {

@Test
public void testNotifyForChangesets() throws IOException, InterruptedException {
testNotify(false, "/scm/api/v2/ci/ns/one/changesets/abc/jenkins/hitchhiker%2Fheart-of-gold");
testNotify("/scm/api/v2/ci/ns/one/changesets/abc/jenkins/hitchhiker%2Fheart-of-gold", "hitchhiker/heart-of-gold", null);

verify(
putRequestedFor(urlMatching("/scm/api/v2/ci/ns/one/changesets/abc/jenkins/hitchhiker%2Fheart-of-gold"))
.withHeader("Authenticated", equalTo("yes; awesome"))
.withHeader("Content-Type", equalTo("application/vnd.scmm-cistatus+json;v=2"))
.withRequestBody(
matchingJsonPath("$.type", equalTo("jenkins"))
)
.withRequestBody(
matchingJsonPath("$.name", equalTo("hitchhiker/heart-of-gold"))
)
.withRequestBody(
matchingJsonPath("$.url", equalTo("https://hitchhiker.com"))
)
.withRequestBody(
matchingJsonPath("$.status", equalTo("SUCCESS"))
)
);
}

@Test
public void testNotifyForPullRequests() throws IOException, InterruptedException {
testNotify(true, "/scm/api/v2/ci/ns/one/pullrequest/abc/jenkins/hitchhiker%2Fheart-of-gold");
testNotify("/scm/api/v2/ci/ns/one/pullrequest/abc/jenkins/hitchhiker%2Fpr-1", "hitchhiker/heart-of-gold", "hitchhiker/pr-1");

verify(
putRequestedFor(urlMatching("/scm/api/v2/ci/ns/one/pullrequest/abc/jenkins/hitchhiker%2Fpr-1"))
.withHeader("Authenticated", equalTo("yes; awesome"))
.withHeader("Content-Type", equalTo("application/vnd.scmm-cistatus+json;v=2"))
.withRequestBody(
matchingJsonPath("$.type", equalTo("jenkins"))
)
.withRequestBody(
matchingJsonPath("$.name", equalTo("hitchhiker/pr-1"))
)
.withRequestBody(
matchingJsonPath("$.url", equalTo("https://hitchhiker.com"))
)
.withRequestBody(
matchingJsonPath("$.status", equalTo("SUCCESS"))
)
.withRequestBody(
matchingJsonPath("$.replaces", equalTo("hitchhiker/hitchhiker%2Fheart-of-gold"))
)
);
}

private void testNotify(boolean pullRequest, String notificationUrl) throws IOException, InterruptedException {
private void testNotify(String notificationUrl, String branch, String pullRequest) throws IOException, InterruptedException {
stubFor(
put(notificationUrl)
.willReturn(
Expand All @@ -44,15 +83,16 @@ private void testNotify(boolean pullRequest, String notificationUrl) throws IOEx
new ScmV2Notifier(
instanceURL,
namespaceAndName, req -> req.setHeader("Authenticated", "yes; awesome"),
pullRequest);
pullRequest != null,
pullRequest == null ? null : branch);

CountDownLatch cdl = new CountDownLatch(1);
try (AsyncHttpClient client = new AsyncHttpClient()) {
notifier.setClient(client);
notifier.setCompletionListener((response -> cdl.countDown()));

BuildStatus status = BuildStatus.success(
"hitchhiker/heart-of-gold",
pullRequest != null ? pullRequest : branch,
"hitchhiker >> heart-of-gold",
"https://hitchhiker.com"
);
Expand All @@ -61,25 +101,6 @@ private void testNotify(boolean pullRequest, String notificationUrl) throws IOEx

cdl.await(30, TimeUnit.SECONDS);
}

verify(
putRequestedFor(urlMatching(notificationUrl))
.withHeader("Authenticated", equalTo("yes; awesome"))
.withHeader("Content-Type", equalTo("application/vnd.scmm-cistatus+json;v=2"))
.withRequestBody(
matchingJsonPath("$.type", equalTo("jenkins"))
)
.withRequestBody(
matchingJsonPath("$.name", equalTo("hitchhiker/heart-of-gold"))
)
.withRequestBody(
matchingJsonPath("$.url", equalTo("https://hitchhiker.com"))
)
.withRequestBody(
matchingJsonPath("$.status", equalTo("SUCCESS"))
)
);

}

private URL createInstanceURL() throws MalformedURLException {
Expand Down

0 comments on commit 5f77ff1

Please sign in to comment.