/* * Copyright 2013-2017 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.cloudfoundry.operations.services; import com.fasterxml.jackson.databind.ObjectMapper; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.ApplicationEntity; import org.cloudfoundry.client.v2.applications.ApplicationResource; import org.cloudfoundry.client.v2.applications.GetApplicationRequest; import org.cloudfoundry.client.v2.applications.GetApplicationResponse; import org.cloudfoundry.client.v2.applications.ListApplicationServiceBindingsRequest; import org.cloudfoundry.client.v2.jobs.JobEntity; import org.cloudfoundry.client.v2.organizations.ListOrganizationPrivateDomainsRequest; import org.cloudfoundry.client.v2.privatedomains.PrivateDomainResource; import org.cloudfoundry.client.v2.routes.ListRoutesRequest; import org.cloudfoundry.client.v2.routes.RouteResource; import org.cloudfoundry.client.v2.servicebindings.CreateServiceBindingRequest; import org.cloudfoundry.client.v2.servicebindings.CreateServiceBindingResponse; import org.cloudfoundry.client.v2.servicebindings.DeleteServiceBindingRequest; import org.cloudfoundry.client.v2.servicebindings.DeleteServiceBindingResponse; import org.cloudfoundry.client.v2.servicebindings.ListServiceBindingsRequest; import org.cloudfoundry.client.v2.servicebindings.ServiceBindingResource; import org.cloudfoundry.client.v2.serviceinstances.AbstractServiceInstanceResource; import org.cloudfoundry.client.v2.serviceinstances.BaseServiceInstanceEntity; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceResponse; import org.cloudfoundry.client.v2.serviceinstances.DeleteServiceInstanceResponse; import org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceResponse; import org.cloudfoundry.client.v2.serviceinstances.LastOperation; import org.cloudfoundry.client.v2.serviceinstances.ListServiceInstanceServiceKeysRequest; import org.cloudfoundry.client.v2.serviceinstances.UnionServiceInstanceEntity; import org.cloudfoundry.client.v2.serviceinstances.UnionServiceInstanceResource; import org.cloudfoundry.client.v2.serviceinstances.UpdateServiceInstanceResponse; import org.cloudfoundry.client.v2.servicekeys.AbstractServiceKeyResource; import org.cloudfoundry.client.v2.servicekeys.CreateServiceKeyResponse; import org.cloudfoundry.client.v2.servicekeys.ServiceKeyEntity; import org.cloudfoundry.client.v2.servicekeys.ServiceKeyResource; import org.cloudfoundry.client.v2.serviceplans.GetServicePlanRequest; import org.cloudfoundry.client.v2.serviceplans.GetServicePlanResponse; import org.cloudfoundry.client.v2.serviceplans.ListServicePlansRequest; import org.cloudfoundry.client.v2.serviceplans.ServicePlanEntity; import org.cloudfoundry.client.v2.serviceplans.ServicePlanResource; import org.cloudfoundry.client.v2.serviceplanvisibilities.ListServicePlanVisibilitiesRequest; import org.cloudfoundry.client.v2.serviceplanvisibilities.ServicePlanVisibilityResource; import org.cloudfoundry.client.v2.services.GetServiceRequest; import org.cloudfoundry.client.v2.services.GetServiceResponse; import org.cloudfoundry.client.v2.services.ServiceEntity; import org.cloudfoundry.client.v2.services.ServiceResource; import org.cloudfoundry.client.v2.shareddomains.ListSharedDomainsRequest; import org.cloudfoundry.client.v2.shareddomains.SharedDomainResource; import org.cloudfoundry.client.v2.spaces.GetSpaceSummaryRequest; import org.cloudfoundry.client.v2.spaces.GetSpaceSummaryResponse; import org.cloudfoundry.client.v2.spaces.ListSpaceApplicationsRequest; import org.cloudfoundry.client.v2.spaces.ListSpaceServiceInstancesRequest; import org.cloudfoundry.client.v2.spaces.ListSpaceServicesRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.AssociateUserProvidedServiceInstanceRouteRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.AssociateUserProvidedServiceInstanceRouteResponse; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceResponse; import org.cloudfoundry.client.v2.userprovidedserviceinstances.DeleteUserProvidedServiceInstanceRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.RemoveUserProvidedServiceInstanceRouteRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.UpdateUserProvidedServiceInstanceResponse; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.ExceptionUtils; import org.cloudfoundry.util.JobUtils; import org.cloudfoundry.util.LastOperationUtils; import org.cloudfoundry.util.PaginationUtils; import org.cloudfoundry.util.ResourceUtils; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.function.UnaryOperator; import java.util.stream.Collectors; import static org.cloudfoundry.util.tuple.TupleUtils.function; public final class DefaultServices implements Services { private static final int CF_ROUTE_SERVICE_ALREADY_BOUND = 130008; private static final int CF_SERVICE_ALREADY_BOUND = 90003; private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private final Mono<CloudFoundryClient> cloudFoundryClient; private final Mono<String> organizationId; private final Mono<String> spaceId; public DefaultServices(Mono<CloudFoundryClient> cloudFoundryClient, Mono<String> organizationId, Mono<String> spaceId) { this.cloudFoundryClient = cloudFoundryClient; this.spaceId = spaceId; this.organizationId = organizationId; } @Override public Mono<Void> bind(BindServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getApplicationId(cloudFoundryClient, request.getApplicationName(), spaceId), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, applicationId, serviceInstanceId) -> createServiceBinding(cloudFoundryClient, applicationId, serviceInstanceId, request.getParameters()))) .then() .transform(OperationsLogging.log("Bind Service Instance")) .checkpoint(); } @Override public Mono<Void> bindRoute(BindRouteServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.organizationId, this.spaceId) .then(function((cloudFoundryClient, organizationId, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getDomainId(cloudFoundryClient, request.getDomainName(), organizationId), Mono.just(spaceId) ))) .then(function((cloudFoundryClient, domainId, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getRouteId(cloudFoundryClient, request.getDomainName(), domainId, request.getHostname(), request.getPath()), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, routeId, userProvidedServiceInstanceId) -> createRouteBinding(cloudFoundryClient, routeId, userProvidedServiceInstanceId, request.getParameters()))) .then() .transform(OperationsLogging.log("Bind Route to Service Instance")) .checkpoint(); } @Override public Mono<Void> createInstance(CreateServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(spaceId), getServiceIdByName(cloudFoundryClient, spaceId, request.getServiceName()) ))) .then(function((cloudFoundryClient, spaceId, serviceId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(spaceId), getServicePlanIdByName(cloudFoundryClient, serviceId, request.getPlanName()) ))) .then(function((cloudFoundryClient, spaceId, planId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(request.getCompletionTimeout()), createServiceInstance(cloudFoundryClient, spaceId, planId, request) ))) .then(function(DefaultServices::waitForCreateInstance)) .transform(OperationsLogging.log("Create Service Instance")) .checkpoint(); } @Override public Mono<Void> createServiceKey(CreateServiceKeyRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, serviceInstanceId) -> requestCreateServiceKey(cloudFoundryClient, serviceInstanceId, request.getServiceKeyName(), request.getParameters()))) .then() .transform(OperationsLogging.log("Create Service Key")) .checkpoint(); } @Override public Mono<Void> createUserProvidedInstance(CreateUserProvidedServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> requestCreateUserProvidedServiceInstance(cloudFoundryClient, request.getName(), request.getCredentials(), request.getRouteServiceUrl(), spaceId, request.getSyslogDrainUrl()))) .then() .transform(OperationsLogging.log("Create User Provided Service Instance")) .checkpoint(); } @Override public Mono<Void> deleteInstance(DeleteServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(request.getCompletionTimeout()), getSpaceServiceInstance(cloudFoundryClient, request.getName(), spaceId) ))) .then(function(DefaultServices::deleteServiceInstance)) .transform(OperationsLogging.log("Delete Service Instance")) .checkpoint(); } @Override public Mono<Void> deleteServiceKey(DeleteServiceKeyRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, serviceInstanceId) -> Mono .when( Mono.just(cloudFoundryClient), getServiceKey(cloudFoundryClient, serviceInstanceId, request.getServiceKeyName()) .map(ResourceUtils::getId) ))) .then(function(DefaultServices::requestDeleteServiceKey)) .transform(OperationsLogging.log("Delete Service Key")) .checkpoint(); } @Override public Mono<ServiceInstance> getInstance(GetServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstance(cloudFoundryClient, request.getName(), spaceId) ))) .then(function((cloudFoundryClient, resource) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(resource), getServicePlanEntity(cloudFoundryClient, ResourceUtils.getEntity(resource).getServicePlanId()) ))) .then(function((cloudFoundryClient, resource, servicePlanEntity) -> Mono .when( Mono.just(resource), Mono.just(Optional.ofNullable(servicePlanEntity.getName())), getBoundApplications(cloudFoundryClient, ResourceUtils.getId(resource)), getServiceEntity(cloudFoundryClient, Optional.ofNullable(servicePlanEntity.getServiceId())) ))) .map(function(DefaultServices::toServiceInstance)) .transform(OperationsLogging.log("Get Service Instance")) .checkpoint(); } @Override public Mono<ServiceKey> getServiceKey(GetServiceKeyRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, serviceInstanceId) -> getServiceKey(cloudFoundryClient, serviceInstanceId, request.getServiceKeyName()))) .map(DefaultServices::toServiceKey) .transform(OperationsLogging.log("Get Service Key")) .checkpoint(); } @Override public Flux<ServiceInstanceSummary> listInstances() { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> cloudFoundryClient.spaces() .getSummary(GetSpaceSummaryRequest.builder() .spaceId(spaceId) .build()))) .flatMapMany(DefaultServices::toServiceInstanceSummary) .transform(OperationsLogging.log("List Service Instances")) .checkpoint(); } @Override public Flux<ServiceKey> listServiceKeys(ListServiceKeysRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .flatMapMany(function((cloudFoundryClient, serviceInstanceId) -> requestListServiceInstanceServiceKeys(cloudFoundryClient, serviceInstanceId))) .map(DefaultServices::toServiceKey) .transform(OperationsLogging.log("List Service Keys")) .checkpoint(); } @Override public Flux<ServiceOffering> listServiceOfferings(ListServiceOfferingsRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .flatMapMany(function((cloudFoundryClient, spaceId) -> Optional .ofNullable(request.getServiceName()) .map(serviceName -> getSpaceService(cloudFoundryClient, spaceId, serviceName).flux()) .orElse(requestListServices(cloudFoundryClient, spaceId)) .map(resource -> Tuples.of(cloudFoundryClient, resource)) )) .flatMap(function((cloudFoundryClient, resource) -> Mono .when( Mono.just(resource), getServicePlans(cloudFoundryClient, ResourceUtils.getId(resource)) ))) .map(function(DefaultServices::toServiceOffering)) .transform(OperationsLogging.log("List Service Offerings")) .checkpoint(); } @Override public Mono<Void> renameInstance(RenameServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceServiceInstance(cloudFoundryClient, request.getName(), spaceId) ))) .then(function((cloudFoundryClient, serviceInstance) -> renameServiceInstance(cloudFoundryClient, serviceInstance, request.getNewName()))) .then() .transform(OperationsLogging.log("Rename Service Instance")) .checkpoint(); } @Override public Mono<Void> unbind(UnbindServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getApplicationId(cloudFoundryClient, request.getApplicationName(), spaceId), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, applicationId, serviceInstanceId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(request.getCompletionTimeout()), getServiceBindingId(cloudFoundryClient, applicationId, serviceInstanceId, request.getServiceInstanceName()) ))) .then(function(DefaultServices::deleteServiceBinding)) .then() .transform(OperationsLogging.log("Unbind Service Instance")) .checkpoint(); } @Override public Mono<Void> unbindRoute(UnbindRouteServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.organizationId, this.spaceId) .then(function((cloudFoundryClient, organizationId, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getDomainId(cloudFoundryClient, request.getDomainName(), organizationId), Mono.just(spaceId) ))) .then(function((cloudFoundryClient, domainId, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getRouteId(cloudFoundryClient, request.getDomainName(), domainId, request.getHostname(), request.getPath()), getSpaceServiceInstanceId(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function(DefaultServices::requestRemoveRoute)) .then() .transform(OperationsLogging.log("Unbind Route from Service Instance")) .checkpoint(); } @Override public Mono<Void> updateInstance(UpdateServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.organizationId, this.spaceId) .then(function((cloudFoundryClient, organizationId, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(organizationId), getSpaceServiceInstance(cloudFoundryClient, request.getServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, organizationId, serviceInstance) -> Mono .when( Mono.just(cloudFoundryClient), Mono.just(ResourceUtils.getId(serviceInstance)), getOptionalValidatedServicePlanId(cloudFoundryClient, request.getPlanName(), serviceInstance, organizationId) ))) .then(function((cloudFoundryClient, serviceInstanceId, servicePlanId) -> updateServiceInstance(cloudFoundryClient, request, serviceInstanceId, servicePlanId.orElse(null)))) .then() .transform(OperationsLogging.log("Update Service Instance")) .checkpoint(); } @Override public Mono<Void> updateUserProvidedInstance(UpdateUserProvidedServiceInstanceRequest request) { return Mono .when(this.cloudFoundryClient, this.spaceId) .then(function((cloudFoundryClient, spaceId) -> Mono .when( Mono.just(cloudFoundryClient), getSpaceUserProvidedServiceInstanceId(cloudFoundryClient, request.getUserProvidedServiceInstanceName(), spaceId) ))) .then(function((cloudFoundryClient, userProvidedServiceInstanceId) -> updateUserProvidedServiceInstance(cloudFoundryClient, request, userProvidedServiceInstanceId))) .then() .transform(OperationsLogging.log("Update User Provided Service Instance")) .checkpoint(); } private static Mono<Optional<String>> checkVisibility(CloudFoundryClient cloudFoundryClient, String organizationId, ServicePlanResource resource) { String servicePlanId = ResourceUtils.getId(resource); if (resource.getEntity().getPubliclyVisible()) { return Mono.just(Optional.of(servicePlanId)); } return requestListServicePlanVisibilities(cloudFoundryClient, organizationId, servicePlanId) .next() .switchIfEmpty(ExceptionUtils.illegalArgument("Service Plan %s is not visible to your organization", resource.getEntity().getName())) .then(Mono.just(Optional.of(servicePlanId))); } private static Mono<AssociateUserProvidedServiceInstanceRouteResponse> createRouteBinding(CloudFoundryClient cloudFoundryClient, String routeId, String userProvidedServiceInstanceId, Map<String, Object> parameters) { return requestCreateRouteBinding(cloudFoundryClient, routeId, userProvidedServiceInstanceId, parameters) .onErrorResume(ExceptionUtils.statusCode(CF_ROUTE_SERVICE_ALREADY_BOUND), t -> Mono.empty()); } private static Mono<CreateServiceBindingResponse> createServiceBinding(CloudFoundryClient cloudFoundryClient, String applicationId, String serviceInstanceId, Map<String, Object> parameters) { return requestCreateServiceBinding(cloudFoundryClient, applicationId, serviceInstanceId, parameters) .onErrorResume(ExceptionUtils.statusCode(CF_SERVICE_ALREADY_BOUND), t -> Mono.empty()); } private static Mono<AbstractServiceInstanceResource> createServiceInstance(CloudFoundryClient cloudFoundryClient, String spaceId, String planId, CreateServiceInstanceRequest request) { return requestCreateServiceInstance(cloudFoundryClient, spaceId, planId, request.getServiceInstanceName(), request.getParameters(), request.getTags()) .cast(AbstractServiceInstanceResource.class); } private static Mono<Void> deleteServiceBinding(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, String serviceBindingId) { return requestDeleteServiceBinding(cloudFoundryClient, serviceBindingId) .then(job -> JobUtils.waitForCompletion(cloudFoundryClient, completionTimeout, job)); } private static Mono<Void> deleteServiceInstance(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, UnionServiceInstanceResource serviceInstance) { if (isUserProvidedService(serviceInstance)) { return requestDeleteUserProvidedServiceInstance(cloudFoundryClient, ResourceUtils.getId(serviceInstance)); } else { return requestDeleteServiceInstance(cloudFoundryClient, ResourceUtils.getId(serviceInstance)) .then(response -> { Object entity = response.getEntity(); if (entity instanceof JobEntity) { return JobUtils.waitForCompletion(cloudFoundryClient, completionTimeout, (JobEntity) response.getEntity()); } else { return LastOperationUtils .waitForCompletion(completionTimeout, () -> requestGetServiceInstance(cloudFoundryClient, ResourceUtils.getId(serviceInstance)) .map(r -> ResourceUtils.getEntity(r).getLastOperation())); } }); } } private static Mono<ApplicationResource> getApplication(CloudFoundryClient cloudFoundryClient, String applicationName, String spaceId) { return requestListApplications(cloudFoundryClient, applicationName, spaceId) .single() .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Application %s does not exist", applicationName)); } private static Mono<String> getApplicationId(CloudFoundryClient cloudFoundryClient, String applicationName, String spaceId) { return getApplication(cloudFoundryClient, applicationName, spaceId) .map(ResourceUtils::getId); } private static Mono<List<String>> getBoundApplications(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return requestListServiceBindings(cloudFoundryClient, serviceInstanceId) .map(resource -> ResourceUtils.getEntity(resource).getApplicationId()) .flatMap(applicationId -> requestGetApplication(cloudFoundryClient, applicationId)) .map(ResourceUtils::getEntity) .map(ApplicationEntity::getName) .collectList(); } private static Mono<String> getDomainId(CloudFoundryClient cloudFoundryClient, String domain, String organizationId) { return getPrivateDomainId(cloudFoundryClient, domain, organizationId) .switchIfEmpty(getSharedDomainId(cloudFoundryClient, domain)) .switchIfEmpty(ExceptionUtils.illegalArgument("Domain %s not found", domain)); } @SuppressWarnings("unchecked") private static String getExtraValue(String extra, String key) { if (extra == null || extra.isEmpty()) { return null; } try { return (String) OBJECT_MAPPER.readValue(extra, Map.class).get(key); } catch (IOException e) { throw Exceptions.propagate(e); } } private static Mono<Optional<String>> getOptionalValidatedServicePlanId(CloudFoundryClient cloudFoundryClient, String planName, UnionServiceInstanceResource serviceInstance, String organizationId) { if (planName == null || planName.isEmpty()) { return Mono.just(Optional.empty()); } String servicePlanId = serviceInstance.getEntity().getServicePlanId(); if (servicePlanId == null || servicePlanId.isEmpty()) { return ExceptionUtils.illegalArgument("Plan does not exist for the %s service", serviceInstance.getEntity().getName()); } return getServiceId(cloudFoundryClient, servicePlanId) .then(serviceId -> requestGetService(cloudFoundryClient, serviceId)) .filter(DefaultServices::isPlanUpdateable) .switchIfEmpty(ExceptionUtils.illegalArgument("Plan for the %s service cannot be updated", serviceInstance.getEntity().getName())) .flatMapMany(response -> requestListServicePlans(cloudFoundryClient, ResourceUtils.getId(response))) .filter(resource -> planName.equals(resource.getEntity().getName())) .singleOrEmpty() .switchIfEmpty(ExceptionUtils.illegalArgument("New service plan %s not found", planName)) .then(resource -> checkVisibility(cloudFoundryClient, organizationId, resource)); } private static Mono<String> getPrivateDomainId(CloudFoundryClient cloudFoundryClient, String domain, String organizationId) { return requestPrivateDomain(cloudFoundryClient, domain, organizationId) .map(ResourceUtils::getId) .singleOrEmpty(); } private static Mono<RouteResource> getRoute(CloudFoundryClient cloudFoundryClient, String domain, String domainId, String host, String path) { return getRoute(cloudFoundryClient, domainId, host, path) .switchIfEmpty(ExceptionUtils.illegalArgument("Route %s.%s does not exist", host, domain)); } private static Mono<RouteResource> getRoute(CloudFoundryClient cloudFoundryClient, String domainId, String host, String routePath) { return requestRoutes(cloudFoundryClient, domainId, host, routePath) .filter(resource -> isIdentical(host, ResourceUtils.getEntity(resource).getHost())) .filter(resource -> isIdentical(Optional.ofNullable(routePath).orElse(""), ResourceUtils.getEntity(resource).getPath())) .singleOrEmpty(); } private static Mono<String> getRouteId(CloudFoundryClient cloudFoundryClient, String domain, String domainId, String host, String path) { return getRoute(cloudFoundryClient, domain, domainId, host, path) .map(ResourceUtils::getId); } private static Mono<String> getServiceBindingId(CloudFoundryClient cloudFoundryClient, String applicationId, String serviceInstanceId, String serviceInstanceName) { return requestListServiceBindings(cloudFoundryClient, applicationId, serviceInstanceId) .singleOrEmpty() .switchIfEmpty(ExceptionUtils.illegalState("Service instance %s is not bound to application", serviceInstanceName)) .map(ResourceUtils::getId); } private static Mono<ServiceEntity> getServiceEntity(CloudFoundryClient cloudFoundryClient, Optional<String> serviceId) { return Mono .justOrEmpty(serviceId) .then(serviceId1 -> requestGetService(cloudFoundryClient, serviceId1)) .map(ResourceUtils::getEntity) .switchIfEmpty(Mono.just(ServiceEntity.builder().build())); } private static Mono<String> getServiceId(CloudFoundryClient cloudFoundryClient, String servicePlanId) { return requestGetServicePlan(cloudFoundryClient, servicePlanId) .map(response -> response.getEntity().getServiceId()); } private static Mono<String> getServiceIdByName(CloudFoundryClient cloudFoundryClient, String spaceId, String service) { return getSpaceService(cloudFoundryClient, spaceId, service) .map(ResourceUtils::getId); } private static Mono<ServiceKeyResource> getServiceKey(CloudFoundryClient cloudFoundryClient, String serviceInstanceId, String serviceKey) { return requestListServiceInstanceServiceKeys(cloudFoundryClient, serviceInstanceId, serviceKey) .single() .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Service key %s does not exist", serviceKey)); } private static Mono<ServicePlanEntity> getServicePlanEntity(CloudFoundryClient cloudFoundryClient, String servicePlanId) { return Mono .justOrEmpty(servicePlanId) .then(servicePlanId1 -> requestGetServicePlan(cloudFoundryClient, servicePlanId1)) .map(ResourceUtils::getEntity) .switchIfEmpty(Mono.just(ServicePlanEntity.builder().build())); } private static Mono<String> getServicePlanIdByName(CloudFoundryClient cloudFoundryClient, String serviceId, String plan) { return requestListServicePlans(cloudFoundryClient, serviceId) .filter(resource -> plan.equals(ResourceUtils.getEntity(resource).getName())) .single() .map(ResourceUtils::getId) .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Service plan %s does not exist", plan)); } private static Mono<List<ServicePlanResource>> getServicePlans(CloudFoundryClient cloudFoundryClient, String serviceId) { return requestListServicePlans(cloudFoundryClient, serviceId) .collectList(); } private static Mono<String> getSharedDomainId(CloudFoundryClient cloudFoundryClient, String domain) { return requestSharedDomain(cloudFoundryClient, domain) .map(ResourceUtils::getId) .singleOrEmpty(); } private static Mono<ServiceResource> getSpaceService(CloudFoundryClient cloudFoundryClient, String spaceId, String service) { return requestListServices(cloudFoundryClient, spaceId, service) .single() .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Service %s does not exist", service)); } private static Mono<UnionServiceInstanceResource> getSpaceServiceInstance(CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { return requestListServiceInstances(cloudFoundryClient, spaceId, serviceInstanceName) .single() .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Service instance %s does not exist", serviceInstanceName)); } private static Mono<String> getSpaceServiceInstanceId(CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { return getSpaceServiceInstance(cloudFoundryClient, serviceInstanceName, spaceId) .map(ResourceUtils::getId); } private static Mono<UnionServiceInstanceResource> getSpaceUserProvidedServiceInstance(CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { return requestListServiceInstances(cloudFoundryClient, spaceId, serviceInstanceName) .filter(DefaultServices::isUserProvidedService) .single() .onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("User provided service instance %s does not exist", serviceInstanceName)); } private static Mono<String> getSpaceUserProvidedServiceInstanceId(CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { return getSpaceUserProvidedServiceInstance(cloudFoundryClient, serviceInstanceName, spaceId) .map(ResourceUtils::getId); } private static boolean isIdentical(String s, String t) { return s == null ? t == null : s.equals(t); } private static boolean isPlanUpdateable(GetServiceResponse response) { return response.getEntity().getPlanUpdateable(); } private static boolean isUserProvidedService(UnionServiceInstanceResource serviceInstance) { return ServiceInstanceType.from(ResourceUtils.getEntity(serviceInstance).getType()).equals(ServiceInstanceType.USER_PROVIDED); } private static Mono<BaseServiceInstanceEntity> renameServiceInstance(CloudFoundryClient cloudFoundryClient, UnionServiceInstanceResource serviceInstance, String newName) { if (isUserProvidedService(serviceInstance)) { return requestUserProvidedServiceInstanceUpdate(cloudFoundryClient, ResourceUtils.getId(serviceInstance), newName) .map(ResourceUtils::getEntity) .cast(BaseServiceInstanceEntity.class); } else { return requestServiceInstanceUpdate(cloudFoundryClient, ResourceUtils.getId(serviceInstance), newName) .map(ResourceUtils::getEntity) .cast(BaseServiceInstanceEntity.class); } } private static Mono<AssociateUserProvidedServiceInstanceRouteResponse> requestCreateRouteBinding(CloudFoundryClient cloudFoundryClient, String routeId, String userProvidedServiceInstanceId, Map<String, Object> parameters) { return cloudFoundryClient.userProvidedServiceInstances() .associateRoute(AssociateUserProvidedServiceInstanceRouteRequest.builder() .parameters(parameters) .routeId(routeId) .userProvidedServiceInstanceId(userProvidedServiceInstanceId) .build()); } private static Mono<CreateServiceBindingResponse> requestCreateServiceBinding(CloudFoundryClient cloudFoundryClient, String applicationId, String serviceInstanceId, Map<String, Object> parameters) { return cloudFoundryClient.serviceBindingsV2() .create(CreateServiceBindingRequest.builder() .applicationId(applicationId) .parameters(parameters) .serviceInstanceId(serviceInstanceId) .build()); } private static Mono<CreateServiceInstanceResponse> requestCreateServiceInstance(CloudFoundryClient cloudFoundryClient, String spaceId, String planId, String serviceInstance, Map<String, Object> parameters, List<String> tags) { return cloudFoundryClient.serviceInstances() .create(org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceRequest.builder() .acceptsIncomplete(true) .name(serviceInstance) .servicePlanId(planId) .spaceId(spaceId) .parameters(parameters) .tags(tags) .build()); } private static Mono<CreateServiceKeyResponse> requestCreateServiceKey(CloudFoundryClient cloudFoundryClient, String serviceInstanceId, String serviceKey, Map<String, Object> parameters) { return cloudFoundryClient.serviceKeys() .create(org.cloudfoundry.client.v2.servicekeys.CreateServiceKeyRequest.builder() .serviceInstanceId(serviceInstanceId) .name(serviceKey) .parameters(parameters) .build()); } private static Mono<CreateUserProvidedServiceInstanceResponse> requestCreateUserProvidedServiceInstance(CloudFoundryClient cloudFoundryClient, String name, Map<String, Object> credentials, String routeServiceUrl, String spaceId, String syslogDrainUrl) { return cloudFoundryClient.userProvidedServiceInstances() .create(org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest.builder() .name(name) .credentials(credentials) .routeServiceUrl(routeServiceUrl) .spaceId(spaceId) .syslogDrainUrl(syslogDrainUrl) .build()); } private static Mono<DeleteServiceBindingResponse> requestDeleteServiceBinding(CloudFoundryClient cloudFoundryClient, String serviceBindingId) { return cloudFoundryClient.serviceBindingsV2() .delete(DeleteServiceBindingRequest.builder() .serviceBindingId(serviceBindingId) .async(true) .build()); } private static Mono<DeleteServiceInstanceResponse> requestDeleteServiceInstance(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return cloudFoundryClient.serviceInstances() .delete(org.cloudfoundry.client.v2.serviceinstances.DeleteServiceInstanceRequest.builder() .acceptsIncomplete(true) .serviceInstanceId(serviceInstanceId) .async(true) .build()); } private static Mono<Void> requestDeleteServiceKey(CloudFoundryClient cloudFoundryClient, String serviceKeyId) { return cloudFoundryClient.serviceKeys() .delete(org.cloudfoundry.client.v2.servicekeys.DeleteServiceKeyRequest.builder() .serviceKeyId(serviceKeyId) .build()); } private static Mono<Void> requestDeleteUserProvidedServiceInstance(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return cloudFoundryClient.userProvidedServiceInstances() .delete(DeleteUserProvidedServiceInstanceRequest.builder() .userProvidedServiceInstanceId(serviceInstanceId) .build()); } private static Mono<GetApplicationResponse> requestGetApplication(CloudFoundryClient cloudFoundryClient, String applicationId) { return cloudFoundryClient.applicationsV2() .get(GetApplicationRequest.builder() .applicationId(applicationId) .build()); } private static Mono<GetServiceResponse> requestGetService(CloudFoundryClient cloudFoundryClient, String serviceId) { return cloudFoundryClient.services() .get(GetServiceRequest.builder() .serviceId(serviceId) .build()); } private static Mono<GetServiceInstanceResponse> requestGetServiceInstance(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return cloudFoundryClient.serviceInstances() .get(org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest.builder() .serviceInstanceId(serviceInstanceId) .build()); } private static Mono<GetServicePlanResponse> requestGetServicePlan(CloudFoundryClient cloudFoundryClient, String servicePlanId) { return cloudFoundryClient.servicePlans() .get(GetServicePlanRequest.builder() .servicePlanId(servicePlanId) .build()); } private static Flux<ApplicationResource> requestListApplications(CloudFoundryClient cloudFoundryClient, String application, String spaceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaces() .listApplications(ListSpaceApplicationsRequest.builder() .name(application) .spaceId(spaceId) .page(page) .build())); } private static Flux<ServiceBindingResource> requestListServiceBindings(CloudFoundryClient cloudFoundryClient, String applicationId, String serviceInstanceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.applicationsV2() .listServiceBindings(ListApplicationServiceBindingsRequest.builder() .page(page) .applicationId(applicationId) .serviceInstanceId(serviceInstanceId) .build())); } private static Flux<ServiceBindingResource> requestListServiceBindings(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceBindingsV2() .list(ListServiceBindingsRequest.builder() .page(page) .serviceInstanceId(serviceInstanceId) .build())); } private static Flux<ServiceKeyResource> requestListServiceInstanceServiceKeys(CloudFoundryClient cloudFoundryClient, String serviceInstanceId, String serviceKey) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .listServiceKeys(ListServiceInstanceServiceKeysRequest.builder() .serviceInstanceId(serviceInstanceId) .name(serviceKey) .page(page) .build())); } private static Flux<ServiceKeyResource> requestListServiceInstanceServiceKeys(CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .listServiceKeys(ListServiceInstanceServiceKeysRequest.builder() .page(page) .serviceInstanceId(serviceInstanceId) .build())); } private static Flux<UnionServiceInstanceResource> requestListServiceInstances(CloudFoundryClient cloudFoundryClient, String spaceId, String serviceInstanceName) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaces() .listServiceInstances(ListSpaceServiceInstancesRequest.builder() .page(page) .returnUserProvidedServiceInstances(true) .name(serviceInstanceName) .spaceId(spaceId) .build())); } private static Flux<ServicePlanVisibilityResource> requestListServicePlanVisibilities(CloudFoundryClient cloudFoundryClient, String organizationId, String servicePlanId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.servicePlanVisibilities() .list(ListServicePlanVisibilitiesRequest.builder() .organizationId(organizationId) .page(page) .servicePlanId(servicePlanId) .build())); } private static Flux<ServicePlanResource> requestListServicePlans(CloudFoundryClient cloudFoundryClient, String serviceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.servicePlans() .list(ListServicePlansRequest.builder() .page(page) .serviceId(serviceId) .build())); } private static Flux<ServiceResource> requestListServices(CloudFoundryClient cloudFoundryClient, String spaceId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaces() .listServices(ListSpaceServicesRequest.builder() .page(page) .spaceId(spaceId) .build())); } private static Flux<ServiceResource> requestListServices(CloudFoundryClient cloudFoundryClient, String spaceId, String serviceName) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaces() .listServices(ListSpaceServicesRequest.builder() .label(serviceName) .page(page) .spaceId(spaceId) .build())); } private static Flux<PrivateDomainResource> requestPrivateDomain(CloudFoundryClient cloudFoundryClient, String domain, String organizationId) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.organizations() .listPrivateDomains(ListOrganizationPrivateDomainsRequest.builder() .name(domain) .organizationId(organizationId) .page(page) .build())); } private static Mono<Void> requestRemoveRoute(CloudFoundryClient cloudFoundryClient, String routeId, String userProvidedServiceInstanceId) { return cloudFoundryClient.userProvidedServiceInstances() .removeRoute(RemoveUserProvidedServiceInstanceRouteRequest.builder() .routeId(routeId) .userProvidedServiceInstanceId(userProvidedServiceInstanceId) .build()); } private static Flux<RouteResource> requestRoutes(CloudFoundryClient cloudFoundryClient, UnaryOperator<ListRoutesRequest.Builder> modifier) { ListRoutesRequest.Builder listBuilder = modifier.apply(ListRoutesRequest.builder()); return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.routes() .list(listBuilder .page(page) .build())); } private static Flux<RouteResource> requestRoutes(CloudFoundryClient cloudFoundryClient, String domainId, String host, String path) { return requestRoutes(cloudFoundryClient, builder -> builder .domainId(domainId) .hosts(Optional.ofNullable(host).map(Collections::singletonList).orElse(null)) .paths(Optional.ofNullable(path).map(Collections::singletonList).orElse(null)) ); } private static Mono<UpdateServiceInstanceResponse> requestServiceInstanceUpdate(CloudFoundryClient cloudFoundryClient, String serviceInstanceId, String newName) { return cloudFoundryClient.serviceInstances() .update(org.cloudfoundry.client.v2.serviceinstances.UpdateServiceInstanceRequest.builder() .name(newName) .serviceInstanceId(serviceInstanceId) .build()); } private static Flux<SharedDomainResource> requestSharedDomain(CloudFoundryClient cloudFoundryClient, String domain) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.sharedDomains() .list(ListSharedDomainsRequest.builder() .name(domain) .page(page) .build())); } private static Mono<UpdateUserProvidedServiceInstanceResponse> requestUserProvidedServiceInstanceUpdate(CloudFoundryClient cloudFoundryClient, String serviceInstanceId, String newName) { return cloudFoundryClient.userProvidedServiceInstances() .update(org.cloudfoundry.client.v2.userprovidedserviceinstances.UpdateUserProvidedServiceInstanceRequest.builder() .name(newName) .userProvidedServiceInstanceId(serviceInstanceId) .build()); } private static ServiceInstance toServiceInstance(UnionServiceInstanceResource resource, Optional<String> plan, List<String> applications, ServiceEntity serviceEntity) { String extra = Optional.ofNullable(serviceEntity.getExtra()).orElse(""); Optional<String> documentationUrl = Optional.ofNullable(getExtraValue(extra, "documentationUrl")); UnionServiceInstanceEntity serviceInstanceEntity = resource.getEntity(); LastOperation lastOperation = Optional .ofNullable(serviceInstanceEntity.getLastOperation()) .orElse(LastOperation.builder() .build()); return ServiceInstance.builder() .applications(applications) .dashboardUrl(serviceInstanceEntity.getDashboardUrl()) .description(serviceEntity.getDescription()) .documentationUrl(documentationUrl.orElse(null)) .id(ResourceUtils.getId(resource)) .lastOperation(lastOperation.getType()) .message(lastOperation.getDescription()) .name(serviceInstanceEntity.getName()) .plan(plan.orElse(null)) .service(serviceEntity.getLabel()) .startedAt(lastOperation.getCreatedAt()) .status(lastOperation.getState()) .tags(serviceInstanceEntity.getTags()) .type(ServiceInstanceType.from(serviceInstanceEntity.getType())) .updatedAt(lastOperation.getUpdatedAt()) .build(); } private static Flux<ServiceInstanceSummary> toServiceInstanceSummary(GetSpaceSummaryResponse response) { Map<String, List<String>> applicationBindings = new HashMap<>(); response.getApplications() .forEach(application -> application.getServiceNames() .forEach(serviceName -> { if (!applicationBindings.containsKey(serviceName)) { applicationBindings.put(serviceName, new ArrayList<>()); } applicationBindings.get(serviceName).add(application.getName()); }) ); return Flux.fromIterable(response.getServices()) .map(service -> ServiceInstanceSummary.builder() .applications(Optional.ofNullable(applicationBindings.get(service.getName())).orElse(Collections.emptyList())) .id(service.getId()) .lastOperation(service.getLastOperation() == null ? null : service.getLastOperation().getDescription()) .name(service.getName()) .plan(service.getServicePlan() == null ? null : service.getServicePlan().getName()) .service(service.getServicePlan() == null ? null : service.getServicePlan().getService().getLabel()) .type(service.getServicePlan() == null ? ServiceInstanceType.USER_PROVIDED : ServiceInstanceType.MANAGED) .build()); } private static ServiceKey toServiceKey(AbstractServiceKeyResource resource) { ServiceKeyEntity entity = ResourceUtils.getEntity(resource); return ServiceKey.builder() .credentials(entity.getCredentials()) .id(ResourceUtils.getId(resource)) .name(entity.getName()) .build(); } private static ServiceOffering toServiceOffering(ServiceResource resource, List<ServicePlanResource> servicePlans) { ServiceEntity entity = resource.getEntity(); return ServiceOffering.builder() .description(entity.getDescription()) .id(ResourceUtils.getId(resource)) .label(entity.getLabel()) .servicePlans(toServicePlans(servicePlans)) .build(); } private static ServicePlan toServicePlan(ServicePlanResource resource) { ServicePlanEntity entity = ResourceUtils.getEntity(resource); return ServicePlan.builder() .description(entity.getDescription()) .free(entity.getFree()) .id(ResourceUtils.getId(resource)) .name(entity.getName()) .build(); } private static List<ServicePlan> toServicePlans(List<ServicePlanResource> servicePlans) { return servicePlans.stream() .map(DefaultServices::toServicePlan) .collect(Collectors.toList()); } private static Mono<UpdateServiceInstanceResponse> updateServiceInstance(CloudFoundryClient cloudFoundryClient, UpdateServiceInstanceRequest request, String serviceInstanceId, String servicePlanId) { return cloudFoundryClient.serviceInstances() .update(org.cloudfoundry.client.v2.serviceinstances .UpdateServiceInstanceRequest.builder() .acceptsIncomplete(true) .parameters(request.getParameters()) .serviceInstanceId(serviceInstanceId) .servicePlanId(servicePlanId) .tags(request.getTags()) .build()); } private static Mono<UpdateUserProvidedServiceInstanceResponse> updateUserProvidedServiceInstance(CloudFoundryClient cloudFoundryClient, UpdateUserProvidedServiceInstanceRequest request, String userProvidedServiceInstanceId) { return cloudFoundryClient.userProvidedServiceInstances() .update(org.cloudfoundry.client.v2.userprovidedserviceinstances.UpdateUserProvidedServiceInstanceRequest.builder() .credentials(request.getCredentials()) .syslogDrainUrl(request.getSyslogDrainUrl()) .userProvidedServiceInstanceId(userProvidedServiceInstanceId) .build()); } private static Mono<Void> waitForCreateInstance(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, AbstractServiceInstanceResource serviceInstance) { return LastOperationUtils .waitForCompletion(completionTimeout, () -> requestGetServiceInstance(cloudFoundryClient, ResourceUtils.getId(serviceInstance)) .map(response -> ResourceUtils.getEntity(response).getLastOperation())); } }