From f4ba485014111b6da3a47837be9918725b7e6bd9 Mon Sep 17 00:00:00 2001 From: "Boillat, Marcus" Date: Tue, 3 Oct 2023 20:54:03 +0200 Subject: [PATCH 1/4] Issue #290: Implement compatibility with new Bitbucket rest api and fix missing "parent" property in JSON for required builds --- .../stashNotifier/BuildStatusUriFactory.java | 11 ++- .../DefaultApacheHttpNotifier.java | 1 + .../stashNotifier/NotificationResult.java | 2 +- .../plugins/stashNotifier/StashNotifier.java | 93 ++++++++----------- .../stashNotifier/StashNotifier/config.jelly | 10 +- .../BuildStatusUriFactoryTest.java | 8 +- .../DefaultApacheHttpNotifierTest.java | 2 +- .../stashNotifier/StashNotifierTest.java | 30 ++++++ 8 files changed, 94 insertions(+), 63 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactory.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactory.java index b6c3adf1..11502268 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactory.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactory.java @@ -8,9 +8,16 @@ public class BuildStatusUriFactory { private BuildStatusUriFactory() { } - public static URI create(String baseUri, String commit) { + public static URI create(String baseUri, String projectKey, String slug, String commit) { String tidyBase = StringUtils.removeEnd(baseUri, "/"); - String uri = String.join("/", tidyBase, "rest/build-status/1.0/commits", commit); + + String uri; + if(projectKey == null || slug == null || projectKey.isEmpty() || slug.isEmpty()) { + uri = String.join("/", tidyBase, "rest/build-status/1.0/commits", commit); + } + else { + uri = String.join("/", tidyBase, "rest/api/latest/projects", projectKey, "repos", slug, "commits", commit, "builds"); + } return URI.create(uri); } } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java index 74e0bc69..ebf635a2 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java @@ -60,6 +60,7 @@ class DefaultApacheHttpNotifier implements HttpNotifier { try (CloseableHttpClient client = getHttpClient(logger, uri, settings.isIgnoreUnverifiedSSL())) { HttpPost req = createRequest(uri, payload, settings.getCredentials(), context); HttpResponse res = client.execute(req); + if (res.getStatusLine().getStatusCode() != 204) { return NotificationResult.newFailure(EntityUtils.toString(res.getEntity())); } else { diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationResult.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationResult.java index 410f2a91..68e5ced4 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationResult.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationResult.java @@ -60,7 +60,7 @@ public static NotificationResult newFailure(String message) { * @param initSuccess success flag * @param initMessage message in case notification was not successful */ - protected NotificationResult( + NotificationResult( final boolean initSuccess, final String initMessage) { diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java index d91c3a9f..fc2bc489 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java @@ -153,12 +153,22 @@ public class StashNotifier extends Notifier implements SimpleBuildStep { private boolean includeBuildNumberInKey; /** - * specify project key manually + * specify Bitbucket project key + */ + private String bbProjectKey; + + /** + * specify Bitbucket repository slug + */ + private String repositorySlug; + + /** + * specify Jenkins project key manually */ private String projectKey; /** - * append parent project key to key formation + * append parent Jenkins project key to key formation */ private boolean prependParentProjectKey; @@ -172,7 +182,7 @@ public class StashNotifier extends Notifier implements SimpleBuildStep { */ private boolean considerUnstableAsSuccess; - private JenkinsLocationConfiguration globalConfig; + private final JenkinsLocationConfiguration globalConfig; /** * gives us the desired {@link HttpNotifier}. Transient because @@ -190,6 +200,8 @@ public BuildStepMonitor getRequiredMonitorService() { StashNotifier( String stashServerBaseUrl, String credentialsId, + String bbProjectKey, + String repositorySlug, boolean ignoreUnverifiedSSLPeer, String commitSha1, String buildStatus, @@ -205,6 +217,8 @@ public BuildStepMonitor getRequiredMonitorService() { this.globalConfig = globalConfig; setStashServerBaseUrl(stashServerBaseUrl); setCredentialsId(credentialsId); + setBbProjectKey(bbProjectKey); + setRepositorySlug(repositorySlug); setIgnoreUnverifiedSSLPeer(ignoreUnverifiedSSLPeer); setCommitSha1(commitSha1); setBuildStatus(buildStatus); @@ -304,6 +318,15 @@ public void setIncludeBuildNumberInKey(boolean includeBuildNumberInKey) { this.includeBuildNumberInKey = includeBuildNumberInKey; } + public String getBbProjectKey() { return bbProjectKey; } + + @DataBoundSetter + public void setBbProjectKey(String bbProjectKey) { this.bbProjectKey = bbProjectKey; } + + public String getRepositorySlug() { return repositorySlug; } + + @DataBoundSetter + public void setRepositorySlug(String repositorySlug) { this.repositorySlug = repositorySlug; } public String getProjectKey() { return projectKey; } @@ -452,6 +475,7 @@ private boolean processJenkinsEvent( try { NotificationResult result = notifyStash(logger, run, commitSha1, listener, state); + if (result.indicatesSuccess) { logger.println("Notified Bitbucket for commit with id " + commitSha1); } else { @@ -851,14 +875,15 @@ protected NotificationResult notifyStash( String stashURL = expandStashURL(run, listener); - logger.println("Notifying Bitbucket at \"" + stashURL + "\""); - Credentials usernamePasswordCredentials = getCredentials(UsernamePasswordCredentials.class, run.getParent()); Credentials stringCredentials = getCredentials(StringCredentials.class, run.getParent()); - URI uri = BuildStatusUriFactory.create(stashURL, commitSha1); + URI uri = BuildStatusUriFactory.create(stashURL, bbProjectKey, repositorySlug, commitSha1); + + logger.println("Sending notification to \"" + uri + "?key=" + abbreviate(getBuildKey(run, listener), MAX_FIELD_LENGTH) + "\""); + NotificationSettings settings = new NotificationSettings( ignoreUnverifiedSSLPeer || getDescriptor().isIgnoreUnverifiedSsl(), stringCredentials != null ? stringCredentials : usernamePasswordCredentials @@ -923,48 +948,6 @@ protected StashBuildState getPushedBuildStatus(StashBuildState currentBuildStatu } } - /** - * Returns the HTTP POST request ready to be sent to the Bitbucket build API for - * the given run and change set. - * - * @see DefaultApacheHttpNotifier#createRequest(URI, JSONObject, Credentials, NotificationContext) - * @deprecated in favor of method overload - * @param stashBuildNotificationEntity a entity containing the parameters for Bitbucket - * @param commitSha1 the SHA1 of the commit that was built - * @param url Bitbucket URL - * @return the HTTP POST request to the Bitbucket build API - */ - protected HttpPost createRequest( - final HttpEntity stashBuildNotificationEntity, - final Item project, - final String commitSha1, - final String url) throws AuthenticationException { - - HttpPost req = new HttpPost( - url - + "/rest/build-status/1.0/commits/" - + commitSha1); - - // If we have a credential defined then we need to determine if it - // is a basic auth - UsernamePasswordCredentials usernamePasswordCredentials - = getCredentials(UsernamePasswordCredentials.class, project); - - if (usernamePasswordCredentials != null) { - req.addHeader(new BasicScheme().authenticate( - new org.apache.http.auth.UsernamePasswordCredentials( - usernamePasswordCredentials.getUsername(), - usernamePasswordCredentials.getPassword().getPlainText()), - req, - null)); - } - - req.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - req.setEntity(stashBuildNotificationEntity); - - return req; - } - private String expandStashURL(Run run, final TaskListener listener) { String url = stashServerBaseUrl; DescriptorImpl descriptor = getDescriptor(); @@ -1018,12 +1001,18 @@ private JSONObject createNotificationPayload( final StashBuildState state, TaskListener listener) { + String buildId = abbreviate(getBuildKey(run, listener), MAX_FIELD_LENGTH); + JSONObject json = new JSONObject(); + json.put("key", buildId); + json.put("parent", buildId); json.put("state", state.name()); - json.put("key", abbreviate(getBuildKey(run, listener), MAX_FIELD_LENGTH)); + json.put("url", abbreviate(getBuildUrl(run), MAX_URL_FIELD_LENGTH)); json.put("name", abbreviate(getBuildName(run), MAX_FIELD_LENGTH)); json.put("description", abbreviate(getBuildDescription(run, state), MAX_FIELD_LENGTH)); - json.put("url", abbreviate(getBuildUrl(run), MAX_URL_FIELD_LENGTH)); + json.put("buildNumber", run.getNumber()); + json.put("duration", run.getDuration()); + return json; } @@ -1076,9 +1065,7 @@ protected String getBuildKey(final Run run, StringBuilder key = new StringBuilder(); if (prependParentProjectKey || getDescriptor().isPrependParentProjectKey()) { - if (null != run.getParent().getParent()) { - key.append(run.getParent().getParent().getFullName()).append('-'); - } + key.append(run.getParent().getParent().getFullName()).append('-'); } if (projectKey != null && projectKey.trim().length() > 0) { diff --git a/src/main/resources/org/jenkinsci/plugins/stashNotifier/StashNotifier/config.jelly b/src/main/resources/org/jenkinsci/plugins/stashNotifier/StashNotifier/config.jelly index e4795e7c..76c4fec5 100644 --- a/src/main/resources/org/jenkinsci/plugins/stashNotifier/StashNotifier/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/stashNotifier/StashNotifier/config.jelly @@ -7,6 +7,12 @@ + + + + + + @@ -22,10 +28,10 @@ - + - + diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactoryTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactoryTest.java index d7355ec2..eb66e939 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactoryTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactoryTest.java @@ -13,7 +13,7 @@ public class BuildStatusUriFactoryTest { public void shouldHandleTrailingSlash() { String baseUri = "http://localhost:12345/"; URI expected = URI.create("http://localhost:12345/rest/build-status/1.0/commits/25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); - URI actual = BuildStatusUriFactory.create(baseUri, "25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); + URI actual = BuildStatusUriFactory.create(baseUri, "", "","25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); assertThat(actual, equalTo(expected)); } @@ -21,7 +21,7 @@ public void shouldHandleTrailingSlash() { public void shouldHandleNoTrailingSlash() { String baseUri = "http://localhost:12345"; URI expected = URI.create("http://localhost:12345/rest/build-status/1.0/commits/25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); - URI actual = BuildStatusUriFactory.create(baseUri, "25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); + URI actual = BuildStatusUriFactory.create(baseUri, "","","25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); assertThat(actual, equalTo(expected)); } @@ -29,7 +29,7 @@ public void shouldHandleNoTrailingSlash() { public void shouldHandleBasePathTrailingSlash() { String baseUri = "http://localhost:12345/some-path/"; URI expected = URI.create("http://localhost:12345/some-path/rest/build-status/1.0/commits/25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); - URI actual = BuildStatusUriFactory.create(baseUri, "25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); + URI actual = BuildStatusUriFactory.create(baseUri, "","","25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); assertThat(actual, equalTo(expected)); } @@ -37,7 +37,7 @@ public void shouldHandleBasePathTrailingSlash() { public void shouldHandleBasePathNoTrailingSlash() { String baseUri = "http://localhost:12345/some-path"; URI expected = URI.create("http://localhost:12345/some-path/rest/build-status/1.0/commits/25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); - URI actual = BuildStatusUriFactory.create(baseUri, "25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); + URI actual = BuildStatusUriFactory.create(baseUri,"","","25a4b3c9b494fc7ac65b80e3b0ecce63f235f20d"); assertThat(actual, equalTo(expected)); } } diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java index ce3a4958..c4e81fc3 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java @@ -144,7 +144,7 @@ public void before() { private NotificationResult notifyStash(int statusCode) throws Exception { PrintStream logger = mock(PrintStream.class); - URI uri = BuildStatusUriFactory.create("http://localhost", "df02f57eea1cda72fa2412102f061dd7f6188e98"); + URI uri = BuildStatusUriFactory.create("http://localhost", "","", "df02f57eea1cda72fa2412102f061dd7f6188e98"); when(buildListener.getLogger()).thenReturn(logger); CloseableHttpResponse resp = mock(CloseableHttpResponse.class); StatusLine sl = mock(StatusLine.class); diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java index 304bed91..d5e38ea7 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java @@ -91,6 +91,8 @@ private StashNotifier buildStashNotifier(String stashBaseUrl, StashNotifier notifier = new StashNotifier( stashBaseUrl, "scot", + "", + "", true, null, null, @@ -251,6 +253,8 @@ public void test_build_http_client_https() throws Exception { sn = new StashNotifier( "https://localhost", "scot", + "", + "", true, null, null, @@ -504,6 +508,8 @@ public void lookupCommitSha1s() { sn = new StashNotifier( "https://localhost", "scot", + "", + "", true, sha1, null, @@ -534,6 +540,8 @@ private void lookupCommitSha1s_Exception(Exception e) { sn = new StashNotifier( "http://localhost", "scot", + "", + "", true, sha1, null, @@ -602,6 +610,8 @@ public void test_getPushedBuildState_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, state.name(), @@ -627,6 +637,8 @@ public void test_getPushedBuildState_not_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -655,6 +667,8 @@ public void test_getBuildName_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -682,6 +696,8 @@ public void test_getBuildName_not_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -710,6 +726,8 @@ public void test_getBuildUrl_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -737,6 +755,8 @@ public void test_getBuildUrl_not_overwritten() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -768,6 +788,8 @@ public void test_getBuildKey() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -801,6 +823,8 @@ public void test_getBuildKey_withBuildName() { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -835,6 +859,8 @@ public void test_getRunKey() throws Exception { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -866,6 +892,8 @@ private void getBuildKey_Exception(Exception e) { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, @@ -901,6 +929,8 @@ private void getRunKey_Exception(Exception e) throws IOException { sn = new StashNotifier( "", "scot", + "", + "", true, null, null, From 1ee1209b1d08a2b5e86c53bf0178e068ec05c220 Mon Sep 17 00:00:00 2001 From: "Boillat, Marcus" Date: Tue, 3 Oct 2023 22:24:28 +0200 Subject: [PATCH 2/4] Issue #290: Fix broken tests --- .../org/jenkinsci/plugins/stashNotifier/StashNotifier.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java index fc2bc489..5f14daa3 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java @@ -1065,7 +1065,11 @@ protected String getBuildKey(final Run run, StringBuilder key = new StringBuilder(); if (prependParentProjectKey || getDescriptor().isPrependParentProjectKey()) { - key.append(run.getParent().getParent().getFullName()).append('-'); + ItemGroup parent = run.getParent().getParent(); + if(parent != null) + { + key.append(parent.getFullName()).append('-'); + } } if (projectKey != null && projectKey.trim().length() > 0) { From cacac93e0df3e4571f3019c1c17b75e8678a21e1 Mon Sep 17 00:00:00 2001 From: "Boillat, Marcus" Date: Thu, 30 May 2024 21:32:11 +0200 Subject: [PATCH 3/4] Issue #290: Use ExtendedApacheHttpNotifier to create payload for builds --- .../stashNotifier/BuildInformation.java | 34 ++ .../DefaultHttpNotifierSelector.java | 25 - .../stashNotifier/NotificationContext.java | 6 +- .../DefaultHttpNotifierSelector.java | 27 + .../ExtendedHttpNotifierSelector.java | 26 + .../HttpNotifierSelector.java | 11 +- .../DefaultApacheHttpNotifier.java | 68 ++- .../Notifiers/ExtendedApacheHttpNotifier.java | 29 + .../{ => Notifiers}/HttpNotifier.java | 9 +- .../stashNotifier/SelectionContext.java | 16 +- .../plugins/stashNotifier/StashNotifier.java | 430 ++++----------- .../stashNotifier/StashNotifierDefault.java | 3 + .../stashNotifier/StashNotifierModule.java | 4 + .../DefaultApacheHttpNotifierTest.java | 66 ++- .../ExtendedApacheHttpNotifierTest.java | 211 ++++++++ .../StashNotifierModuleTest.java | 5 +- .../stashNotifier/StashNotifierTest.java | 500 ++++++++---------- 17 files changed, 805 insertions(+), 665 deletions(-) create mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/BuildInformation.java delete mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultHttpNotifierSelector.java create mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java create mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java rename src/main/java/org/jenkinsci/plugins/stashNotifier/{ => NotifierSelectors}/HttpNotifierSelector.java (73%) rename src/main/java/org/jenkinsci/plugins/stashNotifier/{ => Notifiers}/DefaultApacheHttpNotifier.java (75%) create mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifier.java rename src/main/java/org/jenkinsci/plugins/stashNotifier/{ => Notifiers}/HttpNotifier.java (59%) rename src/test/java/org/jenkinsci/plugins/stashNotifier/{ => Notifiers}/DefaultApacheHttpNotifierTest.java (76%) create mode 100644 src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifierTest.java diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildInformation.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildInformation.java new file mode 100644 index 00000000..343ee428 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/BuildInformation.java @@ -0,0 +1,34 @@ +package org.jenkinsci.plugins.stashNotifier; + +public class BuildInformation { + private final String runId; + private final long runDuration; + private final StashBuildState buildState; + private final String buildKey; + private final String buildUrl; + private final String buildName; + private final String buildDescription; + public BuildInformation(String runId, long runDuration, StashBuildState buildState, String buildKey, String buildUrl, String buildName, String buildDescription) { + this.runId = runId; + this.runDuration = runDuration; + this.buildState = buildState; + this.buildKey = buildKey; + this.buildUrl = buildUrl; + this.buildName = buildName; + this.buildDescription = buildDescription; + } + + public String getRunId() { + return runId; + } + public long getRunDuration() { + return runDuration; + } + public StashBuildState getBuildState() { + return buildState; + } + public String getBuildKey() { return buildKey; } + public String getBuildUrl() { return buildUrl; } + public String getBuildName() { return buildName; } + public String getBuildDescription() { return buildDescription; } +} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultHttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultHttpNotifierSelector.java deleted file mode 100644 index 126db384..00000000 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultHttpNotifierSelector.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.jenkinsci.plugins.stashNotifier; - -import edu.umd.cs.findbugs.annotations.NonNull; - -/** - * This is the default way of selecting a {@link HttpNotifier}. - * - * Always returns {@link DefaultApacheHttpNotifier} for backwards compatibility with v1.20 and earlier. - */ -class DefaultHttpNotifierSelector implements HttpNotifierSelector { - private final HttpNotifier httpNotifier; - - DefaultHttpNotifierSelector(HttpNotifier httpNotifier) { - this.httpNotifier = httpNotifier; - } - - /** - * @param context unused - * @return singleton {@link DefaultApacheHttpNotifier} - */ - @Override - public @NonNull HttpNotifier select(@NonNull SelectionContext context) { - return httpNotifier; - } -} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationContext.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationContext.java index 9823052b..81885714 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationContext.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationContext.java @@ -10,10 +10,12 @@ public class NotificationContext { private final PrintStream logger; private final String runId; + private final BuildInformation buildInformation; - public NotificationContext(PrintStream logger, String runId) { + public NotificationContext(PrintStream logger, String runId, BuildInformation buildInformation) { this.logger = logger; this.runId = runId; + this.buildInformation = buildInformation; } /** @@ -34,4 +36,6 @@ public PrintStream getLogger() { public String getRunId() { return runId; } + + public BuildInformation getBuildInformation() { return buildInformation; } } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java new file mode 100644 index 00000000..5f22fcbd --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java @@ -0,0 +1,27 @@ +package org.jenkinsci.plugins.stashNotifier.NotifierSelectors; + +import edu.umd.cs.findbugs.annotations.NonNull; +import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; +import org.jenkinsci.plugins.stashNotifier.SelectionContext; + +/** + * This is the default way of selecting a {@link HttpNotifier}. + * Always returns {@link DefaultApacheHttpNotifier} for backwards compatibility with v1.20 and earlier. + */ +public class DefaultHttpNotifierSelector implements HttpNotifierSelector { + private final HttpNotifier httpNotifier; + + public DefaultHttpNotifierSelector(HttpNotifier httpNotifier) { + this.httpNotifier = httpNotifier; + } + + /** + * @param context unused + * @return singleton {@link DefaultApacheHttpNotifier} + */ + @Override + public HttpNotifier select(@NonNull SelectionContext context) { + return httpNotifier; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java new file mode 100644 index 00000000..f8c358bb --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java @@ -0,0 +1,26 @@ +package org.jenkinsci.plugins.stashNotifier.NotifierSelectors; + +import edu.umd.cs.findbugs.annotations.NonNull; +import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.ExtendedApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; +import org.jenkinsci.plugins.stashNotifier.SelectionContext; + +import java.util.List; + +public class ExtendedHttpNotifierSelector implements HttpNotifierSelector { + private final List httpNotifiers; + + public ExtendedHttpNotifierSelector(List notifiers) { + httpNotifiers = notifiers; + } + + @Override + public HttpNotifier select(@NonNull SelectionContext context) { + if(context.getBitBucketProjectKey().isEmpty() || context.getBitBucketProjectSlug().isEmpty()) + { + return httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(DefaultApacheHttpNotifier.class.getName())).findFirst().get(); + } + return httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(ExtendedApacheHttpNotifier.class.getName())).findFirst().get(); + } +} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/HttpNotifierSelector.java similarity index 73% rename from src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifierSelector.java rename to src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/HttpNotifierSelector.java index 457f67cf..32ed8635 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifierSelector.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/HttpNotifierSelector.java @@ -1,4 +1,4 @@ -package org.jenkinsci.plugins.stashNotifier; +package org.jenkinsci.plugins.stashNotifier.NotifierSelectors; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.FilePath; @@ -7,6 +7,9 @@ import hudson.model.BuildListener; import hudson.model.Run; import hudson.model.TaskListener; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; +import org.jenkinsci.plugins.stashNotifier.SelectionContext; +import org.jenkinsci.plugins.stashNotifier.StashNotifier; /** * Implement this interface to have more control over which {@link HttpNotifier} @@ -21,10 +24,10 @@ public interface HttpNotifierSelector { * this method useful for performing migrations on a running system without * restarts. * - * @see StashNotifier#prebuild(AbstractBuild, BuildListener) - * @see StashNotifier#perform(Run, FilePath, Launcher, TaskListener) * @param context parameters useful for selecting a notifier * @return selected notifier + * @see StashNotifier#prebuild(AbstractBuild, BuildListener) + * @see StashNotifier#perform(Run, FilePath, Launcher, TaskListener) */ - @NonNull HttpNotifier select(@NonNull SelectionContext context); + HttpNotifier select(@NonNull SelectionContext context); } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifier.java similarity index 75% rename from src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java rename to src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifier.java index ebf635a2..999bbd0f 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifier.java @@ -1,4 +1,4 @@ -package org.jenkinsci.plugins.stashNotifier; +package org.jenkinsci.plugins.stashNotifier.Notifiers; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.common.CertificateCredentials; @@ -36,6 +36,7 @@ import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.jenkinsci.plugins.stashNotifier.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,12 +52,16 @@ import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; -class DefaultApacheHttpNotifier implements HttpNotifier { +public class DefaultApacheHttpNotifier implements HttpNotifier { + protected static final int MAX_FIELD_LENGTH = 255; + protected static final int MAX_URL_FIELD_LENGTH = 450; + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultApacheHttpNotifier.class); @Override - public @NonNull NotificationResult send(@NonNull URI uri, @NonNull JSONObject payload, @NonNull NotificationSettings settings, @NonNull NotificationContext context) { + public @NonNull NotificationResult send(@NonNull URI uri, @NonNull NotificationSettings settings, @NonNull NotificationContext context) { PrintStream logger = context.getLogger(); + JSONObject payload = createNotificationPayload(context); try (CloseableHttpClient client = getHttpClient(logger, uri, settings.isIgnoreUnverifiedSSL())) { HttpPost req = createRequest(uri, payload, settings.getCredentials(), context); HttpResponse res = client.execute(req); @@ -67,17 +72,14 @@ class DefaultApacheHttpNotifier implements HttpNotifier { return NotificationResult.newSuccess(); } } catch (Exception e) { - LOGGER.warn("{} failed to send {} to Bitbucket Server at {}", context.getRunId(), payload, uri, e); + LOGGER.warn("{} failed to send {} to Bitbucket Server at {}", context.getRunId(), payload, uri); logger.println("Failed to notify Bitbucket Server"); return NotificationResult.newFailure(e.getMessage()); } } - HttpPost createRequest( - final URI uri, - final JSONObject payload, - final Credentials credentials, - @NonNull NotificationContext context) throws AuthenticationException { + protected HttpPost createRequest(@NonNull final URI uri, final JSONObject payload, final Credentials credentials, + @NonNull NotificationContext context) throws AuthenticationException { HttpPost req = new HttpPost(uri.toString()); if (credentials != null) { @@ -95,7 +97,7 @@ else if (credentials instanceof StringCredentials) { req.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + ((StringCredentials)credentials).getSecret().getPlainText()); } else { - throw new AuthenticationException("Unsupported credials"); + throw new AuthenticationException("Unsupported credentials"); } } @@ -105,7 +107,28 @@ else if (credentials instanceof StringCredentials) { return req; } - CloseableHttpClient getHttpClient(PrintStream logger, URI stashServer, boolean ignoreUnverifiedSSL) throws Exception { + /** + * Returns the HTTP POST entity body with the JSON representation of the + * run result to be sent to the Bitbucket build API. + * + * @param context the NotificationContext for the current build + * @return JSON body for POST to Bitbucket build API + */ + public JSONObject createNotificationPayload(NotificationContext context) { + + BuildInformation information = context.getBuildInformation(); + String buildId = abbreviate(information.getBuildKey(), MAX_FIELD_LENGTH); + + JSONObject json = new JSONObject(); + json.put("state", information.getBuildState().name()); + json.put("key", buildId); + json.put("name", abbreviate(information.getBuildName(), MAX_FIELD_LENGTH)); + json.put("description", abbreviate(information.getBuildDescription(), MAX_FIELD_LENGTH)); + json.put("url", abbreviate(information.getBuildUrl(), MAX_URL_FIELD_LENGTH)); + return json; + } + + protected CloseableHttpClient getHttpClient(PrintStream logger, @NonNull URI stashServer, boolean ignoreUnverifiedSSL) throws Exception { final int timeoutInMilliseconds = 60_000; RequestConfig.Builder requestBuilder = RequestConfig.custom() @@ -154,7 +177,7 @@ CloseableHttpClient getHttpClient(PrintStream logger, URI stashServer, boolean i return clientBuilder.build(); } - SSLContext buildSslContext(boolean ignoreUnverifiedSSL, Credentials credentials) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { + protected SSLContext buildSslContext(boolean ignoreUnverifiedSSL, Credentials credentials) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { SSLContextBuilder contextBuilder = SSLContexts.custom(); contextBuilder.setProtocol("TLS"); if (credentials instanceof CertificateCredentials) { @@ -180,13 +203,13 @@ void configureProxy(HttpClientBuilder builder, URL url) { return; } - SocketAddress addr = proxy.address(); - if (!(addr instanceof InetSocketAddress)) { + SocketAddress address = proxy.address(); + if (!(address instanceof InetSocketAddress)) { return; } - InetSocketAddress proxyAddr = (InetSocketAddress) addr; - HttpHost proxyHost = new HttpHost(proxyAddr.getAddress().getHostAddress(), proxyAddr.getPort()); + InetSocketAddress proxyAddress = (InetSocketAddress) address; + HttpHost proxyHost = new HttpHost(proxyAddress.getAddress().getHostAddress(), proxyAddress.getPort()); builder.setProxy(proxyHost); String proxyUser = proxyConfig.getUserName(); @@ -199,4 +222,17 @@ void configureProxy(HttpClientBuilder builder, URL url) { .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); } } + + protected static String abbreviate(String text, int maxWidth) { + if (text == null) { + return null; + } + if (maxWidth < 4) { + throw new IllegalArgumentException("Minimum abbreviation width is 4"); + } + if (text.length() <= maxWidth) { + return text; + } + return text.substring(0, maxWidth - 3) + "..."; + } } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifier.java new file mode 100644 index 00000000..d08eca47 --- /dev/null +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifier.java @@ -0,0 +1,29 @@ +package org.jenkinsci.plugins.stashNotifier.Notifiers; + +import net.sf.json.JSONObject; +import org.jenkinsci.plugins.stashNotifier.BuildInformation; +import org.jenkinsci.plugins.stashNotifier.NotificationContext; + +public class ExtendedApacheHttpNotifier extends DefaultApacheHttpNotifier { + /** + * Returns the HTTP POST entity body with the JSON representation of the + * run result to be sent to the Bitbucket build API. + * + * @param context the NotificationContext for the current build + * @return JSON body for POST to Bitbucket build API + */ + @Override + public JSONObject createNotificationPayload(NotificationContext context) { + + BuildInformation information = context.getBuildInformation(); + String buildId = abbreviate(information.getBuildKey(), MAX_FIELD_LENGTH); + + JSONObject json = super.createNotificationPayload(context); + + json.put("parent", buildId); + json.put("buildNumber", information.getRunId()); + json.put("duration", information.getRunDuration()); + + return json; + } +} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/HttpNotifier.java similarity index 59% rename from src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifier.java rename to src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/HttpNotifier.java index af5038a5..1ab24cf7 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/Notifiers/HttpNotifier.java @@ -1,7 +1,9 @@ -package org.jenkinsci.plugins.stashNotifier; +package org.jenkinsci.plugins.stashNotifier.Notifiers; import edu.umd.cs.findbugs.annotations.NonNull; -import net.sf.json.JSONObject; +import org.jenkinsci.plugins.stashNotifier.NotificationContext; +import org.jenkinsci.plugins.stashNotifier.NotificationResult; +import org.jenkinsci.plugins.stashNotifier.NotificationSettings; import java.net.URI; @@ -13,11 +15,10 @@ public interface HttpNotifier { * Basic contract for sending Bitbucket build status notifications. * * @param uri fully-formed URI (stash-base-uri/rest/build-status/1.0/commits/commit-id) - * @param payload body of status to post * @param settings user or administrator defined settings for the request * @param context build info * @return result of posting status */ @NonNull - NotificationResult send(@NonNull URI uri, @NonNull JSONObject payload, @NonNull NotificationSettings settings, @NonNull NotificationContext context); + NotificationResult send(@NonNull URI uri, @NonNull NotificationSettings settings, @NonNull NotificationContext context); } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/SelectionContext.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/SelectionContext.java index 2bdcfe1e..dc3378dd 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/SelectionContext.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/SelectionContext.java @@ -1,6 +1,8 @@ package org.jenkinsci.plugins.stashNotifier; import hudson.model.AbstractItem; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import java.util.Objects; import java.util.StringJoiner; @@ -12,9 +14,13 @@ */ public class SelectionContext { private final String jobFullName; + private final String bitBucketProjectKey; + private final String bitBucketProjectSlug; - public SelectionContext(String jobFullName) { + public SelectionContext(String jobFullName, String projectKey, String projectSlug) { this.jobFullName = jobFullName; + this.bitBucketProjectKey = projectKey; + this.bitBucketProjectSlug = projectSlug; } /** @@ -26,6 +32,14 @@ public String getJobFullName() { return jobFullName; } + public String getBitBucketProjectKey() { + return bitBucketProjectKey; + } + + public String getBitBucketProjectSlug() { + return bitBucketProjectSlug; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java index 5f14daa3..939b73be 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifier.java @@ -27,7 +27,6 @@ import hudson.Extension; import hudson.FilePath; import hudson.Launcher; -import hudson.ProxyConfiguration; import hudson.model.*; import hudson.plugins.git.Revision; import hudson.plugins.git.util.BuildData; @@ -45,31 +44,11 @@ import net.sf.json.JSONObject; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.AuthenticationException; -import org.apache.http.client.config.CookieSpecs; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.HttpClientConnectionManager; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.conn.ssl.TrustAllStrategy; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.auth.BasicScheme; -import org.apache.http.impl.client.*; -import org.apache.http.impl.conn.BasicHttpClientConnectionManager; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.SSLContexts; import org.jenkinsci.Symbol; import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; import org.jenkinsci.plugins.plaincredentials.StringCredentials; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; import org.jenkinsci.plugins.tokenmacro.TokenMacro; import org.kohsuke.stapler.*; @@ -77,22 +56,12 @@ import org.slf4j.LoggerFactory; import jakarta.inject.Inject; -import javax.net.ssl.SSLContext; + import java.io.IOException; import java.io.PrintStream; -import java.net.InetSocketAddress; -import java.net.Proxy; -import java.net.SocketAddress; import java.net.URI; import java.net.URL; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; +import java.util.*; /** * Notifies a configured Atlassian Bitbucket server instance of build results @@ -104,9 +73,6 @@ public class StashNotifier extends Notifier implements SimpleBuildStep { private static final Logger LOGGER = LoggerFactory.getLogger(StashNotifier.class); - public static final int MAX_FIELD_LENGTH = 255; - public static final int MAX_URL_FIELD_LENGTH = 450; - // attributes -------------------------------------------------------------- /** @@ -135,6 +101,8 @@ public class StashNotifier extends Notifier implements SimpleBuildStep { */ private StashBuildState buildStatus; + private String buildStatusString; + /** * specify a build name to be included in the Bitbucket notification. * If null, the usual full project name will be used. @@ -280,9 +248,11 @@ public StashBuildState getBuildStatus() { public void setBuildStatus(Object buildStatus) { if (buildStatus instanceof StashBuildState) { this.buildStatus = (StashBuildState) buildStatus; + setBuildStatusString((StashBuildState) buildStatus); } else if (buildStatus instanceof String) { try { this.buildStatus = StashBuildState.valueOf((String) buildStatus); + setBuildStatusString((String) buildStatus); } catch (Exception e) { // ignore unknown or null values } @@ -291,6 +261,14 @@ public void setBuildStatus(Object buildStatus) { } } + protected void setBuildStatusString(String buildStatusString) { + this.buildStatusString = buildStatusString; + } + + protected void setBuildStatusString(StashBuildState buildStatus) { + this.buildStatusString = buildStatus.toString(); + } + public String getBuildName() { return buildName; } @@ -402,9 +380,9 @@ public void perform(@NonNull Run run, } } - private boolean perform(Run run, + private boolean perform(@NonNull Run run, FilePath workspace, - TaskListener listener, + @NonNull TaskListener listener, boolean disableInProgress) { StashBuildState state; @@ -455,12 +433,7 @@ private String getRootUrl() { * @return always true in order not to abort the Job in case of * notification failures */ - private boolean processJenkinsEvent( - final Run run, - final FilePath workspace, - final TaskListener listener, - final StashBuildState state) { - + private boolean processJenkinsEvent(final Run run, final FilePath workspace, @NonNull final TaskListener listener, final StashBuildState state) { PrintStream logger = listener.getLogger(); // Exit if Jenkins root URL is not configured. Bitbucket run API @@ -496,12 +469,8 @@ private boolean processJenkinsEvent( return true; } - protected Collection lookupCommitSha1s( - @SuppressWarnings("rawtypes") Run run, - FilePath workspace, - TaskListener listener) { - - if (commitSha1 != null && commitSha1.trim().length() > 0) { + protected Collection lookupCommitSha1s(@SuppressWarnings("rawtypes") Run run, FilePath workspace, TaskListener listener) { + if (commitSha1 != null && !commitSha1.trim().isEmpty()) { PrintStream logger = listener.getLogger(); try { @@ -544,120 +513,6 @@ protected Collection lookupCommitSha1s( return sha1s; } - /** - * Returns the HttpClient through which the REST call is made. Uses an - * unsafe TrustStrategy in case the user specified a HTTPS URL and - * set the ignoreUnverifiedSSLPeer flag. - * @see DefaultApacheHttpNotifier#getHttpClient(PrintStream, URI, boolean) - */ - @Deprecated - protected CloseableHttpClient getHttpClient(PrintStream logger, Run run, String stashServer) throws Exception { - DescriptorImpl globalSettings = getDescriptor(); - - final int timeoutInMilliseconds = 60_000; - - RequestConfig.Builder requestBuilder = RequestConfig.custom() - .setSocketTimeout(timeoutInMilliseconds) - .setConnectTimeout(timeoutInMilliseconds) - .setConnectionRequestTimeout(timeoutInMilliseconds) - .setCookieSpec(CookieSpecs.STANDARD); - - HttpClientBuilder clientBuilder = HttpClients.custom(); - clientBuilder.setDefaultRequestConfig(requestBuilder.build()); - - URL url = new URL(stashServer); - boolean ignoreUnverifiedSSL = ignoreUnverifiedSSLPeer || globalSettings.isIgnoreUnverifiedSsl(); - - if (url.getProtocol().equals("https") && ignoreUnverifiedSSL) { - // add unsafe trust manager to avoid thrown SSLPeerUnverifiedException - try { - SSLContext sslContext = buildSslContext(ignoreUnverifiedSSL, null); - SSLConnectionSocketFactory sslConnSocketFactory = new SSLConnectionSocketFactory( - sslContext, - new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}, - null, - NoopHostnameVerifier.INSTANCE - ); - clientBuilder.setSSLSocketFactory(sslConnSocketFactory); - - Registry registry = RegistryBuilder.create() - .register("https", sslConnSocketFactory) - .register("http", PlainConnectionSocketFactory.INSTANCE) - .build(); - - HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(registry); - clientBuilder.setConnectionManager(connectionManager); - } catch (NoSuchAlgorithmException nsae) { - logger.println("Could not establish SSL context"); - LOGGER.error("{} could not establish SSL context", idOf(run), nsae); - } catch (KeyManagementException | KeyStoreException e) { - logger.println("Could not initialize SSL context"); - LOGGER.error("{} could not initialize SSL context", idOf(run), e); - } - } - - // Configure the proxy, if needed - // Using the Jenkins methods handles the noProxyHost settings - configureProxy(clientBuilder, url); - - return clientBuilder.build(); - } - - /** - * Helper in place to allow us to define out HttpClient SSL context - * @see DefaultApacheHttpNotifier#buildSslContext(boolean, Credentials) - */ - @Deprecated - private SSLContext buildSslContext(boolean ignoreUnverifiedSSL, Credentials credentials) throws UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException { - SSLContextBuilder contextBuilder = SSLContexts.custom(); - contextBuilder.setProtocol("TLS"); - if (credentials instanceof CertificateCredentials) { - contextBuilder.loadKeyMaterial( - ((CertificateCredentials) credentials).getKeyStore(), - ((CertificateCredentials) credentials).getPassword().getPlainText().toCharArray()); - } - if (ignoreUnverifiedSSL) { - contextBuilder.loadTrustMaterial(null, TrustAllStrategy.INSTANCE); - } - return contextBuilder.build(); - } - - /** - * @see DefaultApacheHttpNotifier#configureProxy(HttpClientBuilder, URL) - */ - @Deprecated - private void configureProxy(HttpClientBuilder builder, URL url) { - Jenkins jenkins = Jenkins.get(); - ProxyConfiguration proxyConfig = jenkins.proxy; - if (proxyConfig == null) { - return; - } - - Proxy proxy = proxyConfig.createProxy(url.getHost()); - if (proxy == null || proxy.type() != Proxy.Type.HTTP) { - return; - } - - SocketAddress addr = proxy.address(); - if (!(addr instanceof InetSocketAddress)) { - return; - } - - InetSocketAddress proxyAddr = (InetSocketAddress) addr; - HttpHost proxyHost = new HttpHost(proxyAddr.getAddress().getHostAddress(), proxyAddr.getPort()); - builder.setProxy(proxyHost); - - String proxyUser = proxyConfig.getUserName(); - if (proxyUser != null) { - String proxyPass = proxyConfig.getPassword(); - BasicCredentialsProvider cred = new BasicCredentialsProvider(); - cred.setCredentials(new AuthScope(proxyHost), - new org.apache.http.auth.UsernamePasswordCredentials(proxyUser, proxyPass)); - builder.setDefaultCredentialsProvider(cred) - .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - } - } - @Override public DescriptorImpl getDescriptor() { // see Descriptor javadoc for more about what a descriptor is. @@ -666,8 +521,7 @@ public DescriptorImpl getDescriptor() { @Symbol({"notifyBitbucket", "notifyStash"}) @Extension - public static final class DescriptorImpl - extends BuildStepDescriptor { + public static final class DescriptorImpl extends BuildStepDescriptor { /** * To persist global configuration information, @@ -689,7 +543,7 @@ public DescriptorImpl() { this(true); } - protected DescriptorImpl(boolean load) { + DescriptorImpl(boolean load) { if (load) load(); } @@ -832,10 +686,7 @@ public String getDisplayName() { } @Override - public boolean configure( - StaplerRequest req, - JSONObject formData) { - + public boolean configure(StaplerRequest req, JSONObject formData) { this.considerUnstableAsSuccess = false; this.credentialsId = null; this.disableInprogressNotification = false; @@ -864,36 +715,30 @@ public boolean configure( * @param listener the run listener for logging * @param state the state of the build as defined by the Bitbucket API. */ - protected NotificationResult notifyStash( - final PrintStream logger, - final Run run, - final String commitSha1, - final TaskListener listener, - final StashBuildState state) { - StashBuildState buildStatus = getPushedBuildStatus(state); - JSONObject payload = createNotificationPayload(run, buildStatus, listener); + protected NotificationResult notifyStash(final PrintStream logger, final Run run, final String commitSha1, + final TaskListener listener, final StashBuildState state) { String stashURL = expandStashURL(run, listener); - Credentials usernamePasswordCredentials - = getCredentials(UsernamePasswordCredentials.class, run.getParent()); - Credentials stringCredentials - = getCredentials(StringCredentials.class, run.getParent()); + Credentials usernamePasswordCredentials = getCredentials(UsernamePasswordCredentials.class, run.getParent()); + Credentials stringCredentials = getCredentials(StringCredentials.class, run.getParent()); URI uri = BuildStatusUriFactory.create(stashURL, bbProjectKey, repositorySlug, commitSha1); - logger.println("Sending notification to \"" + uri + "?key=" + abbreviate(getBuildKey(run, listener), MAX_FIELD_LENGTH) + "\""); + BuildInformation buildInformation = new BuildInformation(run.getId(), run.getDuration(), + getPushedBuildStatus(state), getBuildKey(run, listener), getBuildUrl(run), + getBuildName(run), getBuildDescription(run, state)); NotificationSettings settings = new NotificationSettings( ignoreUnverifiedSSLPeer || getDescriptor().isIgnoreUnverifiedSsl(), stringCredentials != null ? stringCredentials : usernamePasswordCredentials ); - NotificationContext context = new NotificationContext( - logger, - run.getExternalizableId() - ); - HttpNotifier notifier = getHttpNotifierSelector().select(new SelectionContext(run.getParent().getFullName())); - return notifier.send(uri, payload, settings, context); + + NotificationContext context = new NotificationContext(logger, run.getExternalizableId(), buildInformation); + + HttpNotifier notifier = getHttpNotifierSelector().select(new SelectionContext(run.getParent().getFullName(), bbProjectKey, repositorySlug)); + + return notifier.send(uri, settings, context); } /** @@ -933,102 +778,6 @@ private T getCredentials(final Class clazz, final Ite return credentials; } - /** - * Returns the build state to be pushed. This will select the specifically overwritten build state - * or the current build state else. - * - * @param currentBuildStatus the state of the current build - * @return the current build status - */ - protected StashBuildState getPushedBuildStatus(StashBuildState currentBuildStatus) { - if (buildStatus != null) { - return buildStatus; - } else { - return currentBuildStatus; - } - } - - private String expandStashURL(Run run, final TaskListener listener) { - String url = stashServerBaseUrl; - DescriptorImpl descriptor = getDescriptor(); - if (url == null || url.isEmpty()) { - url = descriptor.getStashRootUrl(); - } - - try { - if (!(run instanceof AbstractBuild)) { - url = TokenMacro.expandAll(run, new FilePath(run.getRootDir()), listener, url); - } else { - url = TokenMacro.expandAll((AbstractBuild) run, listener, url); - } - - } catch (IOException | InterruptedException | MacroEvaluationException ex) { - PrintStream logger = listener.getLogger(); - logger.println("Unable to expand Bitbucket server URL"); - LOGGER.error("{} unable to expand Bitbucket server URL", idOf(run), ex); - } - return url; - } - - /** - * Returns the HTTP POST entity body with the JSON representation of the - * run result to be sent to the Bitbucket build API. - * - * @see #createNotificationPayload(Run, StashBuildState, TaskListener) - * @deprecated in favor of client-agnostic methods - * @param run the run to notify Bitbucket of - * @return HTTP entity body for POST to Bitbucket build API - */ - @Deprecated - private HttpEntity newStashBuildNotificationEntity( - final Run run, - final StashBuildState state, - TaskListener listener) { - - JSONObject json = createNotificationPayload(run, state, listener); - return new StringEntity(json.toString(), "UTF-8"); - } - - /** - * Returns the HTTP POST entity body with the JSON representation of the - * run result to be sent to the Bitbucket build API. - * - * @param run the run to notify Bitbucket of - * @return JSON body for POST to Bitbucket build API - */ - private JSONObject createNotificationPayload( - final Run run, - final StashBuildState state, - TaskListener listener) { - - String buildId = abbreviate(getBuildKey(run, listener), MAX_FIELD_LENGTH); - - JSONObject json = new JSONObject(); - json.put("key", buildId); - json.put("parent", buildId); - json.put("state", state.name()); - json.put("url", abbreviate(getBuildUrl(run), MAX_URL_FIELD_LENGTH)); - json.put("name", abbreviate(getBuildName(run), MAX_FIELD_LENGTH)); - json.put("description", abbreviate(getBuildDescription(run, state), MAX_FIELD_LENGTH)); - json.put("buildNumber", run.getNumber()); - json.put("duration", run.getDuration()); - - return json; - } - - private static String abbreviate(String text, int maxWidth) { - if (text == null) { - return null; - } - if (maxWidth < 4) { - throw new IllegalArgumentException("Minimum abbreviation width is 4"); - } - if (text.length() <= maxWidth) { - return text; - } - return text.substring(0, maxWidth - 3) + "..."; - } - /** * Return the old-fashion build key * @@ -1039,8 +788,7 @@ private String getDefaultBuildKey(final Run run) { StringBuilder key = new StringBuilder(); key.append(run.getParent().getName()); - if (includeBuildNumberInKey - || getDescriptor().isIncludeBuildNumberInKey()) { + if (includeBuildNumberInKey || getDescriptor().isIncludeBuildNumberInKey()) { key.append('-').append(run.getNumber()); } key.append('-').append(getRootUrl()); @@ -1059,8 +807,7 @@ private String getDefaultBuildKey(final Run run) { * @param run the run to notify Bitbucket of * @return the run key for the Bitbucket notification */ - protected String getBuildKey(final Run run, - TaskListener listener) { + protected String getBuildKey(final Run run, TaskListener listener) { StringBuilder key = new StringBuilder(); @@ -1072,7 +819,7 @@ protected String getBuildKey(final Run run, } } - if (projectKey != null && projectKey.trim().length() > 0) { + if (projectKey != null && !projectKey.trim().isEmpty()) { PrintStream logger = listener.getLogger(); try { if (!(run instanceof AbstractBuild)) { @@ -1092,22 +839,29 @@ protected String getBuildKey(final Run run, return StringEscapeUtils.escapeJavaScript(key.toString()); } - private static String idOf(Run run) { - return run != null ? run.getExternalizableId() : "(absent run)"; - } - /** - * Returns the build name to be pushed. This will select the specifically overwritten build name - * or get the build name from the {@link Run}. + * Returns the description of the run used for the Bitbucket notification. + * Uses the run description provided by the Jenkins job, if available. * - * @param run the run to notify Bitbucket of - * @return the name of the run + * @param run the run to be described + * @param state the state of the run + * @return the description of the run */ - protected String getBuildName(final Run run) { - if (buildName != null && buildName.trim().length() > 0) { - return buildName; + protected String getBuildDescription(final Run run, final StashBuildState state) { + + String runDescription = run.getDescription(); + + if (runDescription != null + && !runDescription.trim().isEmpty()) { + + return runDescription; } else { - return run.getFullDisplayName(); + switch (state) { + case INPROGRESS: + return "building on Jenkins @ " + getRootUrl(); + default: + return "built by Jenkins @ " + getRootUrl(); + } } } @@ -1127,32 +881,58 @@ protected String getBuildUrl(final Run run) { } /** - * Returns the description of the run used for the Bitbucket notification. - * Uses the run description provided by the Jenkins job, if available. + * Returns the build name to be pushed. This will select the specifically overwritten build name + * or get the build name from the {@link Run}. * - * @param run the run to be described - * @param state the state of the run - * @return the description of the run + * @param run the run to notify Bitbucket of + * @return the name of the run */ - protected String getBuildDescription( - final Run run, - final StashBuildState state) { + protected String getBuildName(final Run run) { + if (buildName != null && !buildName.trim().isEmpty()) { + return buildName; + } else { + return run.getFullDisplayName(); + } + } - String runDescription = run.getDescription(); + /** + * Returns the build state to be pushed. This will select the specifically overwritten build state + * or the current build state else. + * + * @param currentBuildStatus the state of the current build + * @return the current build status + */ + protected StashBuildState getPushedBuildStatus(StashBuildState currentBuildStatus) { + if (buildStatus != null) { + return buildStatus; + } else { + return currentBuildStatus; + } + } - if (runDescription != null - && runDescription.trim().length() > 0) { + private String expandStashURL(Run run, final TaskListener listener) { + String url = stashServerBaseUrl; + DescriptorImpl descriptor = getDescriptor(); + if (url == null || url.isEmpty()) { + url = descriptor.getStashRootUrl(); + } - return runDescription; - } else { - switch (state) { - case INPROGRESS: - return "building on Jenkins @ " - + getRootUrl(); - default: - return "built by Jenkins @ " - + getRootUrl(); + try { + if (!(run instanceof AbstractBuild)) { + url = TokenMacro.expandAll(run, new FilePath(run.getRootDir()), listener, url); + } else { + url = TokenMacro.expandAll((AbstractBuild) run, listener, url); } + + } catch (IOException | InterruptedException | MacroEvaluationException ex) { + PrintStream logger = listener.getLogger(); + logger.println("Unable to expand Bitbucket server URL"); + LOGGER.error("{} unable to expand Bitbucket server URL", idOf(run), ex); } + return url; + } + + private static String idOf(Run run) { + return run != null ? run.getExternalizableId() : "(absent run)"; } } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierDefault.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierDefault.java index 7af3b22b..b1d0be13 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierDefault.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierDefault.java @@ -1,6 +1,9 @@ package org.jenkinsci.plugins.stashNotifier; import jakarta.inject.Qualifier; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; + import java.lang.annotation.Retention; import java.lang.annotation.Target; diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java index 6c269fb8..f66d263b 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java @@ -4,6 +4,10 @@ import com.google.inject.Provides; import hudson.Extension; import hudson.ExtensionList; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.DefaultHttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifierTest.java similarity index 76% rename from src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java rename to src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifierTest.java index c4e81fc3..f8d97cde 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifierTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/DefaultApacheHttpNotifierTest.java @@ -1,6 +1,7 @@ -package org.jenkinsci.plugins.stashNotifier; +package org.jenkinsci.plugins.stashNotifier.Notifiers; import com.cloudbees.plugins.credentials.CredentialsProvider; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.EnvVars; import hudson.FilePath; import hudson.model.AbstractBuild; @@ -13,7 +14,6 @@ import hudson.plugins.git.util.BuildData; import hudson.util.Secret; import jenkins.model.Jenkins; -import net.sf.json.JSONObject; import org.acegisecurity.Authentication; import org.apache.http.StatusLine; import org.apache.http.client.config.CookieSpecs; @@ -24,6 +24,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.jenkinsci.plugins.stashNotifier.*; import org.jenkinsci.plugins.tokenmacro.TokenMacro; import org.junit.AfterClass; import org.junit.Before; @@ -142,34 +143,32 @@ public void before() { when(httpClientBuilder.build()).thenReturn(client); } - private NotificationResult notifyStash(int statusCode) throws Exception { - PrintStream logger = mock(PrintStream.class); - URI uri = BuildStatusUriFactory.create("http://localhost", "","", "df02f57eea1cda72fa2412102f061dd7f6188e98"); - when(buildListener.getLogger()).thenReturn(logger); - CloseableHttpResponse resp = mock(CloseableHttpResponse.class); - StatusLine sl = mock(StatusLine.class); - when(sl.getStatusCode()).thenReturn(statusCode); - when(resp.getStatusLine()).thenReturn(sl); - when(resp.getEntity()).thenReturn(new StringEntity("")); - when(client.execute(any(HttpPost.class))).thenReturn(resp); - return httpNotifier.send(uri, new JSONObject(), new NotificationSettings(false, null), new NotificationContext(logger, "some-build#15")); + @Test + public void notifyStash_noSlug_success() throws Exception { + NotificationResult notificationResult = notifyStash(204, "", ""); + assertThat(notificationResult.indicatesSuccess, is(true)); } @Test - public void notifyStash_success() throws Exception { - NotificationResult notificationResult = notifyStash(204); + public void notifyStash_noSlug_fail() throws Exception { + NotificationResult notificationResult = notifyStash(400, "", ""); + assertThat(notificationResult.indicatesSuccess, is(false)); + } + @Test + public void notifyStash_Slug_success() throws Exception { + NotificationResult notificationResult = notifyStash(204, "testProject", "someslug"); assertThat(notificationResult.indicatesSuccess, is(true)); } @Test - public void notifyStash_fail() throws Exception { - NotificationResult notificationResult = notifyStash(400); + public void notifyStash_Slug_fail() throws Exception { + NotificationResult notificationResult = notifyStash(400, "testProject", "someslug"); assertThat(notificationResult.indicatesSuccess, is(false)); } @Test - public void notifyStashUsesRequestParameters() throws Exception { - notifyStash(204); + public void notifyStashUsesRequestParametersNoSlug() throws Exception { + notifyStash(204, "", ""); final ArgumentCaptor captor = ArgumentCaptor.forClass(RequestConfig.class); verify(httpClientBuilder).setDefaultRequestConfig(captor.capture()); @@ -180,4 +179,33 @@ public void notifyStashUsesRequestParameters() throws Exception { assertThat(config.getConnectionRequestTimeout(), is(60_000)); assertThat(config.getCookieSpec(), is(CookieSpecs.STANDARD)); } + + @Test + public void notifyStashUsesRequestParametersSlug() throws Exception { + notifyStash(204, "testProject", "someslug"); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(RequestConfig.class); + verify(httpClientBuilder).setDefaultRequestConfig(captor.capture()); + + final RequestConfig config = captor.getValue(); + assertThat(config.getSocketTimeout(), is(60_000)); + assertThat(config.getConnectTimeout(), is(60_000)); + assertThat(config.getConnectionRequestTimeout(), is(60_000)); + assertThat(config.getCookieSpec(), is(CookieSpecs.STANDARD)); + } + + @NonNull + private NotificationResult notifyStash(int statusCode, String projectKey, String slug) throws Exception { + PrintStream logger = mock(PrintStream.class); + URI uri = BuildStatusUriFactory.create("http://localhost", projectKey, slug, "df02f57eea1cda72fa2412102f061dd7f6188e98"); + when(buildListener.getLogger()).thenReturn(logger); + CloseableHttpResponse resp = mock(CloseableHttpResponse.class); + StatusLine sl = mock(StatusLine.class); + when(sl.getStatusCode()).thenReturn(statusCode); + when(resp.getStatusLine()).thenReturn(sl); + when(resp.getEntity()).thenReturn(new StringEntity("")); + when(client.execute(any(HttpPost.class))).thenReturn(resp); + BuildInformation information = new BuildInformation("1", 30000, StashBuildState.SUCCESSFUL, "ci-testbuild", uri.toString(), "ci-testbuild", "A Testbuild for Stash/Bitbucket"); + return httpNotifier.send(uri, new NotificationSettings(false, null), new NotificationContext(logger, "some-build#15", information)); + } } diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifierTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifierTest.java new file mode 100644 index 00000000..acec59d5 --- /dev/null +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/Notifiers/ExtendedApacheHttpNotifierTest.java @@ -0,0 +1,211 @@ +package org.jenkinsci.plugins.stashNotifier.Notifiers; + +import com.cloudbees.plugins.credentials.CredentialsProvider; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.EnvVars; +import hudson.FilePath; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.ItemGroup; +import hudson.model.Run; +import hudson.plugins.git.Revision; +import hudson.plugins.git.util.Build; +import hudson.plugins.git.util.BuildData; +import hudson.util.Secret; +import jenkins.model.Jenkins; +import org.acegisecurity.Authentication; +import org.apache.http.StatusLine; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.jenkinsci.plugins.stashNotifier.*; +import org.jenkinsci.plugins.tokenmacro.TokenMacro; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.PrintStream; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ExtendedApacheHttpNotifierTest { + + final static String sha1 = "1234567890123456789012345678901234567890"; + private static CloseableHttpClient client; + private static MockedStatic mockedJenkins; + private static MockedStatic mockedCredentialsProvider; + private static MockedStatic mockedSecret; + private static MockedStatic mockedStaticHttpClientBuilder; + private static MockedStatic mockedTokenMacro; + private final HttpNotifier httpNotifier = new ExtendedApacheHttpNotifier(); + + private static BuildListener buildListener; + private HttpClientBuilder httpClientBuilder; + + @BeforeClass + public static void setUp() throws Exception { + mockedSecret = mockStatic(Secret.class); + mockedJenkins = mockStatic(Jenkins.class); + mockedStaticHttpClientBuilder = mockStatic(HttpClientBuilder.class); + mockedTokenMacro = mockStatic(TokenMacro.class); + mockedCredentialsProvider = mockStatic(com.cloudbees.plugins.credentials.CredentialsProvider.class); + + buildListener = mock(BuildListener.class); + Jenkins jenkins = mock(Jenkins.class); + AbstractBuild build = mock(AbstractBuild.class); + Run run = mock(Run.class); + + AbstractProject project = mock(AbstractProject.class); + File file = mock(File.class); + when(file.getPath()).thenReturn("/tmp/fake/path"); + FilePath filePath = new FilePath(file); + when(project.getSomeWorkspace()).thenReturn(filePath); + FilePath workspace = project.getSomeWorkspace(); + EnvVars environment = mock(EnvVars.class); + PrintStream logger = System.out; + Secret secret = mock(Secret.class); + + client = mock(CloseableHttpClient.class); + CloseableHttpResponse resp = mock(CloseableHttpResponse.class); + StatusLine statusLine = mock(StatusLine.class); + BuildData action = mock(BuildData.class); + Revision revision = mock(Revision.class); + Build lastBuild = mock(Build.class); + List actions = Collections.singletonList(action); + + when(Jenkins.get()).thenReturn(jenkins); + when(jenkins.getRootUrl()).thenReturn("http://localhost/"); + when(build.getEnvironment(buildListener)).thenReturn(environment); + when(action.getLastBuiltRevision()).thenReturn(revision); + when(revision.getSha1String()).thenReturn(sha1); + doReturn(project).when(build).getProject(); + doReturn(project).when(run).getParent(); + when(build.getFullDisplayName()).thenReturn("foo"); + when(build.getUrl()).thenReturn("foo"); + when(build.getActions(BuildData.class)).thenReturn(actions); + when(environment.expand(anyString())).thenReturn(sha1); + when(buildListener.getLogger()).thenReturn(logger); + when(Secret.fromString("tiger")).thenReturn(secret); + when(Secret.toString(secret)).thenReturn("tiger"); + when(secret.getPlainText()).thenReturn("tiger"); + when(client.execute(any(HttpUriRequest.class))).thenReturn(resp); + when(resp.getStatusLine()).thenReturn(statusLine); + when(statusLine.getStatusCode()).thenReturn(204); + action.lastBuild = lastBuild; + when(lastBuild.getMarked()).thenReturn(revision); + + when(TokenMacro.expandAll(build, buildListener, "test-project")).thenReturn("prepend-key"); + when(CredentialsProvider.lookupCredentials( + any(), + any(ItemGroup.class), + any(Authentication.class), + anyList() + )).thenReturn(new ArrayList<>()); + } + + @AfterClass + public static void close() { + mockedJenkins.close(); + mockedCredentialsProvider.close(); + mockedSecret.close(); + mockedStaticHttpClientBuilder.close(); + mockedTokenMacro.close(); + } + + @Before + public void before() { + httpClientBuilder = mock(HttpClientBuilder.class); + when(HttpClientBuilder.create()).thenReturn(httpClientBuilder); + when(httpClientBuilder.build()).thenReturn(client); + } + + @Test + public void notifyStash_noSlug_success() throws Exception { + NotificationResult notificationResult = notifyStash(204, "", ""); + assertThat(notificationResult.indicatesSuccess, is(true)); + } + + @Test + public void notifyStash_noSlug_fail() throws Exception { + NotificationResult notificationResult = notifyStash(400, "", ""); + assertThat(notificationResult.indicatesSuccess, is(false)); + } + @Test + public void notifyStash_Slug_success() throws Exception { + NotificationResult notificationResult = notifyStash(204, "testProject", "someslug"); + assertThat(notificationResult.indicatesSuccess, is(true)); + } + + @Test + public void notifyStash_Slug_fail() throws Exception { + NotificationResult notificationResult = notifyStash(400, "testProject", "someslug"); + assertThat(notificationResult.indicatesSuccess, is(false)); + } + + @Test + public void notifyStashUsesRequestParametersNoSlug() throws Exception { + notifyStash(204, "", ""); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(RequestConfig.class); + verify(httpClientBuilder).setDefaultRequestConfig(captor.capture()); + + final RequestConfig config = captor.getValue(); + assertThat(config.getSocketTimeout(), is(60_000)); + assertThat(config.getConnectTimeout(), is(60_000)); + assertThat(config.getConnectionRequestTimeout(), is(60_000)); + assertThat(config.getCookieSpec(), is(CookieSpecs.STANDARD)); + } + + @Test + public void notifyStashUsesRequestParametersSlug() throws Exception { + notifyStash(204, "testProject", "someslug"); + + final ArgumentCaptor captor = ArgumentCaptor.forClass(RequestConfig.class); + verify(httpClientBuilder).setDefaultRequestConfig(captor.capture()); + + final RequestConfig config = captor.getValue(); + assertThat(config.getSocketTimeout(), is(60_000)); + assertThat(config.getConnectTimeout(), is(60_000)); + assertThat(config.getConnectionRequestTimeout(), is(60_000)); + assertThat(config.getCookieSpec(), is(CookieSpecs.STANDARD)); + } + + @NonNull + private NotificationResult notifyStash(int statusCode, String projectKey, String slug) throws Exception { + PrintStream logger = mock(PrintStream.class); + URI uri = BuildStatusUriFactory.create("http://localhost", projectKey, slug, "df02f57eea1cda72fa2412102f061dd7f6188e98"); + when(buildListener.getLogger()).thenReturn(logger); + CloseableHttpResponse resp = mock(CloseableHttpResponse.class); + StatusLine sl = mock(StatusLine.class); + when(sl.getStatusCode()).thenReturn(statusCode); + when(resp.getStatusLine()).thenReturn(sl); + when(resp.getEntity()).thenReturn(new StringEntity("")); + when(client.execute(any(HttpPost.class))).thenReturn(resp); + BuildInformation information = new BuildInformation("1", 30000, StashBuildState.SUCCESSFUL, "ci-testbuild", uri.toString(), "ci-testbuild", "A Testbuild for Stash/Bitbucket"); + return httpNotifier.send(uri, new NotificationSettings(false, null), new NotificationContext(logger, "some-build#15", information)); + } +} diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModuleTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModuleTest.java index 03361fd4..e2365134 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModuleTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModuleTest.java @@ -2,7 +2,8 @@ import com.google.common.collect.Lists; import edu.umd.cs.findbugs.annotations.NonNull; -import net.sf.json.JSONObject; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.junit.Test; import java.net.URI; @@ -68,7 +69,7 @@ public HttpNotifier select(@NonNull SelectionContext context) { private static class AlwaysSucceedsHttpNotifier implements HttpNotifier { @Override - public @NonNull NotificationResult send(@NonNull URI uri, @NonNull JSONObject payload, @NonNull NotificationSettings settings, @NonNull NotificationContext context) { + public @NonNull NotificationResult send(@NonNull URI uri, @NonNull NotificationSettings settings, @NonNull NotificationContext context) { return NotificationResult.newSuccess(); } } diff --git a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java index d5e38ea7..9a6fa49f 100644 --- a/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java +++ b/src/test/java/org/jenkinsci/plugins/stashNotifier/StashNotifierTest.java @@ -3,7 +3,6 @@ import hudson.EnvVars; import hudson.FilePath; import hudson.Launcher; -import hudson.ProxyConfiguration; import hudson.model.*; import hudson.plugins.git.Revision; import hudson.plugins.git.util.Build; @@ -13,21 +12,16 @@ import jenkins.model.JenkinsLocationConfiguration; import org.acegisecurity.Authentication; import org.apache.commons.lang.StringEscapeUtils; -import org.apache.http.HttpHost; import org.apache.http.StatusLine; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.AuthenticationStrategy; -import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.conn.HttpClientConnectionManager; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.jenkinsci.plugins.displayurlapi.DisplayURLProvider; +import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; +import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; import org.jenkinsci.plugins.tokenmacro.TokenMacro; import org.junit.AfterClass; @@ -39,10 +33,8 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; + import org.mockito.MockedStatic; import static org.hamcrest.MatcherAssert.assertThat; @@ -68,52 +60,24 @@ public class StashNotifierTest { final static String sha1 = "1234567890123456789012345678901234567890"; - private static HttpClientBuilder httpClientBuilder; private static CloseableHttpClient client; private static Jenkins jenkins; private static final HttpNotifierSelector httpNotifierSelector = mock(HttpNotifierSelector.class); private static final HttpNotifier httpNotifier = mock(HttpNotifier.class); private static final DisplayURLProvider displayURLProvider = mock(DisplayURLProvider.class); + private static BuildListener buildListener; + private static AbstractBuild build; + private static Run run; + private static FilePath workspace; + private static MockedStatic mockedJenkins; private static MockedStatic mockedCredentialsProvider; private static MockedStatic mockedSecret; private static MockedStatic mockedHttpClientBuilder; private static MockedStatic mockedDisplayURLProvider; - private StashNotifier buildStashNotifier(String stashBaseUrl) { - return buildStashNotifier(stashBaseUrl, false, false); - } - - private StashNotifier buildStashNotifier(String stashBaseUrl, - boolean disableInprogressNotification, - boolean considerUnstableAsSuccess) { - StashNotifier notifier = new StashNotifier( - stashBaseUrl, - "scot", - "", - "", - true, - null, - null, - null, - null, - true, - "test-project", - true, - disableInprogressNotification, - considerUnstableAsSuccess, - mock(JenkinsLocationConfiguration.class) - ); - notifier.setHttpNotifierSelector(httpNotifierSelector); - return notifier; - } - private StashNotifier sn; - private static BuildListener buildListener; - private static AbstractBuild build; - private static Run run; - private static FilePath workspace; @BeforeClass public static void setUp() throws Exception { @@ -137,7 +101,7 @@ public static void setUp() throws Exception { EnvVars environment = mock(EnvVars.class); PrintStream logger = System.out; Secret secret = mock(Secret.class); - httpClientBuilder = mock(HttpClientBuilder.class); + HttpClientBuilder httpClientBuilder = mock(HttpClientBuilder.class); client = mock(CloseableHttpClient.class); CloseableHttpResponse resp = mock(CloseableHttpResponse.class); HttpUriRequest req = mock(HttpUriRequest.class); @@ -182,11 +146,6 @@ public static void setUp() throws Exception { when(httpNotifierSelector.select(any())).thenReturn(httpNotifier); } - @Before - public void setup() { - sn = buildStashNotifier("http://localhost"); - } - @AfterClass public static void close() { mockedJenkins.close(); @@ -195,6 +154,11 @@ public static void close() { mockedHttpClientBuilder.close(); } + @Before + public void setup() { + sn = buildStashNotifier("http://localhost"); + } + @Test public void test_prebuild_normal() { assertTrue(sn.prebuild(build, buildListener)); @@ -206,121 +170,6 @@ public void test_prebuild_null_revision() { assertTrue(sn.prebuild(build, buildListener)); } - @Test - public void test_build_http_client_with_proxy() throws Exception { - //given - String address = "192.168.1.1"; - int port = 8080; - String login = "admin"; - String password = "123"; - - Secret secret = mock(Secret.class); - when(Secret.fromString(password)).thenReturn(secret); - when(Secret.toString(secret)).thenReturn(password); - when(secret.getPlainText()).thenReturn(password); - - when(httpClientBuilder.setProxy(any(HttpHost.class))).thenReturn(httpClientBuilder); - when(httpClientBuilder.setDefaultCredentialsProvider(any(CredentialsProvider.class))).thenReturn(httpClientBuilder); - when(httpClientBuilder.setProxyAuthenticationStrategy(any(AuthenticationStrategy.class))).thenReturn(httpClientBuilder); - - jenkins.proxy = new ProxyConfiguration(address, port, login, password); - PrintStream logger = mock(PrintStream.class); - - //when - sn.getHttpClient(logger, build, "http://localhost"); - - //then - ArgumentCaptor proxyCaptor = ArgumentCaptor.forClass(HttpHost.class); - verify(httpClientBuilder).setProxy(proxyCaptor.capture()); - HttpHost proxy = proxyCaptor.getValue(); - //address - assertThat(proxy.getHostName(), is(address)); - assertThat(proxy.getPort(), is(port)); - assertThat(proxy.getSchemeName(), is("http")); - - ArgumentCaptor credentialsProviderCaptor = ArgumentCaptor.forClass(CredentialsProvider.class); - verify(httpClientBuilder).setDefaultCredentialsProvider(credentialsProviderCaptor.capture()); - CredentialsProvider credentialsProvider = credentialsProviderCaptor.getValue(); - org.apache.http.auth.UsernamePasswordCredentials credentials = (UsernamePasswordCredentials) credentialsProvider.getCredentials(new AuthScope(proxy)); - //credentials - assertThat(credentials.getUserName(), is(login)); - assertThat(credentials.getPassword(), is(password)); - } - - @Test - public void test_build_http_client_https() throws Exception { - //given - sn = new StashNotifier( - "https://localhost", - "scot", - "", - "", - true, - null, - null, - null, - null, - true, - null, - false, - false, - false, - mock(JenkinsLocationConfiguration.class)); - - PrintStream logger = mock(PrintStream.class); - - //when - sn.getHttpClient(logger, build, "https://localhost"); - - //then - verify(httpClientBuilder).setSSLSocketFactory(any(SSLConnectionSocketFactory.class)); - verify(httpClientBuilder).setConnectionManager(any(HttpClientConnectionManager.class)); - } - - private void test_perform_buildstep(Result result, - PrintStream logger, - NotificationResult notificationResult, - List hashes) { - //given - Launcher launcher = test_perform(result, logger, notificationResult, hashes); - - //when - boolean perform = sn.perform(build, launcher, buildListener); - - //then - assertThat(perform, is(true)); - } - - private void test_perform_simplebuildstep(Result result, - PrintStream logger, - NotificationResult notificationResult, - List hashes) { - //given - Launcher launcher = test_perform(result, logger, notificationResult, hashes); - - //when - sn.perform(build, workspace, launcher, buildListener); - - //then - assertThat(build.getResult(), is(result)); - } - - private Launcher test_perform(Result result, PrintStream logger, NotificationResult notificationResult, List hashes) { - when(buildListener.getLogger()).thenReturn(logger); - when(build.getResult()).thenReturn(result); - Launcher launcher = mock(Launcher.class); - sn = spy(sn); - doReturn(hashes).when(sn).lookupCommitSha1s(eq(build), nullable(FilePath.class), eq(buildListener)); - doReturn(notificationResult).when(sn).notifyStash( - any(PrintStream.class), - any(AbstractBuild.class), - eq(sha1), - eq(buildListener), - any(StashBuildState.class) - ); - return launcher; - } - @Test public void test_perform_build_step_success() { //given @@ -531,38 +380,6 @@ public void lookupCommitSha1s() { } } - private void lookupCommitSha1s_Exception(Exception e) { - //given - PrintStream logger = mock(PrintStream.class); - when(buildListener.getLogger()).thenReturn(logger); - try (MockedStatic tokenMacroMock = mockStatic(TokenMacro.class)) { - tokenMacroMock.when(() -> TokenMacro.expandAll(any(), any(), any())).thenThrow(e); - sn = new StashNotifier( - "http://localhost", - "scot", - "", - "", - true, - sha1, - null, - null, - null, - true, - null, - false, - false, - false, - mock(JenkinsLocationConfiguration.class)); - - //when - Collection hashes = sn.lookupCommitSha1s(build, null, buildListener); - - //then - assertThat(hashes.isEmpty(), is(true)); - verify(logger).println("Unable to expand commit SHA value"); - } - } - @Test public void test_lookupCommitSha1s_IOException() { lookupCommitSha1s_Exception(new IOException("BOOM")); @@ -591,10 +408,6 @@ public void test_getBuildDescription() { assertThat(description, is("some description")); } - private String getBuildDescriptionWhenBuildDescriptionIsNull(StashBuildState buildState) { - return sn.getBuildDescription(mock(AbstractBuild.class), buildState); - } - @Test public void test_getBuildDescription_state() { assertThat(getBuildDescriptionWhenBuildDescriptionIsNull(StashBuildState.SUCCESSFUL), is("built by Jenkins @ http://localhost/")); @@ -842,6 +655,50 @@ public void test_getBuildKey_withBuildName() { //then assertThat(buildKey, is(StringEscapeUtils.escapeJavaScript(parentName + "-" + number + "-" + jenkins.getRootUrl() + "-" + buildName))); + + //cleanup + when(build.getParent().getName()).thenCallRealMethod(); + when(build.getNumber()).thenCallRealMethod(); + } + + @Test + public void test_getBuildKey_BuildName_ProjectKey_Slug() { + //given + String parentName = "someKey"; + int number = 11; + String buildName = "buildName"; + String projectKey = "codetest"; + String slug = "mybitbucketslug"; + + when(build.getParent().getName()).thenReturn(parentName); + when(build.getNumber()).thenReturn(number); + + sn = new StashNotifier( + "", + "scot", + projectKey, + slug, + true, + null, + null, + buildName, + null, + true, + null, + true, + false, + false, + mock(JenkinsLocationConfiguration.class)); + + //when + String buildKey = sn.getBuildKey(build, buildListener); + + //then + assertThat(buildKey, is(StringEscapeUtils.escapeJavaScript(parentName + "-" + number + "-" + jenkins.getRootUrl() + "-" + buildName))); + + //cleanup + when(build.getParent().getName()).thenCallRealMethod(); + when(build.getNumber()).thenCallRealMethod(); } @Test @@ -881,6 +738,125 @@ public void test_getRunKey() throws Exception { } } + @Test + public void test_getBuildKey_IOException() { + getBuildKey_Exception(new IOException("BOOM")); + } + + @Test + public void test_getBuildKey_InterruptedException() { + getBuildKey_Exception(new InterruptedException("BOOM")); + } + + @Test + public void test_getBuildKey_MacroEvaluationException() { + getBuildKey_Exception(new MacroEvaluationException("BOOM")); + } + + @Test + public void test_getRunKey_IOException() throws Exception { + getRunKey_Exception(new IOException("BOOM")); + } + + @Test + public void test_getRunKey_InterruptedException() throws Exception { + getRunKey_Exception(new InterruptedException("BOOM")); + } + + @Test + public void test_getRunKey_MacroEvaluationException() throws Exception { + getRunKey_Exception(new MacroEvaluationException("BOOM")); + } + + @Test + public void notifyStashDelegatesToHttpNotifier() throws Exception { + NotificationResult result = NotificationResult.newFailure("some value for test"); + when(httpNotifier.send(any(), any(), any())).thenReturn(result); + NotificationResult notificationResult = notifyStash(204); + verify(httpNotifier).send(any(), any(), any()); + assertThat(notificationResult, equalTo(result)); + } + + @Test + public void setBuildStatus_string() { + sn.setBuildStatus("SUCCESSFUL"); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); + + sn.setBuildStatus("FAILED"); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.FAILED)); + + sn.setBuildStatus("INPROGRESS"); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.INPROGRESS)); + } + + @Test + public void setBuildStatus_stashBuildState() { + sn.setBuildStatus(StashBuildState.SUCCESSFUL); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); + + sn.setBuildStatus(StashBuildState.FAILED); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.FAILED)); + + sn.setBuildStatus(StashBuildState.INPROGRESS); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.INPROGRESS)); + } + + @Test + public void setBuildStatus_null() { + sn.setBuildStatus(null); + assertThat(sn.getBuildStatus(), nullValue()); + + sn.setBuildStatus(StashBuildState.SUCCESSFUL); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); + + sn.setBuildStatus(null); + assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); + } + + private void test_perform_buildstep(Result result, + PrintStream logger, + NotificationResult notificationResult, + List hashes) { + //given + Launcher launcher = test_perform(result, logger, notificationResult, hashes); + + //when + boolean perform = sn.perform(build, launcher, buildListener); + + //then + assertThat(perform, is(true)); + } + + private void test_perform_simplebuildstep(Result result, + PrintStream logger, + NotificationResult notificationResult, + List hashes) { + //given + Launcher launcher = test_perform(result, logger, notificationResult, hashes); + + //when + sn.perform(build, workspace, launcher, buildListener); + + //then + assertThat(build.getResult(), is(result)); + } + + private Launcher test_perform(Result result, PrintStream logger, NotificationResult notificationResult, List hashes) { + when(buildListener.getLogger()).thenReturn(logger); + when(build.getResult()).thenReturn(result); + Launcher launcher = mock(Launcher.class); + sn = spy(sn); + doReturn(hashes).when(sn).lookupCommitSha1s(eq(build), nullable(FilePath.class), eq(buildListener)); + doReturn(notificationResult).when(sn).notifyStash( + any(PrintStream.class), + any(AbstractBuild.class), + eq(sha1), + eq(buildListener), + any(StashBuildState.class) + ); + return launcher; + } + private void getBuildKey_Exception(Exception e) { //given String key = "someKey"; @@ -953,34 +929,40 @@ private void getRunKey_Exception(Exception e) throws IOException { } } - @Test - public void test_getBuildKey_IOException() { - getBuildKey_Exception(new IOException("BOOM")); - } - - @Test - public void test_getBuildKey_InterruptedException() { - getBuildKey_Exception(new InterruptedException("BOOM")); - } - - @Test - public void test_getBuildKey_MacroEvaluationException() { - getBuildKey_Exception(new MacroEvaluationException("BOOM")); - } + private void lookupCommitSha1s_Exception(Exception e) { + //given + PrintStream logger = mock(PrintStream.class); + when(buildListener.getLogger()).thenReturn(logger); + try (MockedStatic tokenMacroMock = mockStatic(TokenMacro.class)) { + tokenMacroMock.when(() -> TokenMacro.expandAll(any(), any(), any())).thenThrow(e); + sn = new StashNotifier( + "http://localhost", + "scot", + "", + "", + true, + sha1, + null, + null, + null, + true, + null, + false, + false, + false, + mock(JenkinsLocationConfiguration.class)); - @Test - public void test_getRunKey_IOException() throws Exception { - getRunKey_Exception(new IOException("BOOM")); - } + //when + Collection hashes = sn.lookupCommitSha1s(build, null, buildListener); - @Test - public void test_getRunKey_InterruptedException() throws Exception { - getRunKey_Exception(new InterruptedException("BOOM")); + //then + assertThat(hashes.isEmpty(), is(true)); + verify(logger).println("Unable to expand commit SHA value"); + } } - @Test - public void test_getRunKey_MacroEvaluationException() throws Exception { - getRunKey_Exception(new MacroEvaluationException("BOOM")); + private String getBuildDescriptionWhenBuildDescriptionIsNull(StashBuildState buildState) { + return sn.getBuildDescription(mock(AbstractBuild.class), buildState); } private NotificationResult notifyStash(int statusCode) throws Exception { @@ -997,53 +979,35 @@ private NotificationResult notifyStash(int statusCode) throws Exception { when(client.execute(eq(httpPost))).thenReturn(resp); try (MockedStatic tokenMacroMock = mockStatic(TokenMacro.class) ) { tokenMacroMock.when(() -> TokenMacro.expandAll(any(), any(), any())).thenReturn("http://localhost"); - doReturn(client).when(sn).getHttpClient(any(PrintStream.class), any(AbstractBuild.class), anyString()); return sn.notifyStash(logger, build, sha1, buildListener, StashBuildState.FAILED); } } - @Test - public void notifyStashDelegatesToHttpNotifier() throws Exception { - NotificationResult result = NotificationResult.newFailure("some value for test"); - when(httpNotifier.send(any(), any(), any(), any())).thenReturn(result); - NotificationResult notificationResult = notifyStash(204); - verify(httpNotifier).send(any(), any(), any(), any()); - assertThat(notificationResult, equalTo(result)); - } - - @Test - public void setBuildStatus_string() { - sn.setBuildStatus("SUCCESSFUL"); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); - - sn.setBuildStatus("FAILED"); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.FAILED)); - - sn.setBuildStatus("INPROGRESS"); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.INPROGRESS)); - } - - @Test - public void setBuildStatus_stashBuildState() { - sn.setBuildStatus(StashBuildState.SUCCESSFUL); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); - - sn.setBuildStatus(StashBuildState.FAILED); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.FAILED)); - - sn.setBuildStatus(StashBuildState.INPROGRESS); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.INPROGRESS)); + private StashNotifier buildStashNotifier(String stashBaseUrl) { + return buildStashNotifier(stashBaseUrl, false, false); } - @Test - public void setBuildStatus_null() { - sn.setBuildStatus(null); - assertThat(sn.getBuildStatus(), nullValue()); - - sn.setBuildStatus(StashBuildState.SUCCESSFUL); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); - - sn.setBuildStatus(null); - assertThat(sn.getBuildStatus(), equalTo(StashBuildState.SUCCESSFUL)); + private StashNotifier buildStashNotifier(String stashBaseUrl, + boolean disableInprogressNotification, + boolean considerUnstableAsSuccess) { + StashNotifier notifier = new StashNotifier( + stashBaseUrl, + "scot", + "", + "", + true, + null, + null, + null, + null, + true, + "test-project", + true, + disableInprogressNotification, + considerUnstableAsSuccess, + mock(JenkinsLocationConfiguration.class) + ); + notifier.setHttpNotifierSelector(httpNotifierSelector); + return notifier; } } From 1338d77e6d321b04e941591cc80c761f410344bb Mon Sep 17 00:00:00 2001 From: "Boillat, Marcus" Date: Mon, 3 Jun 2024 22:50:57 +0200 Subject: [PATCH 4/4] Issue #290: Use ExtendedApacheHttpNotifier to create payload for builds --- .../DefaultHttpNotifierSelector.java | 34 ++++++++++++------- .../ExtendedHttpNotifierSelector.java | 26 -------------- .../stashNotifier/StashNotifierModule.java | 12 ++++--- 3 files changed, 30 insertions(+), 42 deletions(-) delete mode 100644 src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java index 5f22fcbd..d4ad925a 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/DefaultHttpNotifierSelector.java @@ -2,26 +2,36 @@ import edu.umd.cs.findbugs.annotations.NonNull; import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.ExtendedApacheHttpNotifier; import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.jenkinsci.plugins.stashNotifier.SelectionContext; -/** - * This is the default way of selecting a {@link HttpNotifier}. - * Always returns {@link DefaultApacheHttpNotifier} for backwards compatibility with v1.20 and earlier. - */ +import java.util.*; + public class DefaultHttpNotifierSelector implements HttpNotifierSelector { - private final HttpNotifier httpNotifier; + private final List httpNotifiers; + + public DefaultHttpNotifierSelector(List notifiers) { + httpNotifiers = notifiers; + } - public DefaultHttpNotifierSelector(HttpNotifier httpNotifier) { - this.httpNotifier = httpNotifier; + public DefaultHttpNotifierSelector(HttpNotifier notifier) { + httpNotifiers = new ArrayList<>(); + httpNotifiers.add(notifier); } - /** - * @param context unused - * @return singleton {@link DefaultApacheHttpNotifier} - */ @Override public HttpNotifier select(@NonNull SelectionContext context) { - return httpNotifier; + if(context.getBitBucketProjectKey() == null || context.getBitBucketProjectKey().isEmpty() || context.getBitBucketProjectSlug() == null || context.getBitBucketProjectSlug().isEmpty()) + { + Optional defaultNotifier = httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(DefaultApacheHttpNotifier.class.getName())).findFirst(); + if(defaultNotifier.isPresent()) { + return defaultNotifier.get(); + } + } + + Optional extendedNotifier = httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(ExtendedApacheHttpNotifier.class.getName())).findFirst(); + + return extendedNotifier.orElseGet(() -> httpNotifiers.get(0)); } } diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java deleted file mode 100644 index f8c358bb..00000000 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/NotifierSelectors/ExtendedHttpNotifierSelector.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.jenkinsci.plugins.stashNotifier.NotifierSelectors; - -import edu.umd.cs.findbugs.annotations.NonNull; -import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; -import org.jenkinsci.plugins.stashNotifier.Notifiers.ExtendedApacheHttpNotifier; -import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; -import org.jenkinsci.plugins.stashNotifier.SelectionContext; - -import java.util.List; - -public class ExtendedHttpNotifierSelector implements HttpNotifierSelector { - private final List httpNotifiers; - - public ExtendedHttpNotifierSelector(List notifiers) { - httpNotifiers = notifiers; - } - - @Override - public HttpNotifier select(@NonNull SelectionContext context) { - if(context.getBitBucketProjectKey().isEmpty() || context.getBitBucketProjectSlug().isEmpty()) - { - return httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(DefaultApacheHttpNotifier.class.getName())).findFirst().get(); - } - return httpNotifiers.stream().filter(hn -> hn.getClass().getName().equals(ExtendedApacheHttpNotifier.class.getName())).findFirst().get(); - } -} diff --git a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java index f66d263b..c1f50b3e 100644 --- a/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java +++ b/src/main/java/org/jenkinsci/plugins/stashNotifier/StashNotifierModule.java @@ -7,12 +7,16 @@ import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.DefaultHttpNotifierSelector; import org.jenkinsci.plugins.stashNotifier.NotifierSelectors.HttpNotifierSelector; import org.jenkinsci.plugins.stashNotifier.Notifiers.DefaultApacheHttpNotifier; +import org.jenkinsci.plugins.stashNotifier.Notifiers.ExtendedApacheHttpNotifier; import org.jenkinsci.plugins.stashNotifier.Notifiers.HttpNotifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jakarta.inject.Named; import jakarta.inject.Singleton; + +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; @Extension @@ -26,15 +30,15 @@ protected void configure() { @Provides @Singleton @StashNotifierDefault - HttpNotifier providesDefaultHttpNotifier() { - return new DefaultApacheHttpNotifier(); + List providesDefaultHttpNotifiers() { + return new ArrayList<>(Arrays.asList(new DefaultApacheHttpNotifier(), new ExtendedApacheHttpNotifier())); } @Provides @Singleton @StashNotifierDefault - HttpNotifierSelector providesDefaultApacheHttpNotifierSelector(@StashNotifierDefault HttpNotifier httpNotifier) { - return new DefaultHttpNotifierSelector(httpNotifier); + HttpNotifierSelector providesDefaultApacheHttpNotifierSelector(@StashNotifierDefault List httpNotifiers) { + return new DefaultHttpNotifierSelector(httpNotifiers); } @Provides