From d0570985f32e9095bc561c365b0b9a7d6fc3cf27 Mon Sep 17 00:00:00 2001 From: theghost5800 Date: Tue, 1 Jul 2025 17:47:05 +0300 Subject: [PATCH 1/2] Add retrieval of app features JIRA:LMCROSSITXSADEPLOY-3111 --- .../client/facade/CloudControllerClient.java | 25 ++-- .../facade/CloudControllerClientImpl.java | 18 ++- .../client/facade/domain/Staging.java | 10 ++ .../rest/CloudControllerRestClient.java | 11 +- .../rest/CloudControllerRestClientImpl.java | 139 ++++++++++-------- 5 files changed, 113 insertions(+), 90 deletions(-) diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClient.java b/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClient.java index c5e8b5631..cea52891f 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClient.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClient.java @@ -8,8 +8,6 @@ import java.util.Set; import java.util.UUID; -import org.cloudfoundry.client.v3.Metadata; - import com.sap.cloudfoundry.client.facade.domain.CloudApplication; import com.sap.cloudfoundry.client.facade.domain.CloudAsyncJob; import com.sap.cloudfoundry.client.facade.domain.CloudBuild; @@ -34,10 +32,10 @@ import com.sap.cloudfoundry.client.facade.domain.Upload; import com.sap.cloudfoundry.client.facade.domain.UserRole; import com.sap.cloudfoundry.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.client.v3.Metadata; /** * The interface defining operations making up the Cloud Foundry Java client's API. - * */ public interface CloudControllerClient { @@ -104,7 +102,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, String createServiceBroker(CloudServiceBroker serviceBroker); /** - * * @param keyModel service-key cloud object * @param serviceInstanceName name of related service instance * @return the service-key object populated with new guid @@ -112,7 +109,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName); /** - * * @param keyModel service-key cloud object * @param serviceInstanceName name of related service instance * @return job id for async polling if present @@ -121,7 +117,7 @@ Optional bindServiceInstance(String bindingName, String applicationName, /** * Create a service key. - * + * * @param serviceInstanceName name of service instance * @param serviceKeyName name of service-key * @param parameters parameters of service-key @@ -171,7 +167,6 @@ Optional bindServiceInstance(String bindingName, String applicationName, void deleteServiceInstance(String serviceInstance); /** - * * @param serviceInstance {@link CloudServiceInstance} */ void deleteServiceInstance(CloudServiceInstance serviceInstance); @@ -194,7 +189,7 @@ Optional bindServiceInstance(String bindingName, String applicationName, /** * Delete a service binding. - * + * * @param serviceInstanceName name of service instance * @param serviceKeyName name of service key * @return job id for async polling if present @@ -203,7 +198,7 @@ Optional bindServiceInstance(String bindingName, String applicationName, /** * Delete a service binding. - * + * * @param bindingGuid The GUID of the binding * @param serviceBindingOperationCallback callback used for error handling * @return job id for async polling if present @@ -287,6 +282,8 @@ Optional bindServiceInstance(String bindingName, String applicationName, boolean getApplicationSshEnabled(UUID applicationGuid); + Map getApplicationFeatures(UUID applicationGuid); + /** * Get all applications in the currently targeted space. This method has EXTREMELY poor performance for spaces with a lot of * applications. @@ -364,7 +361,7 @@ Optional bindServiceInstance(String bindingName, String applicationName, /** * Get the GUID of a service instance. - * + * * @param serviceInstanceName the name of the service instance * @return the service instance GUID */ @@ -447,7 +444,7 @@ Optional bindServiceInstance(String bindingName, String applicationName, /** * Get all user-provided service instance parameters - * + * * @param guid The service instance guid * @return user-provided service instance parameters in key-value pairs */ @@ -670,7 +667,7 @@ CloudPackage asyncUploadApplicationWithExponentialBackoff(String applicationName /** * Get the list of one-off tasks currently known for the given application. - * + * * @param applicationName the application to look for tasks * @return the list of known tasks * @throws UnsupportedOperationException if the targeted controller does not support tasks @@ -679,7 +676,7 @@ CloudPackage asyncUploadApplicationWithExponentialBackoff(String applicationName /** * Run a one-off task on an application. - * + * * @param applicationName the application to run the task on * @param task the task to run * @return the ran task @@ -689,7 +686,7 @@ CloudPackage asyncUploadApplicationWithExponentialBackoff(String applicationName /** * Cancel the given task. - * + * * @param taskGuid the GUID of the task to cancel * @return the cancelled task */ diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClientImpl.java b/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClientImpl.java index 0fd3ec065..a309be427 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClientImpl.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/CloudControllerClientImpl.java @@ -10,11 +10,6 @@ import java.util.UUID; import java.util.function.Supplier; -import org.cloudfoundry.AbstractCloudFoundryException; -import org.cloudfoundry.client.v3.Metadata; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; - import com.sap.cloudfoundry.client.facade.domain.CloudApplication; import com.sap.cloudfoundry.client.facade.domain.CloudAsyncJob; import com.sap.cloudfoundry.client.facade.domain.CloudBuild; @@ -42,10 +37,13 @@ import com.sap.cloudfoundry.client.facade.rest.CloudControllerRestClient; import com.sap.cloudfoundry.client.facade.rest.CloudControllerRestClientFactory; import com.sap.cloudfoundry.client.facade.rest.ImmutableCloudControllerRestClientFactory; +import org.cloudfoundry.AbstractCloudFoundryException; +import org.cloudfoundry.client.v3.Metadata; +import org.springframework.http.HttpStatus; +import org.springframework.util.Assert; /** * A Java client to exercise the Cloud Foundry API. - * */ public class CloudControllerClientImpl implements CloudControllerClient { @@ -61,7 +59,8 @@ public CloudControllerClientImpl(URL controllerUrl, CloudCredentials credentials public CloudControllerClientImpl(URL controllerUrl, CloudCredentials credentials, CloudSpace target, boolean trustSelfSignedCerts) { Assert.notNull(controllerUrl, "URL for cloud controller cannot be null"); CloudControllerRestClientFactory restClientFactory = ImmutableCloudControllerRestClientFactory.builder() - .shouldTrustSelfSignedCertificates(trustSelfSignedCerts) + .shouldTrustSelfSignedCertificates( + trustSelfSignedCerts) .build(); this.delegate = restClientFactory.createClient(controllerUrl, credentials, target); } @@ -264,6 +263,11 @@ public boolean getApplicationSshEnabled(UUID applicationGuid) { return handleExceptions(() -> delegate.getApplicationSshEnabled(applicationGuid)); } + @Override + public Map getApplicationFeatures(UUID applicationGuid) { + return handleExceptions(() -> delegate.getApplicationFeatures(applicationGuid)); + } + @Override public List getApplications() { return handleExceptions(() -> delegate.getApplications()); diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/domain/Staging.java b/src/main/java/com/sap/cloudfoundry/client/facade/domain/Staging.java index d9d763523..7cfa36cca 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/domain/Staging.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/domain/Staging.java @@ -1,6 +1,7 @@ package com.sap.cloudfoundry.client.facade.domain; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; @@ -55,6 +56,15 @@ public interface Staging { @Nullable Boolean isSshEnabled(); + /** + * Retrieves the application features map. The map contains feature names as keys and their enabled/disabled state as Boolean values. + * This allows specifying which features should be enabled or disabled for the application during staging. + * + * @return a map of application features and their enabled/disabled state + */ + @SkipNulls + Map getAppFeatures(); + /** * @return the stack to use when staging the application, or null to use the default stack */ diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClient.java b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClient.java index b2640b593..5d5066aca 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClient.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClient.java @@ -8,9 +8,6 @@ import java.util.Set; import java.util.UUID; -import com.sap.cloudfoundry.client.facade.dto.ApplicationToCreateDto; -import org.cloudfoundry.client.v3.Metadata; - import com.sap.cloudfoundry.client.facade.UploadStatusCallback; import com.sap.cloudfoundry.client.facade.domain.CloudApplication; import com.sap.cloudfoundry.client.facade.domain.CloudAsyncJob; @@ -35,10 +32,11 @@ import com.sap.cloudfoundry.client.facade.domain.Staging; import com.sap.cloudfoundry.client.facade.domain.Upload; import com.sap.cloudfoundry.client.facade.domain.UserRole; +import com.sap.cloudfoundry.client.facade.dto.ApplicationToCreateDto; +import org.cloudfoundry.client.v3.Metadata; /** * Interface defining operations available for the cloud controller REST client implementations - * */ public interface CloudControllerRestClient { @@ -50,7 +48,8 @@ public interface CloudControllerRestClient { Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName); - Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, Map parameters); + Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, + Map parameters); void createApplication(ApplicationToCreateDto applicationToCreateDto); @@ -110,6 +109,8 @@ public interface CloudControllerRestClient { boolean getApplicationSshEnabled(UUID applicationGuid); + Map getApplicationFeatures(UUID applicationGuid); + List getApplications(); CloudDomain getDefaultDomain(); diff --git a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java index 384f02899..3f60d7967 100644 --- a/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java +++ b/src/main/java/com/sap/cloudfoundry/client/facade/rest/CloudControllerRestClientImpl.java @@ -91,6 +91,7 @@ import org.cloudfoundry.client.v3.Resource; import org.cloudfoundry.client.v3.ToOneRelationship; import org.cloudfoundry.client.v3.applications.Application; +import org.cloudfoundry.client.v3.applications.ApplicationFeature; import org.cloudfoundry.client.v3.applications.ApplicationRelationships; import org.cloudfoundry.client.v3.applications.ApplicationState; import org.cloudfoundry.client.v3.applications.CreateApplicationRequest; @@ -108,6 +109,7 @@ import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledRequest; import org.cloudfoundry.client.v3.applications.GetApplicationSshEnabledResponse; import org.cloudfoundry.client.v3.applications.ListApplicationBuildsRequest; +import org.cloudfoundry.client.v3.applications.ListApplicationFeaturesRequest; import org.cloudfoundry.client.v3.applications.ListApplicationPackagesRequest; import org.cloudfoundry.client.v3.applications.ListApplicationRoutesRequest; import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; @@ -321,8 +323,7 @@ private Lifecycle buildApplicationLifecycle(Staging staging) { // Determine lifecycle type, defaulting to BUILDPACK if lifecycleType is null LifecycleType lifecycleType = staging.getLifecycleType() != null ? LifecycleType.valueOf(staging.getLifecycleType() - .name()) - : LifecycleType.BUILDPACK; + .name()) : LifecycleType.BUILDPACK; return createLifecycleByType(staging, lifecycleType); } @@ -382,9 +383,9 @@ private void updateApplicationAttributes(ApplicationToCreateDto applicationToCre } private void updateApplicationProcess(UUID applicationGuid, Staging staging) { - if (staging.isSshEnabled() != null) { - updateSsh(applicationGuid, staging.isSshEnabled()); - } + staging.getAppFeatures() + .entrySet() + .forEach(entry -> updateAppFeature(applicationGuid, entry.getKey(), entry.getValue())); GetApplicationProcessResponse getApplicationProcessResponse = getApplicationProcessResource(applicationGuid); UpdateProcessRequest.Builder updateProcessRequestBuilder = UpdateProcessRequest.builder() .processId(getApplicationProcessResponse.getId()) @@ -407,6 +408,16 @@ private void updateSsh(UUID applicationGuid, boolean isSshEnabled) { .block(); } + private void updateAppFeature(UUID applicationGuid, String featureName, boolean enabled) { + delegate.applicationsV3() + .updateFeature(UpdateApplicationFeatureRequest.builder() + .featureName(featureName) + .enabled(enabled) + .applicationId(applicationGuid.toString()) + .build()) + .block(); + } + private GetApplicationProcessResponse getApplicationProcessResource(UUID applicationGuid) { return delegate.applicationsV3() .getProcess(GetApplicationProcessRequest.builder() @@ -494,9 +505,9 @@ public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String CloudServiceInstance serviceInstance = getServiceInstance(serviceInstanceName); doCreateServiceKeySync(keyModel.getName(), keyModel.getCredentials(), keyModel.getV3Metadata(), serviceInstance); - return fetchWithAuxiliaryContent(() -> getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), - getGuid(serviceInstance)), - fetchedKey -> zipWithAuxiliaryServiceKeyContent(fetchedKey, serviceInstance)); + return fetchWithAuxiliaryContent( + () -> getServiceKeyResourceByNameAndServiceInstanceGuid(keyModel.getName(), getGuid(serviceInstance)), + fetchedKey -> zipWithAuxiliaryServiceKeyContent(fetchedKey, serviceInstance)); } @Override @@ -536,8 +547,8 @@ private Optional doCreateServiceKey(String name, Map par private CreateServiceBindingRequest buildServiceCredentialBindingRequest(String name, Map parameters, Metadata metadata, CloudServiceInstance serviceInstance) { if (serviceInstance.getType() != ServiceInstanceType.MANAGED) { - throw new IllegalArgumentException(String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, - serviceInstance.getName())); + throw new IllegalArgumentException( + String.format(Messages.CANT_CREATE_SERVICE_KEY_FOR_USER_PROVIDED_SERVICE, serviceInstance.getName())); } UUID serviceInstanceGuid = getGuid(serviceInstance); @@ -621,8 +632,7 @@ public void deleteRoute(String host, String domainName, String path) { assertSpaceProvided("delete route for domain"); UUID routeGuid = getRouteGuid(getRequiredDomainGuid(domainName), host, path); if (routeGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, - "Not Found", + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Host " + host + " not found for domain " + domainName + "."); } doDeleteRoute(routeGuid); @@ -750,6 +760,19 @@ public boolean getApplicationSshEnabled(UUID applicationGuid) { .block(); } + @Override + public Map getApplicationFeatures(UUID applicationGuid) { + IntFunction pageRequestSupplier = page -> ListApplicationFeaturesRequest.builder() + .applicationId( + applicationGuid.toString()) + .page(page) + .build(); + return PaginationUtils.requestClientV3Resources(page -> delegate.applicationsV3() + .listFeatures(pageRequestSupplier.apply(page))) + .collect(Collectors.toMap(ApplicationFeature::getName, ApplicationFeature::getEnabled)) + .block(); + } + @Override public List getApplications() { return fetchList(this::getApplicationResources, application -> ImmutableRawCloudApplication.builder() @@ -953,11 +976,11 @@ private Mono> getServiceKeyCredentials(String keyGuid) { .serviceBindingId(keyGuid) .build()) // CF V3 API returns 404 when fetching credentials of a service key which creation failed - .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> Mono.just(GetServiceBindingDetailsResponse.builder() - .volumeMounts(Collections.emptyList()) - .credentials(Collections.emptyMap()) - .build())) + .onErrorResume(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), t -> Mono.just( + GetServiceBindingDetailsResponse.builder() + .volumeMounts(Collections.emptyList()) + .credentials(Collections.emptyMap()) + .build())) .map(GetServiceBindingDetailsResponse::getCredentials); } @@ -1347,8 +1370,8 @@ private boolean isProtocolChanged(UUID applicationGuid, CloudRoute currentRoute, return currentRoute.getDestinations() .stream() .filter(routeDestination -> Objects.equals(routeDestination.getApplicationGuid(), applicationGuid)) - .noneMatch(routeDestination -> Objects.equals(routeDestination.getProtocol(), - updatedRoute.getRequestedProtocol())); + .noneMatch( + routeDestination -> Objects.equals(routeDestination.getProtocol(), updatedRoute.getRequestedProtocol())); } private Set getNewRoutes(UUID applicationGuid, List currentRoutes, Set updatedRoutes) { @@ -1501,8 +1524,7 @@ public DropletInfo getCurrentDropletForApplication(UUID applicationGuid) { .build()) .block(); if (dropletResponse == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, - "Not found", + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not found", "Application with guid " + applicationGuid + " does not have a droplet"); } return parseDropletInfo(dropletResponse); @@ -1567,8 +1589,7 @@ public CloudAsyncJob getAsyncJob(String jobId) { return fetch(() -> delegate.jobsV3() .get(GetJobRequest.builder() .jobId(jobId) - .build()), - ImmutableRawCloudAsyncJob::of); + .build()), ImmutableRawCloudAsyncJob::of); } private void addNonNullDockerCredentials(DockerCredentials dockerCredentials, @@ -1673,20 +1694,20 @@ private Mono> zipWithAuxiliaryServiceInstanceCon .getData() .getId(); - return getServicePlanResource(servicePlanGuid, - serviceInstanceResource.getName()).zipWhen( - servicePlan -> getServiceOffering(servicePlan.getRelationships() - .getServiceOffering() - .getData() - .getId())) - .map(tuple -> ImmutableRawCloudServiceInstance.builder() - .resource( - serviceInstanceResource) - .servicePlan( - tuple.getT1()) - .serviceOffering( - tuple.getT2()) - .build()); + return getServicePlanResource(servicePlanGuid, serviceInstanceResource.getName()).zipWhen(servicePlan -> getServiceOffering( + servicePlan.getRelationships() + .getServiceOffering() + .getData() + .getId())) + .map( + tuple -> ImmutableRawCloudServiceInstance.builder() + .resource( + serviceInstanceResource) + .servicePlan( + tuple.getT1()) + .serviceOffering( + tuple.getT2()) + .build()); } private boolean isUserProvided(ServiceInstanceResource serviceInstanceResource) { @@ -1705,8 +1726,8 @@ private Flux getServiceBindingResourcesByServi .list(pageRequestSupplier.apply(page))); } - private Mono - getServiceBindingResourceByApplicationGuidAndServiceInstanceGuid(UUID applicationGuid, UUID serviceInstanceGuid) { + private Mono getServiceBindingResourceByApplicationGuidAndServiceInstanceGuid(UUID applicationGuid, + UUID serviceInstanceGuid) { IntFunction pageRequestSupplier = page -> ListServiceBindingsRequest.builder() .applicationId( applicationGuid.toString()) @@ -1726,8 +1747,8 @@ private Flux getServiceBindingResourcesByAppli return getApplicationServiceBindingResources(pageRequestSupplier); } - private Flux - getApplicationServiceBindingResources(IntFunction pageRequestSupplier) { + private Flux getApplicationServiceBindingResources( + IntFunction pageRequestSupplier) { return PaginationUtils.requestClientV3Resources(page -> delegate.serviceBindingsV3() .list(pageRequestSupplier.apply(page))); } @@ -1909,8 +1930,7 @@ private void validateDomainForRoute(CloudRoute route, Map existing String domain = route.getDomain() .getName(); if (!StringUtils.hasLength(domain) || !existingDomains.containsKey(domain)) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, - HttpStatus.NOT_FOUND.getReasonPhrase(), + throw new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), "Domain '" + domain + "' not found for URI " + route.getUrl()); } } @@ -2008,8 +2028,7 @@ private void doDeleteServiceInstance(UUID serviceInstanceGuid) { private Optional doUnbindServiceInstance(UUID applicationGuid, UUID serviceInstanceGuid) { UUID serviceBindingGuid = getServiceBindingGuid(applicationGuid, serviceInstanceGuid); if (serviceBindingGuid == null) { - throw new CloudOperationException(HttpStatus.NOT_FOUND, - "Not Found", + throw new CloudOperationException(HttpStatus.NOT_FOUND, "Not Found", "Service binding between service with GUID " + serviceInstanceGuid + " and application with GUID " + applicationGuid + " not found."); } @@ -2172,18 +2191,14 @@ protected Mono getServiceOffering(String offeringId) return delegate.serviceOfferingsV3() .get(request) .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, - HttpStatus.FORBIDDEN.getReasonPhrase(), + t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), MessageFormat.format( - Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, - offeringId), + Messages.SERVICE_OFFERING_WITH_GUID_0_IS_NOT_AVAILABLE, offeringId), t)) .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, - HttpStatus.NOT_FOUND.getReasonPhrase(), + t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.SERVICE_OFFERING_WITH_GUID_0_NOT_FOUND, - offeringId), - t)); + offeringId), t)); } private Flux getServiceResourcesByBrokerGuid(UUID brokerGuid) { @@ -2245,18 +2260,14 @@ protected Mono getServicePlanResource(String servicePlanG return delegate.servicePlansV3() .get(request) .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.FORBIDDEN), - t -> new CloudOperationException(HttpStatus.FORBIDDEN, - HttpStatus.FORBIDDEN.getReasonPhrase(), + t -> new CloudOperationException(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), MessageFormat.format( Messages.SERVICE_PLAN_WITH_GUID_0_NOT_AVAILABLE_FOR_SERVICE_INSTANCE_1, - servicePlanGuid, serviceInstanceName), - t)) + servicePlanGuid, serviceInstanceName), t)) .onErrorMap(t -> doesErrorMatchStatusCode(t, HttpStatus.NOT_FOUND), - t -> new CloudOperationException(HttpStatus.NOT_FOUND, - HttpStatus.NOT_FOUND.getReasonPhrase(), + t -> new CloudOperationException(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), MessageFormat.format(Messages.NO_SERVICE_PLAN_FOUND, servicePlanGuid, - serviceInstanceName), - t)); + serviceInstanceName), t)); } private Flux getServicePlanResourcesByServiceOfferingGuid(UUID serviceOfferingGuid) { @@ -2433,9 +2444,9 @@ private Map getDomainsFromRoutes(Set routes) { } private UUID getRouteGuid(UUID domainGuid, String host, String path) { - List routeEntitiesResource = getRouteResourcesByDomainGuidHostAndPath(domainGuid, host, - path).collect(Collectors.toList()) - .block(); + List routeEntitiesResource = getRouteResourcesByDomainGuidHostAndPath(domainGuid, host, path).collect( + Collectors.toList()) + .block(); if (CollectionUtils.isEmpty(routeEntitiesResource)) { return null; } From 4ec10501bb7d5aa6e93c4afe007282cdbc02ee56 Mon Sep 17 00:00:00 2001 From: theghost5800 Date: Mon, 14 Jul 2025 16:31:51 +0300 Subject: [PATCH 2/2] Verify app features in integration tests JIRA:LMCROSSITXSADEPLOY-3111 --- ...sCloudControllerClientIntegrationTest.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/test/java/com/sap/cloudfoundry/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/src/test/java/com/sap/cloudfoundry/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index cbc9a6eac..eb210b8bf 100644 --- a/src/test/java/com/sap/cloudfoundry/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/src/test/java/com/sap/cloudfoundry/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -298,8 +298,7 @@ void createDockerPackage() { try { verifyApplicationWillBeCreated(applicationName, ImmutableStaging.builder() .dockerInfo(dockerInfo) - .build(), - Set.of(getImmutableCloudRoute())); + .build(), Set.of(getImmutableCloudRoute())); UUID applicationGuid = client.getApplicationGuid(applicationName); CloudPackage dockerPackage = client.createDockerPackage(applicationGuid, dockerInfo); assertEquals(CloudPackage.Type.DOCKER, dockerPackage.getType()); @@ -344,8 +343,7 @@ void getPackagesForApplication() { try { verifyApplicationWillBeCreated(applicationName, ImmutableStaging.builder() .dockerInfo(dockerInfo) - .build(), - Set.of(getImmutableCloudRoute())); + .build(), Set.of(getImmutableCloudRoute())); UUID applicationGuid = client.getApplicationGuid(applicationName); CloudPackage dockerPackage = client.createDockerPackage(applicationGuid, dockerInfo); List packagesForApplication = client.getPackagesForApplication(applicationGuid); @@ -374,10 +372,9 @@ void getBuildsForApplication() { .map(CloudMetadata::getGuid) .anyMatch(buildGuid -> buildGuid.equals(build.getGuid()))); assertEquals(build.getMetadata() - .getGuid(), - client.getBuild(build.getMetadata() - .getGuid()) - .getGuid()); + .getGuid(), client.getBuild(build.getMetadata() + .getGuid()) + .getGuid()); } catch (Exception e) { fail(e); } finally { @@ -444,6 +441,22 @@ void createCnbApplication() { } } + @Test + @DisplayName("Crete application with enabled SSH and vcap-file-based services feature") + void createApplicationWithSshAndVcapFileBasedServices() { + String applicationName = "test-app-18"; + Staging staging = ImmutableStaging.builder() + .appFeatures(Map.of("file-based-vcap-services", true, "ssh", true)) + .build(); + try { + verifyApplicationWillBeCreated(applicationName, staging, Set.of(getImmutableCloudRoute())); + } catch (Exception e) { + fail(e); + } finally { + client.deleteApplication(applicationName); + } + } + private void verifyApplicationWillBeCreated(String applicationName, Staging staging, Set routes) { ApplicationToCreateDto applicationToCreateDto = ImmutableApplicationToCreateDto.builder() .name(applicationName) @@ -457,8 +470,7 @@ private void verifyApplicationWillBeCreated(String applicationName, Staging stag .name(applicationName) .state(CloudApplication.State.STOPPED) .lifecycle(createLifecycle(staging)) - .build(), - staging, routes); + .build(), staging, routes); } private static void assertApplicationExists(CloudApplication cloudApplication, Staging staging, Set routes) { @@ -473,12 +485,23 @@ private static void assertApplicationExists(CloudApplication cloudApplication, S } assertEquals(MEMORY_IN_MB, process.getMemoryInMb()); assertEquals(DISK_IN_MB, process.getDiskInMb()); + assertAppFeatures(staging, application); + } + + private static void assertAppFeatures(Staging staging, CloudApplication application) { + var appFeatures = client.getApplicationFeatures(application.getGuid()); + for (Map.Entry entry : staging.getAppFeatures() + .entrySet()) { + String featureName = entry.getKey(); + Boolean expectedValue = entry.getValue(); + Boolean actualValue = appFeatures.get(featureName); + assertEquals(expectedValue, actualValue); + } } private void createAndVerifyDefaultApplication(String applicationName) { verifyApplicationWillBeCreated(applicationName, ImmutableStaging.builder() - .build(), - Set.of(getImmutableCloudRoute())); + .build(), Set.of(getImmutableCloudRoute())); } private ImmutableCloudRoute getImmutableCloudRoute() {