diff --git a/pom.xml b/pom.xml index 906105c..d39a235 100644 --- a/pom.xml +++ b/pom.xml @@ -23,12 +23,12 @@ - https://github.com/spring-cloud/spring-cloud-consul + https://github.com/spring-cloud/spring-cloud-cloudfoundry - scm:git:git://github.com/spring-cloud/spring-cloud-consul.git + scm:git:git://github.com/spring-cloud/spring-cloud-cloudfoundry.git - scm:git:ssh://git@github.com/spring-cloud/spring-cloud-consul.git + scm:git:ssh://git@github.com/spring-cloud/spring-cloud-cloudfoundry.git HEAD diff --git a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java index ab03886..4cf5c00 100644 --- a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java +++ b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfiguration.java @@ -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(); - } } diff --git a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java index ad29ff2..d87cb12 100644 --- a/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java +++ b/spring-cloud-cloudfoundry-commons/src/main/java/org/springframework/cloud/cloudfoundry/CloudFoundryProperties.java @@ -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 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))); } } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java b/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java index 55f849a..adacc83 100644 --- a/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java +++ b/spring-cloud-cloudfoundry-commons/src/test/java/org/springframework/cloud/cloudfoundry/CloudFoundryClientAutoConfigurationTest.java @@ -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 defaultConfig() { - Map 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> mapMany = cf - .applications() - .get(GetApplicationRequest.builder().name("lo-test").build()) - .flatMapMany(applicationDetail -> { - List instanceDetails = applicationDetail.getInstanceDetails(); - Flux ids = Flux.fromStream(instanceDetails.stream()); - Flux 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); + } } \ No newline at end of file diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java index 1205089..ad9d3f7 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClient.java @@ -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 getServices() { - return this - .cloudFoundryOperations + return this.cloudFoundryOperations .applications() .list() .map(ApplicationSummary::getName) diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java index 4e84892..bf63744 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryDiscoveryClientConfiguration.java @@ -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( diff --git a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java index 463143a..d5f91c4 100644 --- a/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java +++ b/spring-cloud-cloudfoundry-discovery/src/main/java/org/springframework/cloud/cloudfoundry/discovery/CloudFoundryServerList.java @@ -30,12 +30,11 @@ import com.netflix.loadbalancer.AbstractServerList; * @author Josh Long */ public class CloudFoundryServerList extends AbstractServerList { - - 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 getInitialListOfServers() { - return this.cloudFoundryServers(); + return cloudFoundryServers(); } @Override public List getUpdatedListOfServers() { - return this.cloudFoundryServers(); + return cloudFoundryServers(); } private List cloudFoundryServers() { @@ -69,7 +68,7 @@ public class CloudFoundryServerList extends AbstractServerList