/*
* 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.spaces;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v2.Resource;
import org.cloudfoundry.client.v2.applications.ApplicationResource;
import org.cloudfoundry.client.v2.organizations.AssociateOrganizationUserByUsernameRequest;
import org.cloudfoundry.client.v2.organizations.AssociateOrganizationUserByUsernameResponse;
import org.cloudfoundry.client.v2.organizations.GetOrganizationRequest;
import org.cloudfoundry.client.v2.organizations.GetOrganizationResponse;
import org.cloudfoundry.client.v2.organizations.ListOrganizationPrivateDomainsRequest;
import org.cloudfoundry.client.v2.organizations.ListOrganizationSpaceQuotaDefinitionsRequest;
import org.cloudfoundry.client.v2.organizations.ListOrganizationSpacesRequest;
import org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest;
import org.cloudfoundry.client.v2.organizations.OrganizationResource;
import org.cloudfoundry.client.v2.privatedomains.PrivateDomainResource;
import org.cloudfoundry.client.v2.securitygroups.RuleEntity;
import org.cloudfoundry.client.v2.securitygroups.SecurityGroupEntity;
import org.cloudfoundry.client.v2.securitygroups.SecurityGroupResource;
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.spacequotadefinitions.GetSpaceQuotaDefinitionRequest;
import org.cloudfoundry.client.v2.spacequotadefinitions.GetSpaceQuotaDefinitionResponse;
import org.cloudfoundry.client.v2.spacequotadefinitions.SpaceQuotaDefinitionEntity;
import org.cloudfoundry.client.v2.spacequotadefinitions.SpaceQuotaDefinitionResource;
import org.cloudfoundry.client.v2.spaces.AssociateSpaceDeveloperByUsernameRequest;
import org.cloudfoundry.client.v2.spaces.AssociateSpaceDeveloperByUsernameResponse;
import org.cloudfoundry.client.v2.spaces.AssociateSpaceManagerByUsernameRequest;
import org.cloudfoundry.client.v2.spaces.AssociateSpaceManagerByUsernameResponse;
import org.cloudfoundry.client.v2.spaces.CreateSpaceResponse;
import org.cloudfoundry.client.v2.spaces.DeleteSpaceResponse;
import org.cloudfoundry.client.v2.spaces.ListSpaceApplicationsRequest;
import org.cloudfoundry.client.v2.spaces.ListSpaceSecurityGroupsRequest;
import org.cloudfoundry.client.v2.spaces.ListSpaceServicesRequest;
import org.cloudfoundry.client.v2.spaces.ListSpacesRequest;
import org.cloudfoundry.client.v2.spaces.SpaceResource;
import org.cloudfoundry.client.v2.spaces.UpdateSpaceRequest;
import org.cloudfoundry.client.v2.spaces.UpdateSpaceResponse;
import org.cloudfoundry.operations.spaceadmin.SpaceQuota;
import org.cloudfoundry.operations.util.OperationsLogging;
import org.cloudfoundry.util.ExceptionUtils;
import org.cloudfoundry.util.JobUtils;
import org.cloudfoundry.util.PaginationUtils;
import org.cloudfoundry.util.ResourceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuples;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static org.cloudfoundry.util.tuple.TupleUtils.function;
public final class DefaultSpaces implements Spaces {
private final Mono<CloudFoundryClient> cloudFoundryClient;
private final Mono<String> organizationId;
private final Mono<String> username;
public DefaultSpaces(Mono<CloudFoundryClient> cloudFoundryClient, Mono<String> organizationId, Mono<String> username) {
this.cloudFoundryClient = cloudFoundryClient;
this.organizationId = organizationId;
this.username = username;
}
@Override
public Mono<Void> allowSsh(AllowSpaceSshRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
getOrganizationSpaceIdWhere(cloudFoundryClient, organizationId, request.getName(), sshEnabled(false))
)))
.then(function((cloudFoundryClient, spaceId) -> requestUpdateSpaceSsh(cloudFoundryClient, spaceId, true)))
.then()
.transform(OperationsLogging.log("Allow Space SSH"))
.checkpoint();
}
@Override
public Mono<Void> create(CreateSpaceRequest request) {
return Mono
.when(this.cloudFoundryClient, this.username)
.then(function((cloudFoundryClient, username) -> Mono
.when(
Mono.just(cloudFoundryClient),
Mono.just(username),
getOrganizationIdOrDefault(cloudFoundryClient, request.getOrganization(), this.organizationId)
)))
.then(function((cloudFoundryClient, username, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
Mono.just(username),
Mono.just(organizationId),
getOptionalSpaceQuotaId(cloudFoundryClient, organizationId, request.getSpaceQuota())
)))
.then(function((cloudFoundryClient, username, organizationId, spaceQuotaId) -> Mono
.when(
Mono.just(cloudFoundryClient),
Mono.just(organizationId),
requestCreateSpace(cloudFoundryClient, organizationId, request.getName(), spaceQuotaId.orElse(null))
.map(ResourceUtils::getId),
Mono.just(username)
)))
.then(function((cloudFoundryClient, organizationId, spaceId, username) -> requestAssociateOrganizationUserByUsername(cloudFoundryClient, organizationId, username)
.then(Mono.just(Tuples.of(cloudFoundryClient, organizationId, spaceId, username)))))
.then(function((cloudFoundryClient, organizationId, spaceId, username) -> Mono
.when(
requestAssociateSpaceManagerByUsername(cloudFoundryClient, spaceId, username),
requestAssociateSpaceDeveloperByUsername(cloudFoundryClient, spaceId, username)
)))
.then()
.transform(OperationsLogging.log("Create Space"))
.checkpoint();
}
@Override
public Mono<Void> delete(DeleteSpaceRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
Mono.just(request.getCompletionTimeout()),
getOrganizationSpaceId(cloudFoundryClient, organizationId, request.getName())
)))
.then(function(DefaultSpaces::deleteSpace))
.transform(OperationsLogging.log("Delete Space"))
.checkpoint();
}
@Override
public Mono<Void> disallowSsh(DisallowSpaceSshRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
getOrganizationSpaceIdWhere(cloudFoundryClient, organizationId, request.getName(), sshEnabled(true))
)))
.then(function((cloudFoundryClient, spaceId) -> requestUpdateSpaceSsh(cloudFoundryClient, spaceId, false)))
.then()
.transform(OperationsLogging.log("Disallow Space SSH"))
.checkpoint();
}
@Override
public Mono<SpaceDetail> get(GetSpaceRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
getOrganizationSpace(cloudFoundryClient, organizationId, request.getName())
)))
.then(function((cloudFoundryClient, resource) -> getSpaceDetail(cloudFoundryClient, resource, request)))
.transform(OperationsLogging.log("Get Space"))
.checkpoint();
}
@Override
public Flux<SpaceSummary> list() {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.flatMapMany(function(DefaultSpaces::requestSpaces))
.map(DefaultSpaces::toSpaceSummary)
.transform(OperationsLogging.log("List Spaces"))
.checkpoint();
}
@Override
public Mono<Void> rename(RenameSpaceRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> Mono
.when(
Mono.just(cloudFoundryClient),
getOrganizationSpaceId(cloudFoundryClient, organizationId, request.getName())
)))
.then(function((cloudFoundryClient, spaceId) -> requestUpdateSpace(cloudFoundryClient, spaceId, request.getNewName())))
.then()
.transform(OperationsLogging.log("Rename Space"))
.checkpoint();
}
@Override
public Mono<Boolean> sshAllowed(SpaceSshAllowedRequest request) {
return Mono
.when(this.cloudFoundryClient, this.organizationId)
.then(function((cloudFoundryClient, organizationId) -> getOrganizationSpace(cloudFoundryClient, organizationId, request.getName())))
.map(resource -> ResourceUtils.getEntity(resource).getAllowSsh())
.transform(OperationsLogging.log("Is Space SSH Allowed"))
.checkpoint();
}
private static Mono<Void> deleteSpace(CloudFoundryClient cloudFoundryClient, Duration completionTimeout, String spaceId) {
return requestDeleteSpace(cloudFoundryClient, spaceId)
.then(job -> JobUtils.waitForCompletion(cloudFoundryClient, completionTimeout, job));
}
private static Mono<List<String>> getApplicationNames(CloudFoundryClient cloudFoundryClient, SpaceResource spaceResource) {
return requestSpaceApplications(cloudFoundryClient, ResourceUtils.getId(spaceResource))
.map(applicationResource -> ResourceUtils.getEntity(applicationResource).getName())
.collectList();
}
private static Mono<List<String>> getDomainNames(CloudFoundryClient cloudFoundryClient, SpaceResource spaceResource) {
return requestListPrivateDomains(cloudFoundryClient, spaceResource.getEntity().getOrganizationId())
.map(resource -> resource.getEntity().getName())
.mergeWith(requestListSharedDomains(cloudFoundryClient)
.map(resource -> resource.getEntity().getName()))
.collectList();
}
private static Mono<Optional<SpaceQuota>> getOptionalSpaceQuotaDefinition(CloudFoundryClient cloudFoundryClient, SpaceResource spaceResource) {
String spaceQuotaDefinitionId = ResourceUtils.getEntity(spaceResource).getSpaceQuotaDefinitionId();
if (spaceQuotaDefinitionId == null) {
return Mono.just(Optional.empty());
}
return requestSpaceQuotaDefinition(cloudFoundryClient, spaceQuotaDefinitionId)
.map(DefaultSpaces::toSpaceQuotaDefinition)
.map(Optional::of);
}
private static Mono<Optional<String>> getOptionalSpaceQuotaId(CloudFoundryClient cloudFoundryClient, String organizationId, String spaceQuota) {
if (spaceQuota == null) {
return Mono.just(Optional.empty());
} else {
return getSpaceQuota(cloudFoundryClient, organizationId, spaceQuota)
.map(ResourceUtils::getId)
.map(Optional::of);
}
}
private static Mono<OrganizationResource> getOrganization(CloudFoundryClient cloudFoundryClient, String organization) {
return requestOrganizations(cloudFoundryClient, organization)
.single()
.onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Organization %s does not exist", organization));
}
private static Mono<String> getOrganizationId(CloudFoundryClient cloudFoundryClient, String organization) {
return getOrganization(cloudFoundryClient, organization)
.map(ResourceUtils::getId);
}
private static Mono<String> getOrganizationIdOrDefault(CloudFoundryClient cloudFoundryClient, String organizationName, Mono<String> organizationId) {
return Optional.ofNullable(organizationName)
.map(organization -> getOrganizationId(cloudFoundryClient, organization))
.orElse(organizationId);
}
private static Mono<String> getOrganizationName(CloudFoundryClient cloudFoundryClient, SpaceResource resource) {
return requestOrganization(cloudFoundryClient, ResourceUtils.getEntity(resource).getOrganizationId())
.map(response -> ResourceUtils.getEntity(response).getName());
}
private static Mono<SpaceResource> getOrganizationSpace(CloudFoundryClient cloudFoundryClient, String organizationId, String space) {
return requestOrganizationSpaces(cloudFoundryClient, organizationId, space)
.single()
.onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Space %s does not exist", space));
}
private static Mono<String> getOrganizationSpaceId(CloudFoundryClient cloudFoundryClient, String organizationId, String space) {
return getOrganizationSpace(cloudFoundryClient, organizationId, space)
.map(ResourceUtils::getId);
}
private static Mono<String> getOrganizationSpaceIdWhere(CloudFoundryClient cloudFoundryClient, String organizationId, String space, Predicate<SpaceResource> predicate) {
return getOrganizationSpace(cloudFoundryClient, organizationId, space)
.filter(predicate)
.map(ResourceUtils::getId);
}
private static Mono<List<SecurityGroupEntity>> getSecurityGroups(CloudFoundryClient cloudFoundryClient, SpaceResource spaceResource, boolean withRules) {
return requestSpaceSecurityGroups(cloudFoundryClient, ResourceUtils.getId(spaceResource))
.map(securityGroupResource -> {
SecurityGroupEntity entity = ResourceUtils.getEntity(securityGroupResource);
if (!withRules) {
entity = SecurityGroupEntity.builder()
.name(entity.getName())
.runningDefault(entity.getRunningDefault())
.spacesUrl(entity.getSpacesUrl())
.stagingDefault(entity.getStagingDefault())
.build();
}
return entity;
})
.collectList();
}
private static Mono<List<String>> getServiceNames(CloudFoundryClient cloudFoundryClient, SpaceResource spaceResource) {
return requestSpaceServices(cloudFoundryClient, ResourceUtils.getId(spaceResource))
.map(serviceResource -> ResourceUtils.getEntity(serviceResource).getLabel())
.collectList();
}
private static Mono<SpaceDetail> getSpaceDetail(CloudFoundryClient cloudFoundryClient, SpaceResource resource, GetSpaceRequest request) {
return Mono
.when(
getApplicationNames(cloudFoundryClient, resource),
getDomainNames(cloudFoundryClient, resource),
getOrganizationName(cloudFoundryClient, resource),
getSecurityGroups(cloudFoundryClient, resource, Optional.ofNullable(request.getSecurityGroupRules()).orElse(false)),
getServiceNames(cloudFoundryClient, resource),
getOptionalSpaceQuotaDefinition(cloudFoundryClient, resource)
)
.map(function((applications, domains, organization, securityGroups, services, spaceQuota) ->
toSpaceDetail(applications, domains, organization, resource, securityGroups, services, spaceQuota)));
}
private static Mono<SpaceQuotaDefinitionResource> getSpaceQuota(CloudFoundryClient cloudFoundryClient, String organizationId, String spaceQuota) {
return requestOrganizationSpaceQuotas(cloudFoundryClient, organizationId, spaceQuota)
.single()
.onErrorResume(NoSuchElementException.class, t -> ExceptionUtils.illegalArgument("Space quota definition %s does not exist", spaceQuota));
}
private static Mono<AssociateOrganizationUserByUsernameResponse> requestAssociateOrganizationUserByUsername(CloudFoundryClient cloudFoundryClient, String organizationId, String username) {
return cloudFoundryClient.organizations()
.associateUserByUsername(AssociateOrganizationUserByUsernameRequest.builder()
.organizationId(organizationId)
.username(username)
.build());
}
private static Mono<AssociateSpaceDeveloperByUsernameResponse> requestAssociateSpaceDeveloperByUsername(CloudFoundryClient cloudFoundryClient, String spaceId, String username) {
return cloudFoundryClient.spaces()
.associateDeveloperByUsername(AssociateSpaceDeveloperByUsernameRequest.builder()
.spaceId(spaceId)
.username(username)
.build());
}
private static Mono<AssociateSpaceManagerByUsernameResponse> requestAssociateSpaceManagerByUsername(CloudFoundryClient cloudFoundryClient, String spaceId, String username) {
return cloudFoundryClient.spaces()
.associateManagerByUsername(AssociateSpaceManagerByUsernameRequest.builder()
.spaceId(spaceId)
.username(username)
.build());
}
private static Mono<CreateSpaceResponse> requestCreateSpace(CloudFoundryClient cloudFoundryClient, String organizationId, String space, String spaceQuotaId) {
return cloudFoundryClient.spaces()
.create(org.cloudfoundry.client.v2.spaces.CreateSpaceRequest.builder()
.name(space)
.organizationId(organizationId)
.spaceQuotaDefinitionId(spaceQuotaId)
.build());
}
private static Mono<DeleteSpaceResponse> requestDeleteSpace(CloudFoundryClient cloudFoundryClient, String spaceId) {
return cloudFoundryClient.spaces()
.delete(org.cloudfoundry.client.v2.spaces.DeleteSpaceRequest.builder()
.async(true)
.spaceId(spaceId)
.build());
}
private static Flux<PrivateDomainResource> requestListPrivateDomains(CloudFoundryClient cloudFoundryClient, String organizationId) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.organizations()
.listPrivateDomains(ListOrganizationPrivateDomainsRequest.builder()
.organizationId(organizationId)
.page(page)
.build()));
}
private static Flux<SharedDomainResource> requestListSharedDomains(CloudFoundryClient cloudFoundryClient) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.sharedDomains()
.list(ListSharedDomainsRequest.builder()
.page(page)
.build()));
}
private static Mono<GetOrganizationResponse> requestOrganization(CloudFoundryClient cloudFoundryClient, String organizationId) {
return cloudFoundryClient.organizations()
.get(GetOrganizationRequest.builder()
.organizationId(organizationId)
.build());
}
private static Flux<SpaceQuotaDefinitionResource> requestOrganizationSpaceQuotas(CloudFoundryClient cloudFoundryClient, String organizationId, String spaceQuota) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.organizations()
.listSpaceQuotaDefinitions(ListOrganizationSpaceQuotaDefinitionsRequest.builder()
.page(page)
.organizationId(organizationId)
.build()))
.filter(resource -> ResourceUtils.getEntity(resource).getName().equals(spaceQuota));
}
private static Flux<SpaceResource> requestOrganizationSpaces(CloudFoundryClient cloudFoundryClient, String organizationId, String space) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.organizations()
.listSpaces(ListOrganizationSpacesRequest.builder()
.name(space)
.organizationId(organizationId)
.page(page)
.build()));
}
private static Flux<OrganizationResource> requestOrganizations(CloudFoundryClient cloudFoundryClient, String organizationName) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.organizations()
.list(ListOrganizationsRequest.builder()
.name(organizationName)
.page(page)
.build()));
}
private static Flux<ApplicationResource> requestSpaceApplications(CloudFoundryClient cloudFoundryClient, String spaceId) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.spaces()
.listApplications(ListSpaceApplicationsRequest.builder()
.page(page)
.spaceId(spaceId)
.build()));
}
private static Mono<GetSpaceQuotaDefinitionResponse> requestSpaceQuotaDefinition(CloudFoundryClient cloudFoundryClient, String spaceQuotaDefinitionId) {
return cloudFoundryClient.spaceQuotaDefinitions()
.get(GetSpaceQuotaDefinitionRequest.builder()
.spaceQuotaDefinitionId(spaceQuotaDefinitionId)
.build());
}
private static Flux<SecurityGroupResource> requestSpaceSecurityGroups(CloudFoundryClient cloudFoundryClient, String spaceId) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.spaces()
.listSecurityGroups(ListSpaceSecurityGroupsRequest.builder()
.spaceId(spaceId)
.page(page)
.build()));
}
private static Flux<ServiceResource> requestSpaceServices(CloudFoundryClient cloudFoundryClient, String spaceId) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.spaces()
.listServices(ListSpaceServicesRequest.builder()
.page(page)
.spaceId(spaceId)
.build()));
}
private static Flux<SpaceResource> requestSpaces(CloudFoundryClient cloudFoundryClient, String organizationId) {
return PaginationUtils
.requestClientV2Resources(page -> cloudFoundryClient.spaces()
.list(ListSpacesRequest.builder()
.organizationId(organizationId)
.page(page)
.build()));
}
private static Mono<UpdateSpaceResponse> requestUpdateSpace(CloudFoundryClient cloudFoundryClient, String spaceId, String newName) {
return cloudFoundryClient.spaces()
.update(UpdateSpaceRequest.builder()
.name(newName)
.spaceId(spaceId)
.build());
}
private static Mono<UpdateSpaceResponse> requestUpdateSpaceSsh(CloudFoundryClient cloudFoundryClient, String spaceId, Boolean allowed) {
return cloudFoundryClient.spaces()
.update(UpdateSpaceRequest.builder()
.allowSsh(allowed)
.spaceId(spaceId)
.build());
}
private static Predicate<SpaceResource> sshEnabled(Boolean enabled) {
return resource -> enabled.equals(ResourceUtils.getEntity(resource).getAllowSsh());
}
private static SpaceDetail toSpaceDetail(List<String> applications,
List<String> domains,
String organization,
SpaceResource resource,
List<SecurityGroupEntity> securityGroups,
List<String> services,
Optional<SpaceQuota> spaceQuota) {
return SpaceDetail.builder()
.applications(applications)
.domains(domains)
.id(ResourceUtils.getId(resource))
.name(ResourceUtils.getEntity(resource).getName())
.organization(organization)
.securityGroups(toSpaceDetailSecurityGroups(securityGroups))
.services(services)
.spaceQuota(spaceQuota)
.build();
}
private static List<Rule> toSpaceDetailSecurityGroupRules(List<RuleEntity> rules) {
return Optional.ofNullable(rules)
.map(r -> r.stream()
.map(ruleEntity -> Rule.builder()
.destination(ruleEntity.getDestination())
.ports(ruleEntity.getPorts())
.protocol(ruleEntity.getProtocol())
.build())
.collect(Collectors.toList()))
.orElse(Collections.emptyList());
}
private static List<SecurityGroup> toSpaceDetailSecurityGroups(List<SecurityGroupEntity> securityGroups) {
return securityGroups.stream()
.map(entity -> SecurityGroup.builder()
.name(entity.getName())
.rules(toSpaceDetailSecurityGroupRules(entity.getRules()))
.build())
.collect(Collectors.toList());
}
private static SpaceQuota toSpaceQuotaDefinition(Resource<SpaceQuotaDefinitionEntity> resource) {
SpaceQuotaDefinitionEntity entity = ResourceUtils.getEntity(resource);
return SpaceQuota.builder()
.id(ResourceUtils.getId(resource))
.instanceMemoryLimit(entity.getInstanceMemoryLimit())
.name(entity.getName())
.organizationId(entity.getOrganizationId())
.paidServicePlans(entity.getNonBasicServicesAllowed())
.totalMemoryLimit(entity.getMemoryLimit())
.totalRoutes(entity.getTotalRoutes())
.totalServiceInstances(entity.getTotalServices())
.build();
}
private static SpaceSummary toSpaceSummary(SpaceResource resource) {
return SpaceSummary.builder()
.id(ResourceUtils.getId(resource))
.name(ResourceUtils.getEntity(resource).getName())
.build();
}
}