Improve Cloud Foundry Java Client auto-configuration.
An organization and space are not required by all CF Java Client operations. Those properties should be optional to the client-autoconfiguration. The CF target URL should default to the CF an application is running on when it is deployed to CF.
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -23,12 +23,12 @@
|
||||
</properties>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-consul</url>
|
||||
<url>https://github.com/spring-cloud/spring-cloud-cloudfoundry</url>
|
||||
<connection>
|
||||
scm:git:git://github.com/spring-cloud/spring-cloud-consul.git
|
||||
scm:git:git://github.com/spring-cloud/spring-cloud-cloudfoundry.git
|
||||
</connection>
|
||||
<developerConnection>
|
||||
scm:git:ssh://git@github.com/spring-cloud/spring-cloud-consul.git
|
||||
scm:git:ssh://git@github.com/spring-cloud/spring-cloud-cloudfoundry.git
|
||||
</developerConnection>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
@@ -43,9 +43,10 @@ import org.springframework.context.annotation.Lazy;
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Ben Hale
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "spring.cloud.cloudfoundry", name = {"username", "password", "org", "space"})
|
||||
@ConditionalOnProperty(prefix = "spring.cloud.cloudfoundry", name = {"username", "password"})
|
||||
@ConditionalOnClass(name = {"reactor.core.publisher.Flux", "org.cloudfoundry.operations.DefaultCloudFoundryOperations",
|
||||
"org.cloudfoundry.reactor.client.ReactorCloudFoundryClient", "org.reactivestreams.Publisher"})
|
||||
@EnableConfigurationProperties(CloudFoundryProperties.class)
|
||||
@@ -64,16 +65,6 @@ public class CloudFoundryClientAutoConfiguration {
|
||||
return new CloudFoundryService(cloudFoundryOperations);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorCloudFoundryClient cloudFoundryClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorCloudFoundryClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
@@ -93,18 +84,13 @@ public class CloudFoundryClientAutoConfiguration {
|
||||
.space(space)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public DefaultConnectionContext connectionContext() {
|
||||
|
||||
String apiHost = this.cloudFoundryProperties.getUrl();
|
||||
Boolean skipSslValidation = this.cloudFoundryProperties.isSkipSslValidation();
|
||||
|
||||
return DefaultConnectionContext.builder()
|
||||
.apiHost(apiHost)
|
||||
.skipSslValidation(skipSslValidation)
|
||||
public ReactorCloudFoundryClient cloudFoundryClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorCloudFoundryClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -128,6 +114,29 @@ public class CloudFoundryClientAutoConfiguration {
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorUaaClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public DefaultConnectionContext connectionContext() {
|
||||
String apiHost = this.cloudFoundryProperties.getUrl();
|
||||
Boolean skipSslValidation = this.cloudFoundryProperties.isSkipSslValidation();
|
||||
|
||||
return DefaultConnectionContext.builder()
|
||||
.apiHost(apiHost)
|
||||
.skipSslValidation(skipSslValidation)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
@@ -139,16 +148,6 @@ public class CloudFoundryClientAutoConfiguration {
|
||||
.username(username)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
@ConditionalOnMissingBean
|
||||
public ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
|
||||
return ReactorUaaClient.builder()
|
||||
.connectionContext(connectionContext)
|
||||
.tokenProvider(tokenProvider)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Configuration properties for a connection to a Cloud Foundry platform.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Scott Frederick
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.cloud.cloudfoundry")
|
||||
public class CloudFoundryProperties implements InitializingBean {
|
||||
@@ -36,7 +39,8 @@ public class CloudFoundryProperties implements InitializingBean {
|
||||
/**
|
||||
* URL of Cloud Foundry API (Cloud Controller).
|
||||
*/
|
||||
private String url = "api.run.pivotal.io";
|
||||
@Value("${vcap.application.cf_api:api.run.pivotal.io}")
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* Username to authenticate (usually an email address).
|
||||
@@ -49,12 +53,12 @@ public class CloudFoundryProperties implements InitializingBean {
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* Organization name to authenticate with (default to user's default).
|
||||
* Organization name to initially target.
|
||||
*/
|
||||
private String org;
|
||||
|
||||
/**
|
||||
* Space name to authenticate with (default to user's default).
|
||||
* Space name to initially target.
|
||||
*/
|
||||
@Value("${vcap.application.space_name:}")
|
||||
private String space;
|
||||
@@ -127,19 +131,21 @@ public class CloudFoundryProperties implements InitializingBean {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
public void afterPropertiesSet() {
|
||||
this.url = safeUrl(this.url);
|
||||
this.password = this.password.trim();
|
||||
this.username = this.username.trim();
|
||||
this.org = this.org.trim();
|
||||
this.space = this.space.trim();
|
||||
if (this.org != null) {
|
||||
this.org = this.org.trim();
|
||||
}
|
||||
if (this.space != null) {
|
||||
this.space = this.space.trim();
|
||||
}
|
||||
|
||||
Map<String, String> vals = new HashMap<>();
|
||||
vals.put("org", getOrg());
|
||||
vals.put("url", getUrl());
|
||||
vals.put("username", getUsername());
|
||||
vals.put("password", getPassword());
|
||||
vals.put("space", getSpace());
|
||||
vals.forEach((key, value) -> Assert.hasText(value, String.format("'%s' must be provided", key)));
|
||||
}
|
||||
}
|
||||
@@ -16,39 +16,40 @@
|
||||
|
||||
package org.springframework.cloud.cloudfoundry;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.cloudfoundry.doppler.DopplerClient;
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.DefaultCloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.GetApplicationRequest;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.cloudfoundry.operations.organizations.OrganizationSummary;
|
||||
import org.cloudfoundry.reactor.DefaultConnectionContext;
|
||||
import org.cloudfoundry.reactor.client.ReactorCloudFoundryClient;
|
||||
import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider;
|
||||
import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
|
||||
import org.cloudfoundry.routing.RoutingClient;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.util.function.Tuple2;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class CloudFoundryClientAutoConfigurationTest {
|
||||
|
||||
@SpringBootApplication
|
||||
public static class MyConfig {
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(CloudFoundryClientAutoConfiguration.class));
|
||||
|
||||
private final static String SPRING_CLOUD_PROPERTIES[] = {
|
||||
"spring.cloud.cloudfoundry.username",
|
||||
"spring.cloud.cloudfoundry.password",
|
||||
};
|
||||
|
||||
private static boolean requiredPropertiesSet() {
|
||||
for (String k : SPRING_CLOUD_PROPERTIES) {
|
||||
if (System.getenv(envVarFromProperty(k)) == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String envVarFromProperty(String propertyName) {
|
||||
@@ -57,89 +58,65 @@ public class CloudFoundryClientAutoConfigurationTest {
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
private static String CONFIG_KEYS[] = {"spring.cloud.cloudfoundry.username",
|
||||
"spring.cloud.cloudfoundry.password", "spring.cloud.cloudfoundry.space", "spring.cloud.cloudfoundry.org"};
|
||||
|
||||
private static Map<String, Object> defaultConfig() {
|
||||
Map<String, Object> kvs = new HashMap<>();
|
||||
for (String k : CONFIG_KEYS)
|
||||
kvs.put(k, System.getenv(envVarFromProperty(k)));
|
||||
return kvs;
|
||||
}
|
||||
|
||||
private static boolean configExists() {
|
||||
for (String k : CONFIG_KEYS)
|
||||
if (System.getenv(envVarFromProperty(k)) == null)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void producesAllBeans() {
|
||||
public void autoConfiguresBeansWithAllProperties() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.cloud.cloudfoundry.username=user",
|
||||
"spring.cloud.cloudfoundry.password=secret",
|
||||
"spring.cloud.cloudfoundry.org=myorg",
|
||||
"spring.cloud.cloudfoundry.space=myspace")
|
||||
.run((context) -> {
|
||||
assertCloudFoundryClientBeansPresent(context);
|
||||
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
Class[] tags = {ReactorCloudFoundryClient.class, DefaultCloudFoundryOperations.class,
|
||||
DefaultConnectionContext.class, DopplerClient.class, RoutingClient.class, PasswordGrantTokenProvider.class,
|
||||
ReactorUaaClient.class};
|
||||
for (Class<?> c : tags) {
|
||||
Assertions.assertThat(ctx.getBean(c)).isNotNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectivity() throws Exception {
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
CloudFoundryOperations operations = ctx.getBean(CloudFoundryOperations.class);
|
||||
Assert.assertNotNull(operations);
|
||||
OrganizationSummary summary = operations
|
||||
.organizations()
|
||||
.list()
|
||||
.blockFirst();
|
||||
Assertions.assertThat(summary).isNotNull();
|
||||
Assertions.assertThat(summary.getId()).isNotEmpty();
|
||||
Assertions.assertThat(summary.getName()).isNotEmpty();
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void instances() throws Exception {
|
||||
|
||||
Assume.assumeTrue(configExists());
|
||||
|
||||
Log log = LogFactory.getLog(getClass());
|
||||
ApplicationContext ctx = new SpringApplicationBuilder()
|
||||
.sources(MyConfig.class)
|
||||
.properties(defaultConfig())
|
||||
.run();
|
||||
|
||||
CloudFoundryOperations cf = ctx.getBean(CloudFoundryOperations.class);
|
||||
Flux<Tuple2<ApplicationDetail, InstanceDetail>> mapMany = cf
|
||||
.applications()
|
||||
.get(GetApplicationRequest.builder().name("lo-test").build())
|
||||
.flatMapMany(applicationDetail -> {
|
||||
List<InstanceDetail> instanceDetails = applicationDetail.getInstanceDetails();
|
||||
Flux<InstanceDetail> ids = Flux.fromStream(instanceDetails.stream());
|
||||
Flux<ApplicationDetail> generate = Flux.generate(sink -> sink.next(applicationDetail));
|
||||
return generate.zipWith(ids);
|
||||
DefaultCloudFoundryOperations operations = context.getBean(DefaultCloudFoundryOperations.class);
|
||||
assertThat(operations.getOrganization()).isEqualTo("myorg");
|
||||
assertThat(operations.getSpace()).isEqualTo("myspace");
|
||||
});
|
||||
mapMany
|
||||
.subscribe(p ->
|
||||
log.info(p.getT1().getName() + ':' + p.getT1().getId() + ':' + p.getT2().getIndex() + ':' +
|
||||
p.getT2().getState()));
|
||||
|
||||
Thread.sleep(5 * 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void autoConfiguresBeansWithMinimalProperties() {
|
||||
this.contextRunner
|
||||
.withPropertyValues(
|
||||
"spring.cloud.cloudfoundry.username=user",
|
||||
"spring.cloud.cloudfoundry.password=secret")
|
||||
.run((context) -> {
|
||||
assertCloudFoundryClientBeansPresent(context);
|
||||
|
||||
DefaultCloudFoundryOperations operations = context.getBean(DefaultCloudFoundryOperations.class);
|
||||
assertThat(operations.getOrganization()).isNullOrEmpty();
|
||||
assertThat(operations.getSpace()).isNullOrEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void organizationsRetrievedWithUserProvidedProperties() {
|
||||
Assume.assumeTrue(requiredPropertiesSet());
|
||||
|
||||
this.contextRunner
|
||||
.run((context) -> {
|
||||
assertCloudFoundryClientBeansPresent(context);
|
||||
CloudFoundryOperations operations = context.getBean(CloudFoundryOperations.class);
|
||||
|
||||
OrganizationSummary summary = operations
|
||||
.organizations()
|
||||
.list()
|
||||
.blockFirst();
|
||||
|
||||
assertThat(summary).isNotNull();
|
||||
assertThat(summary.getId()).isNotEmpty();
|
||||
assertThat(summary.getName()).isNotEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
private void assertCloudFoundryClientBeansPresent(AssertableApplicationContext context) {
|
||||
assertThat(context).hasSingleBean(ReactorCloudFoundryClient.class);
|
||||
assertThat(context).hasSingleBean(DefaultCloudFoundryOperations.class);
|
||||
assertThat(context).hasSingleBean(DefaultConnectionContext.class);
|
||||
assertThat(context).hasSingleBean(DopplerClient.class);
|
||||
assertThat(context).hasSingleBean(RoutingClient.class);
|
||||
assertThat(context).hasSingleBean(PasswordGrantTokenProvider.class);
|
||||
assertThat(context).hasSingleBean(ReactorUaaClient.class);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.cloudfoundry.operations.applications.ApplicationDetail;
|
||||
import org.cloudfoundry.operations.applications.ApplicationSummary;
|
||||
import org.cloudfoundry.operations.applications.InstanceDetail;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
@@ -44,10 +43,6 @@ public class CloudFoundryDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
private final String description = "Cloud Foundry " + DiscoveryClient.class.getName() + " implementation";
|
||||
|
||||
@Value("${vcap.application.name:${spring.application.name:application}}")
|
||||
private String vcapApplicationName = "application";
|
||||
|
||||
|
||||
CloudFoundryDiscoveryClient(CloudFoundryOperations cloudFoundryOperations,
|
||||
CloudFoundryService svc) {
|
||||
this.cloudFoundryService = svc;
|
||||
@@ -86,8 +81,7 @@ public class CloudFoundryDiscoveryClient implements DiscoveryClient {
|
||||
|
||||
@Override
|
||||
public List<String> getServices() {
|
||||
return this
|
||||
.cloudFoundryOperations
|
||||
return this.cloudFoundryOperations
|
||||
.applications()
|
||||
.list()
|
||||
.map(ApplicationSummary::getName)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package org.springframework.cloud.cloudfoundry.discovery;
|
||||
|
||||
import org.cloudfoundry.operations.CloudFoundryOperations;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -35,9 +34,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
@EnableConfigurationProperties(CloudFoundryDiscoveryProperties.class)
|
||||
public class CloudFoundryDiscoveryClientConfiguration {
|
||||
|
||||
@Autowired
|
||||
private CloudFoundryDiscoveryProperties discovery;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(CloudFoundryDiscoveryClient.class)
|
||||
public CloudFoundryDiscoveryClient cloudFoundryDiscoveryClient(
|
||||
|
||||
@@ -30,12 +30,11 @@ import com.netflix.loadbalancer.AbstractServerList;
|
||||
* @author Josh Long
|
||||
*/
|
||||
public class CloudFoundryServerList extends AbstractServerList<CloudFoundryServer> {
|
||||
|
||||
private String serviceId;
|
||||
|
||||
private final CloudFoundryService cloudFoundryService;
|
||||
private final CloudFoundryDiscoveryProperties properties;
|
||||
|
||||
private IClientConfig clientConfig;
|
||||
private String serviceId;
|
||||
|
||||
CloudFoundryServerList(CloudFoundryService svc, CloudFoundryDiscoveryProperties properties) {
|
||||
this.cloudFoundryService = svc;
|
||||
@@ -45,17 +44,17 @@ public class CloudFoundryServerList extends AbstractServerList<CloudFoundryServe
|
||||
@Override
|
||||
public void initWithNiwsConfig(IClientConfig clientConfig) {
|
||||
this.clientConfig = clientConfig;
|
||||
this.serviceId = this.clientConfig.getClientName();
|
||||
this.serviceId = clientConfig.getClientName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CloudFoundryServer> getInitialListOfServers() {
|
||||
return this.cloudFoundryServers();
|
||||
return cloudFoundryServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CloudFoundryServer> getUpdatedListOfServers() {
|
||||
return this.cloudFoundryServers();
|
||||
return cloudFoundryServers();
|
||||
}
|
||||
|
||||
private List<CloudFoundryServer> cloudFoundryServers() {
|
||||
@@ -69,7 +68,7 @@ public class CloudFoundryServerList extends AbstractServerList<CloudFoundryServe
|
||||
|
||||
final int port;
|
||||
if (secure != null && secure && securePort != null) {
|
||||
port = securePort;
|
||||
port = securePort;
|
||||
} else if (nonSecurePort != null) {
|
||||
port = nonSecurePort;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user