Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-6933] Handle proxy settings #72

Merged
merged 16 commits into from
Feb 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading