From 1ecdbb44e6320186ad072479893ad5a9822258ca Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Wed, 29 Apr 2026 17:47:31 +0200 Subject: [PATCH 01/26] Support skipping integration tests for unavailable CF features via environment variables (#1346) --- README.md | 4 + .../cloudfoundry/client/v3/stacks/Stack.java | 14 ++ .../applications/DefaultApplicationsTest.java | 6 +- .../org/cloudfoundry/CloudFoundryCleaner.java | 51 ++++-- .../IntegrationTestConfiguration.java | 150 ++++++++++-------- .../org/cloudfoundry/RequiresBrowserAuth.java | 74 +++++++++ .../cloudfoundry/RequiresMetricRegistrar.java | 74 +++++++++ .../org/cloudfoundry/RequiresTcpRouting.java | 77 +++++++++ .../java/org/cloudfoundry/RequiresV2Api.java | 78 +++++++++ .../client/v2/ApplicationsTest.java | 2 + .../client/v2/BlobstoresTest.java | 2 + .../client/v2/BuildpacksTest.java | 2 + .../cloudfoundry/client/v2/DomainsTest.java | 2 + .../cloudfoundry/client/v2/EventsTest.java | 2 + .../client/v2/FeatureFlagsTest.java | 2 + .../org/cloudfoundry/client/v2/InfoTest.java | 2 + .../org/cloudfoundry/client/v2/JobsTest.java | 2 + .../v2/OrganizationQuotaDefinitionsTest.java | 2 + .../client/v2/OrganizationsTest.java | 2 + .../client/v2/PrivateDomainsTest.java | 2 + .../client/v2/RouteMappingsTest.java | 2 + .../cloudfoundry/client/v2/RoutesTest.java | 2 + .../client/v2/SecurityGroupsTest.java | 2 + .../client/v2/ServiceBindingsTest.java | 2 + .../client/v2/ServiceBrokersTest.java | 2 + .../client/v2/ServiceInstancesTest.java | 2 + .../client/v2/ServiceKeysTest.java | 2 + .../v2/ServicePlanVisibilitiesTest.java | 2 + .../client/v2/ServicePlansTest.java | 2 + .../client/v2/ServiceUsageEventsTest.java | 2 + .../cloudfoundry/client/v2/ServicesTest.java | 2 + .../client/v2/SharedDomainsTest.java | 2 + .../client/v2/SpaceQuotaDefinitionsTest.java | 2 + .../cloudfoundry/client/v2/SpacesTest.java | 2 + .../cloudfoundry/client/v2/StacksTest.java | 2 + .../client/v2/UserProvidedServicesTest.java | 2 + .../org/cloudfoundry/client/v2/UsersTest.java | 2 + .../org/cloudfoundry/client/v3/AdminTest.java | 3 + .../client/v3/DeploymentsTest.java | 2 + .../client/v3/IsolationSegmentsTest.java | 2 + .../client/v3/OrganizationsTest.java | 2 + .../cloudfoundry/client/v3/ProcessesTest.java | 2 + .../client/v3/ServiceBindingsTest.java | 2 + .../client/v3/ServiceBrokersTest.java | 2 + .../client/v3/ServiceInstancesTest.java | 2 + .../client/v3/ServiceOfferingsTest.java | 2 + .../client/v3/ServicePlansTest.java | 2 + .../org/cloudfoundry/client/v3/TasksTest.java | 2 + .../logcache/v1/LogCacheTest.java | 5 + .../cloudfoundry/operations/AdvancedTest.java | 2 + .../operations/ApplicationsTest.java | 7 + .../operations/BuildpacksTest.java | 2 + .../cloudfoundry/operations/DomainsTest.java | 2 + .../operations/NetworkPoliciesTest.java | 2 + .../operations/OrganizationAdminTest.java | 2 + .../operations/OrganizationsTest.java | 2 + .../cloudfoundry/operations/RoutesTest.java | 2 + .../operations/ServiceAdminTest.java | 2 + .../cloudfoundry/operations/ServicesTest.java | 2 + .../cloudfoundry/operations/SpacesTest.java | 2 + .../cloudfoundry/operations/StacksTest.java | 1 + .../operations/UserAdminTest.java | 2 + .../routing/v1/RouterGroupsTest.java | 2 + .../routing/v1/TcpRoutesTest.java | 2 + .../cloudfoundry/uaa/AuthorizationsTest.java | 7 + 65 files changed, 577 insertions(+), 76 deletions(-) create mode 100644 integration-test/src/test/java/org/cloudfoundry/RequiresBrowserAuth.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/RequiresMetricRegistrar.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/RequiresTcpRouting.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/RequiresV2Api.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java diff --git a/README.md b/README.md index 715033c14e6..06464884264 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,10 @@ Name | Description `TEST_PROXY_PORT` | _(Optional)_ The port of a proxy to route all requests through. Defaults to `8080`. `TEST_PROXY_USERNAME` | _(Optional)_ The username for a proxy to route all requests through `TEST_SKIPSSLVALIDATION` | _(Optional)_ Whether to skip SSL validation when connecting to the Cloud Foundry instance. Defaults to `false`. +`SKIP_BROWSER_AUTH_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require browser-based authentication (e.g., SSO tests). Useful when the Cloud Foundry instance's UAA does not have browser-based authentication configured. Defaults to `false`. +`SKIP_METRIC_REGISTRAR_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require the Metric Registrar service (e.g., MetricRegistrarTest). Useful when the Cloud Foundry instance does not have the Metric Registrar service available. Defaults to `false`. +`SKIP_TCP_ROUTING_TESTS` | _(Optional)_ Set to `true` to skip TCP routing integration tests (TcpRoutesTest, RouterGroupsTest). Useful when the Cloud Foundry instance does not have TCP routing configured (i.e., no `routing_endpoint` in the info payload). Defaults to `false`. +`SKIP_V2_TESTS` | _(Optional)_ Set to `true` to skip all V2 API integration tests. Useful when the Cloud Foundry V2 API is rate-limited or unavailable. When enabled, tests annotated with `@RequiresV2Api` will be skipped, including V3 tests that depend on V2 API calls (e.g., service broker creation). Defaults to `false`. If you do not have access to a CloudFoundry instance with admin access, you can run one locally using [bosh-deployment](https://github.com/cloudfoundry/bosh-deployment) & [cf-deployment](https://github.com/cloudfoundry/cf-deployment/) and Virtualbox. diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/stacks/Stack.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/stacks/Stack.java index 0e90cc053de..6e6cca0410f 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/stacks/Stack.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/stacks/Stack.java @@ -66,4 +66,18 @@ public abstract class Stack extends Resource { @JsonProperty("default") @Nullable abstract Boolean geDefault(); + + /** + * The state of the stack (e.g. ready) + */ + @JsonProperty("state") + @Nullable + abstract String getState(); + + /** + * The reason for the stack's current state + */ + @JsonProperty("state_reason") + @Nullable + abstract String getStateReason(); } diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 93f83236aa6..f137ac2d84b 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -1378,7 +1378,11 @@ void logsLogCache() { requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications - .logs(ApplicationLogsRequest.builder().name("test-application-name").recent(true).build()) + .logs( + ApplicationLogsRequest.builder() + .name("test-application-name") + .recent(true) + .build()) .as(StepVerifier::create) .expectNextMatches( log -> diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java index d515a86b092..de94246f6b1 100644 --- a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java +++ b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java @@ -28,7 +28,6 @@ import java.util.function.Supplier; import javax.net.ssl.SSLException; import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.client.v2.applications.ListApplicationServiceBindingsRequest; import org.cloudfoundry.client.v2.applications.RemoveApplicationServiceBindingRequest; import org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackRequest; import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest; @@ -79,6 +78,8 @@ import org.cloudfoundry.client.v3.applications.Application; import org.cloudfoundry.client.v3.applications.DeleteApplicationRequest; import org.cloudfoundry.client.v3.applications.ListApplicationsRequest; +import org.cloudfoundry.client.v3.servicebindings.DeleteServiceBindingRequest; +import org.cloudfoundry.client.v3.servicebindings.ListServiceBindingsRequest; import org.cloudfoundry.client.v3.serviceinstances.ListSharedSpacesRelationshipRequest; import org.cloudfoundry.client.v3.serviceinstances.ListSharedSpacesRelationshipResponse; import org.cloudfoundry.client.v3.serviceinstances.UnshareServiceInstanceRequest; @@ -129,6 +130,8 @@ final class CloudFoundryCleaner implements InitializingBean, DisposableBean { private static final Logger LOGGER = LoggerFactory.getLogger("cloudfoundry-client.test"); + private static final boolean RUN_V2_CLEANUP = RequiresV2Api.V2ApiCondition.isEnabled(); + private static final Map STANDARD_FEATURE_FLAGS = FluentMap.builder() .entry("app_bits_upload", true) @@ -186,6 +189,28 @@ public void destroy() { } void clean() { + if (!RUN_V2_CLEANUP) { + LOGGER.info("Skipping V2 API cleanup operations (SKIP_V2_TESTS=true)"); + // Only run V3 and UAA cleanup operations + Flux.empty() + .thenMany( + Mono.when( // No prerequisites - V3/UAA only + cleanClients(this.uaaClient, this.nameFactory), + cleanGroups(this.uaaClient, this.nameFactory), + cleanIdentityProviders(this.uaaClient, this.nameFactory), + cleanIdentityZones(this.uaaClient, this.nameFactory))) + .thenMany( + Mono.when( + cleanApplicationsV3(this.cloudFoundryClient, this.nameFactory), + cleanUsers(this.uaaClient, this.nameFactory))) + .retryWhen(Retry.max(5).filter(SSLException.class::isInstance)) + .doOnSubscribe(s -> LOGGER.debug(">> CLEANUP (V3 only) <<")) + .doOnComplete(() -> LOGGER.debug("<< CLEANUP (V3 only) >>")) + .then() + .block(Duration.ofMinutes(30)); + return; + } + Flux.empty() .thenMany( Mono.when( // Before Routes @@ -216,10 +241,7 @@ void clean() { cleanUsers(this.cloudFoundryClient, this.nameFactory))) .thenMany( Mono.when( - cleanApplicationsV3( - this.cloudFoundryClient, - this.nameFactory), // After Routes, cannot run with - // other cleanApps + cleanApplicationsV3(this.cloudFoundryClient, this.nameFactory), cleanUsers(this.uaaClient, this.nameFactory) // After CF Users )) .thenMany( @@ -1160,21 +1182,24 @@ private static boolean isCleanable(NameFactory nameFactory, UserResource resourc private static Flux removeApplicationServiceBindings( CloudFoundryClient cloudFoundryClient, Application application) { - return PaginationUtils.requestClientV2Resources( + return PaginationUtils.requestClientV3Resources( page -> cloudFoundryClient - .applicationsV2() - .listServiceBindings( - ListApplicationServiceBindingsRequest.builder() + .serviceBindingsV3() + .list( + ListServiceBindingsRequest.builder() .page(page) .applicationId(application.getId()) .build())) .flatMap( serviceBinding -> - requestRemoveServiceBinding( - cloudFoundryClient, - application.getId(), - ResourceUtils.getId(serviceBinding)) + cloudFoundryClient + .serviceBindingsV3() + .delete( + DeleteServiceBindingRequest.builder() + .serviceBindingId(serviceBinding.getId()) + .build()) + .then() .doOnError( t -> LOGGER.error( diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index da390ac2040..46692283d55 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -39,14 +39,17 @@ import java.util.List; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.info.GetInfoRequest; -import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionRequest; -import org.cloudfoundry.client.v2.organizations.AssociateOrganizationManagerRequest; -import org.cloudfoundry.client.v2.organizations.CreateOrganizationRequest; -import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; -import org.cloudfoundry.client.v2.stacks.ListStacksRequest; -import org.cloudfoundry.client.v2.stacks.StackEntity; -import org.cloudfoundry.client.v2.stacks.StackResource; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest; +import org.cloudfoundry.client.v3.Relationship; +import org.cloudfoundry.client.v3.ToOneRelationship; +import org.cloudfoundry.client.v3.organizations.CreateOrganizationRequest; +import org.cloudfoundry.client.v3.organizations.CreateOrganizationResponse; +import org.cloudfoundry.client.v3.roles.CreateRoleRequest; +import org.cloudfoundry.client.v3.roles.RoleRelationships; +import org.cloudfoundry.client.v3.roles.RoleType; +import org.cloudfoundry.client.v3.spaces.CreateSpaceRequest; +import org.cloudfoundry.client.v3.spaces.CreateSpaceResponse; +import org.cloudfoundry.client.v3.spaces.SpaceRelationships; import org.cloudfoundry.doppler.DopplerClient; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.TestLogCacheEndpoints; @@ -86,6 +89,7 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; @@ -346,6 +350,10 @@ RandomNameFactory nameFactory() { @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") + @ConditionalOnProperty( + name = RequiresV2Api.SKIP_V2_TESTS_ENV, + havingValue = "false", + matchIfMissing = true) Mono metricRegistrarServiceInstance( CloudFoundryClient cloudFoundryClient, Mono spaceId, NameFactory nameFactory) { return spaceId.flatMap( @@ -374,52 +382,48 @@ NetworkingClient networkingClient( @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") Mono organizationId( - CloudFoundryClient cloudFoundryClient, - String organizationName, - String organizationQuotaName, - Mono userId) { + CloudFoundryClient cloudFoundryClient, String organizationName, Mono userId) { return userId.flatMap( userId1 -> cloudFoundryClient - .organizationQuotaDefinitions() + .organizationsV3() .create( - CreateOrganizationQuotaDefinitionRequest.builder() - .applicationInstanceLimit(-1) - .applicationTaskLimit(-1) - .instanceMemoryLimit(-1) - .memoryLimit(16384) - .name(organizationQuotaName) - .nonBasicServicesAllowed(true) - .totalPrivateDomains(-1) - .totalReservedRoutePorts(-1) - .totalRoutes(-1) - .totalServiceKeys(-1) - .totalServices(-1) + CreateOrganizationRequest.builder() + .name(organizationName) .build()) - .map(ResourceUtils::getId) + .map(CreateOrganizationResponse::getId) .zipWith(Mono.just(userId1))) - .flatMap( - function( - (quotaId, userId1) -> - cloudFoundryClient - .organizations() - .create( - CreateOrganizationRequest.builder() - .name(organizationName) - .quotaDefinitionId(quotaId) - .build()) - .map(ResourceUtils::getId) - .zipWith(Mono.just(userId1)))) .flatMap( function( (organizationId, userId1) -> cloudFoundryClient - .organizations() - .associateManager( - AssociateOrganizationManagerRequest - .builder() - .organizationId(organizationId) - .managerId(userId1) + .rolesV3() + .create( + CreateRoleRequest.builder() + .type(RoleType.ORGANIZATION_MANAGER) + .relationships( + RoleRelationships.builder() + .user( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + userId1) + .build()) + .build()) + .organization( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + organizationId) + .build()) + .build()) + .build()) .build()) .thenReturn(organizationId))) .doOnSubscribe(s -> this.logger.debug(">> ORGANIZATION ({}) <<", organizationName)) @@ -433,11 +437,6 @@ String organizationName(NameFactory nameFactory) { return nameFactory.getOrganizationName(); } - @Bean - String organizationQuotaName(NameFactory nameFactory) { - return nameFactory.getQuotaDefinitionName(); - } - @Bean String password(NameFactory nameFactory) { return nameFactory.getPassword(); @@ -470,6 +469,10 @@ Version serverVersion(@Qualifier("admin") CloudFoundryClient cloudFoundryClient) @Lazy @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") + @ConditionalOnProperty( + name = RequiresV2Api.SKIP_V2_TESTS_ENV, + havingValue = "false", + matchIfMissing = true) Mono serviceBrokerId( CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, @@ -518,13 +521,25 @@ Mono spaceId( .flatMap( orgId -> cloudFoundryClient - .spaces() + .spacesV3() .create( CreateSpaceRequest.builder() .name(spaceName) - .organizationId(orgId) + .relationships( + SpaceRelationships.builder() + .organization( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + orgId) + .build()) + .build()) + .build()) .build())) - .map(ResourceUtils::getId) + .map(CreateSpaceResponse::getId) .doOnSubscribe(s -> this.logger.debug(">> SPACE ({}) <<", spaceName)) .doOnError(Throwable::printStackTrace) .doOnSuccess(id -> this.logger.debug("<< SPACE ({}) >>", id)) @@ -540,20 +555,20 @@ String spaceName(NameFactory nameFactory) { @DependsOn("cloudFoundryCleaner") Mono stackId(CloudFoundryClient cloudFoundryClient, Mono stackName) { return stackName - .flux() - .flatMap( + .flatMapMany( name -> - PaginationUtils.requestClientV2Resources( + PaginationUtils.requestClientV3Resources( page -> cloudFoundryClient - .stacks() + .stacksV3() .list( - ListStacksRequest.builder() + org.cloudfoundry.client.v3.stacks + .ListStacksRequest.builder() .name(name) .page(page) .build()))) .single() - .map(ResourceUtils::getId) + .map(org.cloudfoundry.client.v3.stacks.StackResource::getId) .doOnSubscribe(s -> this.logger.debug(">> STACK ({}) <<", stackName)) .doOnError(Throwable::printStackTrace) .doOnSuccess(id -> this.logger.debug("<< STACK ({})>>", id)) @@ -567,13 +582,16 @@ Mono stackId(CloudFoundryClient cloudFoundryClient, Mono stackNa @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") Mono stackName(CloudFoundryClient cloudFoundryClient) { - return PaginationUtils.requestClientV2Resources( + return PaginationUtils.requestClientV3Resources( page -> cloudFoundryClient - .stacks() - .list(ListStacksRequest.builder().page(page).build())) - .map(StackResource::getEntity) - .map(StackEntity::getName) + .stacksV3() + .list( + org.cloudfoundry.client.v3.stacks.ListStacksRequest + .builder() + .page(page) + .build())) + .map(org.cloudfoundry.client.v3.stacks.StackResource::getName) .filter(s -> s.matches("^cflinuxfs\\d$")) .sort(Comparator.reverseOrder()) .next() @@ -583,6 +601,10 @@ Mono stackName(CloudFoundryClient cloudFoundryClient) { @Lazy @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") + @ConditionalOnProperty( + name = RequiresV2Api.SKIP_V2_TESTS_ENV, + havingValue = "false", + matchIfMissing = true) Mono testLogCacheApp( CloudFoundryClient cloudFoundryClient, Mono spaceId, @@ -619,6 +641,10 @@ String testLogCacheAppName(NameFactory nameFactory) { @Lazy @Bean + @ConditionalOnProperty( + name = RequiresV2Api.SKIP_V2_TESTS_ENV, + havingValue = "false", + matchIfMissing = true) TestLogCacheEndpoints testLogCacheEndpoints( ConnectionContext connectionContext, TokenProvider tokenProvider, diff --git a/integration-test/src/test/java/org/cloudfoundry/RequiresBrowserAuth.java b/integration-test/src/test/java/org/cloudfoundry/RequiresBrowserAuth.java new file mode 100644 index 00000000000..d675dd63242 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/RequiresBrowserAuth.java @@ -0,0 +1,74 @@ +/* + * Copyright 2026 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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Annotation to mark tests that require browser-based authentication (e.g., OAuth + * authorization code grants, implicit grants). Tests annotated with this will be skipped + * if the environment variable {@code SKIP_BROWSER_AUTH_TESTS} is set to "true". + * + *

Usage: + *

+ * @RequiresBrowserAuth
+ * public class MyAuthTest extends AbstractIntegrationTest {
+ *     // ...
+ * }
+ * 
+ * + *

To skip browser auth tests, set the environment variable: + *

+ * export SKIP_BROWSER_AUTH_TESTS=true
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ExtendWith(RequiresBrowserAuth.BrowserAuthCondition.class) +public @interface RequiresBrowserAuth { + + /** + * JUnit 5 ExecutionCondition that checks if Browser Auth tests should be skipped. + */ + class BrowserAuthCondition implements ExecutionCondition { + + private static final String SKIP_BROWSER_AUTH_ENV = "SKIP_BROWSER_AUTH_TESTS"; + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if ("true".equalsIgnoreCase(System.getenv(SKIP_BROWSER_AUTH_ENV))) { + return ConditionEvaluationResult.disabled( + "Tests requiring Browser Authentication are disabled via " + + SKIP_BROWSER_AUTH_ENV + + " environment variable"); + } + + return ConditionEvaluationResult.enabled("Browser authentication tests are enabled"); + } + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/RequiresMetricRegistrar.java b/integration-test/src/test/java/org/cloudfoundry/RequiresMetricRegistrar.java new file mode 100644 index 00000000000..c70d21dfe25 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/RequiresMetricRegistrar.java @@ -0,0 +1,74 @@ +/* + * Copyright 2026 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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Annotation to mark tests that require the Metric Registrar service to be available. + * Tests annotated with this will be skipped if the environment variable + * {@code SKIP_METRIC_REGISTRAR_TESTS} is set to "true". + * + *

Usage: + *

+ * @RequiresMetricRegistrar
+ * public class MetricTest extends AbstractIntegrationTest {
+ *     // ...
+ * }
+ * 
+ * + *

To skip metric registrar tests, set the environment variable: + *

+ * export SKIP_METRIC_REGISTRAR_TESTS=true
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ExtendWith(RequiresMetricRegistrar.RegistrarCondition.class) +public @interface RequiresMetricRegistrar { + + /** + * JUnit 5 ExecutionCondition that checks if Metric Registrar tests should be skipped. + */ + class RegistrarCondition implements ExecutionCondition { + + private static final String SKIP_REGISTRAR_ENV = "SKIP_METRIC_REGISTRAR_TESTS"; + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if ("true".equalsIgnoreCase(System.getenv(SKIP_REGISTRAR_ENV))) { + return ConditionEvaluationResult.disabled( + "Tests requiring Metric Registrar Service are disabled via " + + SKIP_REGISTRAR_ENV + + " environment variable."); + } + + return ConditionEvaluationResult.enabled("Metric Registrar tests are enabled"); + } + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/RequiresTcpRouting.java b/integration-test/src/test/java/org/cloudfoundry/RequiresTcpRouting.java new file mode 100644 index 00000000000..0ee45aee62c --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/RequiresTcpRouting.java @@ -0,0 +1,77 @@ +/* + * Copyright 2026 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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Annotation to mark tests that require TCP routing to be configured. + * + *

Tests annotated with this (or test classes containing this annotation) + * will be skipped if the {@code SKIP_TCP_ROUTING_TESTS} environment variable + * is set to "true". + * + *

Use this when your Cloud Foundry instance does not have TCP routing + * configured (i.e., when the info payload does not contain a 'routing_endpoint' key). + * + *

Example usage: + *

+ * @RequiresTcpRouting
+ * public class TcpRoutesTest extends AbstractIntegrationTest {
+ *     // tests that require TCP routing
+ * }
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ExtendWith(RequiresTcpRouting.SkipTcpRoutingCondition.class) +public @interface RequiresTcpRouting { + + /** + * JUnit 5 ExecutionCondition that checks if tcp routing tests should be skipped. + */ + class SkipTcpRoutingCondition implements ExecutionCondition { + + private static final String ENV_VAR = "SKIP_TCP_ROUTING_TESTS"; + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + String envValue = System.getenv(ENV_VAR); + + if ("true".equalsIgnoreCase(envValue)) { + return ConditionEvaluationResult.disabled( + "TCP routing tests are disabled via " + + ENV_VAR + + " environment variable" + + ". TCP routing may not be configured on the target CF instance."); + } + + return ConditionEvaluationResult.enabled("TCP routing tests are enabled"); + } + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/RequiresV2Api.java b/integration-test/src/test/java/org/cloudfoundry/RequiresV2Api.java new file mode 100644 index 00000000000..b11fe8ca06b --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/RequiresV2Api.java @@ -0,0 +1,78 @@ +/* + * Copyright 2026 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 java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Annotation to mark tests that require the V2 API. Tests annotated with this + * will be skipped if the environment variable {@code SKIP_V2_TESTS} is set to "true". + * + *

Usage: + *

+ * @RequiresV2Api
+ * public class MyV2Test extends AbstractIntegrationTest {
+ *     // ...
+ * }
+ * 
+ * + *

To skip V2 tests, set the environment variable: + *

+ * export SKIP_V2_TESTS=true
+ * 
+ */ +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ExtendWith(RequiresV2Api.V2ApiCondition.class) +public @interface RequiresV2Api { + + /** Environment variable name used to skip V2 API tests. */ + String SKIP_V2_TESTS_ENV = "SKIP_V2_TESTS"; + + /** + * JUnit 5 ExecutionCondition that checks if V2 tests should be skipped. + */ + class V2ApiCondition implements ExecutionCondition { + + /** Returns {@code true} if V2 API tests should run (i.e., not skipped). */ + public static boolean isEnabled() { + return !"true".equalsIgnoreCase(System.getenv(SKIP_V2_TESTS_ENV)); + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + if (!isEnabled()) { + return ConditionEvaluationResult.disabled( + "V2 API tests are disabled via " + + SKIP_V2_TESTS_ENV + + " environment variable"); + } + return ConditionEvaluationResult.enabled("V2 API tests are enabled"); + } + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java index 9774568bd02..df19fe4c8a4 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ApplicationsTest.java @@ -39,6 +39,7 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.AbstractApplicationResource; import org.cloudfoundry.client.v2.applications.ApplicationEnvironmentRequest; @@ -98,6 +99,7 @@ import reactor.util.function.Tuple2; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java index f9368a07307..223f55eb4ed 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.blobstores.DeleteBlobstoreBuildpackCachesRequest; import org.cloudfoundry.util.JobUtils; @@ -27,6 +28,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class BlobstoresTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/BuildpacksTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/BuildpacksTest.java index 03b7d3f0e95..00afd83bcb9 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/BuildpacksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/BuildpacksTest.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.buildpacks.BuildpackEntity; import org.cloudfoundry.client.v2.buildpacks.BuildpackResource; @@ -40,6 +41,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class BuildpacksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/DomainsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/DomainsTest.java index 676d130484f..8d4841222d7 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/DomainsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/DomainsTest.java @@ -23,6 +23,7 @@ import java.time.Duration; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -59,6 +60,7 @@ import reactor.util.function.Tuple2; @SuppressWarnings("deprecation") +@RequiresV2Api public final class DomainsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/EventsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/EventsTest.java index 76dcea594d2..d52358d9dbf 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/EventsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/EventsTest.java @@ -18,6 +18,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.events.EventResource; import org.cloudfoundry.client.v2.events.GetEventRequest; @@ -29,6 +30,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class EventsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/FeatureFlagsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/FeatureFlagsTest.java index d47962b5aa9..217bdd155a0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/FeatureFlagsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/FeatureFlagsTest.java @@ -26,6 +26,7 @@ import java.util.Set; import java.util.stream.Collectors; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.featureflags.FeatureFlagEntity; import org.cloudfoundry.client.v2.featureflags.GetFeatureFlagRequest; @@ -38,6 +39,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@RequiresV2Api public final class FeatureFlagsTest extends AbstractIntegrationTest { private static final List coreFeatureFlagNameList = diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java index 471cb284e8a..a4395079a54 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java @@ -22,12 +22,14 @@ import com.github.zafarkhaja.semver.Version; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import reactor.test.StepVerifier; +@RequiresV2Api public final class InfoTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/JobsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/JobsTest.java index 0727b062d2e..950ae4e35ad 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/JobsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/JobsTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.jobs.GetJobRequest; import org.cloudfoundry.client.v2.organizations.CreateOrganizationRequest; @@ -31,6 +32,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class JobsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationQuotaDefinitionsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationQuotaDefinitionsTest.java index 8a435cfa81b..b99f91e3076 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationQuotaDefinitionsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationQuotaDefinitionsTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionRequest; import org.cloudfoundry.client.v2.organizationquotadefinitions.CreateOrganizationQuotaDefinitionResponse; @@ -37,6 +38,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class OrganizationQuotaDefinitionsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationsTest.java index e1d1762e649..3c8740d0fb2 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/OrganizationsTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import java.util.function.UnaryOperator; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -98,6 +99,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class OrganizationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/PrivateDomainsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/PrivateDomainsTest.java index 587cfa35aea..4dde9496a38 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/PrivateDomainsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/PrivateDomainsTest.java @@ -23,6 +23,7 @@ import java.time.Duration; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.organizations.AssociateOrganizationAuditorRequest; import org.cloudfoundry.client.v2.organizations.AssociateOrganizationAuditorResponse; @@ -57,6 +58,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; +@RequiresV2Api public final class PrivateDomainsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/RouteMappingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/RouteMappingsTest.java index 1056e983040..9b31ddf78a8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/RouteMappingsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/RouteMappingsTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -43,6 +44,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class RouteMappingsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/RoutesTest.java index 3ee56508931..cb974f35e6b 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/RoutesTest.java @@ -23,6 +23,7 @@ import java.time.Duration; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -53,6 +54,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple3; +@RequiresV2Api public final class RoutesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SecurityGroupsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SecurityGroupsTest.java index 64ee5df01a8..ba249ceff2c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SecurityGroupsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SecurityGroupsTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.securitygroups.AssociateSecurityGroupSpaceRequest; import org.cloudfoundry.client.v2.securitygroups.AssociateSecurityGroupSpaceResponse; @@ -55,6 +56,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class SecurityGroupsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBindingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBindingsTest.java index 668976376d4..f596238704f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBindingsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBindingsTest.java @@ -24,6 +24,7 @@ import java.util.Optional; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -49,6 +50,7 @@ import reactor.util.function.Tuple2; import reactor.util.function.Tuple3; +@RequiresV2Api public final class ServiceBindingsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java index 214577e4a90..61320ccd50d 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceBrokersTest.java @@ -26,6 +26,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CleanupCloudFoundryAfterClass; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebrokers.CreateServiceBrokerRequest; @@ -45,6 +46,7 @@ import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class ServiceBrokersTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceInstancesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceInstancesTest.java index ed152f87a39..c3620f4957e 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceInstancesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceInstancesTest.java @@ -24,6 +24,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -65,6 +66,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@RequiresV2Api public final class ServiceInstancesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceKeysTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceKeysTest.java index 58067e0d31f..3ec01e0d95c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceKeysTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceKeysTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceRequest; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceResponse; @@ -42,6 +43,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class ServiceKeysTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java index 910294d42ac..e2a9ecbece2 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.organizations.CreateOrganizationRequest; @@ -47,6 +48,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@RequiresV2Api public final class ServicePlanVisibilitiesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlansTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlansTest.java index c8f4db7e148..d0cc34c36b9 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlansTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlansTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; @@ -53,6 +54,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class ServicePlansTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceUsageEventsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceUsageEventsTest.java index 987de74b28b..56ddd5bbfc0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceUsageEventsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServiceUsageEventsTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.NameFactory; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceRequest; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceResponse; @@ -40,6 +41,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class ServiceUsageEventsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicesTest.java index 9af396d3159..5410c1a951f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicesTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.serviceinstances.CreateServiceInstanceRequest; @@ -46,6 +47,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class ServicesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SharedDomainsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SharedDomainsTest.java index 063992a6574..bf606542523 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SharedDomainsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SharedDomainsTest.java @@ -19,6 +19,7 @@ import java.time.Duration; import java.util.Optional; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainRequest; import org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainResponse; @@ -37,6 +38,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class SharedDomainsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpaceQuotaDefinitionsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpaceQuotaDefinitionsTest.java index 47c9ae0f5f3..ebe8ca1b618 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpaceQuotaDefinitionsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpaceQuotaDefinitionsTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -48,6 +49,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@RequiresV2Api public final class SpaceQuotaDefinitionsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java index 4193ec53d6e..54fc74e861c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java @@ -27,6 +27,7 @@ import java.util.function.Function; import java.util.function.UnaryOperator; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.ApplicationResource; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; @@ -116,6 +117,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuple2; +@RequiresV2Api public final class SpacesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/StacksTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/StacksTest.java index cbd3ac2d296..3360a86a3f5 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/StacksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/StacksTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.stacks.CreateStackRequest; import org.cloudfoundry.client.v2.stacks.CreateStackResponse; @@ -38,6 +39,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class StacksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/UserProvidedServicesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/UserProvidedServicesTest.java index 555741d6340..a850b861418 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/UserProvidedServicesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/UserProvidedServicesTest.java @@ -24,6 +24,7 @@ import java.util.Collections; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -56,6 +57,7 @@ import reactor.util.function.Tuple3; import reactor.util.function.Tuples; +@RequiresV2Api public final class UserProvidedServicesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/UsersTest.java index 27f1232c3d0..08efb1965e8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/UsersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/UsersTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -80,6 +81,7 @@ import reactor.test.StepVerifier; import reactor.util.function.Tuples; +@RequiresV2Api public final class UsersTest extends AbstractIntegrationTest { private static final String STATUS_FILTER = "active"; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java index d185f15deac..bdbc6778c87 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/AdminTest.java @@ -21,6 +21,7 @@ import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.admin.ClearBuildpackCacheRequest; import org.cloudfoundry.util.JobUtils; @@ -29,6 +30,8 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api +// TODO Does it really require V2? public final class AdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java index d823ad7b1c1..bcec4978420 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/DeploymentsTest.java @@ -25,6 +25,7 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletRequest; import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletResponse; @@ -52,6 +53,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_4) +@RequiresV2Api // Due to ApplicationUtils @CleanupCloudFoundryAfterClass public final class DeploymentsTest extends AbstractIntegrationTest { diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/IsolationSegmentsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/IsolationSegmentsTest.java index 0f0a56fc1cd..580fb302146 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/IsolationSegmentsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/IsolationSegmentsTest.java @@ -22,6 +22,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.organizations.CreateOrganizationRequest; import org.cloudfoundry.client.v2.organizations.CreateOrganizationResponse; @@ -53,6 +54,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_11) +@RequiresV2Api public final class IsolationSegmentsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java index a1fbd52f3df..e3da7d85e66 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java @@ -24,6 +24,7 @@ import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.domains.CreateDomainRequest; import org.cloudfoundry.client.v3.domains.CreateDomainResponse; @@ -57,6 +58,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_12) +@RequiresV2Api public final class OrganizationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java index e4a4184e85a..8e3cbba3cba 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ProcessesTest.java @@ -25,6 +25,7 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.applications.GetApplicationProcessRequest; import org.cloudfoundry.client.v3.applications.GetApplicationProcessResponse; @@ -43,6 +44,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_0) +@RequiresV2Api // Due to ApplicationUtils @CleanupCloudFoundryAfterClass public final class ProcessesTest extends AbstractIntegrationTest { diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java index 81477aef522..3c0695a2da8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java @@ -25,6 +25,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.servicebindings.CreateServiceBindingRequest; import org.cloudfoundry.client.v3.servicebindings.DeleteServiceBindingRequest; @@ -54,6 +55,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_11) +@RequiresV2Api public class ServiceBindingsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java index 656a0b0efe6..4a13ea744cf 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBrokersTest.java @@ -28,6 +28,7 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.servicebrokers.BasicAuthentication; @@ -50,6 +51,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) +@RequiresV2Api @CleanupCloudFoundryAfterClass public final class ServiceBrokersTest extends AbstractIntegrationTest { diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceInstancesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceInstancesTest.java index 507b288a653..1af91bbdf82 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceInstancesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceInstancesTest.java @@ -25,6 +25,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceRequest; import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceResponse; @@ -57,6 +58,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_1) +@RequiresV2Api public final class ServiceInstancesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceOfferingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceOfferingsTest.java index f0b67a37b46..fec36409fe8 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceOfferingsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceOfferingsTest.java @@ -24,6 +24,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.serviceofferings.DeleteServiceOfferingRequest; @@ -43,6 +44,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) +@RequiresV2Api public final class ServiceOfferingsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java index 5e37753b66b..c749b3e23de 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java @@ -24,6 +24,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.serviceplans.DeleteServicePlanRequest; @@ -45,6 +46,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_10) +@RequiresV2Api public final class ServicePlansTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java index 0e3fdada8ce..ec5970a08bb 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/TasksTest.java @@ -25,6 +25,7 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v3.applications.ApplicationResource; import org.cloudfoundry.client.v3.applications.GetApplicationCurrentDropletRequest; @@ -52,6 +53,7 @@ @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_1_12) @CleanupCloudFoundryAfterClass +@RequiresV2Api // Due to ApplicationUtils public final class TasksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java b/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java index 414af210bde..26e5143229c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/logcache/v1/LogCacheTest.java @@ -29,6 +29,8 @@ import org.cloudfoundry.ApplicationUtils; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresMetricRegistrar; +import org.cloudfoundry.RequiresV2Api; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -37,6 +39,7 @@ import reactor.test.StepVerifier; @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_9) +@RequiresV2Api public class LogCacheTest extends AbstractIntegrationTest { @Autowired LogCacheClient logCacheClient; @@ -83,6 +86,7 @@ public void meta() { } @Test + @RequiresMetricRegistrar public void readCounter() { final String name = this.nameFactory.getName("counter-"); final int delta = this.random.nextInt(1000); @@ -102,6 +106,7 @@ public void readCounter() { } @Test + @RequiresMetricRegistrar public void readEvent() { final String title = this.nameFactory.getName("event-"); final String body = "This is the body. " + new BigInteger(1024, this.random).toString(32); diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/AdvancedTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/AdvancedTest.java index 733e95d6bc9..201b9a49883 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/AdvancedTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/AdvancedTest.java @@ -20,10 +20,12 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import reactor.test.StepVerifier; +@RequiresV2Api public final class AdvancedTest extends AbstractIntegrationTest { @Autowired private CloudFoundryOperations cloudFoundryOperations; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 37c701dbbe5..43f65276114 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -29,6 +29,8 @@ import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresTcpRouting; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationEnvironments; @@ -88,6 +90,7 @@ import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class ApplicationsTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; @@ -350,6 +353,7 @@ public void getManifest() throws IOException { } @Test + @RequiresTcpRouting public void getManifestForTcpRoute() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -436,6 +440,7 @@ public void getStopped() throws IOException { } @Test + @RequiresTcpRouting public void getTcp() throws IOException { String applicationName = this.nameFactory.getApplicationName(); String domainName = this.nameFactory.getDomainName(); @@ -1140,6 +1145,7 @@ public void pushRoutePath() throws IOException { } @Test + @RequiresTcpRouting public void pushTcpRoute() throws IOException { String applicationName = this.nameFactory.getApplicationName(); String domainName = this.nameFactory.getDomainName(); @@ -1263,6 +1269,7 @@ public void pushUpdateRoute() throws IOException { } @Test + @RequiresTcpRouting public void pushUpdateTcpRoute() throws IOException { String applicationName = this.nameFactory.getApplicationName(); String domainName = this.nameFactory.getDomainName(); diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java index 08b1dfcc243..fb9026d6fda 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.operations.buildpacks.Buildpack; import org.cloudfoundry.operations.buildpacks.CreateBuildpackRequest; import org.cloudfoundry.operations.buildpacks.DeleteBuildpackRequest; @@ -31,6 +32,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class BuildpacksTest extends AbstractIntegrationTest { @Autowired private CloudFoundryOperations cloudFoundryOperations; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java index f56d7c777b9..87fd783ebf5 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.ClientV2Exception; import org.cloudfoundry.client.v3.domains.GetDomainRequest; @@ -38,6 +39,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class DomainsTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/NetworkPoliciesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/NetworkPoliciesTest.java index 1643a3ef4f8..1d32a910a2d 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/NetworkPoliciesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/NetworkPoliciesTest.java @@ -20,6 +20,7 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.applications.CreateApplicationRequest; import org.cloudfoundry.client.v2.applications.CreateApplicationResponse; @@ -34,6 +35,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class NetworkPoliciesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationAdminTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationAdminTest.java index 35186836804..4a4325d1914 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationAdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationAdminTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.operations.organizationadmin.CreateQuotaRequest; import org.cloudfoundry.operations.organizationadmin.DeleteQuotaRequest; import org.cloudfoundry.operations.organizationadmin.GetQuotaRequest; @@ -33,6 +34,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class OrganizationAdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryOperations cloudFoundryOperations; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationsTest.java index 3e4e2b57a63..ebe3f90ebe9 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/OrganizationsTest.java @@ -18,12 +18,14 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.operations.organizations.CreateOrganizationRequest; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class OrganizationsTest extends AbstractIntegrationTest { @Autowired private CloudFoundryOperations cloudFoundryOperations; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java index 19db1a53b1a..47a37e3fb91 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java @@ -28,6 +28,7 @@ import java.util.function.Predicate; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.operations.applications.ApplicationHealthCheck; import org.cloudfoundry.operations.applications.PushApplicationRequest; import org.cloudfoundry.operations.domains.CreateDomainRequest; @@ -51,6 +52,7 @@ import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class RoutesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java index 7a8f1ec3787..ebd89c92e3f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServiceAdminTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.ServiceBrokerUtils; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.spaces.CreateSpaceRequest; @@ -40,6 +41,7 @@ import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class ServiceAdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java index 520027128be..f81ddb1a674 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ServicesTest.java @@ -24,6 +24,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.servicebindings.ListServiceBindingsRequest; import org.cloudfoundry.client.v2.servicebindings.ServiceBindingResource; @@ -66,6 +67,7 @@ import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass +@RequiresV2Api public final class ServicesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/SpacesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/SpacesTest.java index fbb8dc765b3..7f862e04784 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/SpacesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/SpacesTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.operations.spaces.CreateSpaceRequest; import org.cloudfoundry.operations.spaces.GetSpaceRequest; import org.cloudfoundry.operations.spaces.SpaceDetail; @@ -27,6 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import reactor.test.StepVerifier; +@RequiresV2Api public final class SpacesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryOperations cloudFoundryOperations; diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java @@ -0,0 +1 @@ + diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/UserAdminTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/UserAdminTest.java index b0c12b8ea3f..af534acda39 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/UserAdminTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/UserAdminTest.java @@ -20,6 +20,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.organizations.ListOrganizationAuditorsRequest; import org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest; @@ -47,6 +48,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresV2Api public final class UserAdminTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; diff --git a/integration-test/src/test/java/org/cloudfoundry/routing/v1/RouterGroupsTest.java b/integration-test/src/test/java/org/cloudfoundry/routing/v1/RouterGroupsTest.java index 8253f29ad5a..7a812c7e262 100644 --- a/integration-test/src/test/java/org/cloudfoundry/routing/v1/RouterGroupsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/routing/v1/RouterGroupsTest.java @@ -18,6 +18,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresTcpRouting; import org.cloudfoundry.routing.RoutingClient; import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsRequest; import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsResponse; @@ -29,6 +30,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +@RequiresTcpRouting public final class RouterGroupsTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/routing/v1/TcpRoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/routing/v1/TcpRoutesTest.java index a562fa42232..6fb7af061b0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/routing/v1/TcpRoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/routing/v1/TcpRoutesTest.java @@ -19,6 +19,7 @@ import java.time.Duration; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.NameFactory; +import org.cloudfoundry.RequiresTcpRouting; import org.cloudfoundry.routing.RoutingClient; import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsRequest; import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsResponse; @@ -39,6 +40,7 @@ import reactor.test.StepVerifier; import reactor.util.retry.Retry; +@RequiresTcpRouting public final class TcpRoutesTest extends AbstractIntegrationTest { private static final String DEFAULT_ROUTER_GROUP = "default-tcp"; diff --git a/integration-test/src/test/java/org/cloudfoundry/uaa/AuthorizationsTest.java b/integration-test/src/test/java/org/cloudfoundry/uaa/AuthorizationsTest.java index 6480aef0b9c..e0eac2d2149 100644 --- a/integration-test/src/test/java/org/cloudfoundry/uaa/AuthorizationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/uaa/AuthorizationsTest.java @@ -21,6 +21,7 @@ import java.time.Duration; import java.util.function.Consumer; import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.RequiresBrowserAuth; import org.cloudfoundry.uaa.authorizations.AuthorizeByAuthorizationCodeGrantApiRequest; import org.cloudfoundry.uaa.authorizations.AuthorizeByAuthorizationCodeGrantBrowserRequest; import org.cloudfoundry.uaa.authorizations.AuthorizeByAuthorizationCodeGrantHybridRequest; @@ -55,6 +56,7 @@ public void authorizeByAuthorizationCodeGrantApi() { } @Test + @RequiresBrowserAuth public void authorizeByAuthorizationCodeGrantBrowser() { this.uaaClient .authorizations() @@ -70,6 +72,7 @@ public void authorizeByAuthorizationCodeGrantBrowser() { } @Test + @RequiresBrowserAuth public void authorizeByAuthorizationCodeGrantHybrid() { this.uaaClient .authorizations() @@ -85,6 +88,7 @@ public void authorizeByAuthorizationCodeGrantHybrid() { } @Test + @RequiresBrowserAuth public void authorizeByImplicitGrantBrowser() { this.uaaClient .authorizations() @@ -100,6 +104,7 @@ public void authorizeByImplicitGrantBrowser() { } @Test + @RequiresBrowserAuth public void authorizeByOpenIdWithAuthorizationCodeGrant() { this.uaaClient .authorizations() @@ -116,6 +121,7 @@ public void authorizeByOpenIdWithAuthorizationCodeGrant() { } @Test + @RequiresBrowserAuth public void authorizeByOpenIdWithIdToken() { this.uaaClient .authorizations() @@ -132,6 +138,7 @@ public void authorizeByOpenIdWithIdToken() { } @Test + @RequiresBrowserAuth public void authorizeByOpenIdWithImplicitGrant() { this.uaaClient .authorizations() From 17ad44dae33ab1a2b4b0563c869f1e5e15e09dbf Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Thu, 30 Apr 2026 17:34:41 +0200 Subject: [PATCH 02/26] Update default org quotas in integration tests - Some deployments have TCP routes org quotas set to 0. This adds a property, TEST_QUOTAS_ROUTES_RESERVEDPORTS, to update org quotas on test bootstrap. Signed-off-by: Daniel Garnier-Moiroux --- README.md | 1 + .../IntegrationTestConfiguration.java | 34 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/README.md b/README.md index 06464884264..f353347f074 100644 --- a/README.md +++ b/README.md @@ -356,6 +356,7 @@ Name | Description `SKIP_BROWSER_AUTH_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require browser-based authentication (e.g., SSO tests). Useful when the Cloud Foundry instance's UAA does not have browser-based authentication configured. Defaults to `false`. `SKIP_METRIC_REGISTRAR_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require the Metric Registrar service (e.g., MetricRegistrarTest). Useful when the Cloud Foundry instance does not have the Metric Registrar service available. Defaults to `false`. `SKIP_TCP_ROUTING_TESTS` | _(Optional)_ Set to `true` to skip TCP routing integration tests (TcpRoutesTest, RouterGroupsTest). Useful when the Cloud Foundry instance does not have TCP routing configured (i.e., no `routing_endpoint` in the info payload). Defaults to `false`. +`TEST_QUOTAS_ROUTES_RESERVED_PORTS` | _(Optional)_ When set to a positive integer, sets reserved TCP route ports (`total_reserved_ports`) on the **default** organization quota to that value during integration test bootstrap. When set to -1, sets the reserved ports to _unlimited_. Use this when the platform leaves that limit at `0` on the default quota but you still run TCP routing tests (`SKIP_TCP_ROUTING_TESTS` unset or `false`). If unset, no quota change is applied. Maps to the Spring property `test.quotas.routes.reserved-ports`. `SKIP_V2_TESTS` | _(Optional)_ Set to `true` to skip all V2 API integration tests. Useful when the Cloud Foundry V2 API is rate-limited or unavailable. When enabled, tests annotated with `@RequiresV2Api` will be skipped, including V3 tests that depend on V2 API calls (e.g., service broker creation). Defaults to `false`. If you do not have access to a CloudFoundry instance with admin access, you can run one locally using [bosh-deployment](https://github.com/cloudfoundry/bosh-deployment) & [cf-deployment](https://github.com/cloudfoundry/cf-deployment/) and Virtualbox. diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 46692283d55..bfb6d9bdf1f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -44,6 +44,11 @@ import org.cloudfoundry.client.v3.ToOneRelationship; import org.cloudfoundry.client.v3.organizations.CreateOrganizationRequest; import org.cloudfoundry.client.v3.organizations.CreateOrganizationResponse; +import org.cloudfoundry.client.v3.quotas.Routes; +import org.cloudfoundry.client.v3.quotas.organizations.ListOrganizationQuotasRequest; +import org.cloudfoundry.client.v3.quotas.organizations.ListOrganizationQuotasResponse; +import org.cloudfoundry.client.v3.quotas.organizations.OrganizationQuotaResource; +import org.cloudfoundry.client.v3.quotas.organizations.UpdateOrganizationQuotaRequest; import org.cloudfoundry.client.v3.roles.CreateRoleRequest; import org.cloudfoundry.client.v3.roles.RoleRelationships; import org.cloudfoundry.client.v3.roles.RoleType; @@ -759,6 +764,35 @@ String username(NameFactory nameFactory) { return nameFactory.getUserName(); } + @Bean + @ConditionalOnProperty(value = "test.quotas.routes.reserved-ports", matchIfMissing = false) + Integer tcpRouteQuota( + @Value("${test.quotas.routes.reserved-ports}") Integer portsQuota, + @Qualifier("admin") CloudFoundryClient cloudFoundryClient) { + cloudFoundryClient + .organizationQuotasV3() + .list(ListOrganizationQuotasRequest.builder().name("default").build()) + .flatMapIterable(ListOrganizationQuotasResponse::getResources) + .map(OrganizationQuotaResource::getId) + .last() + .flatMap( + orgId -> + cloudFoundryClient + .organizationQuotasV3() + .update( + UpdateOrganizationQuotaRequest.builder() + .organizationQuotaId(orgId) + .routes( + Routes.builder() + .totalReservedPorts( + portsQuota) + .build()) + .build())) + .block(); + this.logger.debug("APPLIED ORG QUOTA, reserved-route-ports={}", portsQuota); + return portsQuota; + } + private static final class FailingDeserializationProblemHandler extends DeserializationProblemHandler { From 5390ef5a532e0978bf7c9130aeff2633f861f9ab Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Thu, 7 May 2026 13:31:03 +0200 Subject: [PATCH 03/26] Update maven wrapper to 3.9.15 --- .mvn/wrapper/maven-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index d58dfb70bab..2dc39d11157 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -16,4 +16,4 @@ # under the License. wrapperVersion=3.3.2 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip From 48cad87dac8ed60ce355c58b345cf2f1f36bd7c4 Mon Sep 17 00:00:00 2001 From: agilis allievo Date: Mon, 18 May 2026 15:55:42 +0200 Subject: [PATCH 04/26] 5.x.x app features (#1351) * feat: adds schema support for app features --- .../ApplicationManifestUtilsCommon.java | 13 ++-- .../ApplicationManifestUtilsV3.java | 29 +++++-- .../applications/_ManifestV3Application.java | 15 +++- .../ApplicationManifestUtilsV3Test.java | 68 +++++++++++----- .../operations/ApplicationsTest.java | 77 ++++++++++++++++--- 5 files changed, 159 insertions(+), 43 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java index 79b50ab809d..3baca9f4152 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java @@ -16,7 +16,10 @@ package org.cloudfoundry.operations.applications; -import static java.util.Collections.emptyMap; +import org.cloudfoundry.util.tuple.Consumer2; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; import java.io.IOException; import java.io.InputStream; @@ -36,10 +39,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import org.cloudfoundry.util.tuple.Consumer2; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; + +import static java.util.Collections.emptyMap; /** * Common base class for dealing with manifests @@ -321,7 +322,7 @@ static Map getNamedObject(List array, String name) { value -> value instanceof Map && name.equals( - ((Map) value).get("name"))) + ((Map) value).get("name"))) .findFirst() .orElseGet(() -> getEmptyNamedObject(array, name)); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java index da335ae704d..b085234c0d1 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java @@ -16,8 +16,12 @@ package org.cloudfoundry.operations.applications; -import static java.util.Collections.emptyMap; -import static java.util.stream.Collectors.toMap; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; import java.io.IOException; import java.io.OutputStream; @@ -33,12 +37,9 @@ import java.util.TreeMap; import java.util.regex.Pattern; import java.util.stream.Stream; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; + +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toMap; /** * Utilities for dealing with {@link ManifestV3}s. Includes the functionality to transform to and from standard CLI YAML files. @@ -156,6 +157,13 @@ private static ManifestV3Application.Builder toApplicationManifest( Path root) { toApplicationManifestCommon(application, variables, builder, root); + asMap( + application, + "features", + variables, + String::valueOf, + (k,v) -> builder.feature(k, Boolean.valueOf(v)) + ); asList( application, "processes", @@ -299,6 +307,11 @@ private static Map toYaml(ManifestV3 manifest) { private static Map toApplicationYaml(ManifestV3Application application) { Map yaml = ApplicationManifestUtilsCommon.toApplicationYaml(application); + putIfPresent( + yaml, + "features", + application.getFeatures() + ); putIfPresent( yaml, "processes", diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/_ManifestV3Application.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/_ManifestV3Application.java index b235a1eb5de..fe0ac3412ea 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/_ManifestV3Application.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/_ManifestV3Application.java @@ -16,9 +16,10 @@ package org.cloudfoundry.operations.applications; +import org.cloudfoundry.AllowNulls; import org.cloudfoundry.Nullable; -import org.immutables.value.Value; import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; import java.util.List; import java.util.Map; @@ -36,6 +37,13 @@ abstract class _ManifestV3Application extends _ApplicationManifestCommon { @Nullable abstract Boolean getDefaultRoute(); + /** + * Manage whether optional capabilities are enabled + */ + @AllowNulls + @Nullable + abstract Map getFeatures(); + /** * The metadata for this application */ @@ -61,6 +69,9 @@ abstract class _ManifestV3Application extends _ApplicationManifestCommon { abstract List getSidecars(); public abstract static class Builder implements _ApplicationManifestCommon.Builder { - + abstract Builder feature(String key, Object value); + abstract Builder feature(Map.Entry entry); + abstract Builder features(@Nullable Map entries); + abstract Builder putAllFeatures(Map entries); } } diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java index cf54b8120ba..fac85f0377d 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java @@ -1,15 +1,61 @@ package org.cloudfoundry.operations.applications; -import static org.junit.jupiter.api.Assertions.*; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.junit.jupiter.api.Test; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; class ApplicationManifestUtilsV3Test { + @Test + void testWithDockerApp() throws IOException { + ManifestV3 manifest = + ManifestV3.builder() + .application( + ManifestV3Application.builder() + .name("test-app") + .docker(Docker.builder().image("test-image").build()) + .build()) + .build(); + + assertSerializeDeserialize(manifest); + } + + @Test + void testWithFeature() throws IOException { + ManifestV3 manifest = + ManifestV3.builder() + .application( + ManifestV3Application.builder() + .name("test-app") + .feature("file-based-vcap-services", true) + .build()) + .build(); + + assertSerializeDeserialize(manifest); + } + + @Test + void testWithFeatureAsMap() throws IOException { + Map features = new java.util.HashMap<>(); + features.put("file-based-vcap-services", true); + ManifestV3 manifest = + ManifestV3.builder() + .application( + ManifestV3Application.builder() + .name("test-app") + .features(features) + .build()) + .build(); + + assertSerializeDeserialize(manifest); + } + @Test void testGenericApplication() throws IOException { ManifestV3 manifest = @@ -47,20 +93,6 @@ void testGenericApplication() throws IOException { assertSerializeDeserialize(manifest); } - @Test - void testWithDockerApp() throws IOException { - ManifestV3 manifest = - ManifestV3.builder() - .application( - ManifestV3Application.builder() - .name("test-app") - .docker(Docker.builder().image("test-image").build()) - .build()) - .build(); - - assertSerializeDeserialize(manifest); - } - @Test void testWithMetadata() throws IOException { ManifestV3 manifest = diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 43f65276114..0d3427e6768 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -16,15 +16,6 @@ package org.cloudfoundry.operations; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; @@ -32,6 +23,8 @@ import org.cloudfoundry.RequiresTcpRouting; import org.cloudfoundry.RequiresV2Api; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v3.applications.ApplicationFeatureResource; +import org.cloudfoundry.client.v3.applications.ListApplicationFeaturesRequest; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationEnvironments; import org.cloudfoundry.operations.applications.ApplicationEvent; @@ -81,6 +74,7 @@ import org.cloudfoundry.operations.services.GetServiceInstanceRequest; import org.cloudfoundry.operations.services.ServiceInstance; import org.cloudfoundry.util.FluentMap; +import org.cloudfoundry.util.PaginationUtils; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -89,6 +83,16 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + @CleanupCloudFoundryAfterClass @RequiresV2Api public final class ApplicationsTest extends AbstractIntegrationTest { @@ -750,6 +754,61 @@ public void pushManifestV3() throws IOException { .verify(Duration.ofMinutes(5)); } + @Test + @IfCloudFoundryVersion( + greaterThanOrEqualTo = + CloudFoundryVersion.PCF_4_v3) + public void pushManifestV3WithFeature() throws IOException { + String applicationName = this.nameFactory.getApplicationName(); + + final String featureKey = "ssh"; + final boolean featureValue = false; + ManifestV3 manifest = + ManifestV3.builder() + .application( + ManifestV3Application.builder() + .buildpack("staticfile_buildpack") + .disk(512) + .healthCheckType(ApplicationHealthCheck.PORT) + .memory(64) + .name(applicationName) + .feature(featureKey, false) + .path( + new ClassPathResource("test-application.zip") + .getFile() + .toPath()) + .build()) + .build(); + + this.cloudFoundryOperations + .applications() + .pushManifestV3(PushManifestV3Request.builder().manifest(manifest).build()) + .then( + this.cloudFoundryOperations + .applications() + .get(GetApplicationRequest.builder().name(applicationName).build())) + + .map(ApplicationDetail::getId) + .flatMapMany( + applicationId -> + PaginationUtils.requestClientV3Resources( + page -> + this.cloudFoundryClient + .applicationsV3() + .listFeatures( + ListApplicationFeaturesRequest + .builder() + .applicationId(applicationId) + .page(page) + .build()))) + .filter(feature -> featureKey.equals(feature.getName())) + .map(ApplicationFeatureResource::getEnabled) + .as(StepVerifier::create) + .expectNext(featureValue) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + @Test @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v2) public void pushManifestV3WithMetadata() throws IOException { From 9426f1ed2a2c6d3770d5942b41dc0545345c86b5 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Mon, 18 May 2026 15:56:12 +0200 Subject: [PATCH 05/26] tests: enable ApplicationsTest#pushManifestV3WithFeature --- .../test/java/org/cloudfoundry/operations/ApplicationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 0d3427e6768..936542c24f7 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -757,7 +757,7 @@ public void pushManifestV3() throws IOException { @Test @IfCloudFoundryVersion( greaterThanOrEqualTo = - CloudFoundryVersion.PCF_4_v3) + CloudFoundryVersion.PCF_4_v2) public void pushManifestV3WithFeature() throws IOException { String applicationName = this.nameFactory.getApplicationName(); From ee2531eb9f77ccdcf35979b9b8b05df26d36c493 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 19 May 2026 10:49:43 +0200 Subject: [PATCH 06/26] Use v3 version when v2 unavailable (#1359) * make compatibilityChecker robust if v2 api is disabled Co-authored-by: Georg Lokowandt --- ...loudFoundryClientCompatibilityChecker.java | 45 +++- .../reactor/client/ReactorRoot.java | 53 +++++ .../client/_ReactorCloudFoundryClient.java | 10 +- .../client/CloudFoundryClient.java | 7 + .../java/org/cloudfoundry/client/Root.java | 33 +++ .../cloudfoundry/client/_GetRootRequest.java | 27 +++ .../cloudfoundry/client/_GetRootResponse.java | 218 ++++++++++++++++++ .../ApplicationManifestUtilsCommon.java | 13 +- .../ApplicationManifestUtilsV3.java | 26 +-- .../ApplicationManifestUtilsV3Test.java | 9 +- .../IntegrationTestConfiguration.java | 28 ++- .../cloudfoundry/client/RootEndpointTest.java | 28 +++ .../operations/ApplicationsTest.java | 27 +-- 13 files changed, 470 insertions(+), 54 deletions(-) create mode 100644 cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java index e2aa39459bd..d540e1f70cd 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java @@ -20,27 +20,30 @@ import com.github.zafarkhaja.semver.Version; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.GetRootRequest; +import org.cloudfoundry.client.Root; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.info.Info; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; final class CloudFoundryClientCompatibilityChecker { private final Logger logger = LoggerFactory.getLogger("cloudfoundry-client.compatibility"); private final Info info; + private final Root root; - CloudFoundryClientCompatibilityChecker(Info info) { + CloudFoundryClientCompatibilityChecker(Info info, Root root) { this.info = info; + this.root = root; } void check() { - this.info - .get(GetInfoRequest.builder().build()) - .map(response -> Version.valueOf(response.getApiVersion())) - .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION))) + Mono> v2 = checkV2(); + v2.switchIfEmpty(checkV3()) .subscribe( consumer( (server, supported) -> @@ -51,11 +54,41 @@ void check() { t)); } + private Mono> checkV2() { + return this.info + .get(GetInfoRequest.builder().build()) + .flatMap( + response -> { + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + if ("CF API v2 is disabled".equals(response.getSupport())) { + this.logger.warn( + "calling v2 info endpoint but CF API v2 is disabled", + response); + } + return Mono.empty(); + } else { + return Mono.just(Version.valueOf(version)); + } + }) + .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION))); + } + + private Mono> checkV3() { + return this.root + .get(GetRootRequest.builder().build()) + .map( + response -> { + String versionV3 = response.getApiVersionV3(); + return Version.valueOf(versionV3); + }) + .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION_V3))); + } + private static void logCompatibility(Version server, Version supported, Logger logger) { String message = "Client supports API version {} and is connected to server with API version {}." + " Things may not work as expected."; - if (server.greaterThan(supported)) { logger.info(message, supported, server); } else if (server.lessThan(supported)) { diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java new file mode 100644 index 00000000000..7cf690307a0 --- /dev/null +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2021 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.reactor.client; + +import java.util.Map; +import org.cloudfoundry.client.GetRootRequest; +import org.cloudfoundry.client.GetRootResponse; +import org.cloudfoundry.client.Root; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; +import reactor.core.publisher.Mono; + +/** + * The Reactor-based implementation of {@link Root} + */ +public class ReactorRoot extends AbstractClientV3Operations implements Root { + + /** + * Creates an instance + * + * @param connectionContext the {@link ConnectionContext} to use when communicating with the server + * @param root the root URI of the server. Typically something like {@code https://api.run.pivotal.io}. + * @param tokenProvider the {@link TokenProvider} to use when communicating with the server + * @param requestTags map with custom http headers which will be added to web request + */ + public ReactorRoot( + ConnectionContext connectionContext, + Mono root, + TokenProvider tokenProvider, + Map requestTags) { + super(connectionContext, root, tokenProvider, requestTags); + } + + @Override + public Mono get(GetRootRequest request) { + return get(request, GetRootResponse.class, builder -> builder.pathSegment("")).checkpoint(); + } +} diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java index d131ea8a490..d646300f283 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java @@ -18,6 +18,7 @@ import jakarta.annotation.PostConstruct; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.Root; import org.cloudfoundry.client.v2.applications.ApplicationsV2; import org.cloudfoundry.client.v2.applicationusageevents.ApplicationUsageEvents; import org.cloudfoundry.client.v2.blobstores.Blobstores; @@ -205,7 +206,7 @@ public Builds builds() { @PostConstruct public void checkCompatibility() { - new CloudFoundryClientCompatibilityChecker(info()).check(); + new CloudFoundryClientCompatibilityChecker(info(), rootEndpoint()).check(); } @Override @@ -257,6 +258,13 @@ public Info info() { return new ReactorInfo(getConnectionContext(), getRootV2(), getTokenProvider(), getRequestTags()); } + @Override + @Value.Derived + public Root rootEndpoint() { + Mono root = getConnectionContext().getRootProvider().getRoot(getConnectionContext()); + return new ReactorRoot(getConnectionContext(), root, getTokenProvider(), getRequestTags()); + } + @Override @Value.Derived public IsolationSegments isolationSegments() { diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index bfd4f1bd2ad..171dd5d284d 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -85,6 +85,8 @@ public interface CloudFoundryClient { */ String SUPPORTED_API_VERSION = "2.272.0"; + String SUPPORTED_API_VERSION_V3 = "3.216.0"; + /** * Main entry point to the Cloud Foundry Application Usage Events Client API */ @@ -170,6 +172,11 @@ public interface CloudFoundryClient { */ Info info(); + /** + * Main entry point to the Cloud Foundry root endpoint + */ + Root rootEndpoint(); + /** * Main entry point to the Cloud Foundry Isolation Segments API */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java new file mode 100644 index 00000000000..37c59a4d7e9 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2026 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.client; + +import reactor.core.publisher.Mono; + +/** + * Main entry point to the Cloud Foundry RootV3 Client API + */ +public interface Root { + + /** + * Makes the Get root request + * + * @param request the Get Root request + * @return the response from the Get Root request + */ + Mono get(GetRootRequest request); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java new file mode 100644 index 00000000000..a0aab3e5f14 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2021 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.client; + +import org.immutables.value.Value; + +/** + * The request payload for the Get root "/" operation + */ +@Value.Immutable +abstract class _GetRootRequest { + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java new file mode 100644 index 00000000000..f56a890a5cf --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java @@ -0,0 +1,218 @@ +/* + * Copyright 2013-2021 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.client; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; + +import org.cloudfoundry.Nullable; +import org.immutables.value.Value; + +/** + * The response payload for the get root operation. See V3 API Root + */ +@JsonDeserialize(using = org.cloudfoundry.client._GetRootResponse.RootDeserializer.class) +@Value.Immutable() +public interface _GetRootResponse { + + /** + * The root of the cloud controller Api version 3 + */ + @Value.Parameter + @Nullable + public abstract String getApiV3(); + + /** + * The version of the cloud controller Api version 3 + */ + @Value.Parameter + @Nullable + public abstract String getApiVersionV3(); + + /** + * The root of the cloud controller Api version 2 + */ + @Value.Parameter + @Nullable + public abstract String getApiV2(); + + /** + * The version of the cloud controller Api version 2 (if available) + */ + @Value.Parameter + @Nullable + public abstract String getApiVersion(); + + /** + * The network policy v0 endpoint + */ + @Value.Parameter + @Nullable + public abstract String getNetworkPolicyV0Endpoint(); + + /** + * The network policy v1 endpoint + */ + @Value.Parameter + @Nullable + public abstract String getNetworkPolicyV1Endpoint(); + + /** + * The login endpoint + */ + @Value.Parameter + @Nullable + public abstract String getLoginEndpoint(); + + /** + * The uaa endpoint + */ + @Value.Parameter + @Nullable + public abstract String getUaaEndpoint(); + + /** + * The credhub endpoint + */ + @Value.Parameter + @Nullable + public abstract String getCredhubEndpoint(); + + /** + * The routing endpoint + */ + @Value.Parameter + @Nullable + public abstract String getRoutingEndpoint(); + + /** + * The loggin encpoint + */ + @Value.Parameter + @Nullable + public abstract String getLoggingEndpoint(); + + /** + * The log cache url + */ + @Value.Parameter + @Nullable + public abstract String getLogCacheEndpoint(); + + /** + * The log stream url + */ + @Value.Parameter + @Nullable + public abstract String getLogStreamEndpoint(); + + /** + * The ssh endpoint for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshEndpoint(); + + /** + * The ssh host key fingerprint for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshHostKeyFingerprint(); + + /** + * The ssh oauth client for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshOauthClient(); + + /** + * The self url + */ + @Value.Parameter + @Nullable + public abstract String getSelf(); + + public class RootDeserializer extends StdDeserializer<_GetRootResponse>{ + private static final long serialVersionUID = 1L; + + protected RootDeserializer() { + super(GetRootResponse.class); + } + @Override + public _GetRootResponse deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JacksonException { + JsonNode productNode = jp.getCodec().readTree(jp); + String apiV3Endpoint = getEndpoint("cloud_controller_v3",productNode); + + JsonNode tmp = productNode.get("links").get("cloud_controller_v3"); + String apiVersionV3 = null; + if(tmp!=null) { + apiVersionV3 = tmp.get("meta").get("version").textValue(); + } + + String apiV2Endpoint = getEndpoint("cloud_controller_v2",productNode); + tmp = productNode.get("links").get("cloud_controller_v2"); + String apiVersion = null; + if(tmp!=null) { + apiVersion = tmp.get("meta").get("version").textValue(); + } + String networkPolicyV0Endpoint = getEndpoint("network_policy_v0",productNode); + String networkPolicyV1Endpoint = getEndpoint("network_policy_v1",productNode); + String loginEndpoint = getEndpoint("login",productNode); + String uaaEndpoint = getEndpoint("uaa",productNode); + String credhubEndpoint = getEndpoint("credhub",productNode); + String routingEndpoint = getEndpoint("routing",productNode); + String loggingEndpoint = getEndpoint("logging",productNode); + String logCacheEndpoint = getEndpoint("log_cache",productNode); + String logStreamEndpoint = getEndpoint("log_stream",productNode); + String appSshEndpoint = getEndpoint("app_ssh",productNode); + tmp = productNode.get("links").get("app_ssh"); + String appSshHostKeyFingerprint = null; + if(tmp!=null) { + appSshHostKeyFingerprint = tmp.get("meta").get("host_key_fingerprint").textValue(); + } + tmp = productNode.get("links").get("app_ssh"); + String appSshOauthClient = null; + if(tmp!=null) { + appSshOauthClient = tmp.get("meta").get("oauth_client").textValue(); + } + + String self = getEndpoint("self",productNode); + return GetRootResponse.of(apiV3Endpoint,apiVersionV3,apiV2Endpoint, + apiVersion,networkPolicyV0Endpoint, networkPolicyV1Endpoint, loginEndpoint,uaaEndpoint,credhubEndpoint, + routingEndpoint,loggingEndpoint, logCacheEndpoint, logStreamEndpoint,appSshEndpoint, appSshHostKeyFingerprint, appSshOauthClient,self); + } + + // null safe access to href-endpoints + private String getEndpoint(String name,JsonNode productNode) { + String result = null; + JsonNode tmp = productNode.get("links").get(name); + if(tmp!=null&& !tmp.isNull()) { + result = tmp.get("href").textValue(); + } + return result; + } + } +} diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java index 3baca9f4152..79b50ab809d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java @@ -16,10 +16,7 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.util.tuple.Consumer2; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; +import static java.util.Collections.emptyMap; import java.io.IOException; import java.io.InputStream; @@ -39,8 +36,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; - -import static java.util.Collections.emptyMap; +import org.cloudfoundry.util.tuple.Consumer2; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; /** * Common base class for dealing with manifests @@ -322,7 +321,7 @@ static Map getNamedObject(List array, String name) { value -> value instanceof Map && name.equals( - ((Map) value).get("name"))) + ((Map) value).get("name"))) .findFirst() .orElseGet(() -> getEmptyNamedObject(array, name)); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java index b085234c0d1..5b585b810cd 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java @@ -16,12 +16,8 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toMap; import java.io.IOException; import java.io.OutputStream; @@ -37,9 +33,12 @@ import java.util.TreeMap; import java.util.regex.Pattern; import java.util.stream.Stream; - -import static java.util.Collections.emptyMap; -import static java.util.stream.Collectors.toMap; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; /** * Utilities for dealing with {@link ManifestV3}s. Includes the functionality to transform to and from standard CLI YAML files. @@ -162,8 +161,7 @@ private static ManifestV3Application.Builder toApplicationManifest( "features", variables, String::valueOf, - (k,v) -> builder.feature(k, Boolean.valueOf(v)) - ); + (k, v) -> builder.feature(k, Boolean.valueOf(v))); asList( application, "processes", @@ -307,11 +305,7 @@ private static Map toYaml(ManifestV3 manifest) { private static Map toApplicationYaml(ManifestV3Application application) { Map yaml = ApplicationManifestUtilsCommon.toApplicationYaml(application); - putIfPresent( - yaml, - "features", - application.getFeatures() - ); + putIfPresent(yaml, "features", application.getFeatures()); putIfPresent( yaml, "processes", diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java index fac85f0377d..3e05995f572 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java @@ -1,15 +1,14 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.junit.jupiter.api.Test; class ApplicationManifestUtilsV3Test { @Test diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index bfb6d9bdf1f..6de4016e46c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.GetRootRequest; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.client.v3.Relationship; @@ -462,15 +463,34 @@ RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider t @Bean Version serverVersion(@Qualifier("admin") CloudFoundryClient cloudFoundryClient) { - return cloudFoundryClient - .info() - .get(GetInfoRequest.builder().build()) - .map(response -> Version.valueOf(response.getApiVersion())) + return serverVersionV2(cloudFoundryClient) + .switchIfEmpty(serverVersionV3(cloudFoundryClient)) .doOnSubscribe(s -> this.logger.debug(">> CLOUD FOUNDRY VERSION <<")) .doOnSuccess(r -> this.logger.debug("<< CLOUD FOUNDRY VERSION >>")) .block(); } + private static Mono serverVersionV2(CloudFoundryClient cloudFoundryClient) { + return cloudFoundryClient + .info() + .get(GetInfoRequest.builder().build()) + .flatMap( + response -> { + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + return Mono.empty(); + } + return Mono.just(Version.valueOf(version)); + }); + } + + private static Mono serverVersionV3(CloudFoundryClient cloudFoundryClient) { + return cloudFoundryClient + .rootEndpoint() + .get(GetRootRequest.builder().build()) + .map(response -> Version.valueOf(response.getApiVersionV3())); + } + @Lazy @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") diff --git a/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java b/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java new file mode 100644 index 00000000000..84b04794209 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.client; + +import com.github.zafarkhaja.semver.Version; +import java.time.Duration; +import org.cloudfoundry.AbstractIntegrationTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import reactor.test.StepVerifier; + +class RootEndpointTest extends AbstractIntegrationTest { + + @Qualifier("cloudFoundryClient") + @Autowired + CloudFoundryClient client; + + @Test + void rootVersion() { + client.rootEndpoint() + .get(GetRootRequest.builder().build()) + .map(GetRootResponse::getApiVersionV3) + .map(Version::parse) + .as(StepVerifier::create) + .consumeNextWith(v -> v.isHigherThan(Version.of(3))) + .expectComplete() + .verify(Duration.ofMinutes(1)); + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 936542c24f7..13b9fc1d3b6 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -16,6 +16,15 @@ package org.cloudfoundry.operations; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; @@ -83,16 +92,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - @CleanupCloudFoundryAfterClass @RequiresV2Api public final class ApplicationsTest extends AbstractIntegrationTest { @@ -755,9 +754,7 @@ public void pushManifestV3() throws IOException { } @Test - @IfCloudFoundryVersion( - greaterThanOrEqualTo = - CloudFoundryVersion.PCF_4_v2) + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v2) public void pushManifestV3WithFeature() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -787,7 +784,6 @@ public void pushManifestV3WithFeature() throws IOException { this.cloudFoundryOperations .applications() .get(GetApplicationRequest.builder().name(applicationName).build())) - .map(ApplicationDetail::getId) .flatMapMany( applicationId -> @@ -798,7 +794,8 @@ public void pushManifestV3WithFeature() throws IOException { .listFeatures( ListApplicationFeaturesRequest .builder() - .applicationId(applicationId) + .applicationId( + applicationId) .page(page) .build()))) .filter(feature -> featureKey.equals(feature.getName())) From 1b7cda3be997b12acf1ee7f89fd8f3ef5b0d7846 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 19 May 2026 14:18:26 +0200 Subject: [PATCH 07/26] Remove signature from release script --- ci/create-release.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/create-release.sh b/ci/create-release.sh index 3597667f4c9..e88780eb30e 100755 --- a/ci/create-release.sh +++ b/ci/create-release.sh @@ -8,7 +8,7 @@ SNAPSHOT=$2 ./mvnw versions:set -DnewVersion=$RELEASE -DgenerateBackupPoms=false git add . git commit --message "v$RELEASE Release" -git tag -s v$RELEASE -m "v$RELEASE" +git tag v$RELEASE -m "v$RELEASE" git reset --hard HEAD^1 ./mvnw versions:set -DnewVersion=$SNAPSHOT -DgenerateBackupPoms=false From 411b722dd881b8babbdd0aa67530e171c51696d5 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 19 May 2026 14:18:39 +0200 Subject: [PATCH 08/26] v5.18.0.BUILD-SNAPSHOT Development --- cloudfoundry-client-reactor/pom.xml | 2 +- cloudfoundry-client/pom.xml | 2 +- cloudfoundry-operations/pom.xml | 2 +- cloudfoundry-util/pom.xml | 2 +- integration-test/pom.xml | 2 +- pom.xml | 2 +- test-log-cache/pom.xml | 2 +- test-service-broker/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cloudfoundry-client-reactor/pom.xml b/cloudfoundry-client-reactor/pom.xml index ca355510008..c7159eda543 100644 --- a/cloudfoundry-client-reactor/pom.xml +++ b/cloudfoundry-client-reactor/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT cloudfoundry-client-reactor diff --git a/cloudfoundry-client/pom.xml b/cloudfoundry-client/pom.xml index 056181d1de2..f0b62b1b885 100644 --- a/cloudfoundry-client/pom.xml +++ b/cloudfoundry-client/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT cloudfoundry-client diff --git a/cloudfoundry-operations/pom.xml b/cloudfoundry-operations/pom.xml index 7ae21357bae..afed2ca440b 100644 --- a/cloudfoundry-operations/pom.xml +++ b/cloudfoundry-operations/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT cloudfoundry-operations diff --git a/cloudfoundry-util/pom.xml b/cloudfoundry-util/pom.xml index b4450fd0ed4..f65b35c6d93 100644 --- a/cloudfoundry-util/pom.xml +++ b/cloudfoundry-util/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT cloudfoundry-util diff --git a/integration-test/pom.xml b/integration-test/pom.xml index 6b04dd88d3a..d52a44a9bdc 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT integration-test diff --git a/pom.xml b/pom.xml index f8d9fd1d24e..0e154f8cf63 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ cloudfoundry-java-client Cloud Foundry Java Client Parent A Java language binding for interacting with a Cloud Foundry instance - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT pom https://github.com/cloudfoundry/cf-java-client diff --git a/test-log-cache/pom.xml b/test-log-cache/pom.xml index fc197c23af7..2e1fa1f4f20 100644 --- a/test-log-cache/pom.xml +++ b/test-log-cache/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT test-log-cache diff --git a/test-service-broker/pom.xml b/test-service-broker/pom.xml index 13174cd836d..bbbc50202c8 100644 --- a/test-service-broker/pom.xml +++ b/test-service-broker/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.17.0.BUILD-SNAPSHOT + 5.18.0.BUILD-SNAPSHOT test-service-broker From 1b41db69c6a0dde3e20b8ca971f1f31996713e5d Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Thu, 23 Jul 2026 17:48:40 +0200 Subject: [PATCH 09/26] update dependencies, still supporting java8 --- pom.xml | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/pom.xml b/pom.xml index 0e154f8cf63..d819f0b1c49 100644 --- a/pom.xml +++ b/pom.xml @@ -58,18 +58,18 @@ 1.8 UTF-8 - 2.7.17 + 2.7.18 - 1.27.1 - 3.13.0 + 1.28.0 + 3.20.0 1.3 - 2.10.1 - 2.16.1 + 2.12.2 + 2.19.0 0.10.2 - 0.12.6 - 5.10.1 + 0.12.7 + 5.14.4 5.8.0 - 2.2 + 2.4 3.7.1 3.0.2 @@ -133,7 +133,22 @@ value ${immutables.version} - + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + From aa5b772dbb5678cf169584983658037b9a4c6052 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Thu, 30 Jul 2026 11:36:19 +0200 Subject: [PATCH 10/26] fix #1370, update to latest CAPI version --- .../java/org/cloudfoundry/client/CloudFoundryClient.java | 4 ++-- .../java/org/cloudfoundry/client/v3/spaces/Space.java | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index 171dd5d284d..09d98c57bd9 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -83,9 +83,9 @@ public interface CloudFoundryClient { /** * The currently supported Cloud Controller API version */ - String SUPPORTED_API_VERSION = "2.272.0"; + String SUPPORTED_API_VERSION = "2.290.0"; - String SUPPORTED_API_VERSION_V3 = "3.216.0"; + String SUPPORTED_API_VERSION_V3 = "3.225.0"; /** * Main entry point to the Cloud Foundry Application Usage Events Client API diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/spaces/Space.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/spaces/Space.java index 0ea0fc9d09e..7d69999a2d3 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/spaces/Space.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/spaces/Space.java @@ -45,4 +45,12 @@ public abstract class Space extends Resource { @JsonProperty("relationships") @Nullable public abstract SpaceRelationships getRelationships(); + + /** + * True if the space is suspended and no changes are allowed. + * See: https://v3-apidocs.cloudfoundry.org/index.html#spaces + */ + @JsonProperty("suspended") + @Nullable + public abstract Boolean getSuspended(); } From 3703e93adb64099f72a5f6aaef6a69e9c9f131f3 Mon Sep 17 00:00:00 2001 From: Saifuddin Merchant <5005943+SaifuddinMerchant@users.noreply.github.com> Date: Mon, 5 Jan 2026 08:20:40 -0700 Subject: [PATCH 11/26] Backport #1333 Backport of 59d0e909cd768c5ba14881de279faedf55f22542 Signed-off-by: Daniel Garnier-Moiroux --- .../client/_ReactorCloudFoundryClient.java | 8 + .../client/v3/users/ReactorUsersV3.java | 85 +++++++ .../client/v3/users/ReactorUsersV3Test.java | 215 ++++++++++++++++++ .../client/v3/users/GET_response.json | 33 +++ .../client/v3/users/GET_{id}_response.json | 17 ++ .../client/v3/users/PATCH_{id}_request.json | 10 + .../client/v3/users/PATCH_{id}_response.json | 21 ++ .../client/v3/users/POST_request.json | 3 + .../client/v3/users/POST_response.json | 17 ++ .../client/CloudFoundryClient.java | 6 + .../cloudfoundry/client/v3/users/User.java | 56 +++++ .../cloudfoundry/client/v3/users/UsersV3.java | 65 ++++++ .../client/v3/users/_CreateUserRequest.java | 58 +++++ .../client/v3/users/_CreateUserResponse.java | 27 +++ .../client/v3/users/_DeleteUserRequest.java | 35 +++ .../client/v3/users/_GetUserRequest.java | 33 +++ .../client/v3/users/_GetUserResponse.java | 28 +++ .../client/v3/users/_ListUsersRequest.java | 49 ++++ .../client/v3/users/_ListUsersResponse.java | 16 ++ .../client/v3/users/_UpdateUserRequest.java | 46 ++++ .../client/v3/users/_UpdateUserResponse.java | 28 +++ .../client/v3/users/_UserResource.java | 28 +++ .../v3/users/CreateUserRequestTest.java | 27 +++ .../v3/users/DeleteUserRequestTest.java | 34 +++ .../client/v3/users/GetUserRequestTest.java | 34 +++ .../client/v3/users/ListUsersRequestTest.java | 27 +++ .../v3/users/UpdateUserRequestTest.java | 34 +++ .../org/cloudfoundry/CloudFoundryCleaner.java | 34 ++- .../org/cloudfoundry/client/v3/UsersTest.java | 155 +++++++++++++ 29 files changed, 1210 insertions(+), 19 deletions(-) create mode 100644 cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java create mode 100644 cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3Test.java create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/ListUsersRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/UpdateUserRequestTest.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java index d646300f283..7a26cd13d2c 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java @@ -77,6 +77,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; +import org.cloudfoundry.client.v3.users.UsersV3; import org.cloudfoundry.reactor.ConnectionContext; import org.cloudfoundry.reactor.TokenProvider; import org.cloudfoundry.reactor.client.v2.applications.ReactorApplicationsV2; @@ -137,6 +138,7 @@ import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3; import org.cloudfoundry.reactor.client.v3.stacks.ReactorStacksV3; import org.cloudfoundry.reactor.client.v3.tasks.ReactorTasks; +import org.cloudfoundry.reactor.client.v3.users.ReactorUsersV3; import org.immutables.value.Value; import reactor.core.publisher.Mono; @@ -517,6 +519,12 @@ public Users users() { return new ReactorUsers(getConnectionContext(), getRootV2(), getTokenProvider(), getRequestTags()); } + @Override + @Value.Derived + public UsersV3 usersV3() { + return new ReactorUsersV3(getConnectionContext(), getRootV3(), getTokenProvider(), getRequestTags()); + } + /** * The connection context */ diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java new file mode 100644 index 00000000000..69d48d9baee --- /dev/null +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2025 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.reactor.client.v3.users; + +import java.util.Map; +import org.cloudfoundry.client.v3.users.*; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; +import reactor.core.publisher.Mono; + +/** + * The Reactor-based implementation of {@link UsersV3} + */ +public class ReactorUsersV3 extends AbstractClientV3Operations implements UsersV3 { + + /** + * Creates an instance + * + * @param connectionContext the {@link ConnectionContext} to use when communicating with the server + * @param root the root URI of the server. Typically, something like {@code https://api.cloudfoundry.your.company.com}. + * @param tokenProvider the {@link TokenProvider} to use when communicating with the server + * @param requestTags map with custom http headers which will be added to web request + */ + public ReactorUsersV3( + ConnectionContext connectionContext, + Mono root, + TokenProvider tokenProvider, + Map requestTags) { + super(connectionContext, root, tokenProvider, requestTags); + } + + @Override + public Mono create(CreateUserRequest request) { + return post(request, CreateUserResponse.class, builder -> builder.pathSegment("users")) + .checkpoint(); + } + + @Override + public Mono get(GetUserRequest request) { + return get( + request, + GetUserResponse.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } + + @Override + public Mono list(ListUsersRequest request) { + return get(request, ListUsersResponse.class, builder -> builder.pathSegment("users")) + .checkpoint(); + } + + @Override + public Mono update(UpdateUserRequest request) { + return patch( + request, + UpdateUserResponse.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } + + @Override + public Mono delete(DeleteUserRequest request) { + return delete( + request, + Void.class, + builder -> builder.pathSegment("users", request.getUserId())) + .checkpoint(); + } +} diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3Test.java new file mode 100644 index 00000000000..b9d78e9a69e --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/users/ReactorUsersV3Test.java @@ -0,0 +1,215 @@ +package org.cloudfoundry.reactor.client.v3.users; + +import static io.netty.handler.codec.http.HttpMethod.*; +import static io.netty.handler.codec.http.HttpResponseStatus.*; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import org.cloudfoundry.client.v3.Link; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.Pagination; +import org.cloudfoundry.client.v3.users.*; +import org.cloudfoundry.reactor.InteractionContext; +import org.cloudfoundry.reactor.TestRequest; +import org.cloudfoundry.reactor.TestResponse; +import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +final class ReactorUsersV3Test extends AbstractClientApiTest { + private final ReactorUsersV3 users = + new ReactorUsersV3( + CONNECTION_CONTEXT, this.root, TOKEN_PROVIDER, Collections.emptyMap()); + + @Test + void create() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(POST) + .path("/users") + .payload("fixtures/client/v3/users/POST_request.json") + .build()) + .response( + TestResponse.builder() + .status(CREATED) + .payload("fixtures/client/v3/users/POST_response.json") + .build()) + .build()); + + this.users + .create( + CreateUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .as(StepVerifier::create) + .expectNext( + CreateUserResponse.builder() + .from( + expectedUserResource( + Collections.emptyMap(), Collections.emptyMap())) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void get() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(GET) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload("fixtures/client/v3/users/GET_{id}_response.json") + .build()) + .build()); + + this.users + .get( + GetUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .as(StepVerifier::create) + .expectNext( + GetUserResponse.builder() + .from( + expectedUserResource( + Collections.emptyMap(), Collections.emptyMap())) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void list() { + mockRequest( + InteractionContext.builder() + .request(TestRequest.builder().method(GET).path("/users").build()) + .response( + TestResponse.builder() + .status(OK) + .payload("fixtures/client/v3/users/GET_response.json") + .build()) + .build()); + + Link first = + Link.builder().href("https://api.example.org/v3/users?page=1&per_page=1").build(); + Link last = + Link.builder().href("https://api.example.org/v3/users?page=1&per_page=1").build(); + this.users + .list(ListUsersRequest.builder().build()) + .as(StepVerifier::create) + .expectNext( + ListUsersResponse.builder() + .pagination( + Pagination.builder() + .first(first) + .last(last) + .totalResults(1) + .totalPages(1) + .build()) + .resource( + expectedUserResource( + Collections.emptyMap(), Collections.emptyMap())) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void update() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(PATCH) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .payload("fixtures/client/v3/users/PATCH_{id}_request.json") + .build()) + .response( + TestResponse.builder() + .status(CREATED) + .payload( + "fixtures/client/v3/users/PATCH_{id}_response.json") + .build()) + .build()); + + this.users + .update( + UpdateUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .metadata( + Metadata.builder() + .putAllAnnotations( + Collections.singletonMap( + "note", "detailed information")) + .putAllLabels( + Collections.singletonMap( + "environment", "production")) + .build()) + .build()) + .as(StepVerifier::create) + .expectNext( + UpdateUserResponse.builder() + .from( + expectedUserResource( + Collections.singletonMap( + "note", "detailed information"), + Collections.singletonMap( + "environment", "production"))) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void delete() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(DELETE) + .path("/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .response(TestResponse.builder().status(ACCEPTED).build()) + .build()); + + this.users + .delete( + DeleteUserRequest.builder() + .userId("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .as(StepVerifier::create) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + UserResource expectedUserResource(Map labels, Map annotations) { + return UserResource.builder() + .id("3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .createdAt("2019-03-08T01:06:19Z") + .updatedAt("2019-03-08T01:06:19Z") + .username("some-name") + .presentationName("some-name") + .origin("uaa") + .metadata( + Metadata.builder() + .putAllAnnotations(labels) + .putAllLabels(annotations) + .build()) + .link( + "self", + Link.builder() + .href( + "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5") + .build()) + .build(); + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_response.json new file mode 100644 index 00000000000..c727d7dbe1b --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_response.json @@ -0,0 +1,33 @@ +{ + "pagination": { + "total_results": 1, + "total_pages": 1, + "first": { + "href": "https://api.example.org/v3/users?page=1&per_page=1" + }, + "last": { + "href": "https://api.example.org/v3/users?page=1&per_page=1" + }, + "next": null, + "previous": null + }, + "resources": [ + { + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } + } + ] +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json new file mode 100644 index 00000000000..b60ce5ab333 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/GET_{id}_response.json @@ -0,0 +1,17 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json new file mode 100644 index 00000000000..52c4a1b6106 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_request.json @@ -0,0 +1,10 @@ +{ + "metadata": { + "labels": { + "environment": "production" + }, + "annotations": { + "note": "detailed information" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json new file mode 100644 index 00000000000..f1729ecb51e --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/PATCH_{id}_response.json @@ -0,0 +1,21 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": { + "environment": "production" + }, + "annotations": { + "note": "detailed information" + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json new file mode 100644 index 00000000000..e5463b4edbf --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_request.json @@ -0,0 +1,3 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5" +} \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json new file mode 100644 index 00000000000..b60ce5ab333 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/users/POST_response.json @@ -0,0 +1,17 @@ +{ + "guid": "3a5d3d89-3f89-4f05-8188-8a2b298c79d5", + "created_at": "2019-03-08T01:06:19Z", + "updated_at": "2019-03-08T01:06:19Z", + "username": "some-name", + "presentation_name": "some-name", + "origin": "uaa", + "metadata": { + "labels": {}, + "annotations": {} + }, + "links": { + "self": { + "href": "https://api.example.org/v3/users/3a5d3d89-3f89-4f05-8188-8a2b298c79d5" + } + } +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index 09d98c57bd9..ae1d7fe4666 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -74,6 +74,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; +import org.cloudfoundry.client.v3.users.UsersV3; /** * Main entry point to the Cloud Foundry Client API @@ -382,4 +383,9 @@ public interface CloudFoundryClient { * Main entry point to the Cloud Foundry Users Client API */ Users users(); + + /** + * Main entry point to the Cloud Foundry Users V3 Client API + */ + UsersV3 usersV3(); } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java new file mode 100644 index 00000000000..cf6ffd4a895 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/User.java @@ -0,0 +1,56 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.Resource; + +/** + * Base class for responses that are users + */ +public abstract class User extends Resource { + + /** + * The name registered in UAA; will be null for UAA clients and non-UAA users + */ + @JsonProperty("username") + @Nullable + public abstract String getUsername(); + + /** + * The name displayed for the user; for UAA users, this is the same as the username. For UAA clients, this is the UAA client ID + */ + @JsonProperty("presentation_name") + @Nullable + public abstract String getPresentationName(); + + /** + * The identity provider for the UAA user; will be null for UAA clients + */ + @JsonProperty("origin") + @Nullable + public abstract String getOrigin(); + + /** + * The metadata Labels and Annotations applied to the user + */ + @JsonProperty("metadata") + @Nullable + public abstract Metadata getMetadata(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java new file mode 100644 index 00000000000..906582ead33 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/UsersV3.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import reactor.core.publisher.Mono; + +/** + * Main entry point to the Cloud Foundry Users V3 Client API + */ +public interface UsersV3 { + + /** + * Makes the Create a User request + * + * @param request the Create User request + * @return the response from the Create User request + */ + Mono create(CreateUserRequest request); + + /** + * Makes the Get a User request + * + * @param request the Get User request + * @return the response from the Get User request + */ + Mono get(GetUserRequest request); + + /** + * Makes the List all Users request + * + * @param request the List Users request + * @return the response from the List Users request + */ + Mono list(ListUsersRequest request); + + /** + * Makes the Update a Stack request + * + * @param request the Update User request + * @return the response from the Update User request + */ + Mono update(UpdateUserRequest request); + + /** + * Makes the Delete a User request + * + * @param request the Delete User request + * @return void + */ + Mono delete(DeleteUserRequest request); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java new file mode 100644 index 00000000000..71f6c4823fb --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserRequest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +/** + * The request payload for the Create User operation + */ +@JsonSerialize +@Value.Immutable +abstract class _CreateUserRequest { + + /** + * Unique identifier for the user. For UAA users this will match the UAA user ID; in the case of UAA clients, this will match the UAA client ID + */ + @JsonProperty("guid") + @Nullable + abstract String getUserId(); + + /** + * Username of the user to be created. This can only be provided together with origin. + */ + @JsonProperty("username") + @Nullable + abstract String getUsername(); + + /** + * Origin of the user to be created. This can only be provided together with username and cannot be uaa. + */ + @JsonProperty("origin") + @Nullable + abstract String getOrigin(); + + /** + * The metadata Labels and Annotations applied to the user + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java new file mode 100644 index 00000000000..b1bdd0715d1 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_CreateUserResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Create User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _CreateUserResponse extends User { +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java new file mode 100644 index 00000000000..5010b8193ad --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_DeleteUserRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Delete User operation + * All roles associated with a user will be deleted if the user is deleted. + */ +@Value.Immutable +abstract class _DeleteUserRequest { + + /** + * The User id / guid + */ + @JsonIgnore + abstract String getUserId(); + +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java new file mode 100644 index 00000000000..1f2d0705863 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserRequest.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Get User operation + */ +@Value.Immutable +abstract class _GetUserRequest { + + /** + * The User id / guid + */ + @JsonIgnore + abstract String getUserId(); +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java new file mode 100644 index 00000000000..76adda2fc02 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_GetUserResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Get User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _GetUserResponse extends User { +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersRequest.java new file mode 100644 index 00000000000..c8606ab59cf --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersRequest.java @@ -0,0 +1,49 @@ + + +package org.cloudfoundry.client.v3.users; + +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.FilterParameter; +import org.cloudfoundry.client.v3.PaginatedRequest; +import org.immutables.value.Value; + +import java.util.List; + +/** + * The request payload for the List Users operation. + */ +@Value.Immutable +abstract class _ListUsersRequest extends PaginatedRequest { + + /** + * Comma-delimited list of user guids to filter by + */ + @FilterParameter("guids") + @Nullable + abstract List getGuids(); + + /** + * Comma-delimited list of usernames to filter by. Mutually exclusive with partial_usernames + */ + @FilterParameter("usernames") + abstract List getUsernames(); + + /** + * Comma-delimited list of strings to search by. When using this query parameter, all the users that contain the string provided in their username will be returned. Mutually exclusive with usernames + */ + @FilterParameter("partial_usernames") + abstract List getPartialUsernames(); + + /** + * Comma-delimited list of user origins (user stores) to filter by, for example, users authenticated by UAA have the origin “uaa”; users authenticated by an LDAP provider have the origin “ldap”; when filtering by origins, usernames must be included + */ + @FilterParameter("origins") + abstract List getOrigins(); + + /** + * A query string containing a list of label selector requirements + */ + @FilterParameter("label_selector") + @Nullable + abstract String getLabelSelector(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersResponse.java new file mode 100644 index 00000000000..1cf5a2e87e4 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_ListUsersResponse.java @@ -0,0 +1,16 @@ +package org.cloudfoundry.client.v3.users; + + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.cloudfoundry.client.v3.PaginatedResponse; +import org.cloudfoundry.client.v3.users.UserResource; +import org.immutables.value.Value; + +/** + * The response payload for the List Users operation. + */ +@JsonDeserialize +@Value.Immutable +abstract class _ListUsersResponse extends PaginatedResponse { + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java new file mode 100644 index 00000000000..6258711533d --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserRequest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +/** + * The request payload for the Update User operation + */ +@JsonSerialize +@Value.Immutable +abstract class _UpdateUserRequest { + + /** + * The User id / guid + */ + @JsonIgnore + abstract String getUserId(); + + /** + * The metadata Labels and Annotations applied to the user + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); + +} \ No newline at end of file diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java new file mode 100644 index 00000000000..b264d5bcb1d --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UpdateUserResponse.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Update User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _UpdateUserResponse extends User { +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java new file mode 100644 index 00000000000..65c08410c9e --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/users/_UserResource.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The Resource response payload for the List User operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _UserResource extends User{ +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java new file mode 100644 index 00000000000..66ad42259b5 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/CreateUserRequestTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import org.junit.jupiter.api.Test; + +class CreateUserRequestTest { + + @Test + void valid() { + CreateUserRequest.builder().build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java new file mode 100644 index 00000000000..6d65354fb32 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/DeleteUserRequestTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class DeleteUserRequestTest { + + @Test + void noUserId() { + assertThrows(IllegalStateException.class, () -> DeleteUserRequest.builder().build()); + } + + @Test + void valid() { + DeleteUserRequest.builder().userId("test-stack-id").build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java new file mode 100644 index 00000000000..83bb8541b99 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/GetUserRequestTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +class GetUserRequestTest { + + @Test + void noUserId() { + assertThrows(IllegalStateException.class, () -> GetUserRequest.builder().build()); + } + + @Test + void valid() { + GetUserRequest.builder().userId("test-user-id").build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/ListUsersRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/ListUsersRequestTest.java new file mode 100644 index 00000000000..bf30fb14ff2 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/ListUsersRequestTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import org.junit.jupiter.api.Test; + +public class ListUsersRequestTest { + + @Test + void valid() { + ListUsersRequest.builder().build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/UpdateUserRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/UpdateUserRequestTest.java new file mode 100644 index 00000000000..8a1c131cda3 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/users/UpdateUserRequestTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 2013-2025 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.client.v3.users; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +final class UpdateUserRequestTest { + + @Test + void noOrganizationQuotaId() { + assertThrows(IllegalStateException.class, () -> UpdateUserRequest.builder().build()); + } + + @Test + void valid() { + UpdateUserRequest.builder().userId("test-id").build(); + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java index de94246f6b1..54455f7f4df 100644 --- a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java +++ b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java @@ -72,7 +72,6 @@ import org.cloudfoundry.client.v2.userprovidedserviceinstances.ListUserProvidedServiceInstancesRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.RemoveUserProvidedServiceInstanceRouteRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.UserProvidedServiceInstanceResource; -import org.cloudfoundry.client.v2.users.UserResource; import org.cloudfoundry.client.v3.Metadata; import org.cloudfoundry.client.v3.Relationship; import org.cloudfoundry.client.v3.applications.Application; @@ -87,6 +86,7 @@ import org.cloudfoundry.client.v3.spaces.GetSpaceResponse; import org.cloudfoundry.client.v3.spaces.UpdateSpaceRequest; import org.cloudfoundry.client.v3.spaces.UpdateSpaceResponse; +import org.cloudfoundry.client.v3.users.UserResource; import org.cloudfoundry.networking.NetworkingClient; import org.cloudfoundry.networking.v1.policies.DeletePoliciesRequest; import org.cloudfoundry.networking.v1.policies.Destination; @@ -238,7 +238,7 @@ void clean() { cleanSpaceQuotaDefinitions( this.cloudFoundryClient, this.nameFactory), cleanStacks(this.cloudFoundryClient, this.nameFactory), - cleanUsers(this.cloudFoundryClient, this.nameFactory))) + cleanUsersV3(this.cloudFoundryClient, this.nameFactory))) .thenMany( Mono.when( cleanApplicationsV3(this.cloudFoundryClient, this.nameFactory), @@ -1065,35 +1065,29 @@ private static Flux cleanUserProvidedServiceInstances( t))); } - private static Flux cleanUsers( + private static Flux cleanUsersV3( CloudFoundryClient cloudFoundryClient, NameFactory nameFactory) { - return PaginationUtils.requestClientV2Resources( + return PaginationUtils.requestClientV3Resources( page -> cloudFoundryClient - .users() + .usersV3() .list( - org.cloudfoundry.client.v2.users.ListUsersRequest + org.cloudfoundry.client.v3.users.ListUsersRequest .builder() .page(page) .build())) .filter(resource -> isCleanable(nameFactory, resource)) - .map(resource -> resource.getMetadata().getId()) + .map(UserResource::getId) .flatMap( userId -> cloudFoundryClient - .users() + .usersV3() .delete( - org.cloudfoundry.client.v2.users.DeleteUserRequest + org.cloudfoundry.client.v3.users.DeleteUserRequest .builder() - .async(true) .userId(userId) .build()) - .flatMapMany( - job -> - JobUtils.waitForCompletion( - cloudFoundryClient, - Duration.ofMinutes(5), - job)) + .then() .doOnError( t -> LOGGER.error( @@ -1172,12 +1166,14 @@ private static Flux ifCfVersion( CloudFoundryVersion expectedVersion, Version serverVersion, Supplier> supplier) { - return serverVersion.lessThan(expectedVersion.getVersion()) ? Flux.empty() : supplier.get(); + return serverVersion.isLowerThan(expectedVersion.getVersion()) + ? Flux.empty() + : supplier.get(); } private static boolean isCleanable(NameFactory nameFactory, UserResource resource) { - return nameFactory.isUserId(ResourceUtils.getId(resource)) - || nameFactory.isUserName(ResourceUtils.getEntity(resource).getUsername()); + return nameFactory.isUserId(resource.getId()) + || nameFactory.isUserName(resource.getUsername()); } private static Flux removeApplicationServiceBindings( diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java new file mode 100644 index 00000000000..e3dbab91cb0 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/UsersTest.java @@ -0,0 +1,155 @@ +/* + * Copyright 2013-2025 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.client.v3; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CloudFoundryVersion; +import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v3.users.*; +import org.cloudfoundry.util.PaginationUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v3) +public final class UsersTest extends AbstractIntegrationTest { + + @Autowired private CloudFoundryClient cloudFoundryClient; + + @Test + public void create() { + String userId = this.nameFactory.getUserId(); + + this.cloudFoundryClient + .usersV3() + .create(CreateUserRequest.builder().userId(userId).build()) + .single() + .as(StepVerifier::create) + .expectNextCount(1) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void get() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap( + createUserResponse -> + this.cloudFoundryClient + .usersV3() + .get(GetUserRequest.builder().userId(userId).build())) + .map(GetUserResponse::getId) + .as(StepVerifier::create) + .expectNext(userId) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void list() { + String userId = this.nameFactory.getUserId(); + createUser(this.cloudFoundryClient, userId) + .thenMany( + PaginationUtils.requestClientV3Resources( + page -> + this.cloudFoundryClient + .usersV3() + .list( + ListUsersRequest.builder() + .page(page) + .build()))) + .filter(resource -> userId.equals(resource.getId())) + .map(UserResource::getId) + .as(StepVerifier::create) + .expectNext(userId) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void update() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap( + createUserResponse -> + this.cloudFoundryClient + .usersV3() + .update( + UpdateUserRequest.builder() + .userId(userId) + .metadata( + Metadata.builder() + .annotation( + "annotationKey", + "annotationValue") + .label( + "labelKey", + "labelValue") + .build()) + .build())) + .then(getUser(cloudFoundryClient, userId)) + .as(StepVerifier::create) + .consumeNextWith( + GetUserResponse -> { + Metadata metadata = GetUserResponse.getMetadata(); + assertThat(metadata.getAnnotations().get("annotationKey")) + .isEqualTo("annotationValue"); + assertThat(metadata.getLabels().get("labelKey")) + .isEqualTo("labelValue"); + }) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void delete() { + String userId = this.nameFactory.getUserId(); + + createUser(this.cloudFoundryClient, userId) + .flatMap( + createUserResponse -> + this.cloudFoundryClient + .usersV3() + .delete( + DeleteUserRequest.builder() + .userId(createUserResponse.getId()) + .build())) + .as(StepVerifier::create) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + private static Mono createUser( + CloudFoundryClient cloudFoundryClient, String userId) { + return cloudFoundryClient + .usersV3() + .create(CreateUserRequest.builder().userId(userId).build()); + } + + private static Mono getUser( + CloudFoundryClient cloudFoundryClient, String userId) { + return cloudFoundryClient.usersV3().get(GetUserRequest.builder().userId(userId).build()); + } +} From 3d0bfee03644c22876b884d7e85efc348fbea73a Mon Sep 17 00:00:00 2001 From: joda Date: Tue, 22 Sep 2026 16:02:39 +0200 Subject: [PATCH 12/26] Add attribute allowpublic to uaa Client resource (#1366) Fixes #1357 --- .../uaa/clients/ReactorClientsTest.java | 11 ++++++++ .../fixtures/uaa/clients/POST_request.json | 3 ++- .../fixtures/uaa/clients/POST_response.json | 1 + .../uaa/clients/POST_tx_delete_response.json | 6 +++-- .../uaa/clients/POST_tx_modify_request.json | 3 ++- .../uaa/clients/POST_tx_modify_response.json | 4 ++- .../uaa/clients/POST_tx_secret_response.json | 6 +++-- .../uaa/clients/PUT_{id}_request.json | 3 ++- .../uaa/clients/PUT_{id}_response.json | 1 + .../uaa/clients/AbstractClient.java | 7 ++++++ .../uaa/clients/AbstractCreateClient.java | 7 ++++++ .../uaa/clients/AbstractUpdateClient.java | 7 ++++++ .../org/cloudfoundry/uaa/ClientsTest.java | 25 ++++++++++++++----- 13 files changed, 70 insertions(+), 14 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java index a9d730008a5..ce8d31b5a2b 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java @@ -133,6 +133,7 @@ void batchChangeSecret() { .resourceId("none") .scopes("clients.read", "clients.write") .tokenSalt("uHICvG") + .allowPublic(true) .build()) .client( Client.builder() @@ -155,6 +156,7 @@ void batchChangeSecret() { .resourceId("none") .scopes("clients.read", "clients.write") .tokenSalt("WjlWvu") + .allowPublic(true) .build()) .build()) .expectComplete() @@ -279,6 +281,7 @@ void batchDelete() { .client( Client.builder() .approvalsDeleted(true) + .allowPublic(true) .allowedProviders("uaa", "ldap", "my-saml-provider") .authorities("clients.read", "clients.write") .authorizedGrantType(CLIENT_CREDENTIALS) @@ -296,6 +299,7 @@ void batchDelete() { .client( Client.builder() .approvalsDeleted(true) + .allowPublic(true) .allowedProviders("uaa", "ldap", "my-saml-provider") .authorities("clients.read", "clients.write") .authorizedGrantType(CLIENT_CREDENTIALS) @@ -469,6 +473,7 @@ void create() { .authorities("clients.read", "clients.write") .authorizedGrantType(CLIENT_CREDENTIALS) .autoApprove("true") + .allowPublic(true) .clientId("aPq3I1") .clientSecret("secret") .name("My Client Name") @@ -494,6 +499,7 @@ void create() { .resourceId("none") .scopes("clients.read", "clients.write") .tokenSalt("hRZ21X") + .allowPublic(true) .build()) .expectComplete() .verify(Duration.ofSeconds(5)); @@ -819,6 +825,7 @@ void mixedActions() { .refreshTokenValidity(7000L) .scopes("clients.read", "clients.write") .tokenSalt("UpzrHR") + .allowPublic(true) .build()) .build()) .as(StepVerifier::create) @@ -852,6 +859,7 @@ void mixedActions() { .resourceId("none") .scopes("clients.read", "clients.write") .tokenSalt("WjlWvu") + .allowPublic(true) .build()) .client( ActionClient.builder() @@ -871,6 +879,7 @@ void mixedActions() { .resourceId("none") .scopes("clients.read", "clients.write") .tokenSalt("UpzrHR") + .allowPublic(true) .build()) .build()) .expectComplete() @@ -901,6 +910,7 @@ void update() { .autoApprove("clients.autoapprove") .clientId("55pTMX") .scopes("clients.new", "clients.autoapprove") + .allowPublic(true) .build()) .as(StepVerifier::create) .expectNext( @@ -918,6 +928,7 @@ void update() { .resourceId("none") .scopes("clients.new", "clients.autoapprove") .tokenSalt("8mwCEy") + .allowPublic(true) .build()) .expectComplete() .verify(Duration.ofSeconds(5)); diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json index cd24223fe70..ff9cb074b90 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json @@ -25,5 +25,6 @@ "clients.read", "clients.write" ], - "token_salt": "hRZ21X" + "token_salt": "hRZ21X", + "allowpublic": true } \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_response.json index bc91157c3bb..d61bedfc921 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_response.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_response.json @@ -28,5 +28,6 @@ "my-saml-provider" ], "name": "My Client Name", + "allowpublic": true, "lastModified": 1468364445109 } \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_delete_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_delete_response.json index 4b113ed45a5..84fa404dd14 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_delete_response.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_delete_response.json @@ -30,7 +30,8 @@ ], "name": "My Client Name", "lastModified": 1468364444461, - "approvals_deleted": true + "approvals_deleted": true, + "allowpublic": true }, { "scope": [ @@ -63,6 +64,7 @@ ], "name": "My Client Name", "lastModified": 1468364444868, - "approvals_deleted": true + "approvals_deleted": true, + "allowpublic": true } ] \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_request.json index c25d438d831..976b638b7f0 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_request.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_request.json @@ -39,6 +39,7 @@ "redirect_uri": [ "http://test1.com", "http*://ant.path.wildcard/**/passback/*" - ] + ], + "allowpublic": true } ] diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_response.json index a0bed0ad731..a3fe946d720 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_response.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_modify_response.json @@ -43,7 +43,8 @@ ], "name": "My Client Name", "lastModified": 1474923482302, - "approvals_deleted": true + "approvals_deleted": true, + "allowpublic": true }, { "scope": [ @@ -78,6 +79,7 @@ "my-saml-provider" ], "name": "My Client Name", + "allowpublic": true, "lastModified": 1474923482727 } ] \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_secret_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_secret_response.json index dcfd19a32a3..66cdbf957a1 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_secret_response.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_tx_secret_response.json @@ -32,7 +32,8 @@ ], "name": "My Client Name", "lastModified": 1474923482301, - "approvals_deleted": true + "approvals_deleted": true, + "allowpublic": true }, { "scope": [ @@ -68,6 +69,7 @@ ], "name": "My Client Name", "lastModified": 1474923482302, - "approvals_deleted": true + "approvals_deleted": true, + "allowpublic": true } ] \ No newline at end of file diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_request.json index d3f59d11cc9..6d48b51404b 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_request.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_request.json @@ -9,5 +9,6 @@ ], "autoapprove": [ "clients.autoapprove" - ] + ], + "allowpublic": true } diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_response.json index 64704ce173c..3f5e88a93f2 100644 --- a/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_response.json +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/PUT_{id}_response.json @@ -28,5 +28,6 @@ "my-saml-provider" ], "name": "My Client Name", + "allowpublic": true, "lastModified": 1468364443857 } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractClient.java index a761952b0c2..6fa73e06c03 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractClient.java @@ -54,6 +54,13 @@ abstract class AbstractClient { @Nullable abstract Boolean getApprovalsDeleted(); + /** + * If true, allow to omit client_secret for authorization_code flow in combination with PKCE + */ + @JsonProperty("allowpublic") + @Nullable + abstract Boolean getAllowPublic(); + /** * Scopes that the client is able to grant when creating a client */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractCreateClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractCreateClient.java index f5d10479c17..92e4a952bdf 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractCreateClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractCreateClient.java @@ -57,6 +57,13 @@ void checkAuthorizedGrantTypes() { @Nullable abstract Boolean getApprovalsDeleted(); + /** + * If client allows authentication_code flow with PKCE w/o client_secret + */ + @JsonProperty("allowpublic") + @Nullable + abstract Boolean getAllowPublic(); + /** * Scopes that the client is able to grant when creating a client */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractUpdateClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractUpdateClient.java index de8b2ff317f..bf3466f32ef 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractUpdateClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/clients/AbstractUpdateClient.java @@ -50,6 +50,13 @@ void checkAuthorizedGrantType() { @Nullable abstract Boolean getApprovalsDeleted(); + /** + * If client allows authentication_code flow with PKCE w/o client_secret + */ + @JsonProperty("allowpublic") + @Nullable + abstract Boolean getAllowPublic(); + /** * Scopes that the client is able to grant when creating a client */ diff --git a/integration-test/src/test/java/org/cloudfoundry/uaa/ClientsTest.java b/integration-test/src/test/java/org/cloudfoundry/uaa/ClientsTest.java index 4b52e9e1a6b..87a2cf8d9eb 100644 --- a/integration-test/src/test/java/org/cloudfoundry/uaa/ClientsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/uaa/ClientsTest.java @@ -121,6 +121,7 @@ public void batchCreate() { .authorizedGrantType(PASSWORD) .clientId(clientId1) .clientSecret(clientSecret) + .allowPublic(false) .scopes("client.read", "client.write") .tokenSalt("test-token-salt") .build()) @@ -130,21 +131,33 @@ public void batchCreate() { .authorizedGrantTypes(PASSWORD, REFRESH_TOKEN) .clientId(clientId2) .clientSecret(clientSecret) + .allowPublic(true) .scope("client.write") .tokenSalt("filtered-test-token-salt") .build()) .build()) .flatMapIterable(BatchCreateClientsResponse::getClients) - .filter(client -> clientId1.equals(client.getClientId())) + .collectMap(Client::getClientId) .as(StepVerifier::create) .consumeNextWith( - response -> { - assertThat(response.getAuthorizedGrantTypes()) + clients -> { + Client client1 = clients.get(clientId1); + assertThat(client1).isNotNull(); + assertThat(client1.getAuthorizedGrantTypes()) .containsExactly(PASSWORD, REFRESH_TOKEN); - assertThat(response.getClientId()).isEqualTo(clientId1); - assertThat(response.getScopes()) + assertThat(client1.getScopes()) .containsExactly("client.read", "client.write"); - assertThat(response.getTokenSalt()).isEqualTo("test-token-salt"); + assertThat(client1.getTokenSalt()).isEqualTo("test-token-salt"); + assertThat(client1.getAllowPublic()).isFalse(); + + Client client2 = clients.get(clientId2); + assertThat(client2).isNotNull(); + assertThat(client2.getAuthorizedGrantTypes()) + .containsExactlyInAnyOrder(PASSWORD, REFRESH_TOKEN); + assertThat(client2.getScopes()).containsExactly("client.write"); + assertThat(client2.getTokenSalt()) + .isEqualTo("filtered-test-token-salt"); + assertThat(client2.getAllowPublic()).isTrue(); }) .expectComplete() .verify(Duration.ofMinutes(5)); From acdbd2af13bd9d5168ddac9d78444ea1f444bcaa Mon Sep 17 00:00:00 2001 From: theghost5800 Date: Thu, 12 Mar 2026 18:03:52 +0200 Subject: [PATCH 13/26] Create/Update route options Refactor _RouteOptions class to handle added options in future Fixes #1365 Fixes #1339 Signed-off-by: Daniel Garnier-Moiroux --- .../client/v3/routes/_CreateRouteRequest.java | 7 ++ .../client/v3/routes/_RouteOptions.java | 16 ++- .../client/v3/routes/_UpdateRouteRequest.java | 7 ++ .../v3/routes/CreateRouteRequestTest.java | 35 ++++++ .../v3/routes/UpdateRouteRequestTest.java | 20 +++ .../org/cloudfoundry/CloudFoundryVersion.java | 2 + .../cloudfoundry/client/v3/RoutesTest.java | 118 ++++++++++++++++++ 7 files changed, 204 insertions(+), 1 deletion(-) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_CreateRouteRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_CreateRouteRequest.java index 7f66e5d9cf0..a0848dba77c 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_CreateRouteRequest.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_CreateRouteRequest.java @@ -27,6 +27,13 @@ abstract class _CreateRouteRequest { @JsonProperty("metadata") abstract Metadata getMetadata(); + /** + * Route options + */ + @Nullable + @JsonProperty("options") + abstract RouteOptions getOptions(); + /** * The path */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_RouteOptions.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_RouteOptions.java index 0c8bc8b15c3..772e34b06f3 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_RouteOptions.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_RouteOptions.java @@ -1,5 +1,6 @@ package org.cloudfoundry.client.v3.routes; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.cloudfoundry.Nullable; @@ -13,10 +14,23 @@ abstract class _RouteOptions { /** - * The loadbalancing + * The loadbalancing algorithm */ @JsonProperty("loadbalancing") @Nullable public abstract String getLoadbalancing(); + /** + * The hash header + */ + @JsonProperty("hash_header") + @Nullable + public abstract String getHashHeader(); + + /** + * The hash balance + */ + @JsonProperty("hash_balance") + @Nullable + public abstract String getHashBalance(); } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_UpdateRouteRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_UpdateRouteRequest.java index e375cdeac1f..42a9f97e95a 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_UpdateRouteRequest.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/routes/_UpdateRouteRequest.java @@ -21,6 +21,13 @@ abstract class _UpdateRouteRequest { @Nullable abstract Metadata getMetadata(); + /** + * Route options + */ + @JsonProperty("options") + @Nullable + abstract RouteOptions getOptions(); + /** * The route id */ diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/CreateRouteRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/CreateRouteRequestTest.java index 602ec57f849..9c925b2adcd 100644 --- a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/CreateRouteRequestTest.java +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/CreateRouteRequestTest.java @@ -16,6 +16,7 @@ package org.cloudfoundry.client.v3.routes; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.cloudfoundry.client.v3.Relationship; @@ -94,4 +95,38 @@ void valid() { .build()) .build()); } + + @Test + void validWithRouteOptions() { + CreateRouteRequest request = + CreateRouteRequest.builder() + .relationships( + RouteRelationships.builder() + .domain( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id("test-domain-id") + .build()) + .build()) + .space( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id("test-space-id") + .build()) + .build()) + .build()) + .options( + RouteOptions.builder() + .loadbalancing("hash") + .hashHeader("X-Hash") + .hashBalance("90") + .build()) + .build(); + + assertEquals("hash", request.getOptions().getLoadbalancing()); + assertEquals("X-Hash", request.getOptions().getHashHeader()); + assertEquals("90", request.getOptions().getHashBalance()); + } } diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/UpdateRouteRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/UpdateRouteRequestTest.java index 10e0f1eb0a0..0ca13db01e2 100644 --- a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/UpdateRouteRequestTest.java +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/routes/UpdateRouteRequestTest.java @@ -16,6 +16,7 @@ package org.cloudfoundry.client.v3.routes; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import org.cloudfoundry.client.v3.Metadata; @@ -39,4 +40,23 @@ void valid() { .routeId("test-route-id") .build(); } + + @Test + void validWithRouteOptions() { + UpdateRouteRequest request = + UpdateRouteRequest.builder() + .metadata(Metadata.builder().label("test-key", "test-value").build()) + .options( + RouteOptions.builder() + .loadbalancing("hash") + .hashHeader("X-Hash") + .hashBalance("90") + .build()) + .routeId("test-route-id") + .build(); + + assertEquals("hash", request.getOptions().getLoadbalancing()); + assertEquals("X-Hash", request.getOptions().getHashHeader()); + assertEquals("90", request.getOptions().getHashBalance()); + } } diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryVersion.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryVersion.java index 65d7a8e38ce..bb189878100 100644 --- a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryVersion.java +++ b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryVersion.java @@ -55,6 +55,8 @@ public enum CloudFoundryVersion { PCF_2_13(Version.forIntegers(2, 186, 0)), + CAPI_2_248(Version.forIntegers(2, 248, 0)), + PCF_4_v2(Version.forIntegers(2, 209, 0)), PCF_4_v3(Version.forIntegers(3, 138, 0)), diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java index d3f5bf4ff64..68647d07a55 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java @@ -46,6 +46,7 @@ import org.cloudfoundry.client.v3.routes.RemoveRouteDestinationsRequest; import org.cloudfoundry.client.v3.routes.ReplaceRouteDestinationsRequest; import org.cloudfoundry.client.v3.routes.ReplaceRouteDestinationsResponse; +import org.cloudfoundry.client.v3.routes.RouteOptions; import org.cloudfoundry.client.v3.routes.RouteRelationships; import org.cloudfoundry.client.v3.routes.RouteResource; import org.cloudfoundry.client.v3.routes.UpdateRouteRequest; @@ -128,6 +129,73 @@ public void create() { .verify(Duration.ofMinutes(5)); } + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.CAPI_2_248) + @Test + public void createWithOptions() { + String domainName = this.nameFactory.getDomainName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> { + var options = + RouteOptions.builder() + .loadbalancing("round-robin") + .build(); + return this.cloudFoundryClient + .routesV3() + .create( + CreateRouteRequest.builder() + .metadata( + Metadata.builder() + .label( + "test-createWithOptions-key", + "test-createWithOptions-value") + .build()) + .options(options) + .relationships( + RouteRelationships.builder() + .domain( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + domainId) + .build()) + .build()) + .space( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + spaceId) + .build()) + .build()) + .build()) + .build()) + .thenReturn(domainId); + })) + .flatMapMany(domainId -> requestListRoutes(this.cloudFoundryClient, domainId)) + .map(RouteResource::getOptions) + .map(RouteOptions::getLoadbalancing) + .as(StepVerifier::create) + .expectNext("round-robin") + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_9) @Test public void get() { @@ -966,6 +1034,56 @@ public void update() { .verify(Duration.ofMinutes(5)); } + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.CAPI_2_248) + @Test + public void updateOptions() { + String domainName = this.nameFactory.getDomainName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + Mono.just(domainId), + createRouteId( + this.cloudFoundryClient, + domainId, + "updateOptions", + spaceId)))) + .delayUntil( + function( + (domainId, routeId) -> + this.cloudFoundryClient + .routesV3() + .update( + UpdateRouteRequest.builder() + .routeId(routeId) + .options( + RouteOptions.builder() + .loadbalancing( + "least-connection") + .build()) + .build()))) + .flatMapMany( + function( + (domainId, ignore) -> + requestListRoutes(this.cloudFoundryClient, domainId))) + .map(RouteResource::getOptions) + .map(RouteOptions::getLoadbalancing) + .as(StepVerifier::create) + .expectNext("least-connection") + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + private static Mono createApplicationId( CloudFoundryClient cloudFoundryClient, String applicationName, String spaceId) { return requestCreateApplication(cloudFoundryClient, applicationName, spaceId) From 9fad3c18b7a6667ec6bfcaef5a6a2105bee07fc7 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 22 Sep 2026 23:05:50 +0200 Subject: [PATCH 14/26] Remove var usage from RoutesTest --- .../cloudfoundry/client/v3/RoutesTest.java | 83 +++++++++---------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java index 68647d07a55..05017449a7e 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/RoutesTest.java @@ -145,48 +145,47 @@ public void createWithOptions() { this.spaceId)) .flatMap( function( - (domainId, spaceId) -> { - var options = - RouteOptions.builder() - .loadbalancing("round-robin") - .build(); - return this.cloudFoundryClient - .routesV3() - .create( - CreateRouteRequest.builder() - .metadata( - Metadata.builder() - .label( - "test-createWithOptions-key", - "test-createWithOptions-value") - .build()) - .options(options) - .relationships( - RouteRelationships.builder() - .domain( - ToOneRelationship - .builder() - .data( - Relationship - .builder() - .id( - domainId) - .build()) - .build()) - .space( - ToOneRelationship - .builder() - .data( - Relationship - .builder() - .id( - spaceId) - .build()) - .build()) - .build()) - .build()) - .thenReturn(domainId); - })) + (domainId, spaceId) -> + this.cloudFoundryClient + .routesV3() + .create( + CreateRouteRequest.builder() + .metadata( + Metadata.builder() + .label( + "test-createWithOptions-key", + "test-createWithOptions-value") + .build()) + .options( + RouteOptions.builder() + .loadbalancing( + "round-robin") + .build()) + .relationships( + RouteRelationships.builder() + .domain( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + domainId) + .build()) + .build()) + .space( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + spaceId) + .build()) + .build()) + .build()) + .build()) + .thenReturn(domainId))) .flatMapMany(domainId -> requestListRoutes(this.cloudFoundryClient, domainId)) .map(RouteResource::getOptions) .map(RouteOptions::getLoadbalancing) From c25df0ed07cfa66fcd91f59d6e119ae621263b55 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Wed, 23 Sep 2026 11:09:45 +0200 Subject: [PATCH 15/26] Add env-var to ignore unknown properties (#1369) Fixes #1341 --- README.md | 1 + .../serviceInstances/ReactorServiceInstancesV3Test.java | 3 ++- .../org/cloudfoundry/IntegrationTestConfiguration.java | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f353347f074..f3890790e3e 100644 --- a/README.md +++ b/README.md @@ -353,6 +353,7 @@ Name | Description `TEST_PROXY_PORT` | _(Optional)_ The port of a proxy to route all requests through. Defaults to `8080`. `TEST_PROXY_USERNAME` | _(Optional)_ The username for a proxy to route all requests through `TEST_SKIPSSLVALIDATION` | _(Optional)_ Whether to skip SSL validation when connecting to the Cloud Foundry instance. Defaults to `false`. +`TEST_FAIL_ON_UNKNOWN_PROPERTIES` | _(Optional)_ If "true", unknown values received from CF result in a parsing error. This usually happens when client and server versions do not match. When "false", unknown properties will be ignored allowing the execution of integration-tests. Use with care! Defaults to `true`. `SKIP_BROWSER_AUTH_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require browser-based authentication (e.g., SSO tests). Useful when the Cloud Foundry instance's UAA does not have browser-based authentication configured. Defaults to `false`. `SKIP_METRIC_REGISTRAR_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require the Metric Registrar service (e.g., MetricRegistrarTest). Useful when the Cloud Foundry instance does not have the Metric Registrar service available. Defaults to `false`. `SKIP_TCP_ROUTING_TESTS` | _(Optional)_ Set to `true` to skip TCP routing integration tests (TcpRoutesTest, RouterGroupsTest). Useful when the Cloud Foundry instance does not have TCP routing configured (i.e., no `routing_endpoint` in the info payload). Defaults to `false`. diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java index 1e6df1f2585..8e9ef1dd640 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.cloudfoundry.reactor.client.v3.serviceinstances; +package org.cloudfoundry.reactor.client.v3.serviceInstances; import static io.netty.handler.codec.http.HttpMethod.DELETE; import static io.netty.handler.codec.http.HttpMethod.GET; @@ -62,6 +62,7 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 6de4016e46c..0b44644ca9a 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -307,17 +307,18 @@ DefaultConnectionContext connectionContext( @Value("${test.proxy.password:}") String proxyPassword, @Value("${test.proxy.port:8080}") Integer proxyPort, @Value("${test.proxy.username:}") String proxyUsername, + @Value("${test.fail.on.unknown.properties:true}") Boolean failOnUnknownProperties, @Value("${test.skipSslValidation:false}") Boolean skipSslValidation) { DefaultConnectionContext.Builder connectionContext = DefaultConnectionContext.builder() .apiHost(apiHost) - .problemHandler( - new FailingDeserializationProblemHandler()) // Test-only problem - // handler .skipSslValidation(skipSslValidation) .sslHandshakeTimeout(Duration.ofSeconds(30)); - + if(failOnUnknownProperties) { + connectionContext.problemHandler( + new FailingDeserializationProblemHandler()); // Test-only problem handler + } if (StringUtils.hasText(proxyHost)) { ProxyConfiguration.Builder proxyConfiguration = ProxyConfiguration.builder().host(proxyHost).port(proxyPort); From e22c772b25c96c87dbac69ec3324a85e0cee4024 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Wed, 30 Sep 2026 14:37:18 +0200 Subject: [PATCH 16/26] Fix missing apache commons-lang3 dependency Showed up when we downloaded tar archives with ClassDefNotFound --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index d819f0b1c49..ba7873635d5 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,11 @@ commons-compress ${commons-compress.version} + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + org.atteo evo-inflector From d8ef6595570bc7de371b25da8c3cd27ac3f7789b Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Thu, 1 Oct 2026 16:06:41 +0200 Subject: [PATCH 17/26] Implement Service Route binding from v3 API(#1378) Fixes #1361 --- .../client/_ReactorCloudFoundryClient.java | 7 + .../ReactorServiceRouteBindingsV3.java | 142 +++++ .../ReactorServiceRouteBindingsV3Test.java | 371 +++++++++++++ .../v3/serviceroutebindings/GET_response.json | 99 ++++ .../GET_{id}_parameters_response.json | 4 + .../GET_{id}_response.json | 42 ++ .../PATCH_{id}_request.json | 6 + .../PATCH_{id}_response.json | 44 ++ .../v3/serviceroutebindings/POST_request.json | 14 + .../client/CloudFoundryClient.java | 8 + .../ServiceRouteBinding.java | 58 ++ .../ServiceRouteBindingsV3.java | 76 +++ .../_CreateServiceRouteBindingRequest.java | 56 ++ .../_CreateServiceRouteBindingResponse.java | 39 ++ .../_DeleteServiceRouteBindingRequest.java | 32 ++ .../_DeleteServiceRouteBindingResponse.java | 34 ++ ...tServiceRouteBindingParametersRequest.java | 35 ++ ...ServiceRouteBindingParametersResponse.java | 65 +++ .../_GetServiceRouteBindingRequest.java | 35 ++ .../_GetServiceRouteBindingResponse.java | 31 ++ .../_ListServiceRouteBindingsRequest.java | 64 +++ .../_ListServiceRouteBindingsResponse.java | 33 ++ .../_ServiceRouteBindingRelationships.java | 42 ++ .../_ServiceRouteBindingResource.java | 27 + .../_UpdateServiceRouteBindingRequest.java | 47 ++ .../_UpdateServiceRouteBindingResponse.java | 31 ++ .../CreateServiceRouteBindingRequestTest.java | 56 ++ .../DeleteServiceRouteBindingRequestTest.java | 38 ++ ...viceRouteBindingParametersRequestTest.java | 38 ++ .../GetServiceRouteBindingRequestTest.java | 37 ++ .../ListServiceRouteBindingsRequestTest.java | 27 + .../RelationshipsTest.java | 66 +++ .../UpdateServiceRouteBindingRequestTest.java | 38 ++ .../client/v3/ServiceRouteBindingsTest.java | 505 ++++++++++++++++++ 34 files changed, 2247 insertions(+) create mode 100644 cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3.java create mode 100644 cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3Test.java create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_parameters_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_request.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_response.json create mode 100644 cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/POST_request.json create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBinding.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBindingsV3.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsResponse.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingRelationships.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingResource.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingResponse.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/CreateServiceRouteBindingRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/DeleteServiceRouteBindingRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingParametersRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/ListServiceRouteBindingsRequestTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/RelationshipsTest.java create mode 100644 cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/UpdateServiceRouteBindingRequestTest.java create mode 100644 integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceRouteBindingsTest.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java index 7a26cd13d2c..6fe80b2d96d 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java @@ -73,6 +73,7 @@ import org.cloudfoundry.client.v3.serviceinstances.ServiceInstancesV3; import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingsV3; import org.cloudfoundry.client.v3.serviceplans.ServicePlansV3; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingsV3; import org.cloudfoundry.client.v3.quotas.spaces.SpaceQuotasV3; import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; @@ -134,6 +135,7 @@ import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.cloudfoundry.reactor.client.v3.serviceofferings.ReactorServiceOfferingsV3; import org.cloudfoundry.reactor.client.v3.serviceplans.ReactorServicePlansV3; +import org.cloudfoundry.reactor.client.v3.serviceroutebindings.ReactorServiceRouteBindingsV3; import org.cloudfoundry.reactor.client.v3.quotas.spaces.ReactorSpaceQuotasV3; import org.cloudfoundry.reactor.client.v3.spaces.ReactorSpacesV3; import org.cloudfoundry.reactor.client.v3.stacks.ReactorStacksV3; @@ -554,4 +556,9 @@ Mono getRootV3() { */ abstract TokenProvider getTokenProvider(); + @Override + @Value.Derived + public ServiceRouteBindingsV3 serviceRouteBindingsV3() { + return new ReactorServiceRouteBindingsV3(getConnectionContext(), getRootV3(), getTokenProvider(), getRequestTags()); + } } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3.java new file mode 100644 index 00000000000..38a2a152967 --- /dev/null +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3.java @@ -0,0 +1,142 @@ +/* + * Copyright 2013-2026 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.reactor.client.v3.serviceroutebindings; + +import java.util.Map; +import java.util.Optional; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.DeleteServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.DeleteServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingParametersRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingParametersResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.ListServiceRouteBindingsRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.ListServiceRouteBindingsResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingResource; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingsV3; +import org.cloudfoundry.client.v3.serviceroutebindings.UpdateServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.UpdateServiceRouteBindingResponse; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; +import reactor.core.publisher.Mono; + +/** + * The Reactor-based implementation of {@link ServiceRouteBindingsV3} + */ +public final class ReactorServiceRouteBindingsV3 extends AbstractClientV3Operations + implements ServiceRouteBindingsV3 { + + public ReactorServiceRouteBindingsV3( + ConnectionContext connectionContext, + Mono root, + TokenProvider tokenProvider, + Map requestTags) { + super(connectionContext, root, tokenProvider, requestTags); + } + + @Override + public Mono create( + CreateServiceRouteBindingRequest request) { + return postWithResponse( + request, + ServiceRouteBindingResource.class, + builder -> builder.pathSegment("service_route_bindings")) + .map( + responseTuple -> + CreateServiceRouteBindingResponse.builder() + .serviceRouteBinding(responseTuple.getBody()) + .jobId( + Optional.ofNullable( + extractJobId(responseTuple.getResponse()))) + .build()) + .checkpoint(); + } + + @Override + public Mono delete( + DeleteServiceRouteBindingRequest request) { + return createOperator() + .flatMap( + operator -> + operator.delete() + .uri( + builder -> + builder.pathSegment( + "service_route_bindings", + request.getServiceRouteBindingId())) + .send(request) + .response() + .get()) + .map( + response -> + DeleteServiceRouteBindingResponse.builder() + .jobId(Optional.ofNullable(extractJobId(response))) + .build()) + .checkpoint(); + } + + @Override + public Mono get(GetServiceRouteBindingRequest request) { + return get( + request, + GetServiceRouteBindingResponse.class, + builder -> + builder.pathSegment( + "service_route_bindings", + request.getServiceRouteBindingId())) + .checkpoint(); + } + + @Override + public Mono getParameters( + GetServiceRouteBindingParametersRequest request) { + return get( + request, + GetServiceRouteBindingParametersResponse.class, + builder -> + builder.pathSegment( + "service_route_bindings", + request.getServiceRouteBindingId(), + "parameters")) + .checkpoint(); + } + + @Override + public Mono list(ListServiceRouteBindingsRequest request) { + return get( + request, + ListServiceRouteBindingsResponse.class, + builder -> builder.pathSegment("service_route_bindings")) + .checkpoint(); + } + + @Override + public Mono update( + UpdateServiceRouteBindingRequest request) { + return patch( + request, + UpdateServiceRouteBindingResponse.class, + builder -> + builder.pathSegment( + "service_route_bindings", + request.getServiceRouteBindingId())) + .checkpoint(); + } +} diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3Test.java new file mode 100644 index 00000000000..5e507c2aeb2 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceroutebindings/ReactorServiceRouteBindingsV3Test.java @@ -0,0 +1,371 @@ +/* + * Copyright 2026 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.reactor.client.v3.serviceroutebindings; + +import static io.netty.handler.codec.http.HttpMethod.DELETE; +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.PATCH; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.ACCEPTED; +import static io.netty.handler.codec.http.HttpResponseStatus.NO_CONTENT; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; + +import java.time.Duration; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.cloudfoundry.client.v3.LastOperation; +import org.cloudfoundry.client.v3.Link; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.Pagination; +import org.cloudfoundry.client.v3.Relationship; +import org.cloudfoundry.client.v3.ToOneRelationship; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.DeleteServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.DeleteServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingParametersRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingParametersResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.ListServiceRouteBindingsRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.ListServiceRouteBindingsResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingRelationships; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingResource; +import org.cloudfoundry.client.v3.serviceroutebindings.UpdateServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.UpdateServiceRouteBindingResponse; +import org.cloudfoundry.reactor.InteractionContext; +import org.cloudfoundry.reactor.TestRequest; +import org.cloudfoundry.reactor.TestResponse; +import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.junit.jupiter.api.Test; +import reactor.test.StepVerifier; + +final class ReactorServiceRouteBindingsV3Test extends AbstractClientApiTest { + + private static final String ROUTE_ID = "7304bc3c-7010-11ea-8840-48bf6bec2d78"; + private static final String SERVICE_INSTANCE_ID_1 = "e0e4417c-74ee-11ea-a604-48bf6bec2d78"; + private static final String SERVICE_INSTANCE_ID_2 = "f0a63e60-74ee-11ea-a604-48bf6bec2d78"; + private static final String BINDING_ID_1 = "dde5ad2a-d8f4-44dc-a56f-0452d744f1c3"; + private static final String BINDING_ID_2 = "7aa37bad-6ccb-4ef9-ba48-9ce3a91b2b62"; + private static final String CREATED_AT = "2020-03-10T15:49:29Z"; + private static final String UPDATED_AT = "2020-03-10T15:49:32Z"; + + private final ReactorServiceRouteBindingsV3 serviceRouteBindings = + new ReactorServiceRouteBindingsV3( + CONNECTION_CONTEXT, this.root, TOKEN_PROVIDER, Collections.emptyMap()); + + @Test + void create() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(POST) + .path("/service_route_bindings") + .payload( + "fixtures/client/v3/serviceroutebindings/POST_request.json") + .build()) + .response( + TestResponse.builder() + .status(ACCEPTED) + .header( + "Location", + "https://api.example.org/v3/jobs/af5c57f6-8769-41fa-a499-2c84ed896788") + .build()) + .build()); + + this.serviceRouteBindings + .create( + CreateServiceRouteBindingRequest.builder() + .relationships(relationships(SERVICE_INSTANCE_ID_1)) + .build()) + .as(StepVerifier::create) + .expectNext( + CreateServiceRouteBindingResponse.builder() + .jobId("af5c57f6-8769-41fa-a499-2c84ed896788") + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void delete() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(DELETE) + .path( + "/service_route_bindings/test-service-route-binding-id") + .build()) + .response(TestResponse.builder().status(NO_CONTENT).build()) + .build()); + + this.serviceRouteBindings + .delete( + DeleteServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build()) + .as(StepVerifier::create) + .expectNext(DeleteServiceRouteBindingResponse.builder().build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void get() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(GET) + .path( + "/service_route_bindings/test-service-route-binding-id") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload( + "fixtures/client/v3/serviceroutebindings/GET_{id}_response.json") + .build()) + .build()); + + this.serviceRouteBindings + .get( + GetServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build()) + .as(StepVerifier::create) + .expectNext( + GetServiceRouteBindingResponse.builder() + .id(BINDING_ID_1) + .createdAt(CREATED_AT) + .updatedAt(UPDATED_AT) + .lastOperation(lastOperation("create")) + .relationships(relationships(SERVICE_INSTANCE_ID_1)) + .link("self", link(bindingHref(BINDING_ID_1))) + .link( + "service_instance", + link(serviceInstanceHref(SERVICE_INSTANCE_ID_1))) + .link("route", link(routeHref())) + .link("parameters", link(parametersHref(BINDING_ID_1))) + .metadata(emptyMetadata()) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void getParameters() { + Map parameters = new HashMap<>(); + parameters.put("key1", "value1"); + parameters.put("key2", "value2"); + + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(GET) + .path( + "/service_route_bindings/test-service-route-binding-id/parameters") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload( + "fixtures/client/v3/serviceroutebindings/GET_{id}_parameters_response.json") + .build()) + .build()); + + this.serviceRouteBindings + .getParameters( + GetServiceRouteBindingParametersRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build()) + .as(StepVerifier::create) + .expectNext( + GetServiceRouteBindingParametersResponse.builder() + .parameters(parameters) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void list() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(GET) + .path( + "/service_route_bindings?route_guids=7304bc3c-7010-11ea-8840-48bf6bec2d78&order_by=%2Bcreated_at&page=1") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload( + "fixtures/client/v3/serviceroutebindings/GET_response.json") + .build()) + .build()); + + this.serviceRouteBindings + .list( + ListServiceRouteBindingsRequest.builder() + .page(1) + .orderBy("+created_at") + .routeId(ROUTE_ID) + .build()) + .as(StepVerifier::create) + .expectNext( + ListServiceRouteBindingsResponse.builder() + .pagination( + Pagination.builder() + .totalResults(2) + .first( + link( + "https://api.example.org/v3/service_route_bindings?page=1&per_page=50")) + .last( + link( + "https://api.example.org/v3/service_route_bindings?page=1&per_page=50")) + .build()) + .resource( + bindingResource( + BINDING_ID_1, SERVICE_INSTANCE_ID_1, "create")) + .resource( + bindingResource( + BINDING_ID_2, SERVICE_INSTANCE_ID_2, "create")) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void update() { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(PATCH) + .path( + "/service_route_bindings/test-service-route-binding-id") + .payload( + "fixtures/client/v3/serviceroutebindings/PATCH_{id}_request.json") + .build()) + .response( + TestResponse.builder() + .status(OK) + .payload( + "fixtures/client/v3/serviceroutebindings/PATCH_{id}_response.json") + .build()) + .build()); + + this.serviceRouteBindings + .update( + UpdateServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .metadata(metadataWithLabel("env", "production")) + .build()) + .as(StepVerifier::create) + .expectNext( + UpdateServiceRouteBindingResponse.builder() + .id(BINDING_ID_1) + .createdAt(CREATED_AT) + .updatedAt(UPDATED_AT) + .lastOperation(lastOperation("update")) + .relationships(relationships(SERVICE_INSTANCE_ID_1)) + .link("self", link(bindingHref(BINDING_ID_1))) + .link( + "service_instance", + link(serviceInstanceHref(SERVICE_INSTANCE_ID_1))) + .link("route", link(routeHref())) + .link("parameters", link(parametersHref(BINDING_ID_1))) + .metadata(metadataWithLabel("env", "production")) + .build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + private static LastOperation lastOperation(String type) { + return LastOperation.builder() + .type(type) + .state("succeeded") + .description("Operation succeeded") + .createdAt(CREATED_AT) + .updatedAt(UPDATED_AT) + .build(); + } + + private static ToOneRelationship toOneRelationship(String id) { + return ToOneRelationship.builder().data(Relationship.builder().id(id).build()).build(); + } + + private static ServiceRouteBindingRelationships relationships(String serviceInstanceId) { + return ServiceRouteBindingRelationships.builder() + .route(toOneRelationship(ROUTE_ID)) + .serviceInstance(toOneRelationship(serviceInstanceId)) + .build(); + } + + private static Link link(String href) { + return Link.builder().href(href).build(); + } + + private static String bindingHref(String bindingId) { + return "https://api.example.org/v3/service_route_bindings/" + bindingId; + } + + private static String serviceInstanceHref(String serviceInstanceId) { + return "https://api.example.org/v3/service_instances/" + serviceInstanceId; + } + + private static String routeHref() { + return "https://api.example.org/v3/routes/" + ROUTE_ID; + } + + private static String parametersHref(String bindingId) { + return "https://api.example.org/v3/service_route_bindings/" + bindingId + "/parameters"; + } + + private static Metadata emptyMetadata() { + return Metadata.builder() + .labels(Collections.emptyMap()) + .annotations(Collections.emptyMap()) + .build(); + } + + private static Metadata metadataWithLabel(String key, String value) { + return Metadata.builder().label(key, value).annotations(Collections.emptyMap()).build(); + } + + private static ServiceRouteBindingResource bindingResource( + String bindingId, String serviceInstanceId, String lastOperationType) { + return ServiceRouteBindingResource.builder() + .id(bindingId) + .createdAt(CREATED_AT) + .updatedAt(UPDATED_AT) + .lastOperation(lastOperation(lastOperationType)) + .relationships(relationships(serviceInstanceId)) + .link("self", link(bindingHref(bindingId))) + .link("service_instance", link(serviceInstanceHref(serviceInstanceId))) + .link("route", link(routeHref())) + .link("parameters", link(parametersHref(bindingId))) + .metadata(emptyMetadata()) + .build(); + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_response.json new file mode 100644 index 00000000000..b1e91ee93eb --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_response.json @@ -0,0 +1,99 @@ +{ + "pagination": { + "total_results": 2, + "first": { + "href": "https://api.example.org/v3/service_route_bindings?page=1&per_page=50" + }, + "last": { + "href": "https://api.example.org/v3/service_route_bindings?page=1&per_page=50" + }, + "next": null, + "previous": null + }, + "resources": [ + { + "guid": "dde5ad2a-d8f4-44dc-a56f-0452d744f1c3", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z", + "last_operation": { + "type": "create", + "state": "succeeded", + "description": "Operation succeeded", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z" + }, + "relationships": { + "route": { + "data": { + "guid": "7304bc3c-7010-11ea-8840-48bf6bec2d78" + } + }, + "service_instance": { + "data": { + "guid": "e0e4417c-74ee-11ea-a604-48bf6bec2d78" + } + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3" + }, + "service_instance": { + "href": "https://api.example.org/v3/service_instances/e0e4417c-74ee-11ea-a604-48bf6bec2d78" + }, + "route": { + "href": "https://api.example.org/v3/routes/7304bc3c-7010-11ea-8840-48bf6bec2d78" + }, + "parameters": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3/parameters" + } + }, + "metadata": { + "labels": {}, + "annotations": {} + } + }, + { + "guid": "7aa37bad-6ccb-4ef9-ba48-9ce3a91b2b62", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z", + "last_operation": { + "type": "create", + "state": "succeeded", + "description": "Operation succeeded", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z" + }, + "relationships": { + "route": { + "data": { + "guid": "7304bc3c-7010-11ea-8840-48bf6bec2d78" + } + }, + "service_instance": { + "data": { + "guid": "f0a63e60-74ee-11ea-a604-48bf6bec2d78" + } + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/service_route_bindings/7aa37bad-6ccb-4ef9-ba48-9ce3a91b2b62" + }, + "service_instance": { + "href": "https://api.example.org/v3/service_instances/f0a63e60-74ee-11ea-a604-48bf6bec2d78" + }, + "route": { + "href": "https://api.example.org/v3/routes/7304bc3c-7010-11ea-8840-48bf6bec2d78" + }, + "parameters": { + "href": "https://api.example.org/v3/service_route_bindings/7aa37bad-6ccb-4ef9-ba48-9ce3a91b2b62/parameters" + } + }, + "metadata": { + "labels": {}, + "annotations": {} + } + } + ] +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_parameters_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_parameters_response.json new file mode 100644 index 00000000000..7afc33d536a --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_parameters_response.json @@ -0,0 +1,4 @@ +{ + "key1": "value1", + "key2": "value2" +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_response.json new file mode 100644 index 00000000000..82ddbb78b09 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/GET_{id}_response.json @@ -0,0 +1,42 @@ +{ + "guid": "dde5ad2a-d8f4-44dc-a56f-0452d744f1c3", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z", + "last_operation": { + "type": "create", + "state": "succeeded", + "description": "Operation succeeded", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z" + }, + "relationships": { + "route": { + "data": { + "guid": "7304bc3c-7010-11ea-8840-48bf6bec2d78" + } + }, + "service_instance": { + "data": { + "guid": "e0e4417c-74ee-11ea-a604-48bf6bec2d78" + } + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3" + }, + "service_instance": { + "href": "https://api.example.org/v3/service_instances/e0e4417c-74ee-11ea-a604-48bf6bec2d78" + }, + "route": { + "href": "https://api.example.org/v3/routes/7304bc3c-7010-11ea-8840-48bf6bec2d78" + }, + "parameters": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3/parameters" + } + }, + "metadata": { + "labels": {}, + "annotations": {} + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_request.json new file mode 100644 index 00000000000..229ea92c518 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_request.json @@ -0,0 +1,6 @@ +{ + "metadata": { + "labels": {"env": "production"}, + "annotations": {} + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_response.json new file mode 100644 index 00000000000..b8f4266b7c0 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/PATCH_{id}_response.json @@ -0,0 +1,44 @@ +{ + "guid": "dde5ad2a-d8f4-44dc-a56f-0452d744f1c3", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z", + "last_operation": { + "type": "update", + "state": "succeeded", + "description": "Operation succeeded", + "created_at": "2020-03-10T15:49:29Z", + "updated_at": "2020-03-10T15:49:32Z" + }, + "relationships": { + "route": { + "data": { + "guid": "7304bc3c-7010-11ea-8840-48bf6bec2d78" + } + }, + "service_instance": { + "data": { + "guid": "e0e4417c-74ee-11ea-a604-48bf6bec2d78" + } + } + }, + "links": { + "self": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3" + }, + "service_instance": { + "href": "https://api.example.org/v3/service_instances/e0e4417c-74ee-11ea-a604-48bf6bec2d78" + }, + "route": { + "href": "https://api.example.org/v3/routes/7304bc3c-7010-11ea-8840-48bf6bec2d78" + }, + "parameters": { + "href": "https://api.example.org/v3/service_route_bindings/dde5ad2a-d8f4-44dc-a56f-0452d744f1c3/parameters" + } + }, + "metadata": { + "labels": { + "env": "production" + }, + "annotations": {} + } +} diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/POST_request.json new file mode 100644 index 00000000000..9796cc31382 --- /dev/null +++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/serviceroutebindings/POST_request.json @@ -0,0 +1,14 @@ +{ + "relationships": { + "route": { + "data": { + "guid": "7304bc3c-7010-11ea-8840-48bf6bec2d78" + } + }, + "service_instance": { + "data": { + "guid": "e0e4417c-74ee-11ea-a604-48bf6bec2d78" + } + } + } +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index ae1d7fe4666..7875dca4174 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -71,6 +71,7 @@ import org.cloudfoundry.client.v3.serviceinstances.ServiceInstancesV3; import org.cloudfoundry.client.v3.serviceofferings.ServiceOfferingsV3; import org.cloudfoundry.client.v3.serviceplans.ServicePlansV3; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingsV3; import org.cloudfoundry.client.v3.spaces.SpacesV3; import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; @@ -388,4 +389,11 @@ public interface CloudFoundryClient { * Main entry point to the Cloud Foundry Users V3 Client API */ UsersV3 usersV3(); + + /** + * Main entry point to the Cloud Foundry Service Route Bindings V3 Client API + * See https://v3-apidocs.cloudfoundry.org/index.html#service-route-binding + */ + ServiceRouteBindingsV3 serviceRouteBindingsV3(); + } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBinding.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBinding.java new file mode 100644 index 00000000000..01f1ac35e1a --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBinding.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013-2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.LastOperation; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.Resource; + +/** + * Base class for responses that are service route bindings + * + * see https://v3-apidocs.cloudfoundry.org/index.html#service-route-binding + */ +public abstract class ServiceRouteBinding extends Resource { + + /** + * The last operation + */ + @JsonProperty("last_operation") + @Nullable + public abstract LastOperation getLastOperation(); + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + public abstract Metadata getMetadata(); + + /** + * The relationships + */ + @JsonProperty("relationships") + public abstract ServiceRouteBindingRelationships getRelationships(); + + /** + * The URL of the service that will intercept traffic to the route + */ + @JsonProperty("route_service_url") + @Nullable + public abstract String getRouteServiceUrl(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBindingsV3.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBindingsV3.java new file mode 100644 index 00000000000..cd1c1055eaa --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/ServiceRouteBindingsV3.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2026 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.client.v3.serviceroutebindings; + +import reactor.core.publisher.Mono; + +/** + * Main entry point to the Cloud Foundry Service Route Bindings V3 Client API + * + * see https://v3-apidocs.cloudfoundry.org/index.html#service-route-binding + */ +public interface ServiceRouteBindingsV3 { + + /** + * Makes the Create a service route binding request + * + * @param request the Create Service Route Binding request + * @return the response from the Create Service Route Binding request + */ + Mono create(CreateServiceRouteBindingRequest request); + + /** + * Makes the Delete a service route binding request + * + * @param request the Delete Service Route Binding request + * @return the response from the Delete Service Route Binding request + */ + Mono delete(DeleteServiceRouteBindingRequest request); + + /** + * Makes the Get a service route binding request + * + * @param request the Get Service Route Binding request + * @return the response from the Get Service Route Binding request + */ + Mono get(GetServiceRouteBindingRequest request); + + /** + * Makes the Get parameters for a service route binding request + * + * @param request the Get Service Route Binding Parameters request + * @return the response from the Get Service Route Binding Parameters request + */ + Mono getParameters( + GetServiceRouteBindingParametersRequest request); + + /** + * Makes the List service route bindings request + * + * @param request the List Service Route Bindings request + * @return the response from the List Service Route Bindings request + */ + Mono list(ListServiceRouteBindingsRequest request); + + /** + * Makes the Update a service route binding request + * + * @param request the Update Service Route Binding request + * @return the response from the Update Service Route Binding request + */ + Mono update(UpdateServiceRouteBindingRequest request); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingRequest.java new file mode 100644 index 00000000000..d77297d2a21 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingRequest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.AllowNulls; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +import java.util.Map; + +/** + * The request payload for the Create Service Route Binding operation. + * see https://v3-apidocs.cloudfoundry.org/index.html#create-a-service-route-binding + */ +@JsonSerialize +@Value.Immutable +abstract class _CreateServiceRouteBindingRequest { + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); + + /** + * The parameters + */ + @JsonProperty("parameters") + @Nullable + @AllowNulls + abstract Map getParameters(); + + /** + * The relationships + */ + @JsonProperty("relationships") + abstract ServiceRouteBindingRelationships getRelationships(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingResponse.java new file mode 100644 index 00000000000..d4cf5a98d48 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_CreateServiceRouteBindingResponse.java @@ -0,0 +1,39 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import org.immutables.value.Value; + +import java.util.Optional; + +/** + * The response payload for the Create Service Route Binding operation. + * see https://v3-apidocs.cloudfoundry.org/#create-a-service-credential-binding + */ +@Value.Immutable +abstract class _CreateServiceRouteBindingResponse { + + /** + * The job id (present when the binding is created asynchronously, managed service instance) + */ + public abstract Optional getJobId(); + + /** + * The service route binding (present when the binding is created synchronously, user provided service instance) + */ + public abstract Optional getServiceRouteBinding(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingRequest.java new file mode 100644 index 00000000000..cafb9015736 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingRequest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import org.immutables.value.Value; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The request payload for the Delete Service Route Binding operation. + * see https://v3-apidocs.cloudfoundry.org/index.html#delete-a-service-route-binding + */ +@Value.Immutable +abstract class _DeleteServiceRouteBindingRequest { + + @JsonIgnore + abstract String getServiceRouteBindingId(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingResponse.java new file mode 100644 index 00000000000..809cc27c3a5 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_DeleteServiceRouteBindingResponse.java @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import org.immutables.value.Value; + +import java.util.Optional; + +/** + * The response payload for the Delete Service Route Binding operation. + * see https://v3-apidocs.cloudfoundry.org/index.html#delete-a-service-route-binding + */ +@Value.Immutable +abstract class _DeleteServiceRouteBindingResponse { + + /** + * The job id (present when the binding is deleted asynchronously, managed service instance) + */ + public abstract Optional getJobId(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersRequest.java new file mode 100644 index 00000000000..e23402562fe --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Get Service Route Binding Parameters operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#get-parameters-for-a-route-binding + */ +@Value.Immutable +abstract class _GetServiceRouteBindingParametersRequest { + + /** + * The service route binding id + */ + @JsonIgnore + abstract String getServiceRouteBindingId(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersResponse.java new file mode 100644 index 00000000000..2832398a8f7 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingParametersResponse.java @@ -0,0 +1,65 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import org.cloudfoundry.AllowNulls; +import org.immutables.value.Value; + +import java.io.IOException; +import java.util.Map; + +/** + * The response payload for the Get Service Route Binding Parameters operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#get-parameters-for-a-route-binding + */ +@JsonDeserialize( + using = + _GetServiceRouteBindingParametersResponse + .ServiceRouteBindingParametersResponseDeserializer.class) +@Value.Immutable +abstract class _GetServiceRouteBindingParametersResponse { + + /** + * The parameters + */ + @AllowNulls + abstract Map getParameters(); + + static final class ServiceRouteBindingParametersResponseDeserializer + extends StdDeserializer { + + private static final long serialVersionUID = 1L; + + ServiceRouteBindingParametersResponseDeserializer() { + super(GetServiceRouteBindingParametersResponse.class); + } + + @Override + public GetServiceRouteBindingParametersResponse deserialize( + JsonParser p, DeserializationContext ctxt) throws IOException { + return GetServiceRouteBindingParametersResponse.builder() + .parameters(p.readValueAs(new TypeReference>() {})) + .build(); + } + } +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingRequest.java new file mode 100644 index 00000000000..166c33bb919 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingRequest.java @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.immutables.value.Value; + +/** + * The request payload for the Get Service Route Binding operation. + * + * see https://v3-apidocs.cloudfoundry.org/index.html#get-a-service-route-binding + */ +@Value.Immutable +abstract class _GetServiceRouteBindingRequest { + + /** + * The service route binding id + */ + @JsonIgnore + abstract String getServiceRouteBindingId(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingResponse.java new file mode 100644 index 00000000000..8465e2fb605 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_GetServiceRouteBindingResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Get Service Route Binding operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#get-a-service-route-binding + */ +@JsonDeserialize +@Value.Immutable +abstract class _GetServiceRouteBindingResponse extends ServiceRouteBinding { + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsRequest.java new file mode 100644 index 00000000000..bafe9e1196c --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsRequest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2013-2026 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.client.v3.serviceroutebindings; + +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.FilterParameter; +import org.cloudfoundry.client.v3.PaginatedRequest; +import org.immutables.value.Value; + +import java.util.List; + +/** + * The request payload for the List Service Route Bindings operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#list-service-route-bindings + */ +@Value.Immutable +abstract class _ListServiceRouteBindingsRequest extends PaginatedRequest { + + /** + * The ids + */ + @FilterParameter("guids") + abstract List getIds(); + + /** + * A query string containing a list of label selector requirements + */ + @FilterParameter("label_selector") + @Nullable + abstract String getLabelSelector(); + + /** + * The route ids + */ + @FilterParameter("route_guids") + abstract List getRouteIds(); + + /** + * The service instance ids + */ + @FilterParameter("service_instance_guids") + abstract List getServiceInstanceIds(); + + /** + * The service instance names + */ + @FilterParameter("service_instance_names") + abstract List getServiceInstanceNames(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsResponse.java new file mode 100644 index 00000000000..0b3181b853b --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ListServiceRouteBindingsResponse.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.cloudfoundry.client.v3.PaginatedResponse; +import org.immutables.value.Value; + +/** + * The response payload for the List Service Route Bindings operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#list-service-route-bindings + */ +@JsonDeserialize +@Value.Immutable +abstract class _ListServiceRouteBindingsResponse + extends PaginatedResponse { + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingRelationships.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingRelationships.java new file mode 100644 index 00000000000..373a5508af9 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingRelationships.java @@ -0,0 +1,42 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.cloudfoundry.client.v3.ToOneRelationship; +import org.immutables.value.Value; + +/** + * The relationships for a service route binding + */ +@Value.Immutable +@JsonDeserialize +abstract class _ServiceRouteBindingRelationships { + + /** + * The route relationship + */ + @JsonProperty("route") + abstract ToOneRelationship getRoute(); + + /** + * The service instance relationship + */ + @JsonProperty("service_instance") + abstract ToOneRelationship getServiceInstance(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingResource.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingResource.java new file mode 100644 index 00000000000..f0ba3553b0e --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_ServiceRouteBindingResource.java @@ -0,0 +1,27 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The Resource response payload for the List Service Route Bindings operation + */ +@JsonDeserialize +@Value.Immutable +abstract class _ServiceRouteBindingResource extends ServiceRouteBinding {} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingRequest.java new file mode 100644 index 00000000000..bafdc7ba6e3 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingRequest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.v3.Metadata; +import org.immutables.value.Value; + +/** + * The request payload for the Update Service Route Binding operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#update-a-service-route-binding + */ +@JsonSerialize +@Value.Immutable +abstract class _UpdateServiceRouteBindingRequest { + + /** + * The service route binding id + */ + @JsonIgnore + abstract String getServiceRouteBindingId(); + + /** + * The metadata + */ + @JsonProperty("metadata") + @Nullable + abstract Metadata getMetadata(); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingResponse.java new file mode 100644 index 00000000000..76d03d5796f --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/serviceroutebindings/_UpdateServiceRouteBindingResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.immutables.value.Value; + +/** + * The response payload for the Update Service Route Binding operation + * + * see https://v3-apidocs.cloudfoundry.org/index.html#update-a-service-route-binding + */ +@JsonDeserialize +@Value.Immutable +abstract class _UpdateServiceRouteBindingResponse extends ServiceRouteBinding { + +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/CreateServiceRouteBindingRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/CreateServiceRouteBindingRequestTest.java new file mode 100644 index 00000000000..bb320e107ba --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/CreateServiceRouteBindingRequestTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.cloudfoundry.client.v3.Relationship; +import org.cloudfoundry.client.v3.ToOneRelationship; +import org.junit.jupiter.api.Test; + +final class CreateServiceRouteBindingRequestTest { + + @Test + void noRelationships() { + assertThrows( + IllegalStateException.class, + () -> CreateServiceRouteBindingRequest.builder().build()); + } + + @Test + void valid() { + CreateServiceRouteBindingRequest.builder() + .relationships( + ServiceRouteBindingRelationships.builder() + .route( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id("test-route-id") + .build()) + .build()) + .serviceInstance( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id("test-service-instance-id") + .build()) + .build()) + .build()) + .build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/DeleteServiceRouteBindingRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/DeleteServiceRouteBindingRequestTest.java new file mode 100644 index 00000000000..63ad678fb9b --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/DeleteServiceRouteBindingRequestTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +final class DeleteServiceRouteBindingRequestTest { + + @Test + void noServiceRouteBindingId() { + assertThrows( + IllegalStateException.class, + () -> DeleteServiceRouteBindingRequest.builder().build()); + } + + @Test + void valid() { + DeleteServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingParametersRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingParametersRequestTest.java new file mode 100644 index 00000000000..0b1880a8303 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingParametersRequestTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +final class GetServiceRouteBindingParametersRequestTest { + + @Test + void noServiceRouteBindingId() { + assertThrows( + IllegalStateException.class, + () -> GetServiceRouteBindingParametersRequest.builder().build()); + } + + @Test + void valid() { + GetServiceRouteBindingParametersRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingRequestTest.java new file mode 100644 index 00000000000..e18d6e76cb2 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/GetServiceRouteBindingRequestTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +final class GetServiceRouteBindingRequestTest { + + @Test + void noServiceRouteBindingId() { + assertThrows( + IllegalStateException.class, () -> GetServiceRouteBindingRequest.builder().build()); + } + + @Test + void valid() { + GetServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/ListServiceRouteBindingsRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/ListServiceRouteBindingsRequestTest.java new file mode 100644 index 00000000000..f6056cb11a8 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/ListServiceRouteBindingsRequestTest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import org.junit.jupiter.api.Test; + +final class ListServiceRouteBindingsRequestTest { + + @Test + void valid() { + ListServiceRouteBindingsRequest.builder().build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/RelationshipsTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/RelationshipsTest.java new file mode 100644 index 00000000000..da5cfce0d68 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/RelationshipsTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.cloudfoundry.client.v3.Relationship; +import org.cloudfoundry.client.v3.ToOneRelationship; +import org.junit.jupiter.api.Test; + +final class RelationshipsTest { + + @Test + void noRoute() { + assertThrows( + IllegalStateException.class, + () -> + ServiceRouteBindingRelationships.builder() + .serviceInstance( + ToOneRelationship.builder() + .data(Relationship.builder().id("test-id").build()) + .build()) + .build()); + } + + @Test + void noServiceInstance() { + assertThrows( + IllegalStateException.class, + () -> + ServiceRouteBindingRelationships.builder() + .route( + ToOneRelationship.builder() + .data(Relationship.builder().id("test-id").build()) + .build()) + .build()); + } + + @Test + void valid() { + ServiceRouteBindingRelationships.builder() + .route( + ToOneRelationship.builder() + .data(Relationship.builder().id("test-route-id").build()) + .build()) + .serviceInstance( + ToOneRelationship.builder() + .data(Relationship.builder().id("test-service-instance-id").build()) + .build()) + .build(); + } +} diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/UpdateServiceRouteBindingRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/UpdateServiceRouteBindingRequestTest.java new file mode 100644 index 00000000000..dab59429356 --- /dev/null +++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/serviceroutebindings/UpdateServiceRouteBindingRequestTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2026 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.client.v3.serviceroutebindings; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +final class UpdateServiceRouteBindingRequestTest { + + @Test + void noServiceRouteBindingId() { + assertThrows( + IllegalStateException.class, + () -> UpdateServiceRouteBindingRequest.builder().build()); + } + + @Test + void valid() { + UpdateServiceRouteBindingRequest.builder() + .serviceRouteBindingId("test-service-route-binding-id") + .build(); + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceRouteBindingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceRouteBindingsTest.java new file mode 100644 index 00000000000..d00b79db9aa --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceRouteBindingsTest.java @@ -0,0 +1,505 @@ +/* + * Copyright 2013-2026 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.client.v3; + +import static org.cloudfoundry.util.tuple.TupleUtils.function; + +import java.time.Duration; +import java.util.Collections; +import org.cloudfoundry.AbstractIntegrationTest; +import org.cloudfoundry.CloudFoundryVersion; +import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.Nullable; +import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.v3.domains.CreateDomainRequest; +import org.cloudfoundry.client.v3.domains.CreateDomainResponse; +import org.cloudfoundry.client.v3.domains.DomainRelationships; +import org.cloudfoundry.client.v3.routes.CreateRouteRequest; +import org.cloudfoundry.client.v3.routes.CreateRouteResponse; +import org.cloudfoundry.client.v3.routes.RouteRelationships; +import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceRequest; +import org.cloudfoundry.client.v3.serviceinstances.CreateServiceInstanceResponse; +import org.cloudfoundry.client.v3.serviceinstances.ServiceInstance; +import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceRelationships; +import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.CreateServiceRouteBindingResponse; +import org.cloudfoundry.client.v3.serviceroutebindings.DeleteServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.GetServiceRouteBindingRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.ListServiceRouteBindingsRequest; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBinding; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingRelationships; +import org.cloudfoundry.client.v3.serviceroutebindings.ServiceRouteBindingResource; +import org.cloudfoundry.client.v3.serviceroutebindings.UpdateServiceRouteBindingRequest; +import org.cloudfoundry.util.JobUtils; +import org.cloudfoundry.util.PaginationUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +@IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_11) +public class ServiceRouteBindingsTest extends AbstractIntegrationTest { + + @Autowired private CloudFoundryClient cloudFoundryClient; + + @Autowired private Mono organizationId; + + @Autowired private Mono spaceId; + + @Test + public void create() { + String domainName = this.nameFactory.getDomainName(); + String serviceInstanceName = this.nameFactory.getServiceInstanceName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + createRouteId( + this.cloudFoundryClient, domainId, spaceId), + createUserProvidedServiceInstanceId( + this.cloudFoundryClient, + serviceInstanceName, + spaceId)))) + .flatMap( + function( + (routeId, serviceInstanceId) -> + this.cloudFoundryClient + .serviceRouteBindingsV3() + .create( + CreateServiceRouteBindingRequest.builder() + .relationships( + ServiceRouteBindingRelationships + .builder() + .route( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + routeId) + .build()) + .build()) + .serviceInstance( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + serviceInstanceId) + .build()) + .build()) + .build()) + .build()))) + .map(response -> response.getServiceRouteBinding().isPresent()) + .as(StepVerifier::create) + .expectNext(true) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void delete() { + String domainName = this.nameFactory.getDomainName(); + String serviceInstanceName = this.nameFactory.getServiceInstanceName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + createRouteId( + this.cloudFoundryClient, domainId, spaceId), + createUserProvidedServiceInstanceId( + this.cloudFoundryClient, + serviceInstanceName, + spaceId)))) + .flatMap( + function( + (routeId, serviceInstanceId) -> + createServiceRouteBinding( + this.cloudFoundryClient, + routeId, + serviceInstanceId) + .map(ServiceRouteBinding::getId) + .flatMap( + bindingId -> + this.cloudFoundryClient + .serviceRouteBindingsV3() + .delete( + DeleteServiceRouteBindingRequest + .builder() + .serviceRouteBindingId( + bindingId) + .build())) + .thenMany( + requestListServiceRouteBindings( + this.cloudFoundryClient, routeId)) + .hasElements())) + .as(StepVerifier::create) + .expectNext(false) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void get() { + String domainName = this.nameFactory.getDomainName(); + String serviceInstanceName = this.nameFactory.getServiceInstanceName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + createRouteId( + this.cloudFoundryClient, domainId, spaceId), + createUserProvidedServiceInstanceId( + this.cloudFoundryClient, + serviceInstanceName, + spaceId)))) + .flatMap( + function( + (routeId, serviceInstanceId) -> + createServiceRouteBinding( + this.cloudFoundryClient, + routeId, + serviceInstanceId) + .map(ServiceRouteBinding::getId) + .flatMap( + bindingId -> + this.cloudFoundryClient + .serviceRouteBindingsV3() + .get( + GetServiceRouteBindingRequest + .builder() + .serviceRouteBindingId( + bindingId) + .build())) + .map( + binding -> + binding.getRelationships() + .getServiceInstance() + .getData() + .getId()) + .zipWith(Mono.just(serviceInstanceId)))) + .as(StepVerifier::create) + .consumeNextWith(tupleEquality()) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void list() { + String domainName = this.nameFactory.getDomainName(); + String serviceInstanceName = this.nameFactory.getServiceInstanceName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + createRouteId( + this.cloudFoundryClient, domainId, spaceId), + createUserProvidedServiceInstanceId( + this.cloudFoundryClient, + serviceInstanceName, + spaceId)))) + .flatMap( + function( + (routeId, serviceInstanceId) -> + createServiceRouteBinding( + this.cloudFoundryClient, + routeId, + serviceInstanceId) + .thenMany( + requestListServiceRouteBindings( + this.cloudFoundryClient, routeId)) + .map(ServiceRouteBindingResource::getRelationships) + .map( + relationships -> + relationships + .getServiceInstance() + .getData() + .getId()) + .single() + .zipWith(Mono.just(serviceInstanceId)))) + .as(StepVerifier::create) + .consumeNextWith(tupleEquality()) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void update() { + String domainName = this.nameFactory.getDomainName(); + String serviceInstanceName = this.nameFactory.getServiceInstanceName(); + + this.organizationId + .flatMap( + organizationId -> + Mono.zip( + createDomainId( + this.cloudFoundryClient, + domainName, + organizationId), + this.spaceId)) + .flatMap( + function( + (domainId, spaceId) -> + Mono.zip( + createRouteId( + this.cloudFoundryClient, domainId, spaceId), + createUserProvidedServiceInstanceId( + this.cloudFoundryClient, + serviceInstanceName, + spaceId)))) + .flatMap( + function( + (routeId, serviceInstanceId) -> + createServiceRouteBinding( + this.cloudFoundryClient, + routeId, + serviceInstanceId))) + .map(ServiceRouteBinding::getId) + .flatMap( + bindingId -> + this.cloudFoundryClient + .serviceRouteBindingsV3() + .update( + UpdateServiceRouteBindingRequest.builder() + .serviceRouteBindingId(bindingId) + .metadata( + Metadata.builder() + .label( + "test-label", + "test-label-value") + .build()) + .build())) + .map(binding -> binding.getMetadata().getLabels()) + .as(StepVerifier::create) + .expectNext(Collections.singletonMap("test-label", "test-label-value")) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + private static Mono createDomainId( + CloudFoundryClient cloudFoundryClient, String domainName, String organizationId) { + return requestCreateDomain(cloudFoundryClient, domainName, organizationId) + .map(CreateDomainResponse::getId); + } + + private static Mono createRouteId( + CloudFoundryClient cloudFoundryClient, String domainId, String spaceId) { + return requestCreateRoute(cloudFoundryClient, domainId, spaceId) + .map(CreateRouteResponse::getId); + } + + private static Mono createUserProvidedServiceInstanceId( + CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { + return requestCreateUserProvidedServiceInstance( + cloudFoundryClient, serviceInstanceName, spaceId) + .map( + response -> + response.getServiceInstance() + .map(ServiceInstance::getId) + .orElseThrow( + () -> + new IllegalStateException( + "Service instance not returned" + + " synchronously"))); + } + + private static Mono createServiceRouteBinding( + CloudFoundryClient cloudFoundryClient, String routeId, String serviceInstanceId) { + return requestCreateServiceRouteBinding(cloudFoundryClient, routeId, serviceInstanceId) + .flatMap( + response -> { + if (response.getJobId().isPresent()) { + return JobUtils.waitForCompletion( + cloudFoundryClient, + Duration.ofMinutes(5), + response.getJobId().get()) + .thenMany( + requestListServiceRouteBindingsByServiceInstance( + cloudFoundryClient, serviceInstanceId)) + .single(); + } + return Mono.just(response.getServiceRouteBinding().get()); + }); + } + + private static Mono requestCreateDomain( + CloudFoundryClient cloudFoundryClient, String domainName, String organizationId) { + return cloudFoundryClient + .domainsV3() + .create( + CreateDomainRequest.builder() + .internal(false) + .name(domainName) + .relationships( + DomainRelationships.builder() + .organization( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id(organizationId) + .build()) + .build()) + .build()) + .build()); + } + + private static Mono requestCreateRoute( + CloudFoundryClient cloudFoundryClient, String domainId, String spaceId) { + return cloudFoundryClient + .routesV3() + .create( + CreateRouteRequest.builder() + .relationships( + RouteRelationships.builder() + .domain( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id(domainId) + .build()) + .build()) + .space( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id(spaceId) + .build()) + .build()) + .build()) + .build()); + } + + private static Mono requestCreateUserProvidedServiceInstance( + CloudFoundryClient cloudFoundryClient, String serviceInstanceName, String spaceId) { + return cloudFoundryClient + .serviceInstancesV3() + .create( + CreateServiceInstanceRequest.builder() + .type(ServiceInstanceType.USER_PROVIDED) + .name(serviceInstanceName) + .routeServiceUrl("https://route-service.example.com") + .relationships( + ServiceInstanceRelationships.builder() + .space( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id(spaceId) + .build()) + .build()) + .build()) + .build()); + } + + private static Mono requestCreateServiceRouteBinding( + CloudFoundryClient cloudFoundryClient, String routeId, String serviceInstanceId) { + return cloudFoundryClient + .serviceRouteBindingsV3() + .create( + CreateServiceRouteBindingRequest.builder() + .relationships( + ServiceRouteBindingRelationships.builder() + .route( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id(routeId) + .build()) + .build()) + .serviceInstance( + ToOneRelationship.builder() + .data( + Relationship.builder() + .id( + serviceInstanceId) + .build()) + .build()) + .build()) + .build()); + } + + private static Flux requestListServiceRouteBindings( + CloudFoundryClient cloudFoundryClient, @Nullable String routeId) { + return PaginationUtils.requestClientV3Resources( + page -> { + ListServiceRouteBindingsRequest.Builder builder = + ListServiceRouteBindingsRequest.builder().page(page); + if (routeId != null) { + builder.routeId(routeId); + } + return cloudFoundryClient.serviceRouteBindingsV3().list(builder.build()); + }); + } + + private static Flux + requestListServiceRouteBindingsByServiceInstance( + CloudFoundryClient cloudFoundryClient, String serviceInstanceId) { + return PaginationUtils.requestClientV3Resources( + page -> + cloudFoundryClient + .serviceRouteBindingsV3() + .list( + ListServiceRouteBindingsRequest.builder() + .page(page) + .serviceInstanceId(serviceInstanceId) + .build())); + } +} From 372b8d2e7a7e4faa3345fee16f9cf1e028b0dcd2 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Thu, 1 Oct 2026 16:09:34 +0200 Subject: [PATCH 18/26] Minor fixes to some integration tests (#1364) InfoTest fails if running against a CF where v2 API is disabled. OrganizationsTest fails when running in kind-deployment landscape. The package "serviceInstances" (with capital "I") does not match the package declaration in the contained class. This creates problems in Eclipse on Windows. The old docker image in ApplicationsTest.pushNewDocker was outdated and did not work on recent docker versions. Switching to the image of a better supported test application. Fixes #1363 --- .../ReactorServiceInstancesV3Test.java | 0 .../org/cloudfoundry/client/v2/InfoTest.java | 11 +++++++++-- .../org/cloudfoundry/client/v2/SpacesTest.java | 13 +++++-------- .../client/v3/OrganizationsTest.java | 8 +++++--- .../org/cloudfoundry/client/v3/PackagesTest.java | 16 ++++++++++++++-- .../operations/ApplicationsTest.java | 2 +- .../java/org/cloudfoundry/uaa/TokensTest.java | 12 +++++++----- 7 files changed, 41 insertions(+), 21 deletions(-) rename cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/{serviceInstances => serviceinstances}/ReactorServiceInstancesV3Test.java (100%) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java similarity index 100% rename from cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceInstances/ReactorServiceInstancesV3Test.java rename to cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java index a4395079a54..b94c87a1878 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java @@ -43,8 +43,15 @@ public void info() { .consumeNextWith( response -> { Version expected = Version.valueOf(SUPPORTED_API_VERSION); - Version actual = Version.valueOf(response.getApiVersion()); - + Version actual; + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + assertThat("CF API v2 is disabled") + .isEqualTo(response.getSupport()); + actual = Version.of(0, 0, 0); + } else { + actual = Version.valueOf(version); + } assertThat(actual).isLessThanOrEqualTo(expected); }) .expectComplete() diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java index 54fc74e861c..ee2e2fa6f64 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java @@ -24,6 +24,7 @@ import java.time.Instant; import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.function.Function; import java.util.function.UnaryOperator; import org.cloudfoundry.AbstractIntegrationTest; @@ -109,7 +110,6 @@ import org.cloudfoundry.util.JobUtils; import org.cloudfoundry.util.PaginationUtils; import org.cloudfoundry.util.ResourceUtils; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Flux; @@ -1885,13 +1885,9 @@ public void listServicesFilterByLabel() { .verify(Duration.ofMinutes(5)); } - // TODO: Await https://github.com/cloudfoundry/cloud_controller_ng/issues/856 for this test to - // work - @Disabled( - "Await https://github.com/cloudfoundry/cloud_controller_ng/issues/856 for this test to" - + " work") @Test public void listServicesFilterByServiceBrokerId() { + List expectedValues = List.of(this.serviceName, this.serviceName + "-shareable"); Mono.zip(this.serviceBrokerId, this.spaceId) .flatMapMany( function( @@ -1903,9 +1899,10 @@ public void listServicesFilterByServiceBrokerId() { builder.serviceBrokerId(serviceBrokerId)))) .map(response -> response.getEntity().getLabel()) .as(StepVerifier::create) - .expectNext(this.serviceName) + .consumeNextWith(element -> assertThat(element).isIn(expectedValues)) + .consumeNextWith(element -> assertThat(element).isIn(expectedValues)) .expectComplete() - .verify(Duration.ofMinutes(5)); + .verify(Duration.ofMinutes(10)); } @Test diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java index e3da7d85e66..68432711348 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationsTest.java @@ -168,9 +168,12 @@ public void get() { @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_7) @Test + /* the default domain name is configured by the admin during deployment of the cf landscape. + * There is no way to guarantee that it matches the APIHOST or any substring of it. + * Therefore we only check if the endpoint can be called and returns something. + */ public void getDefaultDomain() { String organizationName = this.nameFactory.getOrganizationName(); - createOrganizationId(this.cloudFoundryClient, organizationName) .flatMap( organizationId -> @@ -182,8 +185,7 @@ public void getDefaultDomain() { .build())) .map(GetOrganizationDefaultDomainResponse::getName) .as(StepVerifier::create) - .consumeNextWith( - name -> assertThat(name).contains("apps.", ".shepherd.tanzu.broadcom.net")) + .consumeNextWith(name -> assertThat(name).isNotEmpty()) .expectComplete() .verify(Duration.ofMinutes(5)); } diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/PackagesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/PackagesTest.java index 967923af843..e35bf3a026c 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/PackagesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/PackagesTest.java @@ -30,9 +30,9 @@ import org.cloudfoundry.client.v3.packages.CreatePackageRequest; import org.cloudfoundry.client.v3.packages.GetPackageRequest; import org.cloudfoundry.client.v3.packages.Package; +import org.cloudfoundry.client.v3.packages.PackageRelationships; import org.cloudfoundry.client.v3.packages.PackageType; import org.cloudfoundry.client.v3.packages.UploadPackageRequest; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; @@ -40,7 +40,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -@Disabled("Until Packages are no longer experimental") public final class PackagesTest extends AbstractIntegrationTest { @Autowired private CloudFoundryClient cloudFoundryClient; @@ -81,6 +80,19 @@ public void upload() { .create( CreatePackageRequest.builder() .type(PackageType.BITS) + .relationships( + PackageRelationships.builder() + .application( + ToOneRelationship + .builder() + .data( + Relationship + .builder() + .id( + applicationId) + .build()) + .build()) + .build()) .build())) .map(Package::getId) .flatMap( diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 13b9fc1d3b6..d84a8207949 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -2020,7 +2020,7 @@ private static Mono createDockerApplication( .push( PushApplicationRequest.builder() .diskQuota(512) - .dockerImage("cloudfoundry/lattice-app") + .dockerImage("cloudfoundry/grace") .healthCheckType(ApplicationHealthCheck.PORT) .memory(64) .name(name) diff --git a/integration-test/src/test/java/org/cloudfoundry/uaa/TokensTest.java b/integration-test/src/test/java/org/cloudfoundry/uaa/TokensTest.java index 39d956f801b..0c9e1070532 100644 --- a/integration-test/src/test/java/org/cloudfoundry/uaa/TokensTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/uaa/TokensTest.java @@ -125,11 +125,13 @@ public void getTokenByClientCredentials() { .verify(Duration.ofMinutes(5)); } - // TODO: Ready to Implement - Await https://github.com/cloudfoundry/cf-java-client/issues/862 to - // get passcode - @Disabled( - "Ready to Implement - Await https://github.com/cloudfoundry/cf-java-client/issues/862" - + " to get passcode") + // When implemented, will fix issue: https://github.com/cloudfoundry/cf-java-client/issues/910. + // Could be done similarly to "getTokenByAuthorizationCode". + // First user_token grant type must be implented. + // See: https://docs.cloudfoundry.org/api/uaa/index.html#user-token-grant + // Then this endpoint must be called first, to create the token. + // See: https://docs.cloudfoundry.org/api/uaa/index.html#saml2-bearer-grant + @Disabled("Ready to Implement") @Test public void getTokenByOneTimePasscode() { this.uaaClient From e4fd9646540f79c84fc56f78c9849d1604d06e23 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Fri, 2 Oct 2026 11:18:03 +0200 Subject: [PATCH 19/26] Fix UAA Authorizations accept headers (#1379) Fixes #1374 --- .../authorizations/ReactorAuthorizations.java | 50 ++++++++++++++++--- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java index f929b35dda2..589772451d4 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java @@ -18,6 +18,8 @@ import static io.netty.handler.codec.http.HttpHeaderNames.AUTHORIZATION; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; import io.netty.util.AsciiString; import java.util.Map; @@ -99,7 +101,12 @@ public Mono authorizationCodeGrantBrowser( builder -> builder.pathSegment("oauth", "authorize") .queryParam("response_type", ResponseType.CODE), - outbound -> {}, + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }, ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -114,7 +121,12 @@ public Mono authorizationCodeGrantHybrid( builder.pathSegment("oauth", "authorize") .queryParam( "response_type", ResponseType.CODE_AND_ID_TOKEN), - outbound -> {}, + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }, ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -126,7 +138,8 @@ public Mono getOpenIdProviderConfigurati return get( request, GetOpenIdProviderConfigurationResponse.class, - builder -> builder.pathSegment(".well-known", "openid-configuration")) + builder -> builder.pathSegment(".well-known", "openid-configuration"), + ReactorAuthorizations::removeAuthorization) .checkpoint(); } @@ -137,7 +150,12 @@ public Mono implicitGrantBrowser(AuthorizeByImplicitGrantBrowserRequest builder -> builder.pathSegment("oauth", "authorize") .queryParam("response_type", ResponseType.TOKEN), - outbound -> {}, + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }, ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -152,7 +170,12 @@ public Mono openIdWithAuthorizationCodeAndIdToken( builder.pathSegment("oauth", "authorize") .queryParam( "response_type", ResponseType.CODE_AND_ID_TOKEN), - outbound -> {}, + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }, ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -164,7 +187,14 @@ public Mono openIdWithIdToken(AuthorizeByOpenIdWithIdTokenRequest reques request, builder -> builder.pathSegment("oauth", "authorize") - .queryParam("response_type", ResponseType.ID_TOKEN)) + .queryParam("response_type", ResponseType.ID_TOKEN), + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }, + ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); } @@ -177,7 +207,13 @@ public Mono openIdWithTokenAndIdToken( builder -> builder.pathSegment("oauth", "authorize") .queryParam( - "response_type", ResponseType.TOKEN_AND_ID_TOKEN)) + "response_type", ResponseType.TOKEN_AND_ID_TOKEN), + outbound -> { + outbound.remove(HttpHeaderNames.ACCEPT); + outbound.add( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); + }) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); } From 042177d90191816bd10376f42a70785366e7c77b Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Fri, 2 Oct 2026 11:22:32 +0200 Subject: [PATCH 20/26] Refactor authorization request accept header management --- .../authorizations/ReactorAuthorizations.java | 60 ++++++++----------- .../client/CloudFoundryClient.java | 1 - .../IntegrationTestConfiguration.java | 2 +- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java index 589772451d4..833248a430d 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/authorizations/ReactorAuthorizations.java @@ -101,12 +101,10 @@ public Mono authorizationCodeGrantBrowser( builder -> builder.pathSegment("oauth", "authorize") .queryParam("response_type", ResponseType.CODE), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }, + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED), ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -121,12 +119,10 @@ public Mono authorizationCodeGrantHybrid( builder.pathSegment("oauth", "authorize") .queryParam( "response_type", ResponseType.CODE_AND_ID_TOKEN), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }, + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED), ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -150,12 +146,10 @@ public Mono implicitGrantBrowser(AuthorizeByImplicitGrantBrowserRequest builder -> builder.pathSegment("oauth", "authorize") .queryParam("response_type", ResponseType.TOKEN), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }, + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED), ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -170,12 +164,10 @@ public Mono openIdWithAuthorizationCodeAndIdToken( builder.pathSegment("oauth", "authorize") .queryParam( "response_type", ResponseType.CODE_AND_ID_TOKEN), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }, + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED), ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -188,12 +180,10 @@ public Mono openIdWithIdToken(AuthorizeByOpenIdWithIdTokenRequest reques builder -> builder.pathSegment("oauth", "authorize") .queryParam("response_type", ResponseType.ID_TOKEN), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }, + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED), ReactorAuthorizations::removeAuthorization) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); @@ -208,12 +198,10 @@ public Mono openIdWithTokenAndIdToken( builder.pathSegment("oauth", "authorize") .queryParam( "response_type", ResponseType.TOKEN_AND_ID_TOKEN), - outbound -> { - outbound.remove(HttpHeaderNames.ACCEPT); - outbound.add( - HttpHeaderNames.ACCEPT, - HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED); - }) + outbound -> + outbound.set( + HttpHeaderNames.ACCEPT, + HttpHeaderValues.APPLICATION_X_WWW_FORM_URLENCODED)) .map(inbound -> inbound.responseHeaders().get(LOCATION)) .checkpoint(); } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index 7875dca4174..3d587dcfe38 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -395,5 +395,4 @@ public interface CloudFoundryClient { * See https://v3-apidocs.cloudfoundry.org/index.html#service-route-binding */ ServiceRouteBindingsV3 serviceRouteBindingsV3(); - } diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 0b44644ca9a..63f062df5af 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -315,7 +315,7 @@ DefaultConnectionContext connectionContext( .apiHost(apiHost) .skipSslValidation(skipSslValidation) .sslHandshakeTimeout(Duration.ofSeconds(30)); - if(failOnUnknownProperties) { + if (failOnUnknownProperties) { connectionContext.problemHandler( new FailingDeserializationProblemHandler()); // Test-only problem handler } From d6159332fec5251d2700396677b75352be438896 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Mon, 5 Oct 2026 11:51:57 +0200 Subject: [PATCH 21/26] Fix integration test compilation for JDK8 --- .../src/test/java/org/cloudfoundry/client/v2/SpacesTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java index ee2e2fa6f64..e729b3d12a6 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/SpacesTest.java @@ -22,6 +22,7 @@ import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; @@ -1887,7 +1888,7 @@ public void listServicesFilterByLabel() { @Test public void listServicesFilterByServiceBrokerId() { - List expectedValues = List.of(this.serviceName, this.serviceName + "-shareable"); + List expectedValues = Arrays.asList(this.serviceName, this.serviceName + "-shareable"); Mono.zip(this.serviceBrokerId, this.spaceId) .flatMapMany( function( From a4ad3db3853ae490fa291166d9dfa143b48dc745 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Mon, 5 Oct 2026 16:12:04 +0200 Subject: [PATCH 22/26] v5.19.0.BUILD-SNAPSHOT Development --- cloudfoundry-client-reactor/pom.xml | 2 +- cloudfoundry-client/pom.xml | 2 +- cloudfoundry-operations/pom.xml | 2 +- cloudfoundry-util/pom.xml | 2 +- integration-test/pom.xml | 2 +- pom.xml | 2 +- test-log-cache/pom.xml | 2 +- test-service-broker/pom.xml | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cloudfoundry-client-reactor/pom.xml b/cloudfoundry-client-reactor/pom.xml index c7159eda543..fe87b98793b 100644 --- a/cloudfoundry-client-reactor/pom.xml +++ b/cloudfoundry-client-reactor/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT cloudfoundry-client-reactor diff --git a/cloudfoundry-client/pom.xml b/cloudfoundry-client/pom.xml index f0b62b1b885..ffa791439c7 100644 --- a/cloudfoundry-client/pom.xml +++ b/cloudfoundry-client/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT cloudfoundry-client diff --git a/cloudfoundry-operations/pom.xml b/cloudfoundry-operations/pom.xml index afed2ca440b..409c3cfdae8 100644 --- a/cloudfoundry-operations/pom.xml +++ b/cloudfoundry-operations/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT cloudfoundry-operations diff --git a/cloudfoundry-util/pom.xml b/cloudfoundry-util/pom.xml index f65b35c6d93..364105cc173 100644 --- a/cloudfoundry-util/pom.xml +++ b/cloudfoundry-util/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT cloudfoundry-util diff --git a/integration-test/pom.xml b/integration-test/pom.xml index d52a44a9bdc..ee663deabb2 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT integration-test diff --git a/pom.xml b/pom.xml index ba7873635d5..0c40fa6ca35 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ cloudfoundry-java-client Cloud Foundry Java Client Parent A Java language binding for interacting with a Cloud Foundry instance - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT pom https://github.com/cloudfoundry/cf-java-client diff --git a/test-log-cache/pom.xml b/test-log-cache/pom.xml index 2e1fa1f4f20..53ff8b615a1 100644 --- a/test-log-cache/pom.xml +++ b/test-log-cache/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT test-log-cache diff --git a/test-service-broker/pom.xml b/test-service-broker/pom.xml index bbbc50202c8..76cad098a7e 100644 --- a/test-service-broker/pom.xml +++ b/test-service-broker/pom.xml @@ -25,7 +25,7 @@ org.cloudfoundry cloudfoundry-java-client - 5.18.0.BUILD-SNAPSHOT + 5.19.0.BUILD-SNAPSHOT test-service-broker From 6ed09b7a95f72979682bf35af1bc118f6702799e Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Thu, 1 Oct 2026 16:07:52 +0200 Subject: [PATCH 23/26] enable validation in branch 5.x.x (#1377) --- .github/workflows/ci-java.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml index 5ccde5ca075..b1cf12b2a49 100644 --- a/.github/workflows/ci-java.yml +++ b/.github/workflows/ci-java.yml @@ -4,9 +4,11 @@ on: push: branches: - main + - 5.x.x pull_request: branches: - main + - 5.x.x jobs: build: From a96d89c3955551a03f3df21367a2c548e1e5bcc1 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 13 Oct 2026 14:26:33 +0200 Subject: [PATCH 24/26] ci: Add GitHub workflow for integration tests on multiple Java versions (#1381) --- .github/scripts/resolve-capi-version.sh | 48 +++++++++ .github/workflows/ci-java.yml | 6 ++ .github/workflows/integration-test.yml | 125 ++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100755 .github/scripts/resolve-capi-version.sh create mode 100644 .github/workflows/integration-test.yml diff --git a/.github/scripts/resolve-capi-version.sh b/.github/scripts/resolve-capi-version.sh new file mode 100755 index 00000000000..5ff2dfec330 --- /dev/null +++ b/.github/scripts/resolve-capi-version.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Prints the newest capi-release version whose CC API (v2) version equals SUPPORTED_API_VERSION +# from CloudFoundryClient.java. Needs only git and curl, no GitHub API and so no token. +# +# A capi-release tag pins cloud_controller_ng as a submodule, and that repo records its v2 API +# version in config/version_v2. The release notes aren't reliable for this. The v2 version only +# grows with the tags, so a binary search over the sorted tags is enough. +set -euo pipefail + +source_file="$(dirname "$0")/../../cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java" +target="$(sed -n 's/.*String SUPPORTED_API_VERSION = "\([0-9.]*\)";.*/\1/p' "$source_file")" +[ -n "$target" ] || { echo "SUPPORTED_API_VERSION not found in $source_file" >&2; exit 1; } + +workdir="$(mktemp -d)" +trap 'rm -rf "$workdir"' EXIT +# commits and trees only, which is all it takes to read the submodule commit of a tag +git clone --quiet --bare --filter=blob:none https://github.com/cloudfoundry/capi-release.git "$workdir/capi-release" + +mapfile -t tags < <(git -C "$workdir/capi-release" tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V) + +v2_version() { + local sha + sha="$(git -C "$workdir/capi-release" ls-tree "$1" src/cloud_controller_ng | awk '{print $3}')" + curl -fsSL --retry 3 "https://raw.githubusercontent.com/cloudfoundry/cloud_controller_ng/$sha/config/version_v2" | tr -d '[:space:]' +} + +# highest index whose v2 version is <= target +lo=0 +hi=$((${#tags[@]} - 1)) +found=-1 +while [ "$lo" -le "$hi" ]; do + mid=$(((lo + hi) / 2)) + v="$(v2_version "${tags[$mid]}")" + if [ "$(printf '%s\n%s\n' "$v" "$target" | sort -V | tail -1)" = "$target" ]; then + found=$mid + lo=$((mid + 1)) + else + hi=$((mid - 1)) + fi +done + +if [ "$found" -lt 0 ] || [ "$(v2_version "${tags[$found]}")" != "$target" ]; then + echo "No capi-release found for CC API version $target" >&2 + exit 1 +fi + +echo "CC API $target -> capi-release ${tags[$found]}" >&2 +echo "${tags[$found]}" diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml index b1cf12b2a49..3d059841490 100644 --- a/.github/workflows/ci-java.yml +++ b/.github/workflows/ci-java.yml @@ -41,3 +41,9 @@ jobs: name: Check style with Spotless run: ./mvnw spotless:check -Pintegration-test + integration-test: + needs: build + uses: ./.github/workflows/integration-test.yml + with: + java-versions: '[21]' + diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml new file mode 100644 index 00000000000..b703c62b452 --- /dev/null +++ b/.github/workflows/integration-test.yml @@ -0,0 +1,125 @@ +name: Integration Tests + +on: + workflow_dispatch: + inputs: + java-versions: + description: 'JSON array of Java versions to test' + type: string + required: false + default: '[8, 11, 17, 21]' + preview: + description: 'Deploy the latest cf-deployment versions instead of the default ones' + type: boolean + required: false + default: false + schedule: + - cron: '0 3 * * 1-5' + workflow_call: + inputs: + java-versions: + description: 'JSON array of Java versions to test' + type: string + required: false + default: '[8, 11, 17, 21]' + preview: + description: 'Deploy the latest cf-deployment versions instead of the default ones' + type: boolean + required: false + default: false + +permissions: + contents: read + +# A new push supersedes the running test. The group name differs from the caller's workflow name on +# purpose: a called workflow shares github.workflow with its caller, which would cancel the caller. +concurrency: + group: integration-test-${{ github.event_name == 'schedule' && 'schedule' || github.ref }} + cancel-in-progress: true + +env: + CF_API_HOST: api.cf.127-0-0-1.nip.io + CF_UAA_HOST: uaa.cf.127-0-0-1.nip.io + +jobs: + integration-test: + if: ${{ github.event_name != 'schedule' || github.repository == 'cloudfoundry/cf-java-client' }} + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + matrix: + java: ${{ fromJSON(inputs.java-versions || (github.event_name == 'schedule' && '[21]' || '[8, 11, 17, 21]')) }} + name: Java ${{ matrix.java }} integration test${{ (inputs.preview || github.event_name == 'schedule') && ' (preview)' || '' }} + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + # Validation runs deploy the capi-release that matches SUPPORTED_API_VERSION. Preview runs + # (scheduled, or started by hand with `preview`) keep the latest versions. + - name: Resolve CAPI version + id: capi + if: ${{ !(inputs.preview || github.event_name == 'schedule') }} + run: | + # separate assignment, so that a failing resolver fails the step + version="$(.github/scripts/resolve-capi-version.sh)" + echo "version=$version" >> "$GITHUB_OUTPUT" + # All components share one Postgres, which runs out of its default 100 connections during a full + # test run. Written first, the CAPI override below appends to the same file. + - name: Write PostgreSQL values override + run: | + cat > "$RUNNER_TEMP/cf-values.yaml" <> "$RUNNER_TEMP/cf-values.yaml" < Date: Tue, 13 Oct 2026 14:39:59 +0200 Subject: [PATCH 25/26] minor corrections to integration tests (#1387) --- .../ReactorServiceInstancesV3Test.java | 3 +- .../client/v2/BlobstoresTest.java | 2 +- .../v2/ServicePlanVisibilitiesTest.java | 10 +++---- .../client/v3/ServiceBindingsTest.java | 2 +- .../client/v3/ServicePlansTest.java | 4 +-- .../operations/ApplicationsTest.java | 29 +++++++++++++++++-- .../cloudfoundry/operations/RoutesTest.java | 2 -- 7 files changed, 36 insertions(+), 16 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java index 8e9ef1dd640..1e6df1f2585 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.cloudfoundry.reactor.client.v3.serviceInstances; +package org.cloudfoundry.reactor.client.v3.serviceinstances; import static io.netty.handler.codec.http.HttpMethod.DELETE; import static io.netty.handler.codec.http.HttpMethod.GET; @@ -62,7 +62,6 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; -import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java index 223f55eb4ed..283ac47f416 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/BlobstoresTest.java @@ -49,6 +49,6 @@ public void delete() { this.cloudFoundryClient, Duration.ofMinutes(5), job)) .as(StepVerifier::create) .expectComplete() - .verify(Duration.ofMinutes(5)); + .verify(Duration.ofMinutes(10)); } } diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java index e2a9ecbece2..6b0ea71e72d 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/ServicePlanVisibilitiesTest.java @@ -78,7 +78,7 @@ public void create() { serviceName, spaceId, false)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); Mono.zip( this.organizationId, @@ -143,7 +143,7 @@ public void delete() { serviceName, spaceId, false)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); Mono.zip( this.organizationId, @@ -277,7 +277,7 @@ public void get() { serviceName, spaceId, false)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); Mono.zip( this.organizationId, @@ -500,7 +500,7 @@ public void listFilterByServicePlanId() { serviceName, spaceId, false)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); Mono.zip( this.organizationId, @@ -573,7 +573,7 @@ public void update() { serviceName, spaceId, false)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); Mono.zip( this.organizationId, diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java index 3c0695a2da8..95d107a7cad 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServiceBindingsTest.java @@ -368,7 +368,7 @@ public void getDetails() { .as(StepVerifier::create) .expectNext(Collections.singletonMap("foo", "bar")) .expectComplete() - .verify(Duration.ofMinutes(5)); + .verify(Duration.ofMinutes(10)); } private static Mono createManagedServiceInstanceId( diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java index c749b3e23de..fd820af7f35 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/ServicePlansTest.java @@ -80,7 +80,7 @@ public void delete() { serviceName, spaceId, true)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); getServicePlanId(this.cloudFoundryClient, serviceBrokerMetadata.serviceBrokerId) .flatMap( @@ -289,7 +289,7 @@ public void update() { serviceName, spaceId, true)) - .block(Duration.ofMinutes(5)); + .block(Duration.ofMinutes(10)); getServicePlanId(this.cloudFoundryClient, serviceBrokerMetadata.serviceBrokerId) .flatMap( diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index d84a8207949..ed8319bafc0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -1798,10 +1798,33 @@ public void terminateTask() throws IOException { .sequenceId(sequenceId) .build()) .thenReturn(sequenceId)) - .flatMapMany( + .flatMap( sequenceId -> - requestListTasks(this.cloudFoundryOperations, applicationName) - .filter(task -> sequenceId.equals(task.getSequenceId()))) + Mono.defer( + () -> + requestListTasks( + this.cloudFoundryOperations, + applicationName) + .filter( + task -> + sequenceId.equals( + task + .getSequenceId())) + .next() + .flatMap( + task -> + TaskState.CANCELING + .equals( + task + .getState()) + ? Mono + .empty() + : Mono.just( + task))) + .repeatWhenEmpty( + repeat -> + repeat.delayElements( + Duration.ofSeconds(2)))) .map(Task::getState) .as(StepVerifier::create) .expectNext(TaskState.FAILED) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java index 47a37e3fb91..76c16e9691e 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/RoutesTest.java @@ -43,7 +43,6 @@ import org.cloudfoundry.operations.routes.UnmapRouteRequest; import org.cloudfoundry.operations.services.BindRouteServiceInstanceRequest; import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; @@ -274,7 +273,6 @@ public void deleteInvalidDomain() { } @Test - @Disabled("fails often for no reasons") public void deleteOrphanedRoutes() { String domainName = this.nameFactory.getDomainName(); String hostName = this.nameFactory.getHostName(); From 4e8ad2e6f265f714a25c7c8d96b8601ce2b99fab Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 16 Oct 2026 14:14:27 +0200 Subject: [PATCH 26/26] feat: Adapt the client and the integration tests to the KinD based CAPI (#1383) * fix: Adapt the client and the integration tests to the KinD based CAPI Make the integration tests pass against a CAPI deployed on KinD. Client: - Send the "resources" field with every bits upload (v3 packages) and also as query parameter for v2 uploads without matched resources, as CAPI otherwise rejects them (CF-AppBitsUploadInvalid / missing :resources). - Add the CA certificates of the LDAP and SAML identity provider configurations. Integration tests: - Reserve the TCP ports handed out by the NameFactory on the default-tcp router group at startup, so the TCP route tests don't depend on the test order or the deployment's default range. Skipped together with the TCP routing tests (SKIP_TCP_ROUTING_TESTS). --- .../applications/ReactorApplicationsV2.java | 16 +++++- .../client/v3/packages/ReactorPackages.java | 12 +++- .../ReactorApplicationsV2Test.java | 55 +++++++++++++++++++ .../v3/packages/ReactorPackagesTest.java | 14 +++++ .../client/CloudFoundryClient.java | 2 +- .../identityproviders/_LdapConfiguration.java | 9 +++ .../identityproviders/_SamlConfiguration.java | 7 +++ .../IntegrationTestConfiguration.java | 53 ++++++++++++++++++ .../org/cloudfoundry/RandomNameFactory.java | 5 ++ 9 files changed, 168 insertions(+), 5 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2.java index 0e87bba1743..36292061de8 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.Map; import org.cloudfoundry.client.v2.applications.ApplicationEnvironmentRequest; import org.cloudfoundry.client.v2.applications.ApplicationEnvironmentResponse; @@ -66,6 +67,7 @@ import org.cloudfoundry.reactor.client.v2.AbstractClientV2Operations; import org.cloudfoundry.reactor.util.MultipartHttpClientRequest; import org.cloudfoundry.util.FileUtils; +import org.springframework.web.util.UriComponentsBuilder; import reactor.core.Exceptions; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -356,13 +358,25 @@ private Mono upload( return put( request, UploadApplicationResponse.class, - builder -> builder.pathSegment("apps", request.getApplicationId(), "bits"), + builder -> uploadUri(builder, request), multipartRequest -> upload(request.getApplication(), multipartRequest, request), onTerminate) .checkpoint(); } + // The nginx upload module of CAPI can drop the "resources" form field ("missing :resources"). + // Cloud Controller also takes it as query parameter, but only an empty list fits into a URL. + private static UriComponentsBuilder uploadUri( + UriComponentsBuilder builder, UploadApplicationRequest request) { + builder.pathSegment("apps", request.getApplicationId(), "bits"); + if (request.getResources().isEmpty()) { + builder.queryParam("resources", "{resources}") + .uriVariables(Collections.singletonMap("resources", "[]")); + } + return builder; + } + private void upload( Path application, MultipartHttpClientRequest multipartRequest, diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackages.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackages.java index c28ea0d1a46..01279e990b9 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackages.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackages.java @@ -16,9 +16,12 @@ package org.cloudfoundry.reactor.client.v3.packages; +import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.Map; import org.cloudfoundry.client.v3.packages.CopyPackageRequest; @@ -169,9 +172,12 @@ private void upload(Path bits, List resources, MultipartHttpCli r.addPart(part -> part.setName("bits").setContentType(APPLICATION_ZIP).sendFile(bits)); } - if (resources != null && !resources.isEmpty()) { - r.addPart(part -> part.setName("resources").send(resources)); - } + // CAPI rejects bits uploads without a "resources" part (CF-AppBitsUploadInvalid) + r.addPart( + part -> + part.setName("resources") + .setContentType(APPLICATION_JSON.toString()) + .send(resources == null ? Collections.emptyList() : resources)); r.done(); } diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2Test.java index c7730693f24..70c8875600d 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v2/applications/ReactorApplicationsV2Test.java @@ -1362,6 +1362,16 @@ void upload() throws IOException { .verify(Duration.ofSeconds(5)); } + @Test + void uploadWithoutResourcesSendsResourcesQueryParameter() throws IOException { + uploadWithoutResources("/apps/test-application-id/bits?resources=%5B%5D", null); + } + + @Test + void uploadAsyncWithoutResourcesSendsResourcesQueryParameter() throws IOException { + uploadWithoutResources("/apps/test-application-id/bits?async=true&resources=%5B%5D", true); + } + @Test void uploadDroplet() throws IOException { mockRequest( @@ -1438,4 +1448,49 @@ void uploadDroplet() throws IOException { .expectComplete() .verify(Duration.ofSeconds(5)); } + + private void uploadWithoutResources(String path, Boolean async) throws IOException { + mockRequest( + InteractionContext.builder() + .request( + TestRequest.builder() + .method(PUT) + .path(path) + .contents( + consumer( + (headers, body) -> + assertThat( + body.readString( + Charset + .defaultCharset())) + .contains( + "name=\"application\"") + .contains( + "name=\"resources\"") + .contains("[]"))) + .build()) + .response( + TestResponse.builder() + .status(CREATED) + .payload( + "fixtures/client/v2/apps/PUT_{id}_bits_response.json") + .build()) + .build()); + + this.applications + .upload( + UploadApplicationRequest.builder() + .application( + new ClassPathResource( + "fixtures/client/v2/apps/test-application.zip") + .getFile() + .toPath()) + .applicationId("test-application-id") + .async(async) + .build()) + .as(StepVerifier::create) + .expectNextCount(1) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } } diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackagesTest.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackagesTest.java index 6ac5f2792ce..cb3bf411c16 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackagesTest.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/packages/ReactorPackagesTest.java @@ -764,6 +764,20 @@ void upload() throws IOException { + "test-content\r\n" + "--" + boundary + + "\r\n" + + "content-disposition:" + + " form-data;" + + " name=\"resources\"\r\n" + + "content-length:" + + " 2\r\n" + + "content-type:" + + " application/json\r\n" + + "content-transfer-encoding:" + + " binary\r\n" + + "\r\n" + + "[]\r\n" + + "--" + + boundary + "--\r\n"); })) .build()) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index 3d587dcfe38..ad6d1f0df5f 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -85,7 +85,7 @@ public interface CloudFoundryClient { /** * The currently supported Cloud Controller API version */ - String SUPPORTED_API_VERSION = "2.290.0"; + String SUPPORTED_API_VERSION = "2.291.0"; String SUPPORTED_API_VERSION_V3 = "3.225.0"; diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_LdapConfiguration.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_LdapConfiguration.java index 75b658b6950..42345e795f5 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_LdapConfiguration.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_LdapConfiguration.java @@ -21,6 +21,8 @@ import org.cloudfoundry.Nullable; import org.immutables.value.Value; +import java.util.List; + /** * The payload for the ldap identity provider configuration */ @@ -63,6 +65,13 @@ abstract class _LdapConfiguration extends AbstractExternalIdentityProviderConfig @Nullable abstract String getBindUserDistinguishedName(); + /** + * The PEM encoded CA certificates used to validate the connection + */ + @JsonProperty("caCertificates") + @Nullable + abstract List getCaCertificates(); + /** * The group role attribute */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_SamlConfiguration.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_SamlConfiguration.java index 4e56d9204e0..6a2e895b1e9 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_SamlConfiguration.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/identityproviders/_SamlConfiguration.java @@ -51,6 +51,13 @@ abstract class _SamlConfiguration extends AbstractExternalIdentityProviderConfig @Nullable abstract List getAuthnContext(); + /** + * The PEM encoded CA certificates used to validate the connection + */ + @JsonProperty("caCertificates") + @Nullable + abstract List getCaCertificates(); + /** * Either EXPLICITLY_MAPPED in order to map external groups to OAuth scopes using the group mappings, or AS_SCOPES to use SAML group names as scopes. */ diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 63f062df5af..e214b2b6b5f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -74,6 +74,9 @@ import org.cloudfoundry.reactor.tokenprovider.PasswordGrantTokenProvider; import org.cloudfoundry.reactor.uaa.ReactorUaaClient; import org.cloudfoundry.routing.RoutingClient; +import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsRequest; +import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsResponse; +import org.cloudfoundry.routing.v1.routergroups.UpdateRouterGroupRequest; import org.cloudfoundry.uaa.UaaClient; import org.cloudfoundry.uaa.clients.CreateClientRequest; import org.cloudfoundry.uaa.groups.AddMemberRequest; @@ -254,6 +257,7 @@ String clientSecret(NameFactory nameFactory) { } @Bean + @DependsOn("routerGroupPorts") CloudFoundryCleaner cloudFoundryCleaner( @Qualifier("admin") CloudFoundryClient cloudFoundryClient, NameFactory nameFactory, @@ -454,6 +458,55 @@ String planName(NameFactory nameFactory) { return nameFactory.getPlanName(); } + // Tests allocate TCP ports from NameFactory, so the default router group has to reserve them + // regardless of the order in which the tests run (or of the deployment's default range). + @Bean(initMethod = "block") + Mono routerGroupPorts( + ConnectionContext connectionContext, + @Value("${test.admin.password}") String password, + @Value("${test.admin.username}") String username, + @Value("${skip.tcp.routing.tests:false}") boolean skipTcpRouting) { + if (skipTcpRouting) { + return Mono.empty(); + } + + // Not the shared routingClient: its token provider depends on the cleaner, which depends on + // this bean + RoutingClient routingClient = + ReactorRoutingClient.builder() + .connectionContext(connectionContext) + .tokenProvider( + PasswordGrantTokenProvider.builder() + .password(password) + .username(username) + .build()) + .build(); + + return routingClient + .routerGroups() + .list(ListRouterGroupsRequest.builder().build()) + .flatMapIterable(ListRouterGroupsResponse::getRouterGroups) + .filter(group -> "default-tcp".equals(group.getName())) + .next() + .switchIfEmpty( + Mono.fromRunnable( + () -> + this.logger.warn( + "No default-tcp router group, TCP route tests will" + + " fail"))) + .flatMap( + group -> + routingClient + .routerGroups() + .update( + UpdateRouterGroupRequest.builder() + .reservablePorts( + RandomNameFactory.PORT_RANGE) + .routerGroupId(group.getRouterGroupId()) + .build())) + .then(); + } + @Bean RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider tokenProvider) { return ReactorRoutingClient.builder() diff --git a/integration-test/src/test/java/org/cloudfoundry/RandomNameFactory.java b/integration-test/src/test/java/org/cloudfoundry/RandomNameFactory.java index 5443f15780e..6f9b92859cf 100644 --- a/integration-test/src/test/java/org/cloudfoundry/RandomNameFactory.java +++ b/integration-test/src/test/java/org/cloudfoundry/RandomNameFactory.java @@ -29,6 +29,11 @@ final class RandomNameFactory implements NameFactory { private static final int PORT_MINIMUM = 1025; + /** + * The router group port range that covers every port handed out by {@link #getPort()} + */ + static final String PORT_RANGE = PORT_MINIMUM + "-" + PORT_MAXIMUM; + private final Random random; private AtomicInteger port = new AtomicInteger(PORT_MINIMUM);