Update custom jgit connection factory for uri placeholders (#1037)
Fixes gh-1028 Fixes gh-1032
This commit is contained in:
committed by
Ryan Baxter
parent
e6b2d9eec0
commit
cf6b1f0a49
@@ -15,27 +15,41 @@
|
||||
*/
|
||||
package org.springframework.cloud.config.server.environment;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.eclipse.jgit.transport.http.HttpConnection;
|
||||
import org.eclipse.jgit.transport.http.apache.HttpClientConnection;
|
||||
|
||||
import org.springframework.cloud.config.server.support.HttpClientSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
/**
|
||||
* @author Dylan Roberts
|
||||
*/
|
||||
public class HttpClientConfigurableHttpConnectionFactory implements ConfigurableHttpConnectionFactory {
|
||||
Log log = LogFactory.getLog(getClass());
|
||||
|
||||
private Map<String, HttpClientBuilder> httpClientsByUri = new HashMap<>();
|
||||
private static final String PLACEHOLDER_PATTERN = "\\{(\\w+)}";
|
||||
|
||||
Map<String, HttpClientBuilder> httpClientBuildersByUri = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void addConfiguration(MultipleJGitEnvironmentProperties environmentProperties)
|
||||
@@ -53,22 +67,81 @@ public class HttpClientConfigurableHttpConnectionFactory implements Configurable
|
||||
|
||||
@Override
|
||||
public HttpConnection create(URL url, Proxy proxy) throws IOException {
|
||||
return new HttpClientConnection(url.toString(), proxy, lookupHttpClientBuilder(url).build());
|
||||
return new HttpClientConnection(url.toString(), null, lookupHttpClientBuilder(url).build());
|
||||
}
|
||||
|
||||
private void addHttpClient(JGitEnvironmentProperties properties) throws GeneralSecurityException {
|
||||
if (properties.getUri().startsWith("http")) {
|
||||
httpClientsByUri.put(properties.getUri(), HttpClientSupport.builder(properties));
|
||||
httpClientBuildersByUri.put(properties.getUri(), HttpClientSupport.builder(properties));
|
||||
}
|
||||
}
|
||||
|
||||
private HttpClientBuilder lookupHttpClientBuilder(URL url) throws MalformedURLException {
|
||||
String spec = url.toString();
|
||||
HttpClientBuilder builder = httpClientsByUri.get(spec);
|
||||
while (builder == null) {
|
||||
spec = spec.substring(0, spec.lastIndexOf(File.separator));
|
||||
builder = httpClientsByUri.get(spec);
|
||||
private HttpClientBuilder lookupHttpClientBuilder(final URL url) {
|
||||
Map<String, HttpClientBuilder> builderMap = httpClientBuildersByUri.entrySet().stream().filter(entry -> {
|
||||
String key = entry.getKey();
|
||||
String spec = getUrlWithPlaceholders(url, key);
|
||||
if (spec.equals(key)) {
|
||||
return true;
|
||||
}
|
||||
int index = spec.lastIndexOf("/");
|
||||
while (index != -1) {
|
||||
spec = spec.substring(0, index);
|
||||
if (spec.equals(key)) {
|
||||
return true;
|
||||
}
|
||||
index = spec.lastIndexOf("/");
|
||||
}
|
||||
return false;
|
||||
}).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
if (builderMap.isEmpty()) {
|
||||
log.warn(String.format("No custom http config found for URL: %s", url));
|
||||
return HttpClients.custom();
|
||||
}
|
||||
return builder;
|
||||
if (builderMap.size() > 1) {
|
||||
log.error(String.format("More than one git repo URL template matched URL: %s, proxy and skipSslValidation config won't be applied. Matched templates: %s", url, builderMap.keySet().stream().collect(Collectors.joining(", "))));
|
||||
return HttpClients.custom();
|
||||
}
|
||||
return new ArrayList<>(builderMap.values()).get(0);
|
||||
}
|
||||
|
||||
private String getUrlWithPlaceholders(URL url, String key) {
|
||||
String spec = url.toString();
|
||||
String[] tokens = key.split(PLACEHOLDER_PATTERN);
|
||||
if (tokens.length > 1) {
|
||||
List<String> placeholders = getPlaceholders(key);
|
||||
List<String> values = getValues(spec, tokens);
|
||||
if (placeholders.size() == values.size()) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
spec = spec.replace(values.get(i), String.format("{%s}", placeholders.get(i)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
private List<String> getValues(String spec, String[] tokens) {
|
||||
List<String> values = new LinkedList<>();
|
||||
for (String token : tokens) {
|
||||
String[] valueTokens = spec.split(token);
|
||||
if (!StringUtils.isEmpty(valueTokens[0])) {
|
||||
values.add(valueTokens[0]);
|
||||
}
|
||||
if (valueTokens.length > 1) {
|
||||
spec = valueTokens[1];
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
private List<String> getPlaceholders(String key) {
|
||||
Pattern pattern = Pattern.compile(PLACEHOLDER_PATTERN);
|
||||
Matcher matcher = pattern.matcher(key);
|
||||
List<String> placeholders = new LinkedList<>();
|
||||
while (matcher.find()) {
|
||||
placeholders.add(matcher.group(1));
|
||||
}
|
||||
return placeholders;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ import static org.hamcrest.Matchers.instanceOf;
|
||||
/**
|
||||
* @author Dylan Roberts
|
||||
*/
|
||||
public class JGitEnvironmentRepositoryHttpProxyTests {
|
||||
public class ConfigurableHttpConnectionFactoryIntegrationTests {
|
||||
private static final ProxyHostProperties AUTHENTICATED_HTTP_PROXY = new ProxyHostProperties();
|
||||
static {
|
||||
AUTHENTICATED_HTTP_PROXY.setHost("http://authenticated.http.proxy");
|
||||
@@ -112,6 +112,20 @@ public class JGitEnvironmentRepositoryHttpProxyTests {
|
||||
makeRequest(httpClient, "https://somehost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpsProxy_placeholderUrl() throws Exception {
|
||||
new SpringApplicationBuilder(TestConfiguration.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.properties(gitProperties("https://myrepo/{placeholder1}/{placeholder2}-repo.git", null, HTTPS_PROXY))
|
||||
.run();
|
||||
HttpClient httpClient = getHttpClientForUrl("https://myrepo/someplaceholdervalue/anotherplaceholdervalue-repo.git");
|
||||
expectedException.expectCause(allOf(
|
||||
instanceOf(UnknownHostException.class),
|
||||
hasProperty("message", containsString(HTTPS_PROXY.getHost()))));
|
||||
|
||||
makeRequest(httpClient, "https://somehost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpsProxy_notCalled() throws Exception {
|
||||
String repoUrl = "https://myrepo/repo.git";
|
||||
@@ -157,6 +171,20 @@ public class JGitEnvironmentRepositoryHttpProxyTests {
|
||||
makeRequest(httpClient, "http://somehost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpProxy_placeholderUrl() throws Exception {
|
||||
new SpringApplicationBuilder(TestConfiguration.class)
|
||||
.web(WebApplicationType.NONE)
|
||||
.properties(gitProperties("https://myrepo/{placeholder}-repo.git", HTTP_PROXY, null))
|
||||
.run();
|
||||
HttpClient httpClient = getHttpClientForUrl("https://myrepo/someplaceholdervalue-repo.git");
|
||||
expectedException.expectCause(allOf(
|
||||
instanceOf(UnknownHostException.class),
|
||||
hasProperty("message", containsString(HTTP_PROXY.getHost()))));
|
||||
|
||||
makeRequest(httpClient, "http://somehost");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpProxy_notCalled() throws Exception {
|
||||
String repoUrl = "https://myrepo/repo.git";
|
||||
@@ -0,0 +1,185 @@
|
||||
package org.springframework.cloud.config.server.environment;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.eclipse.jgit.transport.http.HttpConnection;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class HttpClientConfigurableHttpConnectionFactoryTest {
|
||||
|
||||
private HttpClientConfigurableHttpConnectionFactory connectionFactory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
connectionFactory = new HttpClientConfigurableHttpConnectionFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noConfigAdded() throws Exception {
|
||||
HttpConnection actual = connectionFactory.create(new URL("http://localhost/test.git"));
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchingUrl() throws Exception {
|
||||
String url = "http://localhost/test.git";
|
||||
MultipleJGitEnvironmentProperties properties = new MultipleJGitEnvironmentProperties();
|
||||
properties.setUri(url);
|
||||
connectionFactory.addConfiguration(properties);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(url));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.values().stream().findFirst().get();
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longerUrl() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties = new MultipleJGitEnvironmentProperties();
|
||||
String url = "http://localhost/test.git";
|
||||
properties.setUri(url);
|
||||
connectionFactory.addConfiguration(properties);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(url + "/some/path.properties"));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.values().stream().findFirst().get();
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void urlWithPlaceholders() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties = new MultipleJGitEnvironmentProperties();
|
||||
properties.setUri("http://localhost/{placeholder}-test.git");
|
||||
connectionFactory.addConfiguration(properties);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL("http://localhost/value-test.git" + "/some/path.properties"));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.values().stream().findFirst().get();
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composite_sameHost() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties1 = new MultipleJGitEnvironmentProperties();
|
||||
properties1.setUri("http://localhost/test1.git");
|
||||
MultipleJGitEnvironmentProperties properties2 = new MultipleJGitEnvironmentProperties();
|
||||
properties2.setUri("http://localhost/test2.git");
|
||||
connectionFactory.addConfiguration(properties1);
|
||||
connectionFactory.addConfiguration(properties2);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(properties1.getUri()));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.get(properties1.getUri());
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composite_differentHost() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties1 = new MultipleJGitEnvironmentProperties();
|
||||
properties1.setUri("http://localhost1/test.git");
|
||||
MultipleJGitEnvironmentProperties properties2 = new MultipleJGitEnvironmentProperties();
|
||||
properties2.setUri("http://localhost2/test.git");
|
||||
connectionFactory.addConfiguration(properties1);
|
||||
connectionFactory.addConfiguration(properties2);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(properties1.getUri()));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.get(properties1.getUri());
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composite_urlsWithPlaceholders() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties1 = new MultipleJGitEnvironmentProperties();
|
||||
properties1.setUri("http://localhost/path/{placeholder3}/more/test.git");
|
||||
MultipleJGitEnvironmentProperties properties2 = new MultipleJGitEnvironmentProperties();
|
||||
properties2.setUri("http://localhost/{placeholder1}/path/{placeholder2}-test.git");
|
||||
connectionFactory.addConfiguration(properties1);
|
||||
connectionFactory.addConfiguration(properties2);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(properties2.getUri()
|
||||
.replace("{placeholder1}", "value1")
|
||||
.replace("{placeholder2}", "value2")));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.get(properties2.getUri());
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composite_urlsWithPlaceholders_identicalTemplatesWontBeResolvedProperly() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties1 = new MultipleJGitEnvironmentProperties();
|
||||
properties1.setUri("http://localhost/{placeholder3}/path/{placeholder4}-test.git");
|
||||
MultipleJGitEnvironmentProperties properties2 = new MultipleJGitEnvironmentProperties();
|
||||
properties2.setUri("http://localhost/{placeholder1}/path/{placeholder2}-test.git");
|
||||
connectionFactory.addConfiguration(properties1);
|
||||
connectionFactory.addConfiguration(properties2);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(properties2.getUri()
|
||||
.replace("{placeholder1}", "value1")
|
||||
.replace("{placeholder2}", "value2")));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.get(properties2.getUri());
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isNotSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void composite_longerUrlsWithPlaceholders() throws Exception {
|
||||
MultipleJGitEnvironmentProperties properties1 = new MultipleJGitEnvironmentProperties();
|
||||
properties1.setUri("http://localhost/path/{placeholder3}/{placeholder4}-test.git");
|
||||
MultipleJGitEnvironmentProperties properties2 = new MultipleJGitEnvironmentProperties();
|
||||
properties2.setUri("http://localhost/{placeholder1}/path/{placeholder2}-test.git");
|
||||
connectionFactory.addConfiguration(properties1);
|
||||
connectionFactory.addConfiguration(properties2);
|
||||
|
||||
HttpConnection actualConnection = connectionFactory.create(new URL(properties2.getUri()
|
||||
.replace("{placeholder1}", "value1")
|
||||
.replace("{placeholder2}", "value2") + "/some/path.properties"));
|
||||
|
||||
HttpClientBuilder expectedHttpClientBuilder = connectionFactory.httpClientBuildersByUri.get(properties2.getUri());
|
||||
HttpClientBuilder actualHttpClientBuilder = getActualHttpClientBuilder(actualConnection);
|
||||
assertThat(actualHttpClientBuilder).isSameAs(expectedHttpClientBuilder);
|
||||
}
|
||||
|
||||
private HttpClient getActualHttpClient(HttpConnection actualConnection) {
|
||||
Field clientField = ReflectionUtils.findField(actualConnection.getClass(), "client");
|
||||
ReflectionUtils.makeAccessible(clientField);
|
||||
return (HttpClient) ReflectionUtils.getField(clientField, actualConnection);
|
||||
}
|
||||
|
||||
private HttpClientBuilder getActualHttpClientBuilder(HttpConnection actualConnection) {
|
||||
HttpClient actualHttpClient = getActualHttpClient(actualConnection);
|
||||
Field closeablesField = ReflectionUtils.findField(actualHttpClient.getClass(), "closeables");
|
||||
ReflectionUtils.makeAccessible(closeablesField);
|
||||
List<?> closables = (List<?>) ReflectionUtils.getField(closeablesField, actualHttpClient);
|
||||
return closables.stream()
|
||||
.map(o -> {
|
||||
Field builderField = Arrays.stream(o.getClass().getDeclaredFields()).filter(field -> HttpClientBuilder.class.isAssignableFrom(field.getType())).findFirst().orElse(null);
|
||||
if (builderField != null) {
|
||||
ReflectionUtils.makeAccessible(builderField);
|
||||
return ReflectionUtils.getField(builderField, o);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.map(HttpClientBuilder.class::cast).findFirst().get();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user