/* * 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; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.ApplicationResource; import org.cloudfoundry.client.v2.applications.DeleteApplicationRequest; import org.cloudfoundry.client.v2.applications.ListApplicationServiceBindingsRequest; import org.cloudfoundry.client.v2.applications.ListApplicationsRequest; import org.cloudfoundry.client.v2.applications.RemoveApplicationServiceBindingRequest; import org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackRequest; import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest; import org.cloudfoundry.client.v2.featureflags.ListFeatureFlagsRequest; import org.cloudfoundry.client.v2.featureflags.ListFeatureFlagsResponse; import org.cloudfoundry.client.v2.featureflags.SetFeatureFlagRequest; import org.cloudfoundry.client.v2.jobs.JobEntity; import org.cloudfoundry.client.v2.organizationquotadefinitions.DeleteOrganizationQuotaDefinitionRequest; import org.cloudfoundry.client.v2.organizationquotadefinitions.ListOrganizationQuotaDefinitionsRequest; import org.cloudfoundry.client.v2.organizations.DeleteOrganizationRequest; import org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest; import org.cloudfoundry.client.v2.privatedomains.DeletePrivateDomainRequest; import org.cloudfoundry.client.v2.privatedomains.ListPrivateDomainsRequest; import org.cloudfoundry.client.v2.routes.DeleteRouteRequest; import org.cloudfoundry.client.v2.routes.ListRoutesRequest; import org.cloudfoundry.client.v2.routes.RouteEntity; import org.cloudfoundry.client.v2.securitygroups.DeleteSecurityGroupRequest; import org.cloudfoundry.client.v2.securitygroups.ListSecurityGroupsRequest; import org.cloudfoundry.client.v2.servicebrokers.DeleteServiceBrokerRequest; import org.cloudfoundry.client.v2.servicebrokers.ListServiceBrokersRequest; import org.cloudfoundry.client.v2.serviceinstances.DeleteServiceInstanceRequest; import org.cloudfoundry.client.v2.serviceinstances.GetServiceInstanceRequest; import org.cloudfoundry.client.v2.serviceinstances.ListServiceInstanceRoutesRequest; import org.cloudfoundry.client.v2.serviceinstances.ListServiceInstanceServiceBindingsRequest; import org.cloudfoundry.client.v2.serviceinstances.ListServiceInstanceServiceKeysRequest; import org.cloudfoundry.client.v2.serviceinstances.ListServiceInstancesRequest; import org.cloudfoundry.client.v2.serviceinstances.ServiceInstanceResource; import org.cloudfoundry.client.v2.serviceinstances.UnbindServiceInstanceRouteRequest; import org.cloudfoundry.client.v2.servicekeys.DeleteServiceKeyRequest; import org.cloudfoundry.client.v2.shareddomains.DeleteSharedDomainRequest; import org.cloudfoundry.client.v2.shareddomains.ListSharedDomainsRequest; import org.cloudfoundry.client.v2.spacequotadefinitions.DeleteSpaceQuotaDefinitionRequest; import org.cloudfoundry.client.v2.spacequotadefinitions.ListSpaceQuotaDefinitionsRequest; import org.cloudfoundry.client.v2.spaces.DeleteSpaceRequest; import org.cloudfoundry.client.v2.spaces.ListSpacesRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.DeleteUserProvidedServiceInstanceRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.ListUserProvidedServiceInstanceServiceBindingsRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.ListUserProvidedServiceInstancesRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.UserProvidedServiceInstanceResource; import org.cloudfoundry.client.v2.users.UserResource; import org.cloudfoundry.uaa.UaaClient; import org.cloudfoundry.uaa.clients.DeleteClientRequest; import org.cloudfoundry.uaa.clients.ListClientsRequest; import org.cloudfoundry.uaa.groups.DeleteGroupRequest; import org.cloudfoundry.uaa.groups.Group; import org.cloudfoundry.uaa.groups.ListGroupsRequest; import org.cloudfoundry.uaa.groups.MemberSummary; import org.cloudfoundry.uaa.identityproviders.DeleteIdentityProviderRequest; import org.cloudfoundry.uaa.identityproviders.ListIdentityProvidersRequest; import org.cloudfoundry.uaa.identityproviders.ListIdentityProvidersResponse; import org.cloudfoundry.uaa.identityzones.DeleteIdentityZoneRequest; import org.cloudfoundry.uaa.identityzones.ListIdentityZonesRequest; import org.cloudfoundry.uaa.identityzones.ListIdentityZonesResponse; import org.cloudfoundry.uaa.users.DeleteUserRequest; import org.cloudfoundry.uaa.users.ListUsersRequest; import org.cloudfoundry.util.FluentMap; import org.cloudfoundry.util.JobUtils; import org.cloudfoundry.util.LastOperationUtils; import org.cloudfoundry.util.PaginationUtils; import org.cloudfoundry.util.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; import javax.net.ssl.SSLException; import java.time.Duration; import java.util.Map; import static org.cloudfoundry.util.tuple.TupleUtils.function; import static org.cloudfoundry.util.tuple.TupleUtils.predicate; final class CloudFoundryCleaner { private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test"); private static final Map<String, Boolean> STANDARD_FEATURE_FLAGS = FluentMap.<String, Boolean>builder() .entry("app_bits_upload", true) .entry("app_scaling", true) .entry("diego_docker", true) .entry("private_domain_creation", true) .entry("route_creation", true) .entry("service_instance_creation", true) .entry("set_roles_by_username", true) .entry("unset_roles_by_username", true) .entry("user_org_creation", false) .build(); private final CloudFoundryClient cloudFoundryClient; private final NameFactory nameFactory; private final UaaClient uaaClient; CloudFoundryCleaner(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, UaaClient uaaClient) { this.cloudFoundryClient = cloudFoundryClient; this.nameFactory = nameFactory; this.uaaClient = uaaClient; } void clean() { Flux.empty() .thenMany(Mono.when( // Before Routes cleanServiceInstances(this.cloudFoundryClient, this.nameFactory), cleanUserProvidedServiceInstances(this.cloudFoundryClient, this.nameFactory) )) .thenMany(Mono.when( // No prerequisites cleanBuildpacks(this.cloudFoundryClient, this.nameFactory), cleanClients(this.uaaClient, this.nameFactory), cleanFeatureFlags(this.cloudFoundryClient), cleanGroups(this.uaaClient, this.nameFactory), cleanIdentityProviders(this.uaaClient, this.nameFactory), cleanIdentityZones(this.uaaClient, this.nameFactory), cleanRoutes(this.cloudFoundryClient, this.nameFactory), cleanSecurityGroups(this.cloudFoundryClient, this.nameFactory), cleanServiceBrokers(this.cloudFoundryClient, this.nameFactory), cleanSpaceQuotaDefinitions(this.cloudFoundryClient, this.nameFactory), cleanUsers(this.cloudFoundryClient, this.nameFactory), cleanUsers(this.uaaClient, this.nameFactory) )) .thenMany(cleanApplicationsV2(this.cloudFoundryClient, this.nameFactory)) // After Routes, cannot run with other cleanApps .thenMany(Mono.when( // After Routes/Applications cleanPrivateDomains(this.cloudFoundryClient, this.nameFactory), cleanSharedDomains(this.cloudFoundryClient, this.nameFactory), cleanSpaces(this.cloudFoundryClient, this.nameFactory), cleanUserProvidedServiceInstances(this.cloudFoundryClient, this.nameFactory) )) .thenMany(cleanOrganizations(this.cloudFoundryClient, this.nameFactory)) // After Spaces .thenMany(cleanOrganizationQuotaDefinitions(this.cloudFoundryClient, this.nameFactory)) // After Organizations .retry(5, t -> t instanceof SSLException) .doOnSubscribe(s -> LOGGER.debug(">> CLEANUP <<")) .doOnComplete(() -> LOGGER.debug("<< CLEANUP >>")) .then() .block(Duration.ofMinutes(30)); } private static Flux<Void> cleanApplicationsV2(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.applicationsV2() .list(ListApplicationsRequest.builder() .page(page) .build())) .filter(application -> nameFactory.isApplicationName(ResourceUtils.getEntity(application).getName())) .flatMap(application -> removeApplicationServiceBindings(cloudFoundryClient, application) .thenMany(Flux.just(application))) .flatMap(application -> cloudFoundryClient.applicationsV2() .delete(DeleteApplicationRequest.builder() .applicationId(ResourceUtils.getId(application)) .build()) .doOnError(t -> LOGGER.error("Unable to delete V2 application {}", ResourceUtils.getEntity(application).getName(), t))); } private static Flux<Void> cleanBuildpacks(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.buildpacks() .list(ListBuildpacksRequest.builder() .page(page) .build())) .filter(buildpack -> nameFactory.isBuildpackName(ResourceUtils.getEntity(buildpack).getName())) .flatMap(buildpack -> cloudFoundryClient.buildpacks() .delete(DeleteBuildpackRequest.builder() .async(true) .buildpackId(ResourceUtils.getId(buildpack)) .build()) .doOnError(t -> LOGGER.error("Unable to delete buildpack {}", ResourceUtils.getEntity(buildpack).getName(), t))) .flatMap(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)); } private static Flux<Void> cleanClients(UaaClient uaaClient, NameFactory nameFactory) { return PaginationUtils .requestUaaResources(startIndex -> uaaClient.clients() .list(ListClientsRequest.builder() .startIndex(startIndex) .build())) .filter(client -> nameFactory.isClientId(client.getClientId())) .flatMap(client -> uaaClient.clients() .delete(DeleteClientRequest.builder() .clientId(client.getClientId()) .build()) .doOnError(t -> LOGGER.error("Unable to delete client {}", client.getName(), t)) .then()); } private static Flux<Void> cleanFeatureFlags(CloudFoundryClient cloudFoundryClient) { return cloudFoundryClient.featureFlags() .list(ListFeatureFlagsRequest.builder() .build()) .flatMapIterable(ListFeatureFlagsResponse::getFeatureFlags) .filter(featureFlag -> STANDARD_FEATURE_FLAGS.containsKey(featureFlag.getName())) .filter(featureFlag -> STANDARD_FEATURE_FLAGS.get(featureFlag.getName()) != featureFlag.getEnabled()) .flatMap(featureFlag -> cloudFoundryClient.featureFlags() .set(SetFeatureFlagRequest.builder() .name(featureFlag.getName()) .enabled(STANDARD_FEATURE_FLAGS.get(featureFlag.getName())) .build()) .doOnError(t -> LOGGER.error("Unable to set feature flag {} to {}", featureFlag.getName(), STANDARD_FEATURE_FLAGS.get(featureFlag.getName()), t)) .then()); } private static Flux<Void> cleanGroups(UaaClient uaaClient, NameFactory nameFactory) { return PaginationUtils .requestUaaResources(startIndex -> uaaClient.groups() .list(ListGroupsRequest.builder() .startIndex(startIndex) .build())) .filter(group -> nameFactory.isGroupName(group.getDisplayName())) .sort((group1, group2) -> { if (containsMember(group1, group2)) { return -1; } else if (containsMember(group2, group1)) { return 1; } else { return 0; } }) .concatMap(group -> uaaClient.groups() .delete(DeleteGroupRequest.builder() .groupId(group.getId()) .version("*") .build()) .doOnError(t -> LOGGER.error("Unable to delete group {}", group.getDisplayName(), t)) .then()); } private static Flux<Void> cleanIdentityProviders(UaaClient uaaClient, NameFactory nameFactory) { return uaaClient.identityProviders() .list(ListIdentityProvidersRequest.builder() .build()) .flatMapIterable(ListIdentityProvidersResponse::getIdentityProviders) .filter(provider -> nameFactory.isIdentityProviderName(provider.getName())) .flatMap(provider -> uaaClient.identityProviders() .delete(DeleteIdentityProviderRequest.builder() .identityProviderId(provider.getId()) .build()) .doOnError(t -> LOGGER.error("Unable to delete identity provider {}", provider.getName(), t)) .then()); } private static Flux<Void> cleanIdentityZones(UaaClient uaaClient, NameFactory nameFactory) { return uaaClient.identityZones() .list(ListIdentityZonesRequest.builder() .build()) .flatMapIterable(ListIdentityZonesResponse::getIdentityZones) .filter(zone -> nameFactory.isIdentityZoneName(zone.getName())) .flatMap(zone -> uaaClient.identityZones() .delete(DeleteIdentityZoneRequest.builder() .identityZoneId(zone.getId()) .build()) .doOnError(t -> LOGGER.error("Unable to delete identity zone {}", zone.getName(), t)) .then()); } private static Flux<Void> cleanOrganizationQuotaDefinitions(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.organizationQuotaDefinitions() .list(ListOrganizationQuotaDefinitionsRequest.builder() .page(page) .build())) .filter(domain -> nameFactory.isQuotaDefinitionName(ResourceUtils.getEntity(domain).getName())) .flatMap(organizationQuotaDefinition -> cloudFoundryClient.organizationQuotaDefinitions() .delete(DeleteOrganizationQuotaDefinitionRequest.builder() .async(true) .organizationQuotaDefinitionId(ResourceUtils.getId(organizationQuotaDefinition)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete organization quota definition {}", ResourceUtils.getEntity(organizationQuotaDefinition).getName(), t))); } private static Flux<Void> cleanOrganizations(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.organizations() .list(ListOrganizationsRequest.builder() .page(page) .build())) .filter(organization -> nameFactory.isOrganizationName(ResourceUtils.getEntity(organization).getName())) .flatMap(organization -> cloudFoundryClient.organizations() .delete(DeleteOrganizationRequest.builder() .async(true) .organizationId(ResourceUtils.getId(organization)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete organization {}", ResourceUtils.getEntity(organization).getName(), t))); } private static Flux<Void> cleanPrivateDomains(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.privateDomains() .list(ListPrivateDomainsRequest.builder() .page(page) .build())) .filter(domain -> nameFactory.isDomainName(ResourceUtils.getEntity(domain).getName())) .flatMap(privateDomain -> cloudFoundryClient.privateDomains() .delete(DeletePrivateDomainRequest.builder() .async(true) .privateDomainId(ResourceUtils.getId(privateDomain)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete private domain {}", ResourceUtils.getEntity(privateDomain).getName(), t))); } private static Flux<Void> cleanRoutes(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return getAllDomains(cloudFoundryClient) .flatMapMany(domains -> PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.routes() .list(ListRoutesRequest.builder() .page(page) .build())) .map(resource -> Tuples.of(domains, resource))) .filter(predicate((domains, route) -> nameFactory.isDomainName(domains.get(ResourceUtils.getEntity(route).getDomainId())) || nameFactory.isApplicationName(ResourceUtils.getEntity(route).getHost()) || nameFactory.isHostName(ResourceUtils.getEntity(route).getHost()))) .flatMap(function((domains, route) -> cloudFoundryClient.routes() .delete(DeleteRouteRequest.builder() .async(true) .routeId(ResourceUtils.getId(route)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> { RouteEntity entity = ResourceUtils.getEntity(route); LOGGER.error("Unable to delete route {}.{}:{}{}", entity.getHost(), domains.get(entity.getDomainId()), entity.getPort(), entity.getPath(), t); }))); } private static Flux<Void> cleanSecurityGroups(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.securityGroups() .list(ListSecurityGroupsRequest.builder() .page(page) .build())) .filter(securityGroup -> nameFactory.isSecurityGroupName(ResourceUtils.getEntity(securityGroup).getName())) .flatMap(securityGroup -> cloudFoundryClient.securityGroups() .delete(DeleteSecurityGroupRequest.builder() .securityGroupId(ResourceUtils.getId(securityGroup)) .build()) .doOnError(t -> LOGGER.error("Unable to delete security group {}", ResourceUtils.getEntity(securityGroup).getName(), t)) .then()); } private static Flux<Void> cleanServiceBrokers(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceBrokers() .list(ListServiceBrokersRequest.builder() .page(page) .build())) .filter(serviceBroker -> nameFactory.isServiceBrokerName(ResourceUtils.getEntity(serviceBroker).getName())) .flatMap(serviceBroker -> cloudFoundryClient.serviceBrokers() .delete(DeleteServiceBrokerRequest.builder() .serviceBrokerId(ResourceUtils.getId(serviceBroker)) .build()) .doOnError(t -> LOGGER.error("Unable to delete service broker {}", ResourceUtils.getEntity(serviceBroker).getName(), t))); } private static Flux<Void> cleanServiceInstances(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .list(ListServiceInstancesRequest.builder() .page(page) .build())) .filter(serviceInstance -> nameFactory.isServiceInstanceName(ResourceUtils.getEntity(serviceInstance).getName())) .flatMap(serviceInstance -> removeRouteAssociations(cloudFoundryClient, serviceInstance) .thenMany(Flux.just(serviceInstance))) .flatMap(serviceInstance -> removeServiceInstanceServiceBindings(cloudFoundryClient, serviceInstance) .thenMany(Flux.just(serviceInstance))) .flatMap(serviceInstance -> removeServiceKeys(cloudFoundryClient, serviceInstance) .thenMany(Flux.just(serviceInstance))) .flatMap(serviceInstance -> cloudFoundryClient.serviceInstances() .delete(DeleteServiceInstanceRequest.builder() .async(true) .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build()) .then(response -> { Object entity = response.getEntity(); if (entity instanceof JobEntity) { return JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), (JobEntity) response.getEntity()); } else { return LastOperationUtils .waitForCompletion(Duration.ofMinutes(5), () -> cloudFoundryClient.serviceInstances() .get(GetServiceInstanceRequest.builder() .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build()) .map(r -> ResourceUtils.getEntity(r).getLastOperation())); } }) .doOnError(t -> LOGGER.error("Unable to delete service instance {}", ResourceUtils.getEntity(serviceInstance).getName(), t))); } private static Flux<Void> cleanSharedDomains(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils. requestClientV2Resources(page -> cloudFoundryClient.sharedDomains() .list(ListSharedDomainsRequest.builder() .page(page) .build())) .filter(domain -> nameFactory.isDomainName(ResourceUtils.getEntity(domain).getName())) .flatMap(domain -> cloudFoundryClient.sharedDomains() .delete(DeleteSharedDomainRequest.builder() .async(true) .sharedDomainId(ResourceUtils.getId(domain)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete domain {}", ResourceUtils.getEntity(domain).getName(), t))); } private static Flux<Void> cleanSpaceQuotaDefinitions(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaceQuotaDefinitions() .list(ListSpaceQuotaDefinitionsRequest.builder() .page(page) .build())) .filter(quota -> nameFactory.isQuotaDefinitionName(ResourceUtils.getEntity(quota).getName())) .flatMap(quota -> cloudFoundryClient.spaceQuotaDefinitions() .delete((DeleteSpaceQuotaDefinitionRequest.builder() .async(true) .spaceQuotaDefinitionId(ResourceUtils.getId(quota)) .build())) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete space quota definition {}", ResourceUtils.getEntity(quota).getName(), t))); } private static Flux<Void> cleanSpaces(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.spaces() .list(ListSpacesRequest.builder() .page(page) .build())) .filter(space -> nameFactory.isSpaceName(ResourceUtils.getEntity(space).getName())) .flatMap(space -> cloudFoundryClient.spaces() .delete(DeleteSpaceRequest.builder() .async(true) .spaceId(ResourceUtils.getId(space)) .build()) .flatMapMany(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)) .doOnError(t -> LOGGER.error("Unable to delete space {}", ResourceUtils.getEntity(space).getName(), t))); } private static Flux<Void> cleanUserProvidedServiceInstances(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.userProvidedServiceInstances() .list(ListUserProvidedServiceInstancesRequest.builder() .page(page) .build())) .filter(userProvidedServiceInstance -> nameFactory.isServiceInstanceName(ResourceUtils.getEntity(userProvidedServiceInstance).getName())) .flatMap(userProvidedServiceInstance -> removeUserProvidedServiceInstanceServiceBindings(cloudFoundryClient, userProvidedServiceInstance) .thenMany(Flux.just(userProvidedServiceInstance))) .flatMap(userProvidedServiceInstance -> cloudFoundryClient.userProvidedServiceInstances() .delete(DeleteUserProvidedServiceInstanceRequest.builder() .userProvidedServiceInstanceId(ResourceUtils.getId(userProvidedServiceInstance)) .build()) .doOnError(t -> LOGGER.error("Unable to delete user provided service instance {}", ResourceUtils.getEntity(userProvidedServiceInstance).getName(), t))); } private static Flux<Void> cleanUsers(CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.users() .list(org.cloudfoundry.client.v2.users.ListUsersRequest.builder() .page(page) .build())) .filter(resource -> isCleanable(nameFactory, resource)) .map(resource -> resource.getMetadata().getId()) .flatMap(userId -> cloudFoundryClient.users() .delete(org.cloudfoundry.client.v2.users.DeleteUserRequest.builder() .async(true) .userId(userId) .build()) .doOnError(t -> LOGGER.error("Unable to delete user {}", userId, t))) .flatMap(job -> JobUtils.waitForCompletion(cloudFoundryClient, Duration.ofMinutes(5), job)); } private static Flux<Void> cleanUsers(UaaClient uaaClient, NameFactory nameFactory) { return PaginationUtils .requestUaaResources(startIndex -> uaaClient.users() .list(ListUsersRequest.builder() .startIndex(startIndex) .build())) .filter(user -> nameFactory.isUserName(user.getUserName())) .flatMap(user -> uaaClient.users() .delete(DeleteUserRequest.builder() .userId(user.getId()) .version("*") .build()) .doOnError(t -> LOGGER.error("Unable to delete user {}", user.getName(), t)) .then()); } private static boolean containsMember(Group group, Group candidate) { return group.getMembers().stream() .map(MemberSummary::getMemberId) .anyMatch(id -> candidate.getId().equals(id)); } private static Mono<Map<String, String>> getAllDomains(CloudFoundryClient cloudFoundryClient) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.privateDomains() .list(ListPrivateDomainsRequest.builder() .page(page) .build())) .map(response -> Tuples.of(ResourceUtils.getId(response), ResourceUtils.getEntity(response).getName())) .mergeWith(PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.sharedDomains() .list(ListSharedDomainsRequest.builder() .page(page) .build())) .map(response -> Tuples.of(ResourceUtils.getId(response), ResourceUtils.getEntity(response).getName()))) .collectMap(function((id, name) -> id), function((id, name) -> name)); } private static boolean isCleanable(NameFactory nameFactory, UserResource resource) { return nameFactory.isUserId(ResourceUtils.getId(resource)) || nameFactory.isUserId(ResourceUtils.getEntity(resource).getUsername()); } private static Flux<Void> removeApplicationServiceBindings(CloudFoundryClient cloudFoundryClient, ApplicationResource application) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.applicationsV2() .listServiceBindings(ListApplicationServiceBindingsRequest.builder() .page(page) .applicationId(ResourceUtils.getId(application)) .build())) .flatMap(serviceBinding -> requestRemoveServiceBinding(cloudFoundryClient, ResourceUtils.getId(application), ResourceUtils.getId(serviceBinding)) .doOnError(t -> LOGGER.error("Unable to remove service binding from {}", ResourceUtils.getEntity(application).getName(), t))); } private static Flux<Void> removeRouteAssociations(CloudFoundryClient cloudFoundryClient, ServiceInstanceResource serviceInstance) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .listRoutes(ListServiceInstanceRoutesRequest.builder() .page(page) .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build())) .flatMap(route -> cloudFoundryClient.serviceInstances() .unbindRoute(UnbindServiceInstanceRouteRequest.builder() .routeId(ResourceUtils.getId(route)) .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build()) .doOnError(t -> LOGGER.error("Unable to remove route binding from {}", ResourceUtils.getEntity(serviceInstance).getName(), t))); } private static Flux<Void> removeServiceInstanceServiceBindings(CloudFoundryClient cloudFoundryClient, ServiceInstanceResource serviceInstance) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .listServiceBindings(ListServiceInstanceServiceBindingsRequest.builder() .page(page) .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build())) .flatMap(serviceBinding -> requestRemoveServiceBinding(cloudFoundryClient, ResourceUtils.getEntity(serviceBinding).getApplicationId(), ResourceUtils.getId(serviceBinding)) .doOnError(t -> LOGGER.error("Unable to remove service binding from {}", ResourceUtils.getEntity(serviceInstance).getName(), t))); } private static Flux<Void> removeServiceKeys(CloudFoundryClient cloudFoundryClient, ServiceInstanceResource serviceInstance) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.serviceInstances() .listServiceKeys(ListServiceInstanceServiceKeysRequest.builder() .page(page) .serviceInstanceId(ResourceUtils.getId(serviceInstance)) .build())) .flatMap(serviceKey -> cloudFoundryClient.serviceKeys() .delete(DeleteServiceKeyRequest.builder() .serviceKeyId(ResourceUtils.getId(serviceKey)) .build()) .doOnError(t -> LOGGER.error("Unable to remove service binding from {}", ResourceUtils.getEntity(serviceKey).getName(), t))); } private static Flux<Void> removeUserProvidedServiceInstanceServiceBindings(CloudFoundryClient cloudFoundryClient, UserProvidedServiceInstanceResource serviceInstance) { return PaginationUtils .requestClientV2Resources(page -> cloudFoundryClient.userProvidedServiceInstances() .listServiceBindings(ListUserProvidedServiceInstanceServiceBindingsRequest.builder() .page(page) .userProvidedServiceInstanceId(ResourceUtils.getId(serviceInstance)) .build())) .flatMap(serviceBinding -> requestRemoveServiceBinding(cloudFoundryClient, ResourceUtils.getEntity(serviceBinding).getApplicationId(), ResourceUtils.getId(serviceBinding)) .doOnError(t -> LOGGER.error("Unable to remove service binding from {}", ResourceUtils.getEntity(serviceInstance).getName(), t))); } private static Mono<Void> requestRemoveServiceBinding(CloudFoundryClient cloudFoundryClient, String applicationId, String serviceBindingId) { return cloudFoundryClient.applicationsV2() .removeServiceBinding(RemoveApplicationServiceBindingRequest.builder() .applicationId(applicationId) .serviceBindingId(serviceBindingId) .build()); } }