diff --git a/src/main/asciidoc/README.adoc b/src/main/asciidoc/README.adoc index 05a865f..28c40fb 100644 --- a/src/main/asciidoc/README.adoc +++ b/src/main/asciidoc/README.adoc @@ -1,6 +1,2 @@ include::intro.adoc[] -== Service Broker Example - -include::broker-example.adoc[] - diff --git a/src/main/asciidoc/broker-example.adoc b/src/main/asciidoc/broker-example.adoc deleted file mode 100644 index 7b8b961..0000000 --- a/src/main/asciidoc/broker-example.adoc +++ /dev/null @@ -1,64 +0,0 @@ - -Example script to deploy and regis#ter a broker: - -``` -DOMAIN=mydomain.net -cf push app -p target/*.jar --no-start -cf env app | grep SPRING_PROFILES_ACTIVE || cf set-env app SPRING_PROFILES_ACTIVE cloud -cf env app | grep APPLICATION_DOMAIN || cf set-env app APPLICATION_DOMAIN ${DOMAIN} - -cf services | grep configserver && cf bind app configserver - -cf restart app -cf create-service-broker app user secure http://app.${DOMAIN} - -for f in `cf curl /v2/service_plans | grep '\"guid' | sed -e 's/.*: "//' -e 's/".*//'`; do - cf curl v2/service_plans/$f -X PUT -d '{"public":true}' -done - -cf create-service app free appi -``` - -At which point you have a service called "app" and a service instance called "appi": - -``` -$ cf marketplace -OK - -service plans description -app free Singleton service app -$ cf services -Getting services in org default / space development as admin... -OK - -name service plan bound apps -appi app free -``` - -Your application can define a configuration property -`application.domain` (defaults to "cfapps.io") which will be used to -construct the credentials for any app that binds to your service. Or -it can define the URI directly using -`cloudfoundry.service.definition.metadata.uri`. - -You can change some other basic metadata by setting config properties: - -* `cloudfoundry.service.definition.*` is bound to a - `ServiceDefinition` (defined in spring-boot-cf-service-broker) which - has optional setters for plans and metadata. - -* `cloudfoundry.service.broker.*` is bound to an internal bean. It has - optional setters for "name" (the service name), "description" (user - friendly description) and "prefix" (used to create a unique id from - the name). - -An app which binds to your service will get credentials that contain a -"uri" property linking to your service. A Spring Boot app can bind to -that through the `vcap.services.[service].credentials.uri` environment -property. - -If your service also has a -https://github.com/Netflix/eureka[Eureka core] dependency, and you -can expose it as a Eureka service, then any service which registers -with Eureka will also become a Cloud Foundry service. - diff --git a/src/main/asciidoc/intro.adoc b/src/main/asciidoc/intro.adoc index 50d538a..d853abd 100644 --- a/src/main/asciidoc/intro.adoc +++ b/src/main/asciidoc/intro.adoc @@ -1,3 +1,15 @@ -Integration between https://github.com/cloudfoundry[Cloud Foundry] -and https://github.com/spring-cloud[Spring Cloud]. +Spring Cloud for Cloudfoundry makes it easy to run +https://github.com/spring-cloud[Spring Cloud] apps in +https://github.com/cloudfoundry[Cloud Foundry] (the Platform as a +Service). Cloud Foundry has the notion of a "service", which is +middlware that you "bind" to an app, essentially providing it with an +environment variable containing credentials (e.g. the location and +username to use for the service). +Add this project as a dependency to any Spring Cloud UI app or REST +service and deploy to Cloudfoundry. If you use Spring Cloud Security +OAuth2 features this will make them bindable to Cloud Foundry services +instead of enironment properties in `oauth2.*`. For a UI app you can +declare `@EnableOAuth2Sso` and bind to a service called "sso", and for +a service you can add `@EnableOAuth2Resource` and bind to a service +called "resource" (see below for how to change the names). diff --git a/src/main/asciidoc/quickstart.adoc b/src/main/asciidoc/quickstart.adoc index 9fc88db..f9d491b 100644 --- a/src/main/asciidoc/quickstart.adoc +++ b/src/main/asciidoc/quickstart.adoc @@ -1,7 +1,50 @@ -Add this project to a Spring Boot REST service and deploy to -Cloudfoundry (and use the Actuator for maximum flexibility). It will -expose service-broker endpoints automatically (look in /mappings for -/v2/*) and you can register it with the Cloud Controller as described -here: -http://docs.cloudfoundry.org/services/managing-service-brokers.html[http://docs.cloudfoundry.org/services/managing-service-brokers.html]. +Here's a Spring Cloud app with OAuth2 SSO: + +.app.groovy +[source,java] +---- +@Controller +@EnableOAuth2Sso +class Application { + + @RequestMapping('/') + String home() { + 'Hello World' + } + +} +---- + +If you run it without any service bindings: + +---- +$ spring jar app.jar app.groovy +$ cf push -p app.jar +---- + +it will be secure with (Spring Boot default) Basic authentication, +i.e. the password will be in the logs (or set it with +`security.user.password` as normal). To turn on OAuth2 SSO all you +need to do is bind the app to a service with the right +credentials. For example, a +http://docs.pivotal.io/pivotalcf/devguide/services/user-provided.html[user-provided +service] can be created like this on PWS: + +---- +$ cf create-user-provided-service sso -p '{clientId:"",clientSecret:"",userInfoUri:"https://uaa.run.pivotal.io/userinfo",tokenUri: "https://login.run.pivotal.io/oauth/token",authorizationUri:"https://login.run.pivotal.io/oauth/authorize"} +---- + +Then bind and restart the app: + +---- +$ cf bind app sso +$ cf restart app +---- + +and visit it in a browser. It will redirect to the Cloud Foundry (PWS) +login server instead of challenging for Basic authentication. The +`clientId` and `clientSecret` are credentials of a registered client +in Cloud Foundry. To get a Cloud Foundry client registration for +testing please ask at support@run.pivotal.io (if you want it on PWS), +or your local platform administrator (if it's a private instance). diff --git a/src/main/asciidoc/spring-cloud-cloudfoundry.adoc b/src/main/asciidoc/spring-cloud-cloudfoundry.adoc index e5dc800..7b29d47 100644 --- a/src/main/asciidoc/spring-cloud-cloudfoundry.adoc +++ b/src/main/asciidoc/spring-cloud-cloudfoundry.adoc @@ -2,7 +2,74 @@ include::intro.adoc[] -== Service Broker Example +== Quickstart + +include::quickstart.adoc[] + +== How Does it Work? + +=== OAuth2 Single Sign On + +Spring Cloud Security provides the `@EnableOAuth2Sso` annotation and +binds the app to environment properties in `oauth2.\*`. Spring Cloud +for Cloud Foundry just sets up default environment properties so that +it all just works if you bind to a Cloud Foundry service instance +called "sso". The service credentials are mapped to the SSO +properties, i.e. `clientId`, `clientSecret`, `tokenUri`, +`authorizationUri`, `userInfoUri`, `tokenInfoUri1, `jwt.\*` (refer to +the Spring Cloud Security documentation for details of which +combinations will work together). The main thing is that in Cloud +Foundry you only need one service to cover all the necessary +credentials. + +To use a different sercice instance name (i.e. not "sso") just set +`oauth2.sso.serviceId` to your custom name. + +=== JWT Tokens + +Spring Cloud Security already has support for decoding JWT tokens if +you just provide the verification key (as an environment property). In +Cloud Foundry you can pick that property up from a servcice binding +(`jwt.keyValue` or `jwt.keyUri`). + +For example the `jwt.keyUri` in PWS is +"https://uaa.run.pivotal.io/token_key": + +---- +$ curl https://uaa.run.pivotal.io/token_key +{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}d +---- + +=== OAuth2 Resource Server + +Similarly, the `@EnableOAuth2Resource` annotation will protect your +API endpoints if you bind to a service instance called "resource". +The "sso" service above will work for a resource server as well (so +just bind to that if it's there). If the OAuth2 tokens are JWTs (as in +Cloud Foundry), it is common to use a separate service for resources +to avoid a network round trip decoding the token on every access. A +user-provided-service for an OAuth2 resource can be created like this +on PWS: + +---- +$ cf create-user-provided-service resource -p '{jwt.keyUri:"https://uaa.run.pivotal.io/token_key"} +---- + +To use JWT you need to add the verification key as either +`jwt.keyValue` or `jwt.keyUri` (these could be added to the "sso" +service or the "resource" service if you have one). + +To use a different sercice instance name (i.e. not "resource" or +"sso") just set `oauth2.resource.serviceId` to your custom name. + +=== The Default Environment Keys + +The precise mapppings are as follows: + +* `oauth2.sso.\*` to `vcap.services.${oauth2.sso.serviceId:sso}.credentials.*` + +* `oauth2.client.\*` to `vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:${vcap.services.${oauth2.resource.serviceId:resource}.credentials.*` + +* `oauth2.resource.\*` to `vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenUri:${vcap.services.${oauth2.sso.serviceId:sso}.credentials.*` -include::broker-example.adoc[] diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/FreeServiceDefinitionFactory.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/FreeServiceDefinitionFactory.java deleted file mode 100644 index 58f500a..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/FreeServiceDefinitionFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker; - -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.cloudfoundry.community.servicebroker.model.Plan; -import org.cloudfoundry.community.servicebroker.model.ServiceDefinition; -import org.springframework.util.DigestUtils; - -/** - * @author Dave Syer - * - */ -public class FreeServiceDefinitionFactory { - - private final String prefix; - - public FreeServiceDefinitionFactory() { - this(""); - } - - public FreeServiceDefinitionFactory(String prefix) { - this.prefix = prefix; - } - - public ServiceDefinition create(String name, String description) { - return new ServiceDefinition(getId(name), name, description, true, getPlans(name)); - } - - private List getPlans(String appName) { - Plan plan = new Plan(getId(appName + "-free"), "free", - "This is a default service plan. All services are created equally.", - getServiceDefinitionMetadata(appName), true); - return new ArrayList(Arrays.asList(plan)); - } - - private Map getServiceDefinitionMetadata(String appName) { - Map sdMetadata = new HashMap(); - sdMetadata.put("displayName", appName + "-service"); - sdMetadata.put("longDescription", "Platform Service for " + appName); - sdMetadata.put("providerDisplayName", "Pivotal"); - sdMetadata.put("documentationUrl", "https://github.com/spring-platform"); - sdMetadata.put("supportUrl", "https://github.com/spring-platform"); - return sdMetadata; - } - - protected String getId(String name) { - try { - String id = DigestUtils.md5DigestAsHex((prefix + name).getBytes("UTF-8")); - return id; - } - catch (UnsupportedEncodingException e) { - throw new IllegalStateException(); - } - - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceBindingRepository.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceBindingRepository.java deleted file mode 100644 index c276327..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceBindingRepository.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker; - -import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding; - -/** - * @author Dave Syer - * - */ -public interface ServiceInstanceBindingRepository { - - ServiceInstanceBinding findOne(String bindingId); - - void delete(String id); - - void save(ServiceInstanceBinding binding); - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceRepository.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceRepository.java deleted file mode 100644 index cf1b9e1..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/ServiceInstanceRepository.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker; - -import java.util.List; - -import org.cloudfoundry.community.servicebroker.model.ServiceInstance; - -/** - * @author Dave Syer - * - */ -public interface ServiceInstanceRepository { - - List findAll(); - - ServiceInstance findOne(String serviceInstanceId); - - void save(ServiceInstance instance); - - void delete(String id); - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/CatalogLeaseManager.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/CatalogLeaseManager.java deleted file mode 100644 index 5427e19..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/CatalogLeaseManager.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.configuration; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.cloudfoundry.community.servicebroker.model.Catalog; -import org.cloudfoundry.community.servicebroker.model.ServiceDefinition; -import org.cloudfoundry.community.servicebroker.service.BeanCatalogService; -import org.cloudfoundry.community.servicebroker.service.CatalogService; -import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory; -import org.springframework.cloud.netflix.eureka.server.advice.LeaseManagerLite; -import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceCanceledEvent; -import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent; -import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRenewedEvent; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.event.SmartApplicationListener; - -import com.netflix.appinfo.InstanceInfo; -import com.netflix.appinfo.InstanceInfo.ActionType; -import com.netflix.eureka.lease.Lease; - -/** - * @author Dave Syer - * - */ -public class CatalogLeaseManager implements CatalogService, LeaseManagerLite, SmartApplicationListener { - - private static Log logger = LogFactory.getLog(CatalogLeaseManager.class); - - private Map definitions = new HashMap(); - - private List values = new ArrayList(); - - public CatalogLeaseManager(InstanceInfo config) { - register(config, 0, false); - } - - @Override - public Catalog getCatalog() { - return new Catalog(values); - } - - @Override - public ServiceDefinition getServiceDefinition(String serviceId) { - return new BeanCatalogService(getCatalog()).getServiceDefinition(serviceId); - } - - @Override - public boolean supportsEventType(Class eventType) { - return EurekaInstanceRegisteredEvent.class.isAssignableFrom(eventType) - || EurekaInstanceCanceledEvent.class.isAssignableFrom(eventType) - || EurekaInstanceRenewedEvent.class.isAssignableFrom(eventType); - } - - @Override - public int getOrder() { - return 0; - } - - @Override - public boolean supportsSourceType(Class sourceType) { - return true; - } - - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof EurekaInstanceRegisteredEvent) { - EurekaInstanceRegisteredEvent registered = (EurekaInstanceRegisteredEvent) event; - register(registered.getInstanceInfo(), registered.isReplication()); - } - else if (event instanceof EurekaInstanceCanceledEvent) { - EurekaInstanceCanceledEvent canceled = (EurekaInstanceCanceledEvent) event; - cancel(canceled.getAppName(), canceled.getServerId(), canceled.isReplication()); - } - else if (event instanceof EurekaInstanceRenewedEvent) { - EurekaInstanceRenewedEvent renewed = (EurekaInstanceRenewedEvent) event; - renew(renewed.getAppName(), renewed.getServerId(), renewed.isReplication()); - } - } - - @Override - public void register(InstanceInfo info, boolean isReplication) { - register(info, Lease.DEFAULT_DURATION_IN_SECS, isReplication); - } - - @Override - public void register(InstanceInfo info, int leaseDuration, boolean isReplication) { - logger.info("Registration request for: " + info.getAppName()); - if (!definitions.containsKey(info.getAppName())) { - logger.info("Registering: " + info.getAppName()); - ServiceDefinition definition = getServiceDefinition(info); - definitions.put(info.getAppName(), definition); - values.add(definition); - } - } - - @Override - public boolean cancel(String appName, String id, boolean isReplication) { - ServiceDefinition definition = definitions.remove(appName); - if (definition != null) { - logger.info("Cancelling: " + appName); - values.remove(definition); - } - return true; - } - - @Override - public boolean renew(String appName, String id, boolean isReplication) { - if (definitions.containsKey(appName)) { - logger.info("Renewing: " + appName); - definitions.get(appName).getMetadata() - .put("timestamp", System.currentTimeMillis()); - } - return true; - } - - @Override - public void evict() { - Collection definitions = new ArrayList( - this.definitions.values()); - for (ServiceDefinition definition : definitions) { - InstanceInfo info = (InstanceInfo) definition.getMetadata().get("info"); - if (info.getActionType() == ActionType.DELETED) { - cancel(info.getAppName(), info.getId(), false); - } - } - } - - private ServiceDefinition getServiceDefinition(InstanceInfo info) { - String name = info.getAppName().toLowerCase(); - ServiceDefinition definition = new FreeServiceDefinitionFactory("eureka-") - .create(name, "Eureka-brokered service"); - Map map = new HashMap(); - map.put("info", (Object) info); - map.put("uri", info.getHomePageUrl()); - map.put("timestamp", System.currentTimeMillis()); - definition.setMetadata(map); - return definition; - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/ServiceBrokerAutoConfiguration.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/ServiceBrokerAutoConfiguration.java deleted file mode 100644 index 82bea7e..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/configuration/ServiceBrokerAutoConfiguration.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.configuration; - -import java.util.Arrays; -import java.util.List; - -import org.cloudfoundry.community.servicebroker.config.BrokerApiVersionConfig; -import org.cloudfoundry.community.servicebroker.model.Catalog; -import org.cloudfoundry.community.servicebroker.model.ServiceDefinition; -import org.cloudfoundry.community.servicebroker.service.BeanCatalogService; -import org.cloudfoundry.community.servicebroker.service.CatalogService; -import org.cloudfoundry.community.servicebroker.service.ServiceInstanceBindingService; -import org.cloudfoundry.community.servicebroker.service.ServiceInstanceService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.cloudfoundry.broker.FreeServiceDefinitionFactory; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository; -import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceBindingRepository; -import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceBindingService; -import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceRepository; -import org.springframework.cloud.cloudfoundry.broker.simple.SimpleServiceInstanceService; -import org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.FilterType; -import org.springframework.stereotype.Component; - -import com.netflix.appinfo.EurekaInstanceConfig; -import com.netflix.appinfo.InstanceInfo; -import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider; -import com.netflix.eureka.lease.LeaseManager; - -/** - * Autoconfiguration providing simple service-broker endpoints and Netflix eureka server - * features. The service-broker endpoints will be enabled if spring-boot-cf-service-broker is on the classpath. By default they just make the - * current app into a service brooker for itself. The Eureka features will be added if ereka-core is on the classpath. When - * active any Eureka services registered using native Netflix APIs will be available as - * Cloudfoundry services. They consist of an interceptor for the {@link LeaseManager} that - * manages the {@link Catalog} of Cloudfoundry servvices. - * - * - * @author Dave Syer - * - */ -@Configuration -@ComponentScan(basePackages = { "org.cloudfoundry.community.servicebroker" }, excludeFilters = { - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = BrokerApiVersionConfig.class), - @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = BeanCatalogService.class) }) -@ConditionalOnClass({ServiceInstanceRepository.class, BrokerApiVersionConfig.class }) -@ConditionalOnWebApplication -@AutoConfigureAfter(EurekaClientAutoConfiguration.class) -public class ServiceBrokerAutoConfiguration { - - @Configuration - @ConditionalOnMissingBean(EurekaInstanceConfig.class) - protected static class CatalogConfiguration { - - @Autowired - private BrokerProperties broker; - - @Bean - @ConditionalOnMissingBean(CatalogService.class) - public BeanCatalogService catalogService() { - return new BeanCatalogService(catalog()); - } - - @Bean - public Catalog catalog() { - return new Catalog(Arrays.asList(serviceDefinition())); - } - - @Bean - @ConfigurationProperties("cloudfoundry.service.definition") - public ServiceDefinition serviceDefinition() { - return new FreeServiceDefinitionFactory(broker.getPrefix()).create( - broker.getName(), broker.getDescription()); - } - - @Component - @ConfigurationProperties("cloudfoundry.server.broker") - public static class BrokerProperties { - private String prefix; - @Value("${spring.application.name:application}") - private String name; - private String description; - - public String getPrefix() { - return prefix == null ? name + "-" : prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDescription() { - return description == null ? "Singleton service app" : description; - } - - public void setDescription(String description) { - this.description = description; - } - } - - } - - @Configuration - @ConditionalOnBean(EurekaInstanceConfig.class) - protected static class EurekaCatalogConfiguration { - - @Bean - public CatalogLeaseManager catalogLeaseManager(EurekaInstanceConfig config) { - InstanceInfo info = new EurekaConfigBasedInstanceInfoProvider(config).get(); - return new CatalogLeaseManager(info); - } - - } - - @Bean - @ConditionalOnMissingBean(ServiceInstanceService.class) - public SimpleServiceInstanceService serviceInstanceService( - ServiceInstanceRepository repository) { - return new SimpleServiceInstanceService(repository); - } - - @Bean - @ConditionalOnMissingBean(ServiceInstanceBindingService.class) - public SimpleServiceInstanceBindingService serviceInstanceBindingService( - CatalogService catalog, ServiceInstanceBindingRepository repository) { - return new SimpleServiceInstanceBindingService(catalog, repository); - } - - @Bean - @ConditionalOnMissingBean(ServiceInstanceRepository.class) - public SimpleServiceInstanceRepository serviceInstanceRepository( - CatalogService catalog) { - List definitions = catalog.getCatalog() - .getServiceDefinitions(); - ServiceDefinition definition = definitions.iterator().next(); - return new SimpleServiceInstanceRepository(definition.getId()); - } - - @Bean - @ConditionalOnMissingBean(ServiceInstanceBindingRepository.class) - public SimpleServiceInstanceBindingRepository serviceInstanceBindingRepository( - CatalogService catalog) { - List definitions = catalog.getCatalog() - .getServiceDefinitions(); - ServiceDefinition definition = definitions.iterator().next(); - return new SimpleServiceInstanceBindingRepository(definition.getId()); - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingRepository.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingRepository.java deleted file mode 100644 index 1e98fb5..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingRepository.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.simple; - -import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository; - -/** - * @author Dave Syer - * - */ -public class SimpleServiceInstanceBindingRepository implements ServiceInstanceBindingRepository { - - private String serviceDefinitionId; - - public SimpleServiceInstanceBindingRepository(String serviceDefinitionId) { - this.serviceDefinitionId = serviceDefinitionId; - } - - @Override - public ServiceInstanceBinding findOne(String id) { - return new ServiceInstanceBinding(id, serviceDefinitionId, null, null, null); - } - - @Override - public void save(ServiceInstanceBinding instance) { - } - - @Override - public void delete(String id) { - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingService.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingService.java deleted file mode 100644 index e0f6fe0..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceBindingService.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.simple; - -import java.util.HashMap; -import java.util.Map; - -import org.cloudfoundry.community.servicebroker.exception.ServiceBrokerException; -import org.cloudfoundry.community.servicebroker.exception.ServiceInstanceBindingExistsException; -import org.cloudfoundry.community.servicebroker.model.ServiceDefinition; -import org.cloudfoundry.community.servicebroker.model.ServiceInstance; -import org.cloudfoundry.community.servicebroker.model.ServiceInstanceBinding; -import org.cloudfoundry.community.servicebroker.service.CatalogService; -import org.cloudfoundry.community.servicebroker.service.ServiceInstanceBindingService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceBindingRepository; -import org.springframework.stereotype.Service; - -/** - * Simple impl to bind services. - * - * @author sgreenberg@gopivotal.com - * - */ -@Service -public class SimpleServiceInstanceBindingService implements ServiceInstanceBindingService { - - private ServiceInstanceBindingRepository repository; - private CatalogService catalog; - @Value("${application.domain:cfapps.io}") - private String applicationDomain; - - @Autowired - public SimpleServiceInstanceBindingService(CatalogService catalog, - ServiceInstanceBindingRepository repository) { - this.catalog = catalog; - this.repository = repository; - } - - @Override - public ServiceInstanceBinding createServiceInstanceBinding(String bindingId, - ServiceInstance serviceInstance, String serviceId, String planId, - String appGuid) throws ServiceInstanceBindingExistsException, - ServiceBrokerException { - - ServiceInstanceBinding binding = repository.findOne(bindingId); - if (binding != null && binding.getAppGuid()!=null) { - throw new ServiceInstanceBindingExistsException(binding); - } - - binding = new ServiceInstanceBinding(bindingId, serviceInstance.getId(), - getCredentials(serviceInstance, serviceId, planId, appGuid), null, appGuid); - repository.save(binding); - - return binding; - } - - protected Map getCredentials(ServiceInstance instance, - String serviceId, String planId, String appGuid) { - Map credentials = new HashMap(); - credentials.put("uri", findUriFromService(serviceId)); - credentials.put("domain", applicationDomain); - return credentials; - } - - protected String findUriFromService(String serviceId) { - ServiceDefinition definition = catalog.getServiceDefinition(serviceId); - if (definition != null && definition.getId().equals(serviceId)) { - String uri = (String) definition.getMetadata().get("uri"); - if (uri != null) { - return uri; - } - return "http://" + definition.getName() + "." + applicationDomain; - } - throw new IllegalStateException("Cannot locate service in catalog: " + serviceId); - } - - @Override - public ServiceInstanceBinding getServiceInstanceBinding(String id) { - return repository.findOne(id); - } - - @Override - public ServiceInstanceBinding deleteServiceInstanceBinding(String id) - throws ServiceBrokerException { - ServiceInstanceBinding binding = getServiceInstanceBinding(id); - if (binding != null) { - repository.delete(id); - } - return binding; - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceRepository.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceRepository.java deleted file mode 100644 index 07f61b4..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceRepository.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.simple; - -import java.util.ArrayList; -import java.util.List; - -import org.cloudfoundry.community.servicebroker.model.ServiceInstance; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository; - -/** - * @author Dave Syer - * - */ -public class SimpleServiceInstanceRepository implements ServiceInstanceRepository { - - private String serviceDefinitionId; - - public SimpleServiceInstanceRepository(String serviceDefinitionId) { - // TODO: add dashboard URL - this.serviceDefinitionId = serviceDefinitionId; - } - - @Override - public List findAll() { - return new ArrayList(); - } - - @Override - public ServiceInstance findOne(String serviceInstanceId) { - return new ServiceInstance(serviceInstanceId, serviceDefinitionId, null, null, null, null); - } - - @Override - public void save(ServiceInstance instance) { - } - - @Override - public void delete(String id) { - } - -} diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceService.java b/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceService.java deleted file mode 100644 index 2bf71f6..0000000 --- a/src/main/java/org/springframework/cloud/cloudfoundry/broker/simple/SimpleServiceInstanceService.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.simple; - -import java.util.List; - -import org.cloudfoundry.community.servicebroker.exception.ServiceBrokerException; -import org.cloudfoundry.community.servicebroker.exception.ServiceInstanceExistsException; -import org.cloudfoundry.community.servicebroker.model.ServiceDefinition; -import org.cloudfoundry.community.servicebroker.model.ServiceInstance; -import org.cloudfoundry.community.servicebroker.service.ServiceInstanceService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cloud.cloudfoundry.broker.ServiceInstanceRepository; -import org.springframework.stereotype.Service; - -@Service -public class SimpleServiceInstanceService implements ServiceInstanceService { - - private ServiceInstanceRepository repository; - - @Autowired - public SimpleServiceInstanceService(ServiceInstanceRepository repository) { - this.repository = repository; - } - - @Override - public List getAllServiceInstances() { - return repository.findAll(); - } - - @Override - public ServiceInstance createServiceInstance(ServiceDefinition service, - String serviceInstanceId, String planId, String organizationGuid, - String spaceGuid) - throws ServiceInstanceExistsException, ServiceBrokerException { - ServiceInstance instance = repository.findOne(serviceInstanceId); - if (instance != null && instance.getOrganizationGuid()!=null) { - throw new ServiceInstanceExistsException(instance); - } - instance = new ServiceInstance(serviceInstanceId, service.getId(), - planId, organizationGuid, spaceGuid, null); - repository.save(instance); - return instance; - } - - - @Override - public ServiceInstance getServiceInstance(String id) { - return repository.findOne(id); - } - - @Override - public ServiceInstance deleteServiceInstance(String id) throws ServiceBrokerException { - ServiceInstance instance = repository.findOne(id); - repository.delete(id); - return instance; - } - -} \ No newline at end of file diff --git a/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsListener.java b/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsListener.java new file mode 100644 index 0000000..80a2ed6 --- /dev/null +++ b/src/main/java/org/springframework/cloud/cloudfoundry/environment/VcapServiceCredentialsListener.java @@ -0,0 +1,90 @@ +/* + * Copyright 2013-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.cloud.cloudfoundry.environment; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.context.config.ConfigFileApplicationListener; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.core.Ordered; +import org.springframework.core.env.MapPropertySource; + +/** + * @author Dave Syer + * + */ +public class VcapServiceCredentialsListener implements + ApplicationListener, Ordered { + + // After VcapApplicationListener and ConfigFileApplicationListener so values here can + // use those ones + private int order = ConfigFileApplicationListener.DEFAULT_ORDER + 1; + + @Override + public int getOrder() { + return order; + } + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + Map source = new HashMap(); + source.put("oauth.sso.logoutUri", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.logoutUri:}"); + source.put("oauth2.resource.id", + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.id:}"); + source.put( + "oauth2.resource.userInfoUri", + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.userInfoUri:" + + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.userInfoUri:}}"); + source.put( + "oauth2.resource.tokenInfoUri", + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenInfoUri:" + + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenInfoUri:}}"); + source.put( + "oauth2.resource.jwt.keyUri", + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.keyUri:" + + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.keyUri:}}"); + source.put( + "oauth2.resource.jwt.keyValue", + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.keyValue:" + + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.keyValue:}}"); + source.put( + "oauth2.client.tokenUri", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.tokenUri:" + + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.tokenUri:}}"); + source.put( + "oauth2.client.authorizationUri", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.authorizationUri:" + + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.authorizationUri:}}"); + source.put( + "oauth2.client.clientId", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientId:" + + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientId:}}"); + source.put( + "oauth2.client.clientSecret", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.clientSecret:" + + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.clientSecret:}}"); + source.put( + "oauth2.client.scope", + "${vcap.services.${oauth2.sso.serviceId:sso}.credentials.scope:" + + "${vcap.services.${oauth2.resource.serviceId:resource}.credentials.scope:}}"); + event.getEnvironment().getPropertySources() + .addLast(new MapPropertySource("cloudDefaultBindings", source)); + } + +} diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index 9e386c0..afab75e 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -1,2 +1,3 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -org.springframework.cloud.cloudfoundry.broker.configuration.ServiceBrokerAutoConfiguration \ No newline at end of file +# Application Listeners +org.springframework.context.ApplicationListener=\ +org.springframework.cloud.cloudfoundry.environment.VcapServiceCredentialsListener diff --git a/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/Application.java b/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/Application.java deleted file mode 100644 index ef49dde..0000000 --- a/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/Application.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.sample; - -import java.security.Principal; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.netflix.eureka.EnableEurekaClient; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@Configuration -@EnableEurekaClient -@EnableAutoConfiguration -@RestController -public class Application { - - @RequestMapping("/") - public Principal home(Principal principal) { - return principal; - } - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - -} diff --git a/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/ApplicationTests.java b/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/ApplicationTests.java deleted file mode 100644 index 309fbe6..0000000 --- a/src/test/java/org/springframework/cloud/cloudfoundry/broker/sample/ApplicationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2014 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.springframework.cloud.cloudfoundry.broker.sample; - -import static org.junit.Assert.assertEquals; - -import java.util.Map; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.IntegrationTest; -import org.springframework.boot.test.SpringApplicationConfiguration; -import org.springframework.boot.test.TestRestTemplate; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; -import org.springframework.test.context.web.WebAppConfiguration; - -@RunWith(SpringJUnit4ClassRunner.class) -@SpringApplicationConfiguration(classes = Application.class) -@WebAppConfiguration -@IntegrationTest({ "server.port=0"}) -public class ApplicationTests { - - @Value("${local.server.port}") - private int port = 0; - - @Test - public void catalogLoads() { - @SuppressWarnings("rawtypes") - ResponseEntity entity = new TestRestTemplate().getForEntity( - "http://localhost:" + port + "/v2/catalog", Map.class); - assertEquals(HttpStatus.OK, entity.getStatusCode()); - } - -} \ No newline at end of file