-
Notifications
You must be signed in to change notification settings - Fork 143
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #273 from sghill/iso-client
Swappable HTTP Client
- Loading branch information
Showing
15 changed files
with
827 additions
and
54 deletions.
There are no files selected for viewing
16 changes: 16 additions & 0 deletions
16
src/main/java/org/jenkinsci/plugins/stashNotifier/BuildStatusUriFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import org.apache.commons.lang.StringUtils; | ||
|
||
import java.net.URI; | ||
|
||
public class BuildStatusUriFactory { | ||
private BuildStatusUriFactory() { | ||
} | ||
|
||
public static URI create(String baseUri, String commit) { | ||
String tidyBase = StringUtils.removeEnd(baseUri.toString(), "/"); | ||
String uri = String.join("/", tidyBase, "rest/build-status/1.0/commits", commit); | ||
return URI.create(uri); | ||
} | ||
} |
187 changes: 187 additions & 0 deletions
187
src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultApacheHttpNotifier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import com.cloudbees.plugins.credentials.Credentials; | ||
import com.cloudbees.plugins.credentials.common.CertificateCredentials; | ||
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; | ||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import hudson.ProxyConfiguration; | ||
import jenkins.model.Jenkins; | ||
import net.sf.json.JSONObject; | ||
import org.apache.http.HttpHeaders; | ||
import org.apache.http.HttpHost; | ||
import org.apache.http.HttpResponse; | ||
import org.apache.http.auth.AuthScope; | ||
import org.apache.http.auth.AuthenticationException; | ||
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.BasicCredentialsProvider; | ||
import org.apache.http.impl.client.CloseableHttpClient; | ||
import org.apache.http.impl.client.HttpClientBuilder; | ||
import org.apache.http.impl.client.HttpClients; | ||
import org.apache.http.impl.client.ProxyAuthenticationStrategy; | ||
import org.apache.http.impl.conn.BasicHttpClientConnectionManager; | ||
import org.apache.http.ssl.SSLContextBuilder; | ||
import org.apache.http.ssl.SSLContexts; | ||
import org.apache.http.util.EntityUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import javax.net.ssl.SSLContext; | ||
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; | ||
|
||
class DefaultApacheHttpNotifier implements HttpNotifier { | ||
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) { | ||
PrintStream logger = context.getLogger(); | ||
try (CloseableHttpClient client = getHttpClient(logger, uri, settings.isIgnoreUnverifiedSSL())) { | ||
HttpPost req = createRequest(uri, payload, settings.getCredentials()); | ||
HttpResponse res = client.execute(req); | ||
if (res.getStatusLine().getStatusCode() != 204) { | ||
return NotificationResult.newFailure(EntityUtils.toString(res.getEntity())); | ||
} else { | ||
return NotificationResult.newSuccess(); | ||
} | ||
} catch (Exception e) { | ||
LOGGER.warn("{} failed to send {} to Bitbucket Server at {}", context.getRunId(), payload, uri, e); | ||
logger.println("Failed to notify Bitbucket Server"); | ||
return NotificationResult.newFailure(e.getMessage()); | ||
} | ||
} | ||
|
||
HttpPost createRequest( | ||
final URI uri, | ||
final JSONObject payload, | ||
final UsernamePasswordCredentials credentials) throws AuthenticationException { | ||
|
||
HttpPost req = new HttpPost(uri.toString()); | ||
|
||
if (credentials != null) { | ||
req.addHeader(new BasicScheme().authenticate( | ||
new org.apache.http.auth.UsernamePasswordCredentials( | ||
credentials.getUsername(), | ||
credentials.getPassword().getPlainText()), | ||
req, | ||
null)); | ||
} | ||
|
||
req.addHeader(HttpHeaders.CONTENT_TYPE, "application/json"); | ||
req.setEntity(new StringEntity(payload.toString(), "UTF-8")); | ||
|
||
return req; | ||
} | ||
|
||
CloseableHttpClient getHttpClient(PrintStream logger, URI stashServer, boolean ignoreUnverifiedSSL) throws Exception { | ||
final int timeoutInMilliseconds = 60_000; | ||
|
||
RequestConfig.Builder requestBuilder = RequestConfig.custom() | ||
.setSocketTimeout(timeoutInMilliseconds) | ||
.setConnectTimeout(timeoutInMilliseconds) | ||
.setConnectionRequestTimeout(timeoutInMilliseconds); | ||
|
||
HttpClientBuilder clientBuilder = HttpClients.custom(); | ||
clientBuilder.setDefaultRequestConfig(requestBuilder.build()); | ||
|
||
URL url = stashServer.toURL(); | ||
|
||
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<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | ||
.register("https", sslConnSocketFactory) | ||
.register("http", PlainConnectionSocketFactory.INSTANCE) | ||
.build(); | ||
|
||
HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(registry); | ||
clientBuilder.setConnectionManager(connectionManager); | ||
} catch (NoSuchAlgorithmException nsae) { | ||
logger.println("Couldn't establish SSL context:"); | ||
nsae.printStackTrace(logger); | ||
} catch (KeyManagementException | KeyStoreException e) { | ||
logger.println("Couldn't initialize SSL context:"); | ||
e.printStackTrace(logger); | ||
} | ||
} | ||
|
||
// Configure the proxy, if needed | ||
// Using the Jenkins methods handles the noProxyHost settings | ||
configureProxy(clientBuilder, url); | ||
|
||
return clientBuilder.build(); | ||
} | ||
|
||
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(); | ||
} | ||
|
||
void configureProxy(HttpClientBuilder builder, URL url) { | ||
Jenkins jenkins = Jenkins.getInstance(); | ||
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 == null || !(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()); | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/java/org/jenkinsci/plugins/stashNotifier/DefaultHttpNotifierSelector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
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; | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifier.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import net.sf.json.JSONObject; | ||
|
||
import java.net.URI; | ||
|
||
/** | ||
* Implement this interface to change the way requests are made to Bitbucket. | ||
*/ | ||
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); | ||
} |
30 changes: 30 additions & 0 deletions
30
src/main/java/org/jenkinsci/plugins/stashNotifier/HttpNotifierSelector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import edu.umd.cs.findbugs.annotations.NonNull; | ||
import hudson.FilePath; | ||
import hudson.Launcher; | ||
import hudson.model.AbstractBuild; | ||
import hudson.model.BuildListener; | ||
import hudson.model.Run; | ||
import hudson.model.TaskListener; | ||
|
||
/** | ||
* Implement this interface to have more control over which {@link HttpNotifier} | ||
* will be used at runtime. | ||
* | ||
* @see DefaultHttpNotifierSelector | ||
*/ | ||
public interface HttpNotifierSelector { | ||
|
||
/** | ||
* Invoked once per Bitbucket notification. {@link SelectionContext} makes | ||
* 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 | ||
*/ | ||
@NonNull HttpNotifier select(@NonNull SelectionContext context); | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import hudson.model.Run; | ||
|
||
import java.io.PrintStream; | ||
|
||
/** | ||
* Properties from the build where this is running. | ||
*/ | ||
public class NotificationContext { | ||
private final PrintStream logger; | ||
private final String runId; | ||
|
||
public NotificationContext(PrintStream logger, String runId) { | ||
this.logger = logger; | ||
this.runId = runId; | ||
} | ||
|
||
/** | ||
* Anything logged here will show up in the running build's console log. | ||
* | ||
* @return handle to build's log | ||
*/ | ||
public PrintStream getLogger() { | ||
return logger; | ||
} | ||
|
||
/** | ||
* This is the {@link Run#getExternalizableId()} from the running build, | ||
* useful for detailed server-side logging (such as through slf4j). | ||
* | ||
* @return build's id | ||
*/ | ||
public String getRunId() { | ||
return runId; | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/org/jenkinsci/plugins/stashNotifier/NotificationSettings.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package org.jenkinsci.plugins.stashNotifier; | ||
|
||
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; | ||
import edu.umd.cs.findbugs.annotations.CheckForNull; | ||
|
||
/** | ||
* Properties defined by a user or administrator about how they want the | ||
* notification to be sent. | ||
*/ | ||
public class NotificationSettings { | ||
private final boolean ignoreUnverifiedSSL; | ||
private final UsernamePasswordCredentials credentials; | ||
|
||
public NotificationSettings(boolean ignoreUnverifiedSSL, UsernamePasswordCredentials credentials) { | ||
this.ignoreUnverifiedSSL = ignoreUnverifiedSSL; | ||
this.credentials = credentials; | ||
} | ||
|
||
public boolean isIgnoreUnverifiedSSL() { | ||
return ignoreUnverifiedSSL; | ||
} | ||
|
||
@CheckForNull | ||
public UsernamePasswordCredentials getCredentials() { | ||
return credentials; | ||
} | ||
} |
Oops, something went wrong.