Skip to content

Commit

Permalink
Merge pull request #72 from jenkinsci/handle_proxy_settings
Browse files Browse the repository at this point in the history
[JENKINS-6933] Handle proxy settings
  • Loading branch information
olivierdagenais committed Feb 26, 2016
2 parents dfe4480 + 28552ce commit 2fb1175
Show file tree
Hide file tree
Showing 30 changed files with 657 additions and 21 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -241,6 +241,12 @@
<version>1.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.littleshoot</groupId>
<artifactId>littleproxy</artifactId>
<version>1.1.0-beta1</version>
<scope>test</scope>
</dependency>
</dependencies>

<repositories>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/hudson/plugins/tfs/TeamFoundationServerScm.java
Expand Up @@ -565,6 +565,9 @@ protected PollingResult compareRemoteRevisionWith(
? Change.NONE
: Change.SIGNIFICANT;
return new PollingResult(tfsBaseline, tfsRemote, change);
} catch (final Exception e) {
e.printStackTrace(listener.fatalError(e.getMessage()));
return PollingResult.NO_CHANGES;
} finally {
server.close();
}
Expand Down
Expand Up @@ -2,6 +2,7 @@

import hudson.model.TaskListener;
import hudson.plugins.tfs.model.Server;
import hudson.plugins.tfs.model.WebProxySettings;
import hudson.remoting.Callable;

import java.io.IOException;
Expand All @@ -13,16 +14,18 @@ public abstract class AbstractCallableCommand implements Serializable {
private final String userName;
private final String userPassword;
private final TaskListener listener;
private final WebProxySettings webProxySettings;

protected AbstractCallableCommand(final ServerConfigurationProvider serverConfig) {
url = serverConfig.getUrl();
userName = serverConfig.getUserName();
userPassword = serverConfig.getUserPassword();
listener = serverConfig.getListener();
webProxySettings = serverConfig.getWebProxySettings();
}

public Server createServer() throws IOException {
final Server server = new Server(null, listener, url, userName, userPassword);
final Server server = new Server(null, listener, url, userName, userPassword, webProxySettings);
return server;
}

Expand Down
@@ -1,6 +1,7 @@
package hudson.plugins.tfs.commands;

import hudson.model.TaskListener;
import hudson.plugins.tfs.model.WebProxySettings;

public interface ServerConfigurationProvider {

Expand All @@ -11,4 +12,6 @@ public interface ServerConfigurationProvider {
public String getUserPassword();

public TaskListener getListener();

public WebProxySettings getWebProxySettings();
}
@@ -1,5 +1,6 @@
package hudson.plugins.tfs.model;

import com.microsoft.tfs.core.TFSTeamProjectCollection;
import com.microsoft.tfs.core.clients.versioncontrol.VersionControlClient;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceLocation;
import com.microsoft.tfs.core.clients.versioncontrol.WorkspaceOptions;
Expand Down Expand Up @@ -73,6 +74,11 @@ public void deleteWorkspace(final Workspace workspace) {
vcc.deleteWorkspace(workspace);
}

public TFSTeamProjectCollection getConnection() {
makeSureNotClosed();
return vcc.getConnection();
}

public VersionControlEventEngine getEventEngine() {
makeSureNotClosed();
return vcc.getEventEngine();
Expand Down
@@ -0,0 +1,24 @@
package hudson.plugins.tfs.model;

import java.util.Locale;
import java.util.TimeZone;

import com.microsoft.tfs.core.config.ConnectionInstanceData;
import com.microsoft.tfs.core.config.DefaultConnectionAdvisor;
import com.microsoft.tfs.core.config.httpclient.HTTPClientFactory;
import com.microsoft.tfs.core.httpclient.ProxyHost;

public class ModernConnectionAdvisor extends DefaultConnectionAdvisor {

private final ProxyHost proxyHost;

public ModernConnectionAdvisor(final ProxyHost proxyHost) {
super(Locale.getDefault(), TimeZone.getDefault());
this.proxyHost = proxyHost;
}

@Override
public HTTPClientFactory getHTTPClientFactory(final ConnectionInstanceData connectionInstanceData) {
return new ModernHTTPClientFactory(connectionInstanceData, proxyHost);
}
}
@@ -0,0 +1,27 @@
package hudson.plugins.tfs.model;

import com.microsoft.tfs.core.config.ConnectionInstanceData;
import com.microsoft.tfs.core.config.httpclient.DefaultHTTPClientFactory;
import com.microsoft.tfs.core.httpclient.HostConfiguration;
import com.microsoft.tfs.core.httpclient.HttpClient;
import com.microsoft.tfs.core.httpclient.HttpState;
import com.microsoft.tfs.core.httpclient.ProxyHost;

public class ModernHTTPClientFactory extends DefaultHTTPClientFactory {

private final ProxyHost proxyHost;

public ModernHTTPClientFactory(final ConnectionInstanceData connectionInstanceData) {
this(connectionInstanceData, null);
}

public ModernHTTPClientFactory(final ConnectionInstanceData connectionInstanceData, final ProxyHost proxyHost) {
super(connectionInstanceData);
this.proxyHost = proxyHost;
}

@Override
public void configureClientProxy(final HttpClient httpClient, final HostConfiguration hostConfiguration,final HttpState httpState, final ConnectionInstanceData connectionInstanceData) {
hostConfiguration.setProxyHost(proxyHost);
}
}
53 changes: 52 additions & 1 deletion src/main/java/hudson/plugins/tfs/model/Server.java
Expand Up @@ -5,7 +5,9 @@
import com.microsoft.tfs.core.clients.webservices.IIdentityManagementService;
import com.microsoft.tfs.core.clients.webservices.IdentityManagementException;
import com.microsoft.tfs.core.clients.webservices.IdentityManagementService;
import com.microsoft.tfs.core.httpclient.ProxyHost;
import hudson.Launcher;
import hudson.ProxyConfiguration;
import hudson.model.TaskListener;
import hudson.plugins.tfs.commands.ServerConfigurationProvider;

Expand All @@ -28,6 +30,7 @@
import com.microsoft.tfs.util.Closable;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import jenkins.model.Jenkins;

public class Server implements ServerConfigurationProvider, Closable {

Expand All @@ -40,9 +43,14 @@ public class Server implements ServerConfigurationProvider, Closable {
private final Launcher launcher;
private final TaskListener taskListener;
private final TFSTeamProjectCollection tpc;
private final WebProxySettings webProxySettings;
private MockableVersionControlClient mockableVcc;

public Server(final Launcher launcher, final TaskListener taskListener, final String url, final String username, final String password) throws IOException {
this(launcher, taskListener, url, username, password, null);
}

public Server(final Launcher launcher, final TaskListener taskListener, final String url, final String username, final String password, final WebProxySettings webProxySettings) throws IOException {
this.launcher = launcher;
this.taskListener = taskListener;
this.url = url;
Expand All @@ -63,9 +71,21 @@ else if (username != null && password != null) {
}

if (credentials != null) {
this.tpc = new TFSTeamProjectCollection(uri, credentials);
if (webProxySettings != null) {
this.webProxySettings = webProxySettings;
}
else {
final VirtualChannel channel = launcher != null ? launcher.getChannel() : null;
final ProxyConfiguration proxyConfiguration = determineProxyConfiguration(channel);
this.webProxySettings = new WebProxySettings(proxyConfiguration);
}
final String host = uri.getHost();
final ProxyHost proxyHost = this.webProxySettings.toProxyHost(host);
final ModernConnectionAdvisor advisor = new ModernConnectionAdvisor(proxyHost);
this.tpc = new TFSTeamProjectCollection(uri, credentials, advisor);
}
else {
this.webProxySettings = null;
this.tpc = null;
}
}
Expand All @@ -74,6 +94,33 @@ else if (username != null && password != null) {
this(null, null, url, null, null);
}

static ProxyConfiguration determineProxyConfiguration(final VirtualChannel channel) {
final Jenkins jenkins = Jenkins.getInstance();
final ProxyConfiguration proxyConfiguration;
if (jenkins == null) {
if (channel != null) {
try {
proxyConfiguration = channel.call(new Callable<ProxyConfiguration, Throwable>() {
public ProxyConfiguration call() throws Throwable {
final Jenkins jenkins = Jenkins.getInstance();
final ProxyConfiguration result = jenkins != null ? jenkins.proxy : null;
return result;
}
});
} catch (final Throwable throwable) {
throw new Error(throwable);
}
}
else {
proxyConfiguration = null;
}
}
else {
proxyConfiguration = jenkins.proxy;
}
return proxyConfiguration;
}

public Project getProject(String projectPath) {
if (! projects.containsKey(projectPath)) {
projects.put(projectPath, new Project(this, projectPath));
Expand Down Expand Up @@ -127,6 +174,10 @@ public Launcher getLauncher() {
return launcher;
}

public WebProxySettings getWebProxySettings() {
return webProxySettings;
}

public TaskListener getListener() {
return taskListener;
}
Expand Down
134 changes: 134 additions & 0 deletions src/main/java/hudson/plugins/tfs/model/WebProxySettings.java
@@ -0,0 +1,134 @@
package hudson.plugins.tfs.model;

import com.microsoft.tfs.core.httpclient.ProxyHost;
import hudson.ProxyConfiguration;
import hudson.util.Secret;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

/**
* A {@link Serializable} adapter between {@link ProxyConfiguration} and {@link ProxyHost}.
*/
public class WebProxySettings implements Serializable {
private static final long serialVersionUID = 401L;

private final String hostName;
private final int port;
private final String proxyUser;
private final Secret proxySecret;
private final List<Pattern> noProxyHostPatterns;

@SuppressWarnings("unused" /* Needed by Serializable interface */)
private WebProxySettings() {
this(null, -1, null, null, null);
}

/**
* Convenience constructor, mostly for tests.
*
* @param hostName the name (or address) of the proxy server.
* May be {@code null}, meaning there is no proxy server configured.
* @param port the port that the proxy server is listening on
* @param noProxyHostPatterns a list of {@link Pattern} representing hosts that should not be proxied.
* May be {@code null}, meaning all hosts will be proxied.
* @param proxyUser the name of the user with which to authenticate to the proxy server.
* May be {@code null}, meaning the proxy server doesn't need authentication.
* @param proxySecret the password of the user with which to authenticate to the proxy server.
* May be {@code null}, meaning the proxy server doesn't need authentication.
*/
public WebProxySettings(final String hostName, final int port, final List<Pattern> noProxyHostPatterns, final String proxyUser, final Secret proxySecret) {
this.hostName = hostName;
this.port = port;
this.noProxyHostPatterns = copyNoProxyHostPatterns(noProxyHostPatterns);
this.proxyUser = proxyUser;
this.proxySecret = proxySecret;
}

/**
* Initialize a {@link WebProxySettings} from a Jenkins {@link ProxyConfiguration}.
*
* @param proxyConfiguration the proxy settings as obtained from Jenkins.
* May be {@code null}, meaning there is no proxy configured.
*/
public WebProxySettings(final ProxyConfiguration proxyConfiguration) {
if (proxyConfiguration != null) {
this.hostName = proxyConfiguration.name;
this.port = proxyConfiguration.port;
this.proxyUser = proxyConfiguration.getUserName();
this.noProxyHostPatterns = copyNoProxyHostPatterns(proxyConfiguration.getNoProxyHostPatterns());
this.proxySecret = Secret.fromString(proxyConfiguration.getEncryptedPassword());
}
else {
this.hostName = null;
this.port = -1;
this.proxyUser = null;
this.proxySecret = null;
this.noProxyHostPatterns = copyNoProxyHostPatterns(null);
}
}

private static ArrayList<Pattern> copyNoProxyHostPatterns(final List<Pattern> noProxyHostPatterns) {
return new ArrayList<Pattern>(
noProxyHostPatterns == null
? Collections.<Pattern>emptyList()
: noProxyHostPatterns
);
}

/**
* Initialize a {@link ProxyHost} from this {@link WebProxySettings} for the provided hostToProxy.
* May return null, which either means there is no proxy server configured or it does not apply
* to the provided hostToProxy.
*
* @param hostToProxy the name of the host for which proxying is considered.
* @return an instance of {@link ProxyHost} or {@code null} if no proxy is to be used.
*/
public ProxyHost toProxyHost(final String hostToProxy) {
final ProxyHost proxyHost;
if (this.hostName != null) {
final boolean shouldProxy = shouldProxy(hostToProxy, noProxyHostPatterns);
if (shouldProxy) {
// TODO: The version of httpclient used by the TFS SDK does not support proxy auth
proxyHost = new ProxyHost(hostName, port);
} else {
proxyHost = null;
}
}
else {
proxyHost = null;
}
return proxyHost;
}

static boolean shouldProxy(final String host, final List<Pattern> noProxyHostPatterns) {
// inspired by https://github.com/jenkinsci/git-client-plugin/commit/2fefeae06db79d09d6604994001f8f2bd21549e1
boolean shouldProxy = true;
for (final Pattern p : noProxyHostPatterns) {
if (p.matcher(host).matches()) {
shouldProxy = false;
break;
}
}
return shouldProxy;
}

public String getHostName() {
return hostName;
}

public int getPort() {
return port;
}

public String getProxyUser() {
return proxyUser;
}

public Secret getProxySecret() {
return proxySecret;
}
}

0 comments on commit 2fb1175

Please sign in to comment.