diff --git a/README.md b/README.md
index f353347f07..7fc5cd4a35 100644
--- a/README.md
+++ b/README.md
@@ -353,11 +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`.
-`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`.
+`UAA_API_REQUEST_LIMIT` | _(Optional)_ If your UAA server does rate limiting and returns 429 errors, set this variable to the smallest limit configured there. Whether your server limits UAA calls is shown in the log, together with the location of the configuration file on the server. Defaults to `0` (no limit).
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-reactor/pom.xml b/cloudfoundry-client-reactor/pom.xml
index c7159eda54..2d0931852c 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
+ 6.0.0-SNAPSHOT
cloudfoundry-client-reactor
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 d646300f28..7a26cd13d2 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/quotas/organizations/ReactorOrganizationQuotasV3.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3.java
index 2981f0b0f2..3f6c49d8ec 100644
--- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3.java
+++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3.java
@@ -94,4 +94,18 @@ public Mono delete(DeleteOrganizationQuotaRequest request) {
"organization_quotas", request.getOrganizationQuotaId()))
.checkpoint();
}
+
+ @Override
+ public Mono apply(ApplyOrganizationQuotaRequest request) {
+ return post(
+ request,
+ ApplyOrganizationQuotaResponse.class,
+ builder ->
+ builder.pathSegment(
+ "organization_quotas",
+ request.getOrganizationQuotaId(),
+ "relationships",
+ "organizations"))
+ .checkpoint();
+ }
}
diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3.java
index 93e8182a00..1a6b512520 100644
--- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3.java
+++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3.java
@@ -87,4 +87,33 @@ public Mono delete(DeleteSpaceQuotaRequest request) {
builder -> builder.pathSegment("space_quotas", request.getSpaceQuotaId()))
.checkpoint();
}
+
+ @Override
+ public Mono apply(ApplySpaceQuotaRequest request) {
+ return post(
+ request,
+ ApplySpaceQuotaResponse.class,
+ builder ->
+ builder.pathSegment(
+ "space_quotas",
+ request.getSpaceQuotaId(),
+ "relationships",
+ "spaces"))
+ .checkpoint();
+ }
+
+ @Override
+ public Mono remove(RemoveSpaceQuotaRequest request) {
+ return delete(
+ request,
+ Void.class,
+ builder ->
+ builder.pathSegment(
+ "space_quotas",
+ request.getSpaceQuotaId(),
+ "relationships",
+ "spaces",
+ request.getSpaceId()))
+ .checkpoint();
+ }
}
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 0000000000..69d48d9bae
--- /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/main/java/org/cloudfoundry/reactor/uaa/ReactorRatelimit.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/ReactorRatelimit.java
new file mode 100644
index 0000000000..c350367d61
--- /dev/null
+++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/ReactorRatelimit.java
@@ -0,0 +1,53 @@
+/*
+ * 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.uaa;
+
+import java.util.Map;
+import org.cloudfoundry.reactor.ConnectionContext;
+import org.cloudfoundry.reactor.TokenProvider;
+import org.cloudfoundry.uaa.ratelimit.Ratelimit;
+import org.cloudfoundry.uaa.ratelimit.RatelimitRequest;
+import org.cloudfoundry.uaa.ratelimit.RatelimitResponse;
+import reactor.core.publisher.Mono;
+
+public final class ReactorRatelimit extends AbstractUaaOperations implements Ratelimit {
+
+ /**
+ * 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://uaa.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 ReactorRatelimit(
+ ConnectionContext connectionContext,
+ Mono root,
+ TokenProvider tokenProvider,
+ Map requestTags) {
+ super(connectionContext, root, tokenProvider, requestTags);
+ }
+
+ @Override
+ public Mono getRatelimit(RatelimitRequest request) {
+ return get(
+ request,
+ RatelimitResponse.class,
+ builder -> builder.pathSegment("RateLimitingStatus"))
+ .checkpoint();
+ }
+}
diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java
index 1b2d613c4e..a1b28ca848 100644
--- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java
+++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/uaa/_ReactorUaaClient.java
@@ -33,6 +33,7 @@
import org.cloudfoundry.uaa.groups.Groups;
import org.cloudfoundry.uaa.identityproviders.IdentityProviders;
import org.cloudfoundry.uaa.identityzones.IdentityZones;
+import org.cloudfoundry.uaa.ratelimit.Ratelimit;
import org.cloudfoundry.uaa.serverinformation.ServerInformation;
import org.cloudfoundry.uaa.tokens.Tokens;
import org.cloudfoundry.uaa.users.Users;
@@ -104,6 +105,12 @@ public Users users() {
return new ReactorUsers(getConnectionContext(), getRoot(), getTokenProvider(), getRequestTags());
}
+ @Override
+ @Value.Derived
+ public Ratelimit rateLimit() {
+ return new ReactorRatelimit(getConnectionContext(), getRoot(), getTokenProvider(), getRequestTags());
+ }
+
/**
* The connection context
*/
diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java
index d0f3cc5ae3..4c94223c57 100644
--- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java
+++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/util/RequestLogger.java
@@ -34,7 +34,7 @@ public class RequestLogger {
private long requestSentTime;
public void request(HttpClientRequest request) {
- request(String.format("%-6s {}", request.method()), request.uri());
+ request(String.format("%-6s {}", request.method()), request.resourceUrl());
}
public void response(HttpClientResponse response) {
diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3Test.java
index 9d2485153e..8488b3271a 100644
--- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3Test.java
+++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/organizations/ReactorOrganizationQuotasV3Test.java
@@ -22,11 +22,14 @@
import java.time.Duration;
import java.util.Collections;
+import java.util.Map;
import org.cloudfoundry.client.v3.Link;
import org.cloudfoundry.client.v3.Pagination;
import org.cloudfoundry.client.v3.Relationship;
import org.cloudfoundry.client.v3.ToManyRelationship;
-import org.cloudfoundry.client.v3.quotas.*;
+import org.cloudfoundry.client.v3.quotas.Apps;
+import org.cloudfoundry.client.v3.quotas.Routes;
+import org.cloudfoundry.client.v3.quotas.Services;
import org.cloudfoundry.client.v3.quotas.organizations.*;
import org.cloudfoundry.reactor.InteractionContext;
import org.cloudfoundry.reactor.TestRequest;
@@ -51,13 +54,13 @@ void create() {
.method(POST)
.path("/organization_quotas")
.payload(
- "fixtures/client/v3/organization_quotas/POST_request.json")
+ "fixtures/client/v3/quotas/organizations/POST_request.json")
.build())
.response(
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/organization_quotas/POST_response.json")
+ "fixtures/client/v3/quotas/organizations/POST_response.json")
.build())
.build());
@@ -115,7 +118,7 @@ void get() {
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/organization_quotas/GET_{id}_response.json")
+ "fixtures/client/v3/quotas/organizations/GET_{id}_response.json")
.build())
.build());
@@ -146,7 +149,7 @@ void list() {
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/organization_quotas/GET_response.json")
+ "fixtures/client/v3/quotas/organizations/GET_response.json")
.build())
.build());
@@ -193,13 +196,13 @@ void update() {
.path(
"/organization_quotas/24637893-3b77-489d-bb79-8466f0d88b52")
.payload(
- "fixtures/client/v3/organization_quotas/PATCH_{id}_request.json")
+ "fixtures/client/v3/quotas/organizations/PATCH_{id}_request.json")
.build())
.response(
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/organization_quotas/PATCH_{id}_response.json")
+ "fixtures/client/v3/quotas/organizations/PATCH_{id}_response.json")
.build())
.build());
@@ -217,6 +220,47 @@ void update() {
.verify(Duration.ofSeconds(5));
}
+ @Test
+ void apply() {
+ mockRequest(
+ InteractionContext.builder()
+ .request(
+ TestRequest.builder()
+ .method(POST)
+ .path(
+ "/organization_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/organizations")
+ .payload(
+ "fixtures/client/v3/quotas/organizations/relationships/POST_{id}_request.json")
+ .build())
+ .response(
+ TestResponse.builder()
+ .status(OK)
+ .payload(
+ "fixtures/client/v3/quotas/organizations/relationships/POST_{id}_response.json")
+ .build())
+ .build());
+
+ Relationship org1 = Relationship.builder().id("org-guid1").build();
+ Relationship org2 = Relationship.builder().id("org-guid2").build();
+
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(org1, org2).build();
+
+ this.organizationQuotasV3
+ .apply(
+ ApplyOrganizationQuotaRequest.builder()
+ .organizationQuotaId("24637893-3b77-489d-bb79-8466f0d88b52")
+ .organizationRelationships(organizationRelationships)
+ .build())
+ .as(StepVerifier::create)
+ .expectNext(
+ ApplyOrganizationQuotaResponse.builder()
+ .from(expectedApplyOrganizationQuotaResponse())
+ .build())
+ .expectComplete()
+ .verify(Duration.ofSeconds(5));
+ }
+
@NotNull
private static OrganizationQuotaResource expectedOrganizationQuotaResource1() {
return buildOrganizationQuotaResource(
@@ -232,6 +276,28 @@ private static OrganizationQuotaResource expectedOrganizationQuotaResource2() {
"144251f2-a202-4ffe-ab47-9046c4077e99");
}
+ @NotNull
+ private static ApplyOrganizationQuotaResponse expectedApplyOrganizationQuotaResponse() {
+
+ Relationship org1 = Relationship.builder().id("org-guid1").build();
+ Relationship org2 = Relationship.builder().id("org-guid2").build();
+ Relationship existingOrg = Relationship.builder().id("previous-org-guid").build();
+
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(org1, org2, existingOrg).build();
+ Link selfLink =
+ Link.builder()
+ .href(
+ "https://api.example.org/v3/organization_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/organizations")
+ .build();
+ Map links = Collections.singletonMap("self", selfLink);
+
+ return ApplyOrganizationQuotaResponse.builder()
+ .organizationRelationships(organizationRelationships)
+ .links(links)
+ .build();
+ }
+
@NotNull
private static OrganizationQuotaResource buildOrganizationQuotaResource(
String id, String name, String relatedOrganizationId) {
diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3Test.java
index 34231e9426..7923efbb31 100644
--- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3Test.java
+++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/quotas/spaces/ReactorSpaceQuotasV3Test.java
@@ -16,21 +16,16 @@
package org.cloudfoundry.reactor.client.v3.quotas.spaces;
-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.OK;
+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 org.cloudfoundry.client.v3.Link;
-import org.cloudfoundry.client.v3.Pagination;
-import org.cloudfoundry.client.v3.Relationship;
-import org.cloudfoundry.client.v3.ToManyRelationship;
-import org.cloudfoundry.client.v3.ToOneRelationship;
-import org.cloudfoundry.client.v3.quotas.*;
+import java.util.Map;
+import org.cloudfoundry.client.v3.*;
+import org.cloudfoundry.client.v3.quotas.Apps;
+import org.cloudfoundry.client.v3.quotas.Routes;
+import org.cloudfoundry.client.v3.quotas.Services;
import org.cloudfoundry.client.v3.quotas.spaces.*;
import org.cloudfoundry.reactor.InteractionContext;
import org.cloudfoundry.reactor.TestRequest;
@@ -57,13 +52,13 @@ void create() {
.method(POST)
.path("/space_quotas")
.payload(
- "fixtures/client/v3/space_quotas/POST_request.json")
+ "fixtures/client/v3/quotas/spaces/POST_request.json")
.build())
.response(
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/space_quotas/POST_response.json")
+ "fixtures/client/v3/quotas/spaces/POST_response.json")
.build())
.build());
@@ -143,7 +138,7 @@ void get() {
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/space_quotas/GET_{id}_response.json")
+ "fixtures/client/v3/quotas/spaces/GET_{id}_response.json")
.build())
.build());
@@ -165,7 +160,7 @@ void list() {
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/space_quotas/GET_response.json")
+ "fixtures/client/v3/quotas/spaces/GET_response.json")
.build())
.build());
@@ -211,13 +206,13 @@ void update() {
.method(PATCH)
.path("/space_quotas/" + EXPECTED_SPACE_QUOTA_ID_1)
.payload(
- "fixtures/client/v3/space_quotas/PATCH_{id}_request.json")
+ "fixtures/client/v3/quotas/spaces/PATCH_{id}_request.json")
.build())
.response(
TestResponse.builder()
.status(OK)
.payload(
- "fixtures/client/v3/space_quotas/PATCH_{id}_response.json")
+ "fixtures/client/v3/quotas/spaces/PATCH_{id}_response.json")
.build())
.build());
@@ -235,6 +230,71 @@ void update() {
.verify(Duration.ofSeconds(5));
}
+ @Test
+ void apply() {
+ mockRequest(
+ InteractionContext.builder()
+ .request(
+ TestRequest.builder()
+ .method(POST)
+ .path(
+ "/space_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/spaces")
+ .payload(
+ "fixtures/client/v3/quotas/spaces/relationships/POST_{id}_request.json")
+ .build())
+ .response(
+ TestResponse.builder()
+ .status(OK)
+ .payload(
+ "fixtures/client/v3/quotas/spaces/relationships/POST_{id}_response.json")
+ .build())
+ .build());
+
+ Relationship space1 = Relationship.builder().id("space-guid1").build();
+ Relationship space2 = Relationship.builder().id("space-guid2").build();
+
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(space1, space2).build();
+
+ this.spaceQuotasV3
+ .apply(
+ ApplySpaceQuotaRequest.builder()
+ .spaceQuotaId("24637893-3b77-489d-bb79-8466f0d88b52")
+ .spaceRelationships(organizationRelationships)
+ .build())
+ .as(StepVerifier::create)
+ .expectNext(
+ ApplySpaceQuotaResponse.builder()
+ .from(expectedApplySpaceQuotaResponse())
+ .build())
+ .expectComplete()
+ .verify(Duration.ofSeconds(5));
+ }
+
+ @Test
+ void remove() {
+ mockRequest(
+ InteractionContext.builder()
+ .request(
+ TestRequest.builder()
+ .method(DELETE)
+ .path(
+ "/space_quotas/test-space-quota-id/relationships/spaces/test-space-guid")
+ .build())
+ .response(TestResponse.builder().status(NO_CONTENT).build())
+ .build());
+
+ this.spaceQuotasV3
+ .remove(
+ RemoveSpaceQuotaRequest.builder()
+ .spaceQuotaId("test-space-quota-id")
+ .spaceId("test-space-guid")
+ .build())
+ .as(StepVerifier::create)
+ .expectComplete()
+ .verify(Duration.ofSeconds(5));
+ }
+
@NotNull
private static SpaceQuotaResource expectedSpaceQuotaResource1() {
return buildSpaceQuotaResource(
@@ -252,6 +312,28 @@ private static SpaceQuotaResource expectedSpaceQuotaResource2() {
null);
}
+ @NotNull
+ private static ApplySpaceQuotaResponse expectedApplySpaceQuotaResponse() {
+
+ Relationship space1 = Relationship.builder().id("space-guid1").build();
+ Relationship space2 = Relationship.builder().id("space-guid2").build();
+ Relationship existingSpace = Relationship.builder().id("previous-space-guid").build();
+
+ ToManyRelationship spaceRelationships =
+ ToManyRelationship.builder().data(space1, space2, existingSpace).build();
+ Link selfLink =
+ Link.builder()
+ .href(
+ "https://api.example.org/v3/space_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/spaces")
+ .build();
+ Map links = Collections.singletonMap("self", selfLink);
+
+ return ApplySpaceQuotaResponse.builder()
+ .spaceRelationships(spaceRelationships)
+ .links(links)
+ .build();
+ }
+
@NotNull
private static SpaceQuotaResource buildSpaceQuotaResource(
String id, String name, String relatedOrganizationId, String relatedSpaceId) {
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/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 0000000000..b9d78e9a69
--- /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/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/uaa/clients/ReactorClientsTest.java
index a9d730008a..ce8d31b5a2 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/client/v3/organization_quotas/GET_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/GET_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/GET_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/GET_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/GET_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/GET_{id}_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/GET_{id}_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/GET_{id}_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/PATCH_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/PATCH_{id}_request.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/PATCH_{id}_request.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/PATCH_{id}_request.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/PATCH_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/PATCH_{id}_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/PATCH_{id}_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/PATCH_{id}_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/POST_request.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/POST_request.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/POST_request.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/POST_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/POST_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/organization_quotas/POST_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/POST_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_request.json
new file mode 100644
index 0000000000..093842883d
--- /dev/null
+++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_request.json
@@ -0,0 +1,10 @@
+{
+ "data": [
+ {
+ "guid": "org-guid1"
+ },
+ {
+ "guid": "org-guid2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_response.json
new file mode 100644
index 0000000000..e1abe6da19
--- /dev/null
+++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/organizations/relationships/POST_{id}_response.json
@@ -0,0 +1,18 @@
+{
+ "data": [
+ {
+ "guid": "org-guid1"
+ },
+ {
+ "guid": "org-guid2"
+ },
+ {
+ "guid": "previous-org-guid"
+ }
+ ],
+ "links": {
+ "self": {
+ "href": "https://api.example.org/v3/organization_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/organizations"
+ }
+ }
+}
\ No newline at end of file
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/GET_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/GET_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/GET_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/GET_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/GET_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/GET_{id}_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/GET_{id}_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/GET_{id}_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/PATCH_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/PATCH_{id}_request.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/PATCH_{id}_request.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/PATCH_{id}_request.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/PATCH_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/PATCH_{id}_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/PATCH_{id}_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/PATCH_{id}_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/POST_request.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/POST_request.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/POST_request.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/POST_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/POST_response.json
similarity index 100%
rename from cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/space_quotas/POST_response.json
rename to cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/POST_response.json
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_request.json
new file mode 100644
index 0000000000..9cf906732a
--- /dev/null
+++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_request.json
@@ -0,0 +1,10 @@
+{
+ "data": [
+ {
+ "guid": "space-guid1"
+ },
+ {
+ "guid": "space-guid2"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_response.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_response.json
new file mode 100644
index 0000000000..7b316d68e3
--- /dev/null
+++ b/cloudfoundry-client-reactor/src/test/resources/fixtures/client/v3/quotas/spaces/relationships/POST_{id}_response.json
@@ -0,0 +1,18 @@
+{
+ "data": [
+ {
+ "guid": "space-guid1"
+ },
+ {
+ "guid": "space-guid2"
+ },
+ {
+ "guid": "previous-space-guid"
+ }
+ ],
+ "links": {
+ "self": {
+ "href": "https://api.example.org/v3/space_quotas/24637893-3b77-489d-bb79-8466f0d88b52/relationships/spaces"
+ }
+ }
+}
\ No newline at end of file
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 0000000000..c727d7dbe1
--- /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 0000000000..b60ce5ab33
--- /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 0000000000..52c4a1b610
--- /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 0000000000..f1729ecb51
--- /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 0000000000..e5463b4edb
--- /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 0000000000..b60ce5ab33
--- /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-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json b/cloudfoundry-client-reactor/src/test/resources/fixtures/uaa/clients/POST_request.json
index cd24223fe7..ff9cb074b9 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 bc91157c3b..d61bedfc92 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 4b113ed45a..84fa404dd1 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 c25d438d83..976b638b7f 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 a0bed0ad73..a3fe946d72 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 dcfd19a32a..66cdbf957a 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 d3f59d11cc..6d48b51404 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 64704ce173..3f5e88a93f 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/pom.xml b/cloudfoundry-client/pom.xml
index f0b62b1b88..6b1bbd3dc8 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
+ 6.0.0-SNAPSHOT
cloudfoundry-client
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 171dd5d284..6181dfff7a 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/domains/_CreateDomainRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/domains/_CreateDomainRequest.java
index fe42ce4bc3..d452ebdc2f 100644
--- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/domains/_CreateDomainRequest.java
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/domains/_CreateDomainRequest.java
@@ -56,4 +56,11 @@ abstract class _CreateDomainRequest {
@Nullable
abstract DomainRelationships getRelationships();
+ /**
+ * The router group
+ */
+ @JsonProperty("router_group")
+ @Nullable
+ abstract RouterGroup getRouterGroup();
+
}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/OrganizationQuotasV3.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/OrganizationQuotasV3.java
index cb6f9a4223..624e7374b2 100644
--- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/OrganizationQuotasV3.java
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/OrganizationQuotasV3.java
@@ -66,4 +66,13 @@ public interface OrganizationQuotasV3 {
* @return the response from the Delete Organization Quota request
*/
Mono delete(DeleteOrganizationQuotaRequest request);
+
+ /**
+ * Makes the Apply an Organization Quota to an Organization
+ * request
+ *
+ * @param request the Apply an Organization Quota to an Organization request
+ * @return the response from the Apply an Organization Quota to an Organization request
+ */
+ Mono apply(ApplyOrganizationQuotaRequest request);
}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaRequest.java
new file mode 100644
index 0000000000..84ca9423cf
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaRequest.java
@@ -0,0 +1,28 @@
+package org.cloudfoundry.client.v3.quotas.organizations;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.immutables.value.Value;
+
+/**
+ * The request payload to apply an Organization Quota to an Organization
+ */
+@JsonSerialize
+@Value.Immutable
+abstract class _ApplyOrganizationQuotaRequest {
+
+ /**
+ * The Organization Quota id
+ */
+ @JsonIgnore
+ abstract String getOrganizationQuotaId();
+
+ /**
+ * Relationships to the organizations where the quota is applied
+ * Use of JsonUnwrapped to inline the organization relationships as per the API spec
+ */
+ @JsonUnwrapped
+ abstract ToManyRelationship organizationRelationships();
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaResponse.java
new file mode 100644
index 0000000000..eaf61f61a1
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/organizations/_ApplyOrganizationQuotaResponse.java
@@ -0,0 +1,33 @@
+package org.cloudfoundry.client.v3.quotas.organizations;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.cloudfoundry.AllowNulls;
+import org.cloudfoundry.client.v3.Link;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.immutables.value.Value;
+
+import java.util.Map;
+
+/**
+ * The response payload for applying an Organization Quota to an Organization
+ */
+@JsonDeserialize
+@Value.Immutable
+abstract class _ApplyOrganizationQuotaResponse {
+
+ /**
+ * Relationships to the organizations where the quota is applied
+ * Use of JsonUnwrapped to inline the organization relationships as per the API spec
+ */
+ @JsonUnwrapped
+ abstract ToManyRelationship organizationRelationships();
+
+ /**
+ * Links to related resources and actions for the resource
+ */
+ @AllowNulls
+ @JsonProperty("links")
+ public abstract Map getLinks();
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/SpaceQuotasV3.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/SpaceQuotasV3.java
index cf34330e8c..ae2b00143d 100644
--- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/SpaceQuotasV3.java
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/SpaceQuotasV3.java
@@ -33,7 +33,7 @@ public interface SpaceQuotasV3 {
Mono create(CreateSpaceQuotaRequest request);
/**
- * Makes the Get Space Quota
+ * Makes the Get Space Quota
* request
*
* @param request the Get Space Quota request
@@ -42,7 +42,7 @@ public interface SpaceQuotasV3 {
Mono get(GetSpaceQuotaRequest request);
/**
- * Makes the List all Space Quota
+ * Makes the List all Space Quota
* request
*
* @param request the List all Space Quotas request
@@ -50,7 +50,7 @@ public interface SpaceQuotasV3 {
*/
Mono list(ListSpaceQuotasRequest request);
- /** Makes the Update Space Quota
+ /** Makes the Update Space Quota
* request
*
* @param request the Update Space Quota request
@@ -59,11 +59,29 @@ public interface SpaceQuotasV3 {
Mono update(UpdateSpaceQuotaRequest request);
/**
- * Makes the Delete Space Quota
+ * Makes the Delete Space Quota
* request
*
* @param request the Delete Space Quota request
* @return the response from the Space Organization Quota request
*/
Mono delete(DeleteSpaceQuotaRequest request);
+
+ /**
+ * Makes the Apply a Space Quota to a Space
+ * request
+ *
+ * @param request the Apply a Space Quota to a Space request
+ * @return the response from the Apply a Space Quota to a Space request
+ */
+ Mono apply(ApplySpaceQuotaRequest request);
+
+ /**
+ * Makes the Remove a Space Quota from a Space
+ * request
+ *
+ * @param request the Remove a Space Quota from a Space request
+ * @return the response from the Remove a Space Quota from a Space request
+ */
+ Mono remove(RemoveSpaceQuotaRequest request);
}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaRequest.java
new file mode 100644
index 0000000000..ddb1bb0d0e
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaRequest.java
@@ -0,0 +1,28 @@
+package org.cloudfoundry.client.v3.quotas.spaces;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.immutables.value.Value;
+
+/**
+ * The request payload to apply an Space Quota to a Space
+ */
+@JsonSerialize
+@Value.Immutable
+abstract class _ApplySpaceQuotaRequest {
+
+ /**
+ * The Space Quota id
+ */
+ @JsonIgnore
+ abstract String getSpaceQuotaId();
+
+ /**
+ * Relationships to the spaces where the quota is applied
+ * Use of JsonUnwrapped to inline the space relationships as per the API spec
+ */
+ @JsonUnwrapped
+ abstract ToManyRelationship spaceRelationships();
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaResponse.java
new file mode 100644
index 0000000000..fe721048e7
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_ApplySpaceQuotaResponse.java
@@ -0,0 +1,33 @@
+package org.cloudfoundry.client.v3.quotas.spaces;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.cloudfoundry.AllowNulls;
+import org.cloudfoundry.client.v3.Link;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.immutables.value.Value;
+
+import java.util.Map;
+
+/**
+ * The response payload for applying a Space Quota to a Space
+ */
+@JsonDeserialize
+@Value.Immutable
+abstract class _ApplySpaceQuotaResponse {
+
+ /**
+ * Relationships to the spaces where the quota is applied
+ * Use of JsonUnwrapped to inline the space relationships as per the API spec
+ */
+ @JsonUnwrapped
+ abstract ToManyRelationship spaceRelationships();
+
+ /**
+ * Links to related resources and actions for the resource
+ */
+ @AllowNulls
+ @JsonProperty("links")
+ public abstract Map getLinks();
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_RemoveSpaceQuotaRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_RemoveSpaceQuotaRequest.java
new file mode 100644
index 0000000000..38f08bb525
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/v3/quotas/spaces/_RemoveSpaceQuotaRequest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.quotas.spaces;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.immutables.value.Value;
+
+/**
+ * The request payload to Remove a space quota from a space
+ */
+@Value.Immutable
+abstract class _RemoveSpaceQuotaRequest {
+
+ /**
+ * The space quota id
+ */
+ @JsonIgnore
+ abstract String getSpaceQuotaId();
+
+ /**
+ * The space id
+ */
+ @JsonIgnore
+ abstract String getSpaceId();
+
+}
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 0000000000..cf6ffd4a89
--- /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 0000000000..906582ead3
--- /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 0000000000..71f6c4823f
--- /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 0000000000..b1bdd0715d
--- /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 0000000000..5010b8193a
--- /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 0000000000..1f2d070586
--- /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 0000000000..76adda2fc0
--- /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 0000000000..c8606ab59c
--- /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 0000000000..1cf5a2e87e
--- /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 0000000000..6258711533
--- /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 0000000000..b264d5bcb1
--- /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 0000000000..65c08410c9
--- /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/main/java/org/cloudfoundry/uaa/UaaClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/UaaClient.java
index 9c88d37f59..665fb920ee 100644
--- a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/UaaClient.java
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/UaaClient.java
@@ -21,6 +21,7 @@
import org.cloudfoundry.uaa.groups.Groups;
import org.cloudfoundry.uaa.identityproviders.IdentityProviders;
import org.cloudfoundry.uaa.identityzones.IdentityZones;
+import org.cloudfoundry.uaa.ratelimit.Ratelimit;
import org.cloudfoundry.uaa.serverinformation.ServerInformation;
import org.cloudfoundry.uaa.tokens.Tokens;
import org.cloudfoundry.uaa.users.Users;
@@ -80,4 +81,9 @@ public interface UaaClient {
* Main entry point to the UAA User Client API
*/
Users users();
+
+ /**
+ * Main entry point to the UAA Ratelimit API
+ */
+ Ratelimit rateLimit();
}
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 a761952b0c..6fa73e06c0 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 f5d10479c1..92e4a952bd 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 de8b2ff317..bf3466f32e 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/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/Ratelimit.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/Ratelimit.java
new file mode 100644
index 0000000000..c277fa7331
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/Ratelimit.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.uaa.ratelimit;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Main entry point to the UAA Ratelimit Client API
+ */
+public interface Ratelimit {
+
+ Mono getRatelimit(RatelimitRequest request);
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_Current.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_Current.java
new file mode 100644
index 0000000000..57811e9e59
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_Current.java
@@ -0,0 +1,63 @@
+/*
+ * 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.uaa.ratelimit;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+
+import java.util.Date;
+
+import org.immutables.value.Value;
+
+/**
+ * The payload for the uaa ratelimiting
+ */
+@JsonDeserialize
+@Value.Immutable
+abstract class _Current {
+
+ /**
+ * The number of configured limiter mappings
+ */
+ @JsonProperty("limiterMappings")
+ abstract Integer getLimiterMappings();
+
+ /**
+ * Is ratelimit "ACTIVE" or not? Possible values are DISABLED, PENDING, ACTIVE
+ */
+ @JsonProperty("status")
+ abstract String getStatus();
+
+ /**
+ * Timestamp, when this Current was created.
+ */
+ @JsonProperty("asOf")
+ abstract Date getTimeOfCurrent();
+
+ /**
+ * The credentialIdExtractor
+ */
+ @JsonProperty("credentialIdExtractor")
+ abstract String getCredentialIdExtractor();
+
+ /**
+ * The loggingLevel. Valid values include: "OnlyLimited", "AllCalls" and "AllCallsWithDetails"
+ */
+ @JsonProperty("loggingLevel")
+ abstract String getLoggingLevel();
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitRequest.java
new file mode 100644
index 0000000000..be75d58bfa
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitRequest.java
@@ -0,0 +1,24 @@
+/*
+ * 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.uaa.ratelimit;
+
+import org.immutables.value.Value;
+
+@Value.Immutable
+abstract class _RatelimitRequest {
+
+}
diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitResponse.java
new file mode 100644
index 0000000000..0fe927cbf6
--- /dev/null
+++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/uaa/ratelimit/_RatelimitResponse.java
@@ -0,0 +1,36 @@
+/*
+ * 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.uaa.ratelimit;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.cloudfoundry.Nullable;
+import org.immutables.value.Value;
+
+@JsonDeserialize
+@Value.Immutable
+abstract class _RatelimitResponse {
+
+ @JsonProperty("current")
+ @Nullable
+ abstract Current getCurrentData();
+
+ @JsonProperty("fromSource")
+ @Nullable
+ abstract String getFromSource();
+
+}
diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/organizations/ApplyOrganizationQuotaRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/organizations/ApplyOrganizationQuotaRequestTest.java
new file mode 100644
index 0000000000..6a572188c9
--- /dev/null
+++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/organizations/ApplyOrganizationQuotaRequestTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.quotas.organizations;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.cloudfoundry.client.v3.Relationship;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.junit.jupiter.api.Test;
+
+final class ApplyOrganizationQuotaRequestTest {
+
+ @Test
+ void noOrganizationRelationships() {
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ ApplyOrganizationQuotaRequest.builder()
+ .organizationQuotaId("quota-id")
+ .build());
+ }
+
+ @Test
+ void noOrganizationQuotaId() {
+ Relationship organizationRelationship =
+ Relationship.builder().id("organization-id").build();
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(organizationRelationship).build();
+
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ ApplyOrganizationQuotaRequest.builder()
+ .organizationRelationships(organizationRelationships)
+ .build());
+ }
+
+ @Test
+ void valid() {
+ Relationship organizationRelationship =
+ Relationship.builder().id("organization-id").build();
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(organizationRelationship).build();
+ ApplyOrganizationQuotaRequest.builder()
+ .organizationQuotaId("quota-id")
+ .organizationRelationships(organizationRelationships)
+ .build();
+ }
+}
diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/ApplySpaceQuotaRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/ApplySpaceQuotaRequestTest.java
new file mode 100644
index 0000000000..63b8b8061d
--- /dev/null
+++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/ApplySpaceQuotaRequestTest.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.quotas.spaces;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.cloudfoundry.client.v3.Relationship;
+import org.cloudfoundry.client.v3.ToManyRelationship;
+import org.junit.jupiter.api.Test;
+
+final class ApplySpaceQuotaRequestTest {
+
+ @Test
+ void noSpaceRelationships() {
+ assertThrows(
+ IllegalStateException.class,
+ () -> ApplySpaceQuotaRequest.builder().spaceQuotaId("quota-id").build());
+ }
+
+ @Test
+ void noSpaceQuotaId() {
+ Relationship spaceRelationship = Relationship.builder().id("space-id").build();
+ ToManyRelationship spaceRelationships =
+ ToManyRelationship.builder().data(spaceRelationship).build();
+
+ assertThrows(
+ IllegalStateException.class,
+ () ->
+ ApplySpaceQuotaRequest.builder()
+ .spaceRelationships(spaceRelationships)
+ .build());
+ }
+
+ @Test
+ void valid() {
+ Relationship spaceRelationship = Relationship.builder().id("space-id").build();
+ ToManyRelationship spaceRelationships =
+ ToManyRelationship.builder().data(spaceRelationship).build();
+ ApplySpaceQuotaRequest.builder()
+ .spaceQuotaId("quota-id")
+ .spaceRelationships(spaceRelationships)
+ .build();
+ }
+}
diff --git a/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/RemoveSpaceQuotaRequestTest.java b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/RemoveSpaceQuotaRequestTest.java
new file mode 100644
index 0000000000..64f8490b07
--- /dev/null
+++ b/cloudfoundry-client/src/test/java/org/cloudfoundry/client/v3/quotas/spaces/RemoveSpaceQuotaRequestTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.quotas.spaces;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+final class RemoveSpaceQuotaRequestTest {
+
+ @Test
+ void noSpaceId() {
+ assertThrows(
+ IllegalStateException.class,
+ () -> RemoveSpaceQuotaRequest.builder().spaceQuotaId("quota-id").build());
+ }
+
+ @Test
+ void noSpaceQuotaId() {
+ assertThrows(
+ IllegalStateException.class,
+ () -> RemoveSpaceQuotaRequest.builder().spaceId("space-guid").build());
+ }
+
+ @Test
+ void valid() {
+ RemoveSpaceQuotaRequest.builder().spaceQuotaId("quota-id").spaceId("space-guid").build();
+ }
+}
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 0000000000..66ad42259b
--- /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 0000000000..6d65354fb3
--- /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 0000000000..83bb8541b9
--- /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 0000000000..bf30fb14ff
--- /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 0000000000..8a1c131cda
--- /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/cloudfoundry-operations/pom.xml b/cloudfoundry-operations/pom.xml
index afed2ca440..1bc8c7cb1b 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
+ 6.0.0-SNAPSHOT
cloudfoundry-operations
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java
index 62e442a53d..4db0bd8489 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java
@@ -86,13 +86,25 @@ public Applications applications() {
@Override
@Value.Derived
public Buildpacks buildpacks() {
- return new DefaultBuildpacks(getCloudFoundryClientPublisher());
+ CloudFoundryClient cloudFoundryClient = getCloudFoundryClient();
+ if (cloudFoundryClient == null) {
+ throw new IllegalStateException("CloudFoundryClient must be set");
+ }
+ return new DefaultBuildpacks(cloudFoundryClient);
}
@Override
@Value.Derived
public Domains domains() {
- return new DefaultDomains(getCloudFoundryClientPublisher(), getRoutingClientPublisher());
+ CloudFoundryClient cloudFoundryClient = getCloudFoundryClient();
+ if (cloudFoundryClient == null) {
+ throw new IllegalStateException("CloudFoundryClient must be set");
+ }
+ RoutingClient routingClient = getRoutingClient();
+ if (routingClient == null) {
+ throw new IllegalStateException("RoutingClient must be set");
+ }
+ return new DefaultDomains(cloudFoundryClient, routingClient);
}
@Override
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacks.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacks.java
index ad2958886e..862992e1b1 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacks.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacks.java
@@ -16,104 +16,85 @@
package org.cloudfoundry.operations.buildpacks;
-import static org.cloudfoundry.util.tuple.TupleUtils.function;
-
import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.buildpacks.BuildpackEntity;
-import org.cloudfoundry.client.v2.buildpacks.BuildpackResource;
-import org.cloudfoundry.client.v2.buildpacks.CreateBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest;
-import org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.UploadBuildpackRequest;
-import org.cloudfoundry.client.v2.buildpacks.UploadBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.BuildpackResource;
+import org.cloudfoundry.client.v3.buildpacks.BuildpackState;
+import org.cloudfoundry.client.v3.buildpacks.CreateBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.GetBuildpackRequest;
+import org.cloudfoundry.client.v3.buildpacks.GetBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.ListBuildpacksRequest;
+import org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.UploadBuildpackRequest;
+import org.cloudfoundry.client.v3.buildpacks.UploadBuildpackResponse;
import org.cloudfoundry.operations.util.OperationsLogging;
import org.cloudfoundry.util.ExceptionUtils;
import org.cloudfoundry.util.JobUtils;
import org.cloudfoundry.util.PaginationUtils;
-import org.cloudfoundry.util.ResourceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
+import reactor.util.retry.Retry;
public final class DefaultBuildpacks implements Buildpacks {
- private final Mono cloudFoundryClient;
+ private final CloudFoundryClient cloudFoundryClient;
- public DefaultBuildpacks(Mono cloudFoundryClient) {
+ public DefaultBuildpacks(CloudFoundryClient cloudFoundryClient) {
this.cloudFoundryClient = cloudFoundryClient;
}
+ /**
+ * Create a new instance.
+ *
+ * @deprecated Use {@link DefaultBuildpacks(CloudFoundryClient)} instead.
+ */
+ @Deprecated
+ public DefaultBuildpacks(Mono cloudFoundryClient) {
+ this.cloudFoundryClient =
+ cloudFoundryClient.subscribeOn(Schedulers.boundedElastic()).block();
+ }
+
@Override
public Mono create(CreateBuildpackRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- Mono.just(cloudFoundryClient),
- requestCreateBuildpack(
- cloudFoundryClient,
- request.getName(),
- request.getPosition(),
- request.getEnable())))
+ return requestCreateBuildpack(
+ this.cloudFoundryClient,
+ request.getName(),
+ request.getPosition(),
+ request.getEnable())
.flatMap(
- function(
- (cloudFoundryClient, response) ->
- requestUploadBuildpackBits(
- cloudFoundryClient,
- ResourceUtils.getId(response),
- request.getBuildpack())))
- .then()
+ response ->
+ requestUploadBuildpackBits(
+ response.getId(), request.getBuildpack()))
+ .map(UploadBuildpackResponse::getId)
+ .flatMap(this::waitForBuildpackReady)
.transform(OperationsLogging.log("Create Buildpack"))
.checkpoint();
}
@Override
public Mono delete(DeleteBuildpackRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- getBuildPackId(cloudFoundryClient, request.getName()),
- Mono.just(cloudFoundryClient)))
+ return getBuildPackId(request.getName())
.flatMap(
- function(
- (buildpackId, cloudFoundryClient) ->
- deleteBuildpack(
- cloudFoundryClient,
- buildpackId,
- request.getCompletionTimeout())))
- .then()
+ buildpackId -> deleteBuildpack(buildpackId, request.getCompletionTimeout()))
.transform(OperationsLogging.log("Delete Buildpack"))
.checkpoint();
}
@Override
public Flux list() {
- return this.cloudFoundryClient
- .flatMapMany(DefaultBuildpacks::requestBuildpacks)
- .map(DefaultBuildpacks::toBuildpackResource)
+ return requestBuildpacks(this.cloudFoundryClient)
+ .map(this::toBuildpackResource)
.transform(OperationsLogging.log("List Buildpacks"))
.checkpoint();
}
@Override
public Mono rename(RenameBuildpackRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- getBuildPackId(cloudFoundryClient, request.getName()),
- Mono.just(cloudFoundryClient)))
- .flatMap(
- function(
- (buildpackId, cloudFoundryClient) ->
- requestUpdateBuildpack(
- cloudFoundryClient,
- buildpackId,
- request.getNewName())))
+ return getBuildPackId(request.getName())
+ .flatMap(buildpackId -> requestUpdateBuildpack(buildpackId, request.getNewName()))
.then()
.transform(OperationsLogging.log("Rename Buildpack"))
.checkpoint();
@@ -121,53 +102,44 @@ public Mono rename(RenameBuildpackRequest request) {
@Override
public Mono update(UpdateBuildpackRequest request) {
- return this.cloudFoundryClient
+ return getBuildPackId(request.getName())
.flatMap(
- cloudFoundryClient ->
- Mono.zip(
- getBuildPackId(cloudFoundryClient, request.getName()),
- Mono.just(cloudFoundryClient)))
- .flatMap(
- function(
- (buildpackId, cloudFoundryClient) ->
- Mono.when(
- requestUpdateBuildpack(
- cloudFoundryClient, buildpackId, request),
- uploadBuildpackBits(
- cloudFoundryClient, buildpackId, request))))
+ buildpackId ->
+ Mono.when(
+ requestUpdateBuildpack(buildpackId, request),
+ uploadBuildpackBits(buildpackId, request))
+ .then(Mono.just(buildpackId)))
+ .flatMap(this::waitForBuildpackReady)
.then()
.transform(OperationsLogging.log("Update Buildpack"))
.checkpoint();
}
- private static Mono deleteBuildpack(
- CloudFoundryClient cloudFoundryClient, String buildpackId, Duration timeout) {
- return requestDeleteBuildpack(cloudFoundryClient, buildpackId)
- .flatMap(job -> JobUtils.waitForCompletion(cloudFoundryClient, timeout, job));
+ private Mono deleteBuildpack(String buildpackId, Duration timeout) {
+ return requestDeleteBuildpack(buildpackId)
+ .flatMap(job -> JobUtils.waitForCompletion(this.cloudFoundryClient, timeout, job));
}
- private static Mono getBuildPackId(CloudFoundryClient cloudFoundryClient, String name) {
- return requestBuildpacks(cloudFoundryClient, name)
+ private Mono getBuildPackId(String name) {
+ return requestBuildpacks(name)
.singleOrEmpty()
- .map(ResourceUtils::getId)
+ .map(BuildpackResource::getId)
.switchIfEmpty(ExceptionUtils.illegalArgument("Buildpack %s not found", name));
}
- private static Flux requestBuildpacks(
- CloudFoundryClient cloudFoundryClient) {
- return PaginationUtils.requestClientV2Resources(
+ private Flux requestBuildpacks(CloudFoundryClient cloudFoundryClient) {
+ return PaginationUtils.requestClientV3Resources(
page ->
cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.list(ListBuildpacksRequest.builder().page(page).build()));
}
- private static Flux requestBuildpacks(
- CloudFoundryClient cloudFoundryClient, String name) {
- return PaginationUtils.requestClientV2Resources(
+ private Flux requestBuildpacks(String name) {
+ return PaginationUtils.requestClientV3Resources(
page ->
cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.list(
ListBuildpacksRequest.builder()
.name(name)
@@ -175,40 +147,36 @@ private static Flux requestBuildpacks(
.build()));
}
- private static Mono requestCreateBuildpack(
+ private Mono requestCreateBuildpack(
CloudFoundryClient cloudFoundryClient,
String buildpackName,
Integer position,
Boolean enable) {
return cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.create(
- org.cloudfoundry.client.v2.buildpacks.CreateBuildpackRequest.builder()
+ org.cloudfoundry.client.v3.buildpacks.CreateBuildpackRequest.builder()
.name(buildpackName)
.position(position)
.enabled(Optional.ofNullable(enable).orElse(true))
.build());
}
- private static Mono requestDeleteBuildpack(
- CloudFoundryClient cloudFoundryClient, String buildpackId) {
+ private Mono requestDeleteBuildpack(String buildpackId) {
return cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.delete(
- org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackRequest.builder()
- .async(true)
+ org.cloudfoundry.client.v3.buildpacks.DeleteBuildpackRequest.builder()
.buildpackId(buildpackId)
.build());
}
- private static Mono requestUpdateBuildpack(
- CloudFoundryClient cloudFoundryClient,
- String buildpackId,
- UpdateBuildpackRequest request) {
+ private Mono requestUpdateBuildpack(
+ String buildpackId, UpdateBuildpackRequest request) {
return cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.update(
- org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackRequest.builder()
+ org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackRequest.builder()
.buildpackId(buildpackId)
.enabled(request.getEnable())
.locked(request.getLock())
@@ -216,36 +184,32 @@ private static Mono requestUpdateBuildpack(
.build());
}
- private static Mono requestUpdateBuildpack(
- CloudFoundryClient cloudFoundryClient, String buildpackId, String name) {
+ private Mono requestUpdateBuildpack(String buildpackId, String name) {
return cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.update(
- org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackRequest.builder()
+ org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackRequest.builder()
.buildpackId(buildpackId)
.name(name)
.build());
}
- private static Mono requestUploadBuildpackBits(
- CloudFoundryClient cloudFoundryClient, String buildpackId, Path buildpack) {
+ private Mono requestUploadBuildpackBits(
+ String buildpackId, Path buildpack) {
return cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.upload(
UploadBuildpackRequest.builder()
.buildpackId(buildpackId)
- .filename(buildpack.getFileName().toString())
- .buildpack(buildpack)
+ .bits(buildpack)
.build());
}
- private static Buildpack toBuildpackResource(BuildpackResource resource) {
- BuildpackEntity entity = ResourceUtils.getEntity(resource);
-
+ private Buildpack toBuildpackResource(BuildpackResource entity) {
return Buildpack.builder()
.enabled(entity.getEnabled())
.filename(entity.getFilename())
- .id(ResourceUtils.getId(resource))
+ .id(entity.getId())
.locked(entity.getLocked())
.name(entity.getName())
.position(entity.getPosition())
@@ -253,16 +217,31 @@ private static Buildpack toBuildpackResource(BuildpackResource resource) {
.build();
}
- private static Mono uploadBuildpackBits(
- CloudFoundryClient cloudFoundryClient,
- String buildpackId,
- UpdateBuildpackRequest request) {
+ private Mono uploadBuildpackBits(String buildpackId, UpdateBuildpackRequest request) {
if (request.getBuildpack() != null) {
- return requestUploadBuildpackBits(
- cloudFoundryClient, buildpackId, request.getBuildpack())
+ return requestUploadBuildpackBits(buildpackId, request.getBuildpack())
.then(Mono.empty());
}
return Mono.empty();
}
+
+ private Mono waitForBuildpackReady(String buildpackId) {
+ return requestBuildpack(buildpackId)
+ .flatMap(
+ buildpack -> {
+ if (!buildpack.getState().equals(BuildpackState.READY)) {
+ return Mono.error(new IllegalStateException("Not ready"));
+ }
+ return Mono.empty();
+ })
+ .retryWhen(Retry.backoff(3, Duration.ofSeconds(1)))
+ .then();
+ }
+
+ private Mono requestBuildpack(String buildpackId) {
+ return cloudFoundryClient
+ .buildpacksV3()
+ .get(GetBuildpackRequest.builder().buildpackId(buildpackId).build());
+ }
}
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/DefaultDomains.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/DefaultDomains.java
index aa30f42811..3df9c0d4a0 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/DefaultDomains.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/DefaultDomains.java
@@ -18,22 +18,26 @@
import static org.cloudfoundry.util.tuple.TupleUtils.function;
+import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.organizations.AssociateOrganizationPrivateDomainRequest;
-import org.cloudfoundry.client.v2.organizations.AssociateOrganizationPrivateDomainResponse;
-import org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest;
-import org.cloudfoundry.client.v2.organizations.OrganizationResource;
-import org.cloudfoundry.client.v2.organizations.RemoveOrganizationPrivateDomainRequest;
-import org.cloudfoundry.client.v2.privatedomains.CreatePrivateDomainRequest;
-import org.cloudfoundry.client.v2.privatedomains.CreatePrivateDomainResponse;
import org.cloudfoundry.client.v2.privatedomains.ListPrivateDomainsRequest;
-import org.cloudfoundry.client.v2.privatedomains.PrivateDomainEntity;
import org.cloudfoundry.client.v2.privatedomains.PrivateDomainResource;
import org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainResponse;
import org.cloudfoundry.client.v2.shareddomains.ListSharedDomainsRequest;
-import org.cloudfoundry.client.v2.shareddomains.SharedDomainEntity;
import org.cloudfoundry.client.v2.shareddomains.SharedDomainResource;
+import org.cloudfoundry.client.v3.Relationship;
+import org.cloudfoundry.client.v3.ToOneRelationship;
+import org.cloudfoundry.client.v3.domains.CreateDomainResponse;
+import org.cloudfoundry.client.v3.domains.DomainRelationships;
+import org.cloudfoundry.client.v3.domains.DomainResource;
+import org.cloudfoundry.client.v3.domains.ListDomainsRequest;
+import org.cloudfoundry.client.v3.domains.ShareDomainResponse;
+import org.cloudfoundry.client.v3.organizations.ListOrganizationsRequest;
+import org.cloudfoundry.client.v3.organizations.OrganizationResource;
import org.cloudfoundry.operations.util.OperationsLogging;
import org.cloudfoundry.routing.RoutingClient;
import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsResponse;
@@ -45,89 +49,82 @@
public final class DefaultDomains implements Domains {
- private final Mono cloudFoundryClient;
+ private final CloudFoundryClient cloudFoundryClient;
- private final Mono routingClient;
+ private final RoutingClient routingClient;
- public DefaultDomains(
- Mono cloudFoundryClient, Mono routingClient) {
+ public DefaultDomains(CloudFoundryClient cloudFoundryClient, RoutingClient routingClient) {
this.cloudFoundryClient = cloudFoundryClient;
this.routingClient = routingClient;
}
+ /**
+ * @deprecated use {@link DefaultDomains(CloudFoundryClient, RoutingClient)} instead.
+ */
+ @Deprecated
+ public DefaultDomains(
+ Mono cloudFoundryClient, Mono routingClient) {
+ this.cloudFoundryClient = cloudFoundryClient.block();
+ this.routingClient = routingClient.block();
+ }
+
@Override
public Mono create(CreateDomainRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- Mono.just(cloudFoundryClient),
- getOrganizationId(
- cloudFoundryClient, request.getOrganization())))
+ Mono> organizationId =
+ Mono.justOrEmpty(request.getOrganization())
+ .flatMap(this::getOrganization)
+ .map(OrganizationResource::getId)
+ .map(Optional::of)
+ .defaultIfEmpty(Optional.empty());
+
+ Mono> groupId =
+ Mono.justOrEmpty(request.getRouterGroup())
+ .flatMap(this::getRouterGroupId)
+ .map(Optional::of)
+ .defaultIfEmpty(Optional.empty());
+
+ return Mono.zip(organizationId, groupId)
.flatMap(
function(
- (cloudFoundryClient, organizationId) ->
+ (oId, gId) ->
requestCreateDomain(
- cloudFoundryClient,
request.getDomain(),
- organizationId)))
+ oId.orElse(null),
+ gId.orElse(null))))
.then()
.transform(OperationsLogging.log("Create Domain"))
.checkpoint();
}
@Override
+ @Deprecated
public Mono createShared(CreateSharedDomainRequest request) {
- if (request.getRouterGroup() == null) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- requestCreateSharedDomain(
- cloudFoundryClient, request.getDomain(), null))
- .then()
- .transform(OperationsLogging.log("Create Shared Domain"))
- .checkpoint();
- } else {
- return Mono.zip(this.cloudFoundryClient, this.routingClient)
- .flatMap(
- function(
- (cloudFoundryClient, routingClient) ->
- Mono.zip(
- Mono.just(cloudFoundryClient),
- getRouterGroupId(
- routingClient,
- request.getRouterGroup()))))
- .flatMap(
- function(
- (cloudFoundryClient, routerGroupId) ->
- requestCreateSharedDomain(
- cloudFoundryClient,
- request.getDomain(),
- routerGroupId)))
- .then()
- .transform(OperationsLogging.log("Create Shared Domain"))
- .checkpoint();
- }
+ return create(
+ CreateDomainRequest.builder()
+ .domain(request.getDomain())
+ .routerGroup(request.getRouterGroup())
+ .build());
}
@Override
public Flux list() {
- return this.cloudFoundryClient
+ return requestListRouterGroups()
+ .map(ListRouterGroupsResponse::getRouterGroups)
+ .map(DefaultDomains::indexRouterGroupsById)
.flatMapMany(
- cloudFoundryClient ->
- requestListPrivateDomains(cloudFoundryClient)
- .map(DefaultDomains::toDomain)
- .mergeWith(
- requestListSharedDomains(cloudFoundryClient)
- .map(DefaultDomains::toDomain)))
+ routerGroupsIndexedById ->
+ requestListDomains()
+ .map(
+ domain ->
+ DefaultDomains.toDomain(
+ domain, routerGroupsIndexedById)))
.transform(OperationsLogging.log("List Domains"))
.checkpoint();
}
@Override
public Flux listRouterGroups() {
- return this.routingClient
- .flatMapMany(DefaultDomains::requestListRouterGroups)
+ return requestListRouterGroups()
.flatMapIterable(ListRouterGroupsResponse::getRouterGroups)
.map(DefaultDomains::toRouterGroup)
.transform(OperationsLogging.log("List Router Groups"))
@@ -136,15 +133,10 @@ public Flux listRouterGroups() {
@Override
public Mono share(ShareDomainRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- Mono.just(cloudFoundryClient),
- getPrivateDomainId(cloudFoundryClient, request.getDomain()),
- getOrganizationId(
- cloudFoundryClient, request.getOrganization())))
- .flatMap(function(DefaultDomains::requestAssociateOrganizationPrivateDomainRequest))
+ return Mono.zip(
+ getDomainId(request.getDomain()),
+ getOrganizationId(request.getOrganization()))
+ .flatMap(function(this::requestShareDomain))
.then()
.transform(OperationsLogging.log("Share Domain"))
.checkpoint();
@@ -152,22 +144,16 @@ public Mono share(ShareDomainRequest request) {
@Override
public Mono unshare(UnshareDomainRequest request) {
- return this.cloudFoundryClient
- .flatMap(
- cloudFoundryClient ->
- Mono.zip(
- Mono.just(cloudFoundryClient),
- getPrivateDomainId(cloudFoundryClient, request.getDomain()),
- getOrganizationId(
- cloudFoundryClient, request.getOrganization())))
- .flatMap(function(DefaultDomains::requestRemoveOrganizationPrivateDomainRequest))
+ return Mono.zip(
+ getDomainId(request.getDomain()),
+ getOrganizationId(request.getOrganization()))
+ .flatMap(function(this::requestUnshareDomain))
.transform(OperationsLogging.log("Unshare Domain"))
.checkpoint();
}
- private static Mono getOrganization(
- CloudFoundryClient cloudFoundryClient, String organization) {
- return requestOrganizations(cloudFoundryClient, organization)
+ private Mono getOrganization(String organization) {
+ return requestOrganizations(organization)
.single()
.onErrorResume(
NoSuchElementException.class,
@@ -176,14 +162,14 @@ private static Mono getOrganization(
"Organization %s does not exist", organization));
}
- private static Mono getOrganizationId(
- CloudFoundryClient cloudFoundryClient, String organization) {
- return getOrganization(cloudFoundryClient, organization).map(ResourceUtils::getId);
+ private Mono getOrganizationId(String organization) {
+ return getOrganization(organization).map(OrganizationResource::getId);
}
- private static Mono getPrivateDomain(
- CloudFoundryClient cloudFoundryClient, String domain) {
- return requestListPrivateDomains(cloudFoundryClient, domain)
+ private Mono getDomainId(String domain) {
+ return this.requestListDomains()
+ .filter(d -> d.getName().equals(domain))
+ .map(DomainResource::getId)
.single()
.onErrorResume(
NoSuchElementException.class,
@@ -192,45 +178,63 @@ private static Mono getPrivateDomain(
"Private domain %s does not exist", domain));
}
- private static Mono getPrivateDomainId(
- CloudFoundryClient cloudFoundryClient, String domain) {
- return getPrivateDomain(cloudFoundryClient, domain).map(ResourceUtils::getId);
+ private Mono getPrivateDomain(String domain) {
+ return requestListPrivateDomains(domain)
+ .single()
+ .onErrorResume(
+ NoSuchElementException.class,
+ t ->
+ ExceptionUtils.illegalArgument(
+ "Private domain %s does not exist", domain));
}
- private static Mono getRouterGroupId(RoutingClient routingClient, String routerGroup) {
- return requestListRouterGroups(routingClient)
+ private Mono getPrivateDomainId(String domain) {
+ return getPrivateDomain(domain).map(ResourceUtils::getId);
+ }
+
+ private Mono getRouterGroupId(String routerGroup) {
+ return requestListRouterGroups()
.flatMapIterable(ListRouterGroupsResponse::getRouterGroups)
.filter(group -> routerGroup.equals(group.getName()))
.single()
.map(org.cloudfoundry.routing.v1.routergroups.RouterGroup::getRouterGroupId);
}
- private static Mono
- requestAssociateOrganizationPrivateDomainRequest(
- CloudFoundryClient cloudFoundryClient, String domainId, String organizationId) {
- return cloudFoundryClient
- .organizations()
- .associatePrivateDomain(
- AssociateOrganizationPrivateDomainRequest.builder()
- .organizationId(organizationId)
- .privateDomainId(domainId)
+ private Mono requestShareDomain(String domainId, String organizationId) {
+ return this.cloudFoundryClient
+ .domainsV3()
+ .share(
+ org.cloudfoundry.client.v3.domains.ShareDomainRequest.builder()
+ .domainId(domainId)
+ .data(Relationship.builder().id(organizationId).build())
.build());
}
- private static Mono requestCreateDomain(
- CloudFoundryClient cloudFoundryClient, String domain, String organizationId) {
- return cloudFoundryClient
- .privateDomains()
- .create(
- CreatePrivateDomainRequest.builder()
- .name(domain)
- .owningOrganizationId(organizationId)
- .build());
+ private Mono requestCreateDomain(
+ String domain, String organizationId, String routerGroupId) {
+ org.cloudfoundry.client.v3.domains.CreateDomainRequest.Builder createDomainRequest =
+ org.cloudfoundry.client.v3.domains.CreateDomainRequest.builder().name(domain);
+ if (organizationId != null) {
+ createDomainRequest.relationships(
+ DomainRelationships.builder()
+ .organization(
+ ToOneRelationship.builder()
+ .data(Relationship.builder().id(organizationId).build())
+ .build())
+ .build());
+ }
+ if (routerGroupId != null) {
+ createDomainRequest.routerGroup(
+ org.cloudfoundry.client.v3.domains.RouterGroup.builder()
+ .id(routerGroupId)
+ .build());
+ }
+ return this.cloudFoundryClient.domainsV3().create(createDomainRequest.build());
}
- private static Mono requestCreateSharedDomain(
- CloudFoundryClient cloudFoundryClient, String domain, String routerGroupId) {
- return cloudFoundryClient
+ private Mono requestCreateSharedDomain(
+ String domain, String routerGroupId) {
+ return this.cloudFoundryClient
.sharedDomains()
.create(
org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainRequest.builder()
@@ -239,11 +243,10 @@ private static Mono requestCreateSharedDomain(
.build());
}
- private static Flux requestListPrivateDomains(
- CloudFoundryClient cloudFoundryClient, String domain) {
+ private Flux requestListPrivateDomains(String domain) {
return PaginationUtils.requestClientV2Resources(
page ->
- cloudFoundryClient
+ this.cloudFoundryClient
.privateDomains()
.list(
ListPrivateDomainsRequest.builder()
@@ -252,39 +255,35 @@ private static Flux requestListPrivateDomains(
.build()));
}
- private static Flux requestListPrivateDomains(
- CloudFoundryClient cloudFoundryClient) {
- return PaginationUtils.requestClientV2Resources(
+ private Flux requestListDomains() {
+ return PaginationUtils.requestClientV3Resources(
page ->
- cloudFoundryClient
- .privateDomains()
- .list(ListPrivateDomainsRequest.builder().page(page).build()));
+ this.cloudFoundryClient
+ .domainsV3()
+ .list(ListDomainsRequest.builder().page(page).build()));
}
- private static Mono requestListRouterGroups(
- RoutingClient routingClient) {
- return routingClient
+ private Mono requestListRouterGroups() {
+ return this.routingClient
.routerGroups()
.list(
org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsRequest.builder()
.build());
}
- private static Flux requestListSharedDomains(
- CloudFoundryClient cloudFoundryClient) {
+ private Flux requestListSharedDomains() {
return PaginationUtils.requestClientV2Resources(
page ->
- cloudFoundryClient
+ this.cloudFoundryClient
.sharedDomains()
.list(ListSharedDomainsRequest.builder().page(page).build()));
}
- private static Flux requestOrganizations(
- CloudFoundryClient cloudFoundryClient, String organization) {
- return PaginationUtils.requestClientV2Resources(
+ private Flux requestOrganizations(String organization) {
+ return PaginationUtils.requestClientV3Resources(
page ->
- cloudFoundryClient
- .organizations()
+ this.cloudFoundryClient
+ .organizationsV3()
.list(
ListOrganizationsRequest.builder()
.name(organization)
@@ -292,36 +291,39 @@ private static Flux requestOrganizations(
.build()));
}
- private static Mono requestRemoveOrganizationPrivateDomainRequest(
- CloudFoundryClient cloudFoundryClient, String domainId, String organizationId) {
- return cloudFoundryClient
- .organizations()
- .removePrivateDomain(
- RemoveOrganizationPrivateDomainRequest.builder()
+ private Mono requestUnshareDomain(String domainId, String organizationId) {
+ return this.cloudFoundryClient
+ .domainsV3()
+ .unshare(
+ org.cloudfoundry.client.v3.domains.UnshareDomainRequest.builder()
.organizationId(organizationId)
- .privateDomainId(domainId)
+ .domainId(domainId)
.build());
}
- private static Domain toDomain(PrivateDomainResource resource) {
- PrivateDomainEntity entity = ResourceUtils.getEntity(resource);
-
+ private static Domain toDomain(DomainResource entity, Map type) {
return Domain.builder()
- .id(ResourceUtils.getId(resource))
+ .id(entity.getId())
.name(entity.getName())
- .status(Status.OWNED)
+ .status(
+ entity.getRelationships().getOrganization().getData() != null
+ ? Status.OWNED
+ : Status.SHARED)
+ .type(
+ entity.getRouterGroup() != null
+ ? type.get(entity.getRouterGroup().getId())
+ : null)
.build();
}
- private static Domain toDomain(SharedDomainResource resource) {
- SharedDomainEntity entity = ResourceUtils.getEntity(resource);
-
- return Domain.builder()
- .id(ResourceUtils.getId(resource))
- .name(entity.getName())
- .status(Status.SHARED)
- .type(entity.getRouterGroupType())
- .build();
+ private static Map indexRouterGroupsById(
+ List routeGroups) {
+ return routeGroups.stream()
+ .collect(
+ Collectors.toMap(
+ org.cloudfoundry.routing.v1.routergroups.RouterGroup
+ ::getRouterGroupId,
+ org.cloudfoundry.routing.v1.routergroups.RouterGroup::getType));
}
private static RouterGroup toRouterGroup(
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/Domains.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/Domains.java
index 74ffa068b8..e32fc01ead 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/Domains.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/Domains.java
@@ -37,7 +37,9 @@ public interface Domains {
*
* @param request the Create Shared Domain request
* @return a completion indicator
+ * @deprecated use {@link #create} instead
*/
+ @Deprecated
Mono createShared(CreateSharedDomainRequest request);
/**
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/_CreateDomainRequest.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/_CreateDomainRequest.java
index 924e47e2a8..04d4d2bcfc 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/_CreateDomainRequest.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/domains/_CreateDomainRequest.java
@@ -16,6 +16,7 @@
package org.cloudfoundry.operations.domains;
+import org.cloudfoundry.Nullable;
import org.immutables.value.Value;
/**
@@ -32,6 +33,13 @@ abstract class _CreateDomainRequest {
/**
* The organization name of the domain
*/
+ @Nullable
abstract String getOrganization();
+ /**
+ * The router group of the domain
+ */
+ @Nullable
+ abstract String getRouterGroup();
+
}
diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/stacks/DefaultStacks.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/stacks/DefaultStacks.java
index b57dbca983..5ba88cc57e 100644
--- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/stacks/DefaultStacks.java
+++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/stacks/DefaultStacks.java
@@ -18,12 +18,11 @@
import java.util.NoSuchElementException;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.stacks.ListStacksRequest;
-import org.cloudfoundry.client.v2.stacks.StackResource;
+import org.cloudfoundry.client.v3.stacks.ListStacksRequest;
+import org.cloudfoundry.client.v3.stacks.StackResource;
import org.cloudfoundry.operations.util.OperationsLogging;
import org.cloudfoundry.util.ExceptionUtils;
import org.cloudfoundry.util.PaginationUtils;
-import org.cloudfoundry.util.ResourceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -64,26 +63,26 @@ private static Mono getStack(
private static Flux requestStack(
CloudFoundryClient cloudFoundryClient, String stack) {
- return PaginationUtils.requestClientV2Resources(
+ return PaginationUtils.requestClientV3Resources(
page ->
cloudFoundryClient
- .stacks()
+ .stacksV3()
.list(ListStacksRequest.builder().name(stack).page(page).build()));
}
private static Flux requestStacks(CloudFoundryClient cloudFoundryClient) {
- return PaginationUtils.requestClientV2Resources(
+ return PaginationUtils.requestClientV3Resources(
page ->
cloudFoundryClient
- .stacks()
+ .stacksV3()
.list(ListStacksRequest.builder().page(page).build()));
}
private Stack toStack(StackResource stackResource) {
return Stack.builder()
- .description(ResourceUtils.getEntity(stackResource).getDescription())
- .id(ResourceUtils.getId(stackResource))
- .name(ResourceUtils.getEntity(stackResource).getName())
+ .description(stackResource.getDescription())
+ .id(stackResource.getId())
+ .name(stackResource.getName())
.build();
}
}
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java
index 5a0854f48b..9dd97126e8 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java
@@ -44,11 +44,13 @@
import org.cloudfoundry.client.v2.userprovidedserviceinstances.UserProvidedServiceInstances;
import org.cloudfoundry.client.v2.users.Users;
import org.cloudfoundry.client.v3.applications.ApplicationsV3;
+import org.cloudfoundry.client.v3.buildpacks.BuildpacksV3;
import org.cloudfoundry.client.v3.domains.DomainsV3;
import org.cloudfoundry.client.v3.jobs.JobsV3;
import org.cloudfoundry.client.v3.organizations.OrganizationsV3;
import org.cloudfoundry.client.v3.routes.RoutesV3;
import org.cloudfoundry.client.v3.spaces.SpacesV3;
+import org.cloudfoundry.client.v3.stacks.StacksV3;
import org.cloudfoundry.client.v3.tasks.Tasks;
import org.cloudfoundry.doppler.DopplerClient;
import org.cloudfoundry.logcache.v1.LogCacheClient;
@@ -93,6 +95,7 @@ public abstract class AbstractOperationsTest {
protected final Authorizations authorizations = mock(Authorizations.class, RETURNS_SMART_NULLS);
protected final Buildpacks buildpacks = mock(Buildpacks.class, RETURNS_SMART_NULLS);
+ protected final BuildpacksV3 buildpacksV3 = mock(BuildpacksV3.class, RETURNS_SMART_NULLS);
protected final CloudFoundryClient cloudFoundryClient =
mock(CloudFoundryClient.class, RETURNS_SMART_NULLS);
@@ -155,6 +158,7 @@ public abstract class AbstractOperationsTest {
protected final SpacesV3 spacesV3 = mock(SpacesV3.class, RETURNS_SMART_NULLS);
protected final Stacks stacks = mock(Stacks.class, RETURNS_SMART_NULLS);
+ protected final StacksV3 stacksV3 = mock(StacksV3.class, RETURNS_SMART_NULLS);
protected final Tasks tasks = mock(Tasks.class, RETURNS_SMART_NULLS);
@@ -175,6 +179,7 @@ public final void mockClient() {
when(this.cloudFoundryClient.applicationsV2()).thenReturn(this.applications);
when(this.cloudFoundryClient.applicationsV3()).thenReturn(this.applicationsV3);
when(this.cloudFoundryClient.buildpacks()).thenReturn(this.buildpacks);
+ when(this.cloudFoundryClient.buildpacksV3()).thenReturn(this.buildpacksV3);
when(this.cloudFoundryClient.domains()).thenReturn(this.domains);
when(this.cloudFoundryClient.domainsV3()).thenReturn(this.domainsV3);
when(this.cloudFoundryClient.events()).thenReturn(this.events);
@@ -203,6 +208,7 @@ public final void mockClient() {
when(this.cloudFoundryClient.spaces()).thenReturn(this.spaces);
when(this.cloudFoundryClient.spacesV3()).thenReturn(this.spacesV3);
when(this.cloudFoundryClient.stacks()).thenReturn(this.stacks);
+ when(this.cloudFoundryClient.stacksV3()).thenReturn(this.stacksV3);
when(this.cloudFoundryClient.tasks()).thenReturn(this.tasks);
when(this.cloudFoundryClient.userProvidedServiceInstances())
.thenReturn(this.userProvidedServiceInstances);
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/DefaultCloudFoundryOperationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/DefaultCloudFoundryOperationsTest.java
index fd1027e7f0..02e671a3e6 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/DefaultCloudFoundryOperationsTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/DefaultCloudFoundryOperationsTest.java
@@ -26,6 +26,7 @@ final class DefaultCloudFoundryOperationsTest extends AbstractOperationsTest {
DefaultCloudFoundryOperations.builder()
.cloudFoundryClient(this.cloudFoundryClient)
.dopplerClient(this.dopplerClient)
+ .routingClient(this.routingClient)
.organization(TEST_ORGANIZATION_NAME)
.space(TEST_SPACE_NAME)
.uaaClient(this.uaaClient)
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacksTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacksTest.java
index fdfca5272b..1d4f928a63 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacksTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/buildpacks/DefaultBuildpacksTest.java
@@ -17,6 +17,7 @@
package org.cloudfoundry.operations.buildpacks;
import static org.cloudfoundry.operations.TestObjects.fill;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.nio.file.Path;
@@ -27,18 +28,18 @@
import java.util.Queue;
import java.util.function.Supplier;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.Metadata;
-import org.cloudfoundry.client.v2.buildpacks.BuildpackEntity;
-import org.cloudfoundry.client.v2.buildpacks.BuildpackResource;
-import org.cloudfoundry.client.v2.buildpacks.CreateBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksRequest;
-import org.cloudfoundry.client.v2.buildpacks.ListBuildpacksResponse;
-import org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackResponse;
-import org.cloudfoundry.client.v2.buildpacks.UploadBuildpackResponse;
-import org.cloudfoundry.client.v2.jobs.GetJobRequest;
-import org.cloudfoundry.client.v2.jobs.GetJobResponse;
-import org.cloudfoundry.client.v2.jobs.JobEntity;
+import org.cloudfoundry.client.v3.buildpacks.BuildpackResource;
+import org.cloudfoundry.client.v3.buildpacks.BuildpackState;
+import org.cloudfoundry.client.v3.buildpacks.CreateBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.GetBuildpackRequest;
+import org.cloudfoundry.client.v3.buildpacks.GetBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.ListBuildpacksRequest;
+import org.cloudfoundry.client.v3.buildpacks.ListBuildpacksResponse;
+import org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackResponse;
+import org.cloudfoundry.client.v3.buildpacks.UploadBuildpackResponse;
+import org.cloudfoundry.client.v3.jobs.GetJobRequest;
+import org.cloudfoundry.client.v3.jobs.GetJobResponse;
+import org.cloudfoundry.client.v3.jobs.JobState;
import org.cloudfoundry.operations.AbstractOperationsTest;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -47,17 +48,14 @@
final class DefaultBuildpacksTest extends AbstractOperationsTest {
- private final DefaultBuildpacks buildpacks =
- new DefaultBuildpacks(Mono.just(this.cloudFoundryClient));
+ private final DefaultBuildpacks buildpacks = new DefaultBuildpacks(this.cloudFoundryClient);
@Test
void create() {
+ requestBuildpack(this.cloudFoundryClient);
requestCreateBuildpack(this.cloudFoundryClient, "test-buildpack", 1, true);
requestUploadBuildpack(
- this.cloudFoundryClient,
- "test-buildpack-id",
- Paths.get("test-buildpack"),
- "test-buildpack");
+ this.cloudFoundryClient, "test-buildpack-id", Paths.get("test-buildpack"));
this.buildpacks
.create(
@@ -128,6 +126,7 @@ void rename() {
@Test
void update() {
+ requestBuildpack(this.cloudFoundryClient);
requestListBuildpacks(this.cloudFoundryClient, "test-buildpack");
requestUpdateBuildpack(this.cloudFoundryClient, "test-buildpack-id", true, true, 5);
@@ -146,13 +145,11 @@ void update() {
@Test
void updateWithBits() {
+ requestBuildpack(this.cloudFoundryClient);
requestListBuildpacks(this.cloudFoundryClient, "test-buildpack");
requestUpdateBuildpack(this.cloudFoundryClient, "test-buildpack-id", true, true, 5);
requestUploadBuildpack(
- this.cloudFoundryClient,
- "test-buildpack-id",
- Paths.get("test-buildpack"),
- "test-buildpack");
+ this.cloudFoundryClient, "test-buildpack-id", Paths.get("test-buildpack"));
this.buildpacks
.update(
@@ -168,13 +165,25 @@ void updateWithBits() {
.verify(Duration.ofSeconds(5));
}
+ private static void requestBuildpack(CloudFoundryClient cloudFoundryClient) {
+ when(cloudFoundryClient.buildpacksV3().get(any(GetBuildpackRequest.class)))
+ .thenReturn(
+ Mono.just(
+ fill(GetBuildpackResponse.builder(), "buildpack-")
+ .state(BuildpackState.READY)
+ .build()));
+ }
+
private static void requestBuildpacks(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient.buildpacks().list(ListBuildpacksRequest.builder().page(1).build()))
+ when(cloudFoundryClient
+ .buildpacksV3()
+ .list(ListBuildpacksRequest.builder().page(1).build()))
.thenReturn(
Mono.just(
fill(ListBuildpacksResponse.builder())
.resource(
fill(BuildpackResource.builder(), "buildpack-")
+ .state(BuildpackState.READY)
.build())
.build()));
}
@@ -182,9 +191,9 @@ private static void requestBuildpacks(CloudFoundryClient cloudFoundryClient) {
private static void requestCreateBuildpack(
CloudFoundryClient cloudFoundryClient, String name, Integer position, Boolean enable) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.create(
- org.cloudfoundry.client.v2.buildpacks.CreateBuildpackRequest
+ org.cloudfoundry.client.v3.buildpacks.CreateBuildpackRequest
.builder()
.name(name)
.position(position)
@@ -197,22 +206,17 @@ private static void requestCreateBuildpack(
private static void requestDeleteBuildpack(
CloudFoundryClient cloudFoundryClient, String buildpackId) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.delete(
- org.cloudfoundry.client.v2.buildpacks.DeleteBuildpackRequest
+ org.cloudfoundry.client.v3.buildpacks.DeleteBuildpackRequest
.builder()
- .async(true)
.buildpackId(buildpackId)
.build()))
- .thenReturn(
- Mono.just(
- fill(DeleteBuildpackResponse.builder())
- .entity(fill(JobEntity.builder()).id("test-job-id").build())
- .build()));
+ .thenReturn(Mono.just("test-job-id"));
}
private static void requestJobSuccess(CloudFoundryClient cloudFoundryClient, String jobId) {
- when(cloudFoundryClient.jobs().get(GetJobRequest.builder().jobId(jobId).build()))
+ when(cloudFoundryClient.jobsV3().get(GetJobRequest.builder().jobId(jobId).build()))
.thenReturn(
Mono.defer(
new Supplier>() {
@@ -224,20 +228,10 @@ private static void requestJobSuccess(CloudFoundryClient cloudFoundryClient, Str
GetJobResponse
.builder(),
"test-job-")
- .entity(
- fill(JobEntity
- .builder())
- .status(
- "running")
- .build())
+ .state(JobState.PROCESSING)
.build(),
fill(GetJobResponse.builder(), "job-")
- .entity(
- fill(JobEntity
- .builder())
- .status(
- "finished")
- .build())
+ .state(JobState.COMPLETE)
.build()));
@Override
@@ -249,21 +243,15 @@ public Mono get() {
private static void requestListBuildpacks(CloudFoundryClient cloudFoundryClient, String name) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.list(ListBuildpacksRequest.builder().name(name).page(1).build()))
.thenReturn(
Mono.just(
- ListBuildpacksResponse.builder()
+ fill(ListBuildpacksResponse.builder())
.resource(
- BuildpackResource.builder()
- .metadata(
- Metadata.builder()
- .id("test-buildpack-id")
- .build())
- .entity(
- BuildpackEntity.builder()
- .name(name)
- .build())
+ fill(BuildpackResource.builder())
+ .id("test-buildpack-id")
+ .name(name)
.build())
.build()));
}
@@ -271,9 +259,9 @@ private static void requestListBuildpacks(CloudFoundryClient cloudFoundryClient,
private static void requestUpdateBuildpack(
CloudFoundryClient cloudFoundryClient, String buildpackId, String name) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.update(
- org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackRequest
+ org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackRequest
.builder()
.buildpackId(buildpackId)
.name(name)
@@ -288,9 +276,9 @@ private static void requestUpdateBuildpack(
boolean locked,
Integer position) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.update(
- org.cloudfoundry.client.v2.buildpacks.UpdateBuildpackRequest
+ org.cloudfoundry.client.v3.buildpacks.UpdateBuildpackRequest
.builder()
.buildpackId(buildpackId)
.enabled(enabled)
@@ -301,18 +289,14 @@ private static void requestUpdateBuildpack(
}
private static void requestUploadBuildpack(
- CloudFoundryClient cloudFoundryClient,
- String buildpackId,
- Path buildpack,
- String filename) {
+ CloudFoundryClient cloudFoundryClient, String buildpackId, Path buildpack) {
when(cloudFoundryClient
- .buildpacks()
+ .buildpacksV3()
.upload(
- org.cloudfoundry.client.v2.buildpacks.UploadBuildpackRequest
+ org.cloudfoundry.client.v3.buildpacks.UploadBuildpackRequest
.builder()
.buildpackId(buildpackId)
- .buildpack(buildpack)
- .filename(filename)
+ .bits(buildpack)
.build()))
.thenReturn(Mono.just(fill(UploadBuildpackResponse.builder()).build()));
}
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/CreateDomainRequestTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/CreateDomainRequestTest.java
index bb41e2a15d..d974026dda 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/CreateDomainRequestTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/CreateDomainRequestTest.java
@@ -31,15 +31,6 @@ void noDomain() {
});
}
- @Test
- void noOrganization() {
- assertThrows(
- IllegalStateException.class,
- () -> {
- CreateDomainRequest.builder().domain("test-domain").build();
- });
- }
-
@Test
void valid() {
CreateDomainRequest.builder()
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/DefaultDomainsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/DefaultDomainsTest.java
index 15d97267ad..6fdae8380f 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/DefaultDomainsTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/domains/DefaultDomainsTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2021 the original author or authors.
+ * 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.
@@ -18,29 +18,27 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.cloudfoundry.operations.TestObjects.fill;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.time.Duration;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.Metadata;
-import org.cloudfoundry.client.v2.organizations.AssociateOrganizationPrivateDomainRequest;
-import org.cloudfoundry.client.v2.organizations.ListOrganizationsRequest;
-import org.cloudfoundry.client.v2.organizations.ListOrganizationsResponse;
-import org.cloudfoundry.client.v2.organizations.OrganizationResource;
-import org.cloudfoundry.client.v2.organizations.RemoveOrganizationPrivateDomainRequest;
-import org.cloudfoundry.client.v2.privatedomains.CreatePrivateDomainRequest;
-import org.cloudfoundry.client.v2.privatedomains.CreatePrivateDomainResponse;
-import org.cloudfoundry.client.v2.privatedomains.ListPrivateDomainsRequest;
-import org.cloudfoundry.client.v2.privatedomains.ListPrivateDomainsResponse;
-import org.cloudfoundry.client.v2.privatedomains.PrivateDomainResource;
-import org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainResponse;
-import org.cloudfoundry.client.v2.shareddomains.ListSharedDomainsRequest;
-import org.cloudfoundry.client.v2.shareddomains.ListSharedDomainsResponse;
-import org.cloudfoundry.client.v2.shareddomains.SharedDomainEntity;
-import org.cloudfoundry.client.v2.shareddomains.SharedDomainResource;
+import org.cloudfoundry.client.v3.Pagination;
+import org.cloudfoundry.client.v3.Relationship;
+import org.cloudfoundry.client.v3.ToOneRelationship;
+import org.cloudfoundry.client.v3.domains.CreateDomainResponse;
+import org.cloudfoundry.client.v3.domains.DomainRelationships;
+import org.cloudfoundry.client.v3.domains.DomainResource;
+import org.cloudfoundry.client.v3.domains.ListDomainsRequest;
+import org.cloudfoundry.client.v3.domains.ListDomainsResponse;
+import org.cloudfoundry.client.v3.organizations.ListOrganizationsRequest;
+import org.cloudfoundry.client.v3.organizations.ListOrganizationsResponse;
+import org.cloudfoundry.client.v3.organizations.OrganizationResource;
import org.cloudfoundry.operations.AbstractOperationsTest;
import org.cloudfoundry.routing.RoutingClient;
-import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsRequest;
import org.cloudfoundry.routing.v1.routergroups.ListRouterGroupsResponse;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -49,12 +47,12 @@
final class DefaultDomainsTest extends AbstractOperationsTest {
private final DefaultDomains domains =
- new DefaultDomains(Mono.just(this.cloudFoundryClient), Mono.just(this.routingClient));
+ new DefaultDomains(this.cloudFoundryClient, this.routingClient);
@Test
void createDomain() {
requestOrganizations(this.cloudFoundryClient, "test-organization");
- requestCreatePrivateDomain(this.cloudFoundryClient, "test-domain", "test-organization-id");
+ requestCreateDomain(this.cloudFoundryClient, "test-domain", "test-organization-id");
this.domains
.create(
@@ -63,56 +61,89 @@ void createDomain() {
.organization("test-organization")
.build())
.as(StepVerifier::create)
- .expectComplete()
- .verify(Duration.ofSeconds(5));
+ .verifyComplete();
+
+ verify(this.cloudFoundryClient.domainsV3())
+ .create(
+ argThat(
+ a ->
+ a.getName().equals("test-domain")
+ && a.getRelationships()
+ .getOrganization()
+ .getData()
+ .getId()
+ .equals("test-organization-id")));
+ verifyNoInteractions(this.routingClient.routerGroups().list(any()));
}
@Test
void createSharedDomain() {
- requestCreateSharedDomain(this.cloudFoundryClient, "test-domain");
+ requestCreateDomain(this.cloudFoundryClient, "test-domain");
this.domains
.createShared(CreateSharedDomainRequest.builder().domain("test-domain").build())
.as(StepVerifier::create)
- .expectComplete()
- .verify(Duration.ofSeconds(5));
+ .verifyComplete();
+
+ verify(this.cloudFoundryClient.domainsV3())
+ .create(argThat(a -> a.getName().equals("test-domain")));
+ verifyNoInteractions(this.cloudFoundryClient.organizationsV3().list(any()));
+ verifyNoInteractions(this.routingClient.routerGroups().list(any()));
}
@Test
- void listDomains() {
- requestPrivateDomains(this.cloudFoundryClient);
- requestSharedDomains(this.cloudFoundryClient);
+ void createDomainRouterGroup() {
+ requestCreateDomain(this.cloudFoundryClient, "test-domain");
+ requestListRouterGroups(this.routingClient, "test-router-group");
this.domains
- .list()
- .as(StepVerifier::create)
- .expectNext(
- Domain.builder()
- .id("test-private-domain-id")
- .name("test-private-domain-name")
- .status(Status.OWNED)
- .build(),
- Domain.builder()
- .id("test-shared-domain-id")
- .name("test-shared-domain-name")
- .status(Status.SHARED)
+ .create(
+ CreateDomainRequest.builder()
+ .domain("test-domain")
+ .routerGroup("test-router-group")
.build())
+ .as(StepVerifier::create)
.expectComplete()
.verify(Duration.ofSeconds(5));
+
+ verifyNoInteractions(this.cloudFoundryClient.organizationsV3().list(any()));
+ verify(this.routingClient.routerGroups()).list(any());
+ verify(this.cloudFoundryClient.domainsV3())
+ .create(argThat(a -> a.getRouterGroup().getId().equals("test-routerGroupId")));
}
@Test
- void listDomainsOnlyPrivate() {
- requestPrivateDomains(this.cloudFoundryClient);
- requestSharedDomainsEmpty(this.cloudFoundryClient);
+ void createSharedDomainRouterGroup() {
+ requestCreateDomain(this.cloudFoundryClient, "test-domain");
+ requestListRouterGroups(this.routingClient, "test-router-group");
+
+ this.domains
+ .createShared(
+ CreateSharedDomainRequest.builder()
+ .domain("test-domain")
+ .routerGroup("test-router-group")
+ .build())
+ .as(StepVerifier::create)
+ .verifyComplete();
+
+ verifyNoInteractions(this.cloudFoundryClient.organizationsV3().list(any()));
+ verify(this.routingClient.routerGroups()).list(any());
+ verify(this.cloudFoundryClient.domainsV3())
+ .create(argThat(a -> a.getRouterGroup().getId().equals("test-routerGroupId")));
+ }
+
+ @Test
+ void listDomains() {
+ requestListRouterGroups(this.routingClient, "test-router-group");
+ requestListDomains(this.cloudFoundryClient, "test-organization-id", null);
this.domains
.list()
.as(StepVerifier::create)
.expectNext(
Domain.builder()
- .id("test-private-domain-id")
- .name("test-private-domain-name")
+ .id("test-domain-id")
+ .name("test-domain-name")
.status(Status.OWNED)
.build())
.expectComplete()
@@ -120,17 +151,17 @@ void listDomainsOnlyPrivate() {
}
@Test
- void listDomainsOnlyShared() {
- requestSharedDomains(this.cloudFoundryClient);
- requestPrivateDomainsEmpty(this.cloudFoundryClient);
+ void listDomainsShared() {
+ requestListRouterGroups(this.routingClient, "test-router-group");
+ requestListDomains(this.cloudFoundryClient, null, null);
this.domains
.list()
.as(StepVerifier::create)
.expectNext(
Domain.builder()
- .id("test-shared-domain-id")
- .name("test-shared-domain-name")
+ .id("test-domain-id")
+ .name("test-domain-name")
.status(Status.SHARED)
.build())
.expectComplete()
@@ -139,23 +170,18 @@ void listDomainsOnlyShared() {
@Test
void listDomainsTcp() {
- requestPrivateDomains(this.cloudFoundryClient);
- requestSharedDomainsTcp(this.cloudFoundryClient);
+ requestListDomains(this.cloudFoundryClient, null, "test-routerGroupId");
+ requestListRouterGroups(this.routingClient, "test-tcp-group");
this.domains
.list()
.as(StepVerifier::create)
.expectNext(
Domain.builder()
- .id("test-private-domain-id")
- .name("test-private-domain-name")
- .status(Status.OWNED)
- .build(),
- Domain.builder()
- .id("test-shared-domain-id")
- .name("test-shared-domain-name")
+ .id("test-domain-id")
+ .name("test-domain-name")
.status(Status.SHARED)
- .type("test-shared-domain-type")
+ .type("tcp")
.build())
.expectComplete()
.verify(Duration.ofSeconds(5));
@@ -163,7 +189,7 @@ void listDomainsTcp() {
@Test
void listRouterGroups() {
- requestListRouterGroups(this.routingClient);
+ requestListRouterGroups(this.routingClient, "test-router-group");
this.domains
.listRouterGroups()
@@ -171,8 +197,8 @@ void listRouterGroups() {
.expectNext(
RouterGroup.builder()
.id("test-routerGroupId")
- .name("test-name")
- .type("test-type")
+ .name("test-router-group")
+ .type("tcp")
.build())
.expectComplete()
.verify(Duration.ofSeconds(5));
@@ -180,15 +206,14 @@ void listRouterGroups() {
@Test
void shareDomain() {
- requestListPrivateDomains(this.cloudFoundryClient, "test-domain", "test-domain-id");
requestOrganizations(this.cloudFoundryClient, "test-organization");
- requestAssociateOrganizationPrivateDomain(
- this.cloudFoundryClient, "test-domain-id", "test-organization-id");
+ requestListDomains(this.cloudFoundryClient, "test-organization-id", null);
+ requestShareDomain(this.cloudFoundryClient, "test-domain-id", "test-organization-id");
this.domains
.share(
ShareDomainRequest.builder()
- .domain("test-domain")
+ .domain("test-domain-name")
.organization("test-organization")
.build())
.as(StepVerifier::create)
@@ -197,14 +222,15 @@ void shareDomain() {
}
@Test
- void shareDomainSharedDomain() {
- requestListPrivateDomainsEmpty(this.cloudFoundryClient, "test-domain");
+ void shareDomainDoesNotExist() {
requestOrganizations(this.cloudFoundryClient, "test-organization");
+ requestListDomains(this.cloudFoundryClient, "test-organization-id", null);
+ requestShareDomain(this.cloudFoundryClient, "test-domain-id", "test-organization-id");
this.domains
.share(
ShareDomainRequest.builder()
- .domain("test-domain")
+ .domain("invalid-domain-name")
.organization("test-organization")
.build())
.as(StepVerifier::create)
@@ -212,21 +238,22 @@ void shareDomainSharedDomain() {
t ->
assertThat(t)
.isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Private domain test-domain does not exist"))
+ .hasMessage(
+ "Private domain invalid-domain-name does not"
+ + " exist"))
.verify(Duration.ofSeconds(5));
}
@Test
void unshareDomain() {
- requestListPrivateDomains(this.cloudFoundryClient, "test-domain", "test-domain-id");
+ requestListDomains(this.cloudFoundryClient, "test-organization-id", null);
requestOrganizations(this.cloudFoundryClient, "test-organization");
- requestRemoveOrganizationPrivateDomain(
- this.cloudFoundryClient, "test-domain-id", "test-organization-id");
+ requestUnshareDomain(this.cloudFoundryClient, "test-domain-id", "test-organization-id");
this.domains
.unshare(
UnshareDomainRequest.builder()
- .domain("test-domain")
+ .domain("test-domain-name")
.organization("test-organization")
.build())
.as(StepVerifier::create)
@@ -234,85 +261,110 @@ void unshareDomain() {
.verify(Duration.ofSeconds(5));
}
- private static void requestAssociateOrganizationPrivateDomain(
+ private static void requestShareDomain(
CloudFoundryClient cloudFoundryClient, String domainId, String organizationId) {
when(cloudFoundryClient
- .organizations()
- .associatePrivateDomain(
- AssociateOrganizationPrivateDomainRequest.builder()
- .privateDomainId(domainId)
- .organizationId(organizationId)
+ .domainsV3()
+ .share(
+ org.cloudfoundry.client.v3.domains.ShareDomainRequest.builder()
+ .domainId(domainId)
+ .data(Relationship.builder().id(organizationId).build())
.build()))
.thenReturn(Mono.empty());
}
- private static void requestCreatePrivateDomain(
- CloudFoundryClient cloudFoundryClient, String domain, String organizationId) {
+ private static void requestUnshareDomain(
+ CloudFoundryClient cloudFoundryClient, String domainId, String organizationId) {
when(cloudFoundryClient
- .privateDomains()
- .create(
- CreatePrivateDomainRequest.builder()
- .name(domain)
- .owningOrganizationId(organizationId)
+ .domainsV3()
+ .unshare(
+ org.cloudfoundry.client.v3.domains.UnshareDomainRequest.builder()
+ .domainId(domainId)
+ .organizationId(organizationId)
.build()))
+ .thenReturn(Mono.empty());
+ }
+
+ private static void requestCreateDomain(CloudFoundryClient cloudFoundryClient, String domain) {
+ when(cloudFoundryClient.domainsV3().create(any()))
.thenReturn(
Mono.just(
- fill(CreatePrivateDomainResponse.builder(), "private-domain-")
+ fill(CreateDomainResponse.builder(), "domain-")
+ .isInternal(false)
.build()));
}
- private static void requestCreateSharedDomain(
- CloudFoundryClient cloudFoundryClient, String domain) {
+ private static void requestCreateDomain(
+ CloudFoundryClient cloudFoundryClient, String domain, String organizationId) {
when(cloudFoundryClient
- .sharedDomains()
+ .domainsV3()
.create(
- org.cloudfoundry.client.v2.shareddomains.CreateSharedDomainRequest
- .builder()
+ org.cloudfoundry.client.v3.domains.CreateDomainRequest.builder()
.name(domain)
+ .relationships(
+ DomainRelationships.builder()
+ .organization(
+ ToOneRelationship.builder()
+ .data(
+ Relationship
+ .builder()
+ .id(
+ organizationId)
+ .build())
+ .build())
+ .build())
.build()))
.thenReturn(
Mono.just(
- fill(CreateSharedDomainResponse.builder(), "shared-domain-")
+ fill(CreateDomainResponse.builder(), "domain-")
+ .isInternal(false)
.build()));
}
- private static void requestListPrivateDomains(
- CloudFoundryClient cloudFoundryClient, String domain, String domainId) {
- when(cloudFoundryClient
- .privateDomains()
- .list(ListPrivateDomainsRequest.builder().name(domain).page(1).build()))
+ private static void requestListDomains(
+ CloudFoundryClient cloudFoundryClient, String organizationId, String routerGroupId) {
+ ToOneRelationship organizationRelationShip =
+ organizationId != null
+ ? ToOneRelationship.builder()
+ .data(Relationship.builder().id(organizationId).build())
+ .build()
+ : ToOneRelationship.builder().build();
+ org.cloudfoundry.client.v3.domains.RouterGroup routerGroup =
+ routerGroupId != null
+ ? org.cloudfoundry.client.v3.domains.RouterGroup.builder()
+ .id(routerGroupId)
+ .build()
+ : null;
+
+ when(cloudFoundryClient.domainsV3().list(ListDomainsRequest.builder().page(1).build()))
.thenReturn(
Mono.just(
- fill(ListPrivateDomainsResponse.builder())
+ fill(ListDomainsResponse.builder())
.resource(
- fill(PrivateDomainResource.builder())
- .metadata(
- fill(
- Metadata.builder(),
- "private-domain-")
- .id(domainId)
+ fill(DomainResource.builder(), "domain-")
+ .isInternal(false)
+ .relationships(
+ DomainRelationships.builder()
+ .organization(
+ organizationRelationShip)
.build())
+ .routerGroup(routerGroup)
.build())
- .totalPages(1)
+ .pagination(Pagination.builder().totalPages(1).build())
.build()));
}
- private static void requestListPrivateDomainsEmpty(
- CloudFoundryClient cloudFoundryClient, String domain) {
- when(cloudFoundryClient
- .privateDomains()
- .list(ListPrivateDomainsRequest.builder().name(domain).page(1).build()))
- .thenReturn(Mono.just(fill(ListPrivateDomainsResponse.builder()).build()));
- }
-
- private static void requestListRouterGroups(RoutingClient routingClient) {
- when(routingClient.routerGroups().list(ListRouterGroupsRequest.builder().build()))
+ private static void requestListRouterGroups(
+ RoutingClient routingClient, String routerGroupName) {
+ when(routingClient.routerGroups().list(any()))
.thenReturn(
Mono.just(
ListRouterGroupsResponse.builder()
.routerGroup(
fill(org.cloudfoundry.routing.v1.routergroups
.RouterGroup.builder())
+ .name(routerGroupName)
+ .type("tcp")
.build())
.build()));
}
@@ -320,7 +372,7 @@ private static void requestListRouterGroups(RoutingClient routingClient) {
private static void requestOrganizations(
CloudFoundryClient cloudFoundryClient, String organization) {
when(cloudFoundryClient
- .organizations()
+ .organizationsV3()
.list(
ListOrganizationsRequest.builder()
.name(organization)
@@ -336,90 +388,4 @@ private static void requestOrganizations(
.build())
.build()));
}
-
- private static void requestPrivateDomains(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient
- .privateDomains()
- .list(ListPrivateDomainsRequest.builder().page(1).build()))
- .thenReturn(
- Mono.just(
- fill(ListPrivateDomainsResponse.builder())
- .resource(
- fill(
- PrivateDomainResource.builder(),
- "private-domain-")
- .build())
- .build()));
- }
-
- private static void requestPrivateDomainsEmpty(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient
- .privateDomains()
- .list(ListPrivateDomainsRequest.builder().page(1).build()))
- .thenReturn(Mono.just(fill(ListPrivateDomainsResponse.builder()).build()));
- }
-
- private static void requestRemoveOrganizationPrivateDomain(
- CloudFoundryClient cloudFoundryClient, String domainId, String organizationId) {
- when(cloudFoundryClient
- .organizations()
- .removePrivateDomain(
- RemoveOrganizationPrivateDomainRequest.builder()
- .privateDomainId(domainId)
- .organizationId(organizationId)
- .build()))
- .thenReturn(Mono.empty());
- }
-
- private static void requestSharedDomains(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient
- .sharedDomains()
- .list(ListSharedDomainsRequest.builder().page(1).build()))
- .thenReturn(
- Mono.just(
- fill(ListSharedDomainsResponse.builder())
- .resource(
- fill(
- SharedDomainResource.builder(),
- "shared-domain-")
- .entity(
- fill(
- SharedDomainEntity
- .builder(),
- "shared-domain-")
- .routerGroupType(null)
- .build())
- .build())
- .build()));
- }
-
- private static void requestSharedDomainsEmpty(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient
- .sharedDomains()
- .list(ListSharedDomainsRequest.builder().page(1).build()))
- .thenReturn(Mono.just(fill(ListSharedDomainsResponse.builder()).build()));
- }
-
- private static void requestSharedDomainsTcp(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient
- .sharedDomains()
- .list(ListSharedDomainsRequest.builder().page(1).build()))
- .thenReturn(
- Mono.just(
- fill(ListSharedDomainsResponse.builder())
- .resource(
- fill(
- SharedDomainResource.builder(),
- "shared-domain-")
- .entity(
- fill(
- SharedDomainEntity
- .builder(),
- "shared-domain-")
- .routerGroupType(
- "test-shared-domain-type")
- .build())
- .build())
- .build()));
- }
}
diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/stacks/DefaultStacksTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/stacks/DefaultStacksTest.java
index d64f055eef..1fb1635042 100644
--- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/stacks/DefaultStacksTest.java
+++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/stacks/DefaultStacksTest.java
@@ -21,9 +21,9 @@
import java.time.Duration;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.stacks.ListStacksRequest;
-import org.cloudfoundry.client.v2.stacks.ListStacksResponse;
-import org.cloudfoundry.client.v2.stacks.StackResource;
+import org.cloudfoundry.client.v3.stacks.ListStacksRequest;
+import org.cloudfoundry.client.v3.stacks.ListStacksResponse;
+import org.cloudfoundry.client.v3.stacks.StackResource;
import org.cloudfoundry.operations.AbstractOperationsTest;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -58,7 +58,7 @@ void listStacks() {
}
private static void requestStacks(CloudFoundryClient cloudFoundryClient) {
- when(cloudFoundryClient.stacks().list(ListStacksRequest.builder().page(1).build()))
+ when(cloudFoundryClient.stacksV3().list(ListStacksRequest.builder().page(1).build()))
.thenReturn(
Mono.just(
fill(ListStacksResponse.builder())
@@ -68,7 +68,7 @@ private static void requestStacks(CloudFoundryClient cloudFoundryClient) {
private static void requestStacks(CloudFoundryClient cloudFoundryClient, String name) {
when(cloudFoundryClient
- .stacks()
+ .stacksV3()
.list(ListStacksRequest.builder().name(name).page(1).build()))
.thenReturn(
Mono.just(
diff --git a/cloudfoundry-util/pom.xml b/cloudfoundry-util/pom.xml
index f65b35c6d9..0e5f8ae76d 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
+ 6.0.0-SNAPSHOT
cloudfoundry-util
diff --git a/integration-test/pom.xml b/integration-test/pom.xml
index d52a44a9bd..fbad53c625 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
+ 6.0.0-SNAPSHOT
integration-test
@@ -70,6 +70,16 @@
${project.version}
test
+
+ io.github.resilience4j
+ resilience4j-ratelimiter
+ ${resilience4j.version}
+
+
+ io.github.resilience4j
+ resilience4j-reactor
+ ${resilience4j.version}
+
org.cloudfoundry
cloudfoundry-util
diff --git a/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java b/integration-test/src/test/java/org/cloudfoundry/CloudFoundryCleaner.java
index de94246f6b..54455f7f4d 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/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java
index 6de4016e46..58b01252f0 100644
--- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java
+++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java
@@ -38,24 +38,15 @@
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.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.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;
-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;
@@ -95,7 +86,6 @@
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;
@@ -203,18 +193,25 @@ NetworkingClient adminNetworkingClient(
@Bean
@Qualifier("admin")
- ReactorUaaClient adminUaaClient(
+ UaaClient adminUaaClient(
ConnectionContext connectionContext,
@Value("${test.admin.clientId}") String clientId,
- @Value("${test.admin.clientSecret}") String clientSecret) {
- return ReactorUaaClient.builder()
- .connectionContext(connectionContext)
- .tokenProvider(
- ClientCredentialsGrantTokenProvider.builder()
- .clientId(clientId)
- .clientSecret(clientSecret)
- .build())
- .build();
+ @Value("${test.admin.clientSecret}") String clientSecret,
+ @Value("${uaa.api.request.limit:#{null}}") Integer environmentRequestLimit) {
+ ReactorUaaClient unthrottledClient =
+ ReactorUaaClient.builder()
+ .connectionContext(connectionContext)
+ .tokenProvider(
+ ClientCredentialsGrantTokenProvider.builder()
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .build())
+ .build();
+ if (environmentRequestLimit == null) {
+ return unthrottledClient;
+ } else {
+ return new ThrottlingUaaClient(unthrottledClient, environmentRequestLimit);
+ }
}
@Bean(initMethod = "block")
@@ -356,10 +353,6 @@ 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(
@@ -388,48 +381,52 @@ NetworkingClient networkingClient(
@Bean(initMethod = "block")
@DependsOn("cloudFoundryCleaner")
Mono organizationId(
- CloudFoundryClient cloudFoundryClient, String organizationName, Mono userId) {
+ CloudFoundryClient cloudFoundryClient,
+ String organizationName,
+ String organizationQuotaName,
+ Mono userId) {
return userId.flatMap(
userId1 ->
cloudFoundryClient
- .organizationsV3()
+ .organizationQuotaDefinitions()
.create(
- CreateOrganizationRequest.builder()
- .name(organizationName)
+ CreateOrganizationQuotaDefinitionRequest.builder()
+ .applicationInstanceLimit(-1)
+ .applicationTaskLimit(-1)
+ .instanceMemoryLimit(-1)
+ .memoryLimit(16384)
+ .name(organizationQuotaName)
+ .nonBasicServicesAllowed(true)
+ .totalPrivateDomains(-1)
+ .totalReservedRoutePorts(-1)
+ .totalRoutes(-1)
+ .totalServiceKeys(-1)
+ .totalServices(-1)
.build())
- .map(CreateOrganizationResponse::getId)
+ .map(ResourceUtils::getId)
.zipWith(Mono.just(userId1)))
.flatMap(
function(
- (organizationId, userId1) ->
+ (quotaId, userId1) ->
cloudFoundryClient
- .rolesV3()
+ .organizations()
.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())
+ 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)
.build())
.thenReturn(organizationId)))
.doOnSubscribe(s -> this.logger.debug(">> ORGANIZATION ({}) <<", organizationName))
@@ -443,6 +440,11 @@ String organizationName(NameFactory nameFactory) {
return nameFactory.getOrganizationName();
}
+ @Bean
+ String organizationQuotaName(NameFactory nameFactory) {
+ return nameFactory.getQuotaDefinitionName();
+ }
+
@Bean
String password(NameFactory nameFactory) {
return nameFactory.getPassword();
@@ -463,41 +465,18 @@ RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider t
@Bean
Version serverVersion(@Qualifier("admin") CloudFoundryClient cloudFoundryClient) {
- 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()));
+ .map(response -> Version.valueOf(response.getApiVersion()))
+ .doOnSubscribe(s -> this.logger.debug(">> CLOUD FOUNDRY VERSION <<"))
+ .doOnSuccess(r -> this.logger.debug("<< CLOUD FOUNDRY VERSION >>"))
+ .block();
}
@Lazy
@Bean(initMethod = "block")
@DependsOn("cloudFoundryCleaner")
- @ConditionalOnProperty(
- name = RequiresV2Api.SKIP_V2_TESTS_ENV,
- havingValue = "false",
- matchIfMissing = true)
Mono serviceBrokerId(
CloudFoundryClient cloudFoundryClient,
NameFactory nameFactory,
@@ -546,25 +525,13 @@ Mono spaceId(
.flatMap(
orgId ->
cloudFoundryClient
- .spacesV3()
+ .spaces()
.create(
CreateSpaceRequest.builder()
.name(spaceName)
- .relationships(
- SpaceRelationships.builder()
- .organization(
- ToOneRelationship
- .builder()
- .data(
- Relationship
- .builder()
- .id(
- orgId)
- .build())
- .build())
- .build())
+ .organizationId(orgId)
.build()))
- .map(CreateSpaceResponse::getId)
+ .map(ResourceUtils::getId)
.doOnSubscribe(s -> this.logger.debug(">> SPACE ({}) <<", spaceName))
.doOnError(Throwable::printStackTrace)
.doOnSuccess(id -> this.logger.debug("<< SPACE ({}) >>", id))
@@ -580,20 +547,20 @@ String spaceName(NameFactory nameFactory) {
@DependsOn("cloudFoundryCleaner")
Mono stackId(CloudFoundryClient cloudFoundryClient, Mono stackName) {
return stackName
- .flatMapMany(
+ .flux()
+ .flatMap(
name ->
- PaginationUtils.requestClientV3Resources(
+ PaginationUtils.requestClientV2Resources(
page ->
cloudFoundryClient
- .stacksV3()
+ .stacks()
.list(
- org.cloudfoundry.client.v3.stacks
- .ListStacksRequest.builder()
+ ListStacksRequest.builder()
.name(name)
.page(page)
.build())))
.single()
- .map(org.cloudfoundry.client.v3.stacks.StackResource::getId)
+ .map(ResourceUtils::getId)
.doOnSubscribe(s -> this.logger.debug(">> STACK ({}) <<", stackName))
.doOnError(Throwable::printStackTrace)
.doOnSuccess(id -> this.logger.debug("<< STACK ({})>>", id))
@@ -607,16 +574,13 @@ Mono stackId(CloudFoundryClient cloudFoundryClient, Mono stackNa
@Bean(initMethod = "block")
@DependsOn("cloudFoundryCleaner")
Mono stackName(CloudFoundryClient cloudFoundryClient) {
- return PaginationUtils.requestClientV3Resources(
+ return PaginationUtils.requestClientV2Resources(
page ->
cloudFoundryClient
- .stacksV3()
- .list(
- org.cloudfoundry.client.v3.stacks.ListStacksRequest
- .builder()
- .page(page)
- .build()))
- .map(org.cloudfoundry.client.v3.stacks.StackResource::getName)
+ .stacks()
+ .list(ListStacksRequest.builder().page(page).build()))
+ .map(StackResource::getEntity)
+ .map(StackEntity::getName)
.filter(s -> s.matches("^cflinuxfs\\d$"))
.sort(Comparator.reverseOrder())
.next()
@@ -626,10 +590,6 @@ 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,
@@ -666,10 +626,6 @@ String testLogCacheAppName(NameFactory nameFactory) {
@Lazy
@Bean
- @ConditionalOnProperty(
- name = RequiresV2Api.SKIP_V2_TESTS_ENV,
- havingValue = "false",
- matchIfMissing = true)
TestLogCacheEndpoints testLogCacheEndpoints(
ConnectionContext connectionContext,
TokenProvider tokenProvider,
@@ -697,11 +653,20 @@ PasswordGrantTokenProvider tokenProvider(
}
@Bean
- ReactorUaaClient uaaClient(ConnectionContext connectionContext, TokenProvider tokenProvider) {
- return ReactorUaaClient.builder()
- .connectionContext(connectionContext)
- .tokenProvider(tokenProvider)
- .build();
+ UaaClient uaaClient(
+ ConnectionContext connectionContext,
+ TokenProvider tokenProvider,
+ @Value("${uaa.api.request.limit:#{null}}") Integer environmentRequestLimit) {
+ ReactorUaaClient unthrottledClient =
+ ReactorUaaClient.builder()
+ .connectionContext(connectionContext)
+ .tokenProvider(tokenProvider)
+ .build();
+ if (environmentRequestLimit == null) {
+ return unthrottledClient;
+ } else {
+ return new ThrottlingUaaClient(unthrottledClient, environmentRequestLimit);
+ }
}
@Bean(initMethod = "block")
@@ -784,36 +749,7 @@ 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
+ public static final class FailingDeserializationProblemHandler
extends DeserializationProblemHandler {
@Override
diff --git a/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java b/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java
new file mode 100644
index 0000000000..a4a9d27a61
--- /dev/null
+++ b/integration-test/src/test/java/org/cloudfoundry/ThrottlingUaaClient.java
@@ -0,0 +1,348 @@
+package org.cloudfoundry;
+
+import io.github.resilience4j.ratelimiter.RateLimiter;
+import io.github.resilience4j.ratelimiter.RateLimiterConfig;
+import io.github.resilience4j.reactor.ratelimiter.operator.RateLimiterOperator;
+import java.time.Duration;
+import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
+import org.cloudfoundry.uaa.UaaClient;
+import org.cloudfoundry.uaa.authorizations.Authorizations;
+import org.cloudfoundry.uaa.clients.Clients;
+import org.cloudfoundry.uaa.groups.AddMemberRequest;
+import org.cloudfoundry.uaa.groups.AddMemberResponse;
+import org.cloudfoundry.uaa.groups.CheckMembershipRequest;
+import org.cloudfoundry.uaa.groups.CheckMembershipResponse;
+import org.cloudfoundry.uaa.groups.CreateGroupRequest;
+import org.cloudfoundry.uaa.groups.CreateGroupResponse;
+import org.cloudfoundry.uaa.groups.DeleteGroupRequest;
+import org.cloudfoundry.uaa.groups.DeleteGroupResponse;
+import org.cloudfoundry.uaa.groups.GetGroupRequest;
+import org.cloudfoundry.uaa.groups.GetGroupResponse;
+import org.cloudfoundry.uaa.groups.Groups;
+import org.cloudfoundry.uaa.groups.ListExternalGroupMappingsRequest;
+import org.cloudfoundry.uaa.groups.ListExternalGroupMappingsResponse;
+import org.cloudfoundry.uaa.groups.ListGroupsRequest;
+import org.cloudfoundry.uaa.groups.ListGroupsResponse;
+import org.cloudfoundry.uaa.groups.ListMembersRequest;
+import org.cloudfoundry.uaa.groups.ListMembersResponse;
+import org.cloudfoundry.uaa.groups.MapExternalGroupRequest;
+import org.cloudfoundry.uaa.groups.MapExternalGroupResponse;
+import org.cloudfoundry.uaa.groups.RemoveMemberRequest;
+import org.cloudfoundry.uaa.groups.RemoveMemberResponse;
+import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupDisplayNameRequest;
+import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupDisplayNameResponse;
+import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupIdRequest;
+import org.cloudfoundry.uaa.groups.UnmapExternalGroupByGroupIdResponse;
+import org.cloudfoundry.uaa.groups.UpdateGroupRequest;
+import org.cloudfoundry.uaa.groups.UpdateGroupResponse;
+import org.cloudfoundry.uaa.identityproviders.IdentityProviders;
+import org.cloudfoundry.uaa.identityzones.IdentityZones;
+import org.cloudfoundry.uaa.ratelimit.Ratelimit;
+import org.cloudfoundry.uaa.serverinformation.ServerInformation;
+import org.cloudfoundry.uaa.tokens.Tokens;
+import org.cloudfoundry.uaa.users.ChangeUserPasswordRequest;
+import org.cloudfoundry.uaa.users.ChangeUserPasswordResponse;
+import org.cloudfoundry.uaa.users.CreateUserRequest;
+import org.cloudfoundry.uaa.users.CreateUserResponse;
+import org.cloudfoundry.uaa.users.DeleteUserRequest;
+import org.cloudfoundry.uaa.users.DeleteUserResponse;
+import org.cloudfoundry.uaa.users.ExpirePasswordRequest;
+import org.cloudfoundry.uaa.users.ExpirePasswordResponse;
+import org.cloudfoundry.uaa.users.GetUserVerificationLinkRequest;
+import org.cloudfoundry.uaa.users.GetUserVerificationLinkResponse;
+import org.cloudfoundry.uaa.users.InviteUsersRequest;
+import org.cloudfoundry.uaa.users.InviteUsersResponse;
+import org.cloudfoundry.uaa.users.ListUsersRequest;
+import org.cloudfoundry.uaa.users.ListUsersResponse;
+import org.cloudfoundry.uaa.users.LookupUserIdsRequest;
+import org.cloudfoundry.uaa.users.LookupUserIdsResponse;
+import org.cloudfoundry.uaa.users.UpdateUserRequest;
+import org.cloudfoundry.uaa.users.UpdateUserResponse;
+import org.cloudfoundry.uaa.users.UserInfoRequest;
+import org.cloudfoundry.uaa.users.UserInfoResponse;
+import org.cloudfoundry.uaa.users.Users;
+import org.cloudfoundry.uaa.users.VerifyUserRequest;
+import org.cloudfoundry.uaa.users.VerifyUserResponse;
+import org.immutables.value.Value;
+import reactor.core.publisher.Mono;
+
+public class ThrottlingUaaClient implements UaaClient {
+
+ private final UaaClient delegate;
+ private final int maxRequestsPerSecond;
+ private final RateLimiter rateLimiter;
+ private final ThrottledUsers users;
+ private Groups groups;
+
+ /**
+ * An {@link UaaClient} implementation that throttles calls to the UAA
+ * {@code /Groups} and {@code /Users} endpoints. It uses a single "bucket"
+ * for throttling requests to both endpoints.
+ *
+ * @see resilience4j docs
+ */
+ public ThrottlingUaaClient(ReactorUaaClient delegate, int maxRequestsPerSecond) {
+ // uaaLimit is calls per second. We need the milliseconds for one call because
+ // resilience4j uses sliced timeslots, while the uaa server uses a sliding window.
+ int clockSkewMillis = 20; // 20ms clock skew is a save value for ~5 requests per second.
+ int rateLimitRefreshPeriodMillis = (1000 / maxRequestsPerSecond) + clockSkewMillis;
+ this.delegate = delegate;
+ this.maxRequestsPerSecond = maxRequestsPerSecond;
+ RateLimiterConfig config =
+ RateLimiterConfig.custom()
+ .limitForPeriod(1)
+ .limitRefreshPeriod(Duration.ofMillis(rateLimitRefreshPeriodMillis))
+ .timeoutDuration(Duration.ofSeconds(10))
+ .build();
+ this.rateLimiter = RateLimiter.of("uaa", config);
+
+ this.users = new ThrottledUsers();
+ this.groups = new ThrottledGroups();
+ }
+
+ @Override
+ public Authorizations authorizations() {
+ return this.delegate.authorizations();
+ }
+
+ @Override
+ public Clients clients() {
+ return this.delegate.clients();
+ }
+
+ @Override
+ public Mono getUsername() {
+ return this.delegate.getUsername();
+ }
+
+ @Override
+ public IdentityProviders identityProviders() {
+ return this.delegate.identityProviders();
+ }
+
+ @Override
+ public IdentityZones identityZones() {
+ return this.delegate.identityZones();
+ }
+
+ @Override
+ public ServerInformation serverInformation() {
+ return this.delegate.serverInformation();
+ }
+
+ @Override
+ public Tokens tokens() {
+ return this.delegate.tokens();
+ }
+
+ @Override
+ public Users users() {
+ return users;
+ }
+
+ @Override
+ public Groups groups() {
+ return groups;
+ }
+
+ @Override
+ @Value.Derived
+ public Ratelimit rateLimit() {
+ return this.delegate.rateLimit();
+ }
+
+ public int getMaxRequestsPerSecond() {
+ return maxRequestsPerSecond;
+ }
+
+ public class ThrottledUsers implements Users {
+
+ private final Users usersDelegate;
+
+ public ThrottledUsers() {
+ this.usersDelegate = delegate.users();
+ }
+
+ @Override
+ public Mono changePassword(ChangeUserPasswordRequest request) {
+ return this.usersDelegate
+ .changePassword(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono create(CreateUserRequest request) {
+ return this.usersDelegate
+ .create(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono delete(DeleteUserRequest request) {
+ return this.usersDelegate
+ .delete(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono expirePassword(ExpirePasswordRequest request) {
+ return this.usersDelegate
+ .expirePassword(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono getVerificationLink(
+ GetUserVerificationLinkRequest request) {
+ return this.usersDelegate
+ .getVerificationLink(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono invite(InviteUsersRequest request) {
+ return this.usersDelegate
+ .invite(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono list(ListUsersRequest request) {
+ return this.usersDelegate
+ .list(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono lookup(LookupUserIdsRequest request) {
+ return this.usersDelegate
+ .lookup(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono update(UpdateUserRequest request) {
+ return this.usersDelegate
+ .update(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono userInfo(UserInfoRequest request) {
+ return this.usersDelegate
+ .userInfo(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono verify(VerifyUserRequest request) {
+ return this.usersDelegate
+ .verify(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+ }
+
+ public class ThrottledGroups implements Groups {
+
+ public final Groups groupsDelegate;
+
+ public ThrottledGroups() {
+ this.groupsDelegate = delegate.groups();
+ }
+
+ @Override
+ public Mono addMember(AddMemberRequest request) {
+ return this.groupsDelegate
+ .addMember(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono checkMembership(CheckMembershipRequest request) {
+ return this.groupsDelegate
+ .checkMembership(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono create(CreateGroupRequest request) {
+ return this.groupsDelegate
+ .create(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono delete(DeleteGroupRequest request) {
+ return this.groupsDelegate
+ .delete(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono get(GetGroupRequest request) {
+ return this.groupsDelegate
+ .get(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono list(ListGroupsRequest request) {
+ return this.groupsDelegate
+ .list(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono listExternalGroupMappings(
+ ListExternalGroupMappingsRequest request) {
+ return this.groupsDelegate
+ .listExternalGroupMappings(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono listMembers(ListMembersRequest request) {
+ return this.groupsDelegate
+ .listMembers(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono mapExternalGroup(MapExternalGroupRequest request) {
+ return this.groupsDelegate
+ .mapExternalGroup(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono removeMember(RemoveMemberRequest request) {
+ return this.groupsDelegate
+ .removeMember(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono
+ unmapExternalGroupByGroupDisplayName(
+ UnmapExternalGroupByGroupDisplayNameRequest request) {
+ return this.groupsDelegate
+ .unmapExternalGroupByGroupDisplayName(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono unmapExternalGroupByGroupId(
+ UnmapExternalGroupByGroupIdRequest request) {
+ return this.groupsDelegate
+ .unmapExternalGroupByGroupId(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+
+ @Override
+ public Mono update(UpdateGroupRequest request) {
+ return this.groupsDelegate
+ .update(request)
+ .transformDeferred(RateLimiterOperator.of(rateLimiter));
+ }
+ }
+}
diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationQuotasTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationQuotasTest.java
index 15b0239c96..e719fcc1b2 100644
--- a/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationQuotasTest.java
+++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/OrganizationQuotasTest.java
@@ -19,11 +19,16 @@
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
+import java.util.List;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v3.quotas.*;
+import org.cloudfoundry.client.v3.organizations.CreateOrganizationRequest;
+import org.cloudfoundry.client.v3.organizations.Organization;
+import org.cloudfoundry.client.v3.quotas.Apps;
+import org.cloudfoundry.client.v3.quotas.Routes;
+import org.cloudfoundry.client.v3.quotas.Services;
import org.cloudfoundry.client.v3.quotas.organizations.*;
import org.cloudfoundry.util.JobUtils;
import org.cloudfoundry.util.PaginationUtils;
@@ -197,6 +202,43 @@ public void update() {
.verify(Duration.ofMinutes(5));
}
+ @Test
+ public void apply() {
+ String orgName = this.nameFactory.getOrganizationName();
+ String organizationId = createOrganization(this.cloudFoundryClient, orgName).getId();
+ Relationship organizationRelationship1 = Relationship.builder().id(organizationId).build();
+ ToManyRelationship organizationRelationships =
+ ToManyRelationship.builder().data(organizationRelationship1).build();
+
+ String organizationQuotaName = this.nameFactory.getQuotaDefinitionName();
+
+ createOrganizationQuotaId(this.cloudFoundryClient, organizationQuotaName)
+ .flatMap(
+ organizationQuotaId -> {
+ ApplyOrganizationQuotaRequest applyOrganizationQuotaRequest =
+ ApplyOrganizationQuotaRequest.builder()
+ .organizationQuotaId(organizationQuotaId)
+ .organizationRelationships(organizationRelationships)
+ .build();
+ return this.cloudFoundryClient
+ .organizationQuotasV3()
+ .apply(applyOrganizationQuotaRequest);
+ })
+ .as(StepVerifier::create)
+ .consumeNextWith(
+ applyOrganizationQuotaResponse -> {
+ List organizationRelationshipsData =
+ applyOrganizationQuotaResponse
+ .organizationRelationships()
+ .getData();
+ assertThat(organizationRelationshipsData.size()).isEqualTo(1);
+ assertThat(organizationRelationshipsData.get(0).getId())
+ .isEqualTo(organizationId);
+ })
+ .expectComplete()
+ .verify(Duration.ofMinutes(5));
+ }
+
private static Mono createOrganizationQuotaId(
CloudFoundryClient cloudFoundryClient, String organizationQuotaName) {
return createOrganizationQuota(cloudFoundryClient, organizationQuotaName)
@@ -225,4 +267,12 @@ private static Flux requestListOrganizationQuotas(
.page(page)
.build()));
}
+
+ private static Organization createOrganization(
+ CloudFoundryClient cloudFoundryClient, String orgName) {
+ return cloudFoundryClient
+ .organizationsV3()
+ .create(CreateOrganizationRequest.builder().name(orgName).build())
+ .block(Duration.ofMinutes(5));
+ }
}
diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v3/SpaceQuotasTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v3/SpaceQuotasTest.java
index ede877a34c..a36fb49570 100644
--- a/integration-test/src/test/java/org/cloudfoundry/client/v3/SpaceQuotasTest.java
+++ b/integration-test/src/test/java/org/cloudfoundry/client/v3/SpaceQuotasTest.java
@@ -19,13 +19,18 @@
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.CloudFoundryVersion;
import org.cloudfoundry.IfCloudFoundryVersion;
import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v3.organizations.CreateOrganizationRequest;
import org.cloudfoundry.client.v3.organizations.Organization;
-import org.cloudfoundry.client.v3.quotas.*;
+import org.cloudfoundry.client.v3.quotas.Apps;
+import org.cloudfoundry.client.v3.quotas.Routes;
+import org.cloudfoundry.client.v3.quotas.Services;
import org.cloudfoundry.client.v3.quotas.spaces.*;
import org.cloudfoundry.client.v3.spaces.CreateSpaceRequest;
import org.cloudfoundry.client.v3.spaces.Space;
@@ -105,7 +110,7 @@ public void create() {
public void createWithSpaceRelationship() {
String spaceQuotaName = this.nameFactory.getQuotaDefinitionName();
SpaceQuotaRelationships spaceQuotaRelationships =
- createSpaceQuotaRelationships(organizationId, spaceId);
+ createSpaceQuotaRelationships(organizationId, Collections.singletonList(spaceId));
Apps spaceQuotaAppLimits =
Apps.builder()
@@ -277,6 +282,72 @@ public void delete() {
.verify(Duration.ofMinutes(5));
}
+ @Test
+ public void apply() {
+ String spaceQuotaName = this.nameFactory.getQuotaDefinitionName();
+
+ Relationship spaceRelationship1 = Relationship.builder().id(spaceId).build();
+ ToManyRelationship spaceRelationships =
+ ToManyRelationship.builder().data(spaceRelationship1).build();
+
+ createSpaceQuotaId(this.cloudFoundryClient, spaceQuotaName, organizationId)
+ .flatMap(
+ spaceQuotaId -> {
+ ApplySpaceQuotaRequest applySpaceQuotaRequest =
+ ApplySpaceQuotaRequest.builder()
+ .spaceQuotaId(spaceQuotaId)
+ .spaceRelationships(spaceRelationships)
+ .build();
+ return this.cloudFoundryClient
+ .spaceQuotasV3()
+ .apply(applySpaceQuotaRequest);
+ })
+ .as(StepVerifier::create)
+ .consumeNextWith(
+ applySpaceQuotaResponse -> {
+ List spaceRelationshipsData =
+ applySpaceQuotaResponse.spaceRelationships().getData();
+ assertThat(spaceRelationshipsData.size()).isEqualTo(1);
+ assertThat(spaceRelationshipsData.get(0).getId()).isEqualTo(spaceId);
+ })
+ .expectComplete()
+ .verify(Duration.ofMinutes(5));
+ }
+
+ @Test
+ public void remove() {
+ String spaceQuotaName = this.nameFactory.getQuotaDefinitionName();
+
+ // create a space quota in org with "organizationId" that is pre-associated to the space
+ // with "spaceId"
+ createSpaceQuotaId(
+ this.cloudFoundryClient,
+ spaceQuotaName,
+ organizationId,
+ Collections.singletonList(spaceId))
+ .flatMap(
+ spaceQuotaId -> {
+ RemoveSpaceQuotaRequest removeSpaceQuotaRequest =
+ RemoveSpaceQuotaRequest.builder()
+ .spaceQuotaId(spaceQuotaId)
+ .spaceId(spaceId)
+ .build();
+ return this.cloudFoundryClient
+ .spaceQuotasV3()
+ .remove(removeSpaceQuotaRequest);
+ })
+ .thenMany(requestListSpaceQuotas(this.cloudFoundryClient, spaceQuotaName))
+ .as(StepVerifier::create)
+ .consumeNextWith(
+ spaceQuotaResource -> {
+ List spaceRelationshipsData =
+ spaceQuotaResource.getRelationships().getSpaces().getData();
+ assertThat(spaceRelationshipsData.size()).isEqualTo(0);
+ })
+ .expectComplete()
+ .verify(Duration.ofMinutes(5));
+ }
+
private static Organization createOrganization(
CloudFoundryClient cloudFoundryClient, String orgName) {
return cloudFoundryClient
@@ -314,15 +385,20 @@ private static SpaceQuotaRelationships createSpaceQuotaRelationships(String orgG
@NotNull
private static SpaceQuotaRelationships createSpaceQuotaRelationships(
- String orgGuid, String spaceGuid) {
+ String orgGuid, List spaceGuids) {
ToOneRelationship organizationRelationship =
ToOneRelationship.builder()
.data(Relationship.builder().id(orgGuid).build())
.build();
+
+ // iterate over spaceGuids to create Relationship objects
+ List spaceRelationshipsData =
+ spaceGuids.stream()
+ .map(spaceGuid -> Relationship.builder().id(spaceGuid).build())
+ .collect(Collectors.toList());
+
ToManyRelationship spaceRelationships =
- ToManyRelationship.builder()
- .data(Relationship.builder().id(spaceGuid).build())
- .build();
+ ToManyRelationship.builder().data(spaceRelationshipsData).build();
return SpaceQuotaRelationships.builder()
.organization(organizationRelationship)
.spaces(spaceRelationships)
@@ -347,6 +423,31 @@ private static Mono createSpaceQuota(
.build());
}
+ private static Mono createSpaceQuotaId(
+ CloudFoundryClient cloudFoundryClient,
+ String spaceQuotaName,
+ String orgGuid,
+ List spaceGuids) {
+ return createSpaceQuota(cloudFoundryClient, spaceQuotaName, orgGuid, spaceGuids)
+ .map(CreateSpaceQuotaResponse::getId);
+ }
+
+ private static Mono createSpaceQuota(
+ CloudFoundryClient cloudFoundryClient,
+ String spaceQuotaName,
+ String orgGuid,
+ List spaceGuids) {
+ SpaceQuotaRelationships spaceQuotaRelationships =
+ createSpaceQuotaRelationships(orgGuid, spaceGuids);
+ return cloudFoundryClient
+ .spaceQuotasV3()
+ .create(
+ CreateSpaceQuotaRequest.builder()
+ .name(spaceQuotaName)
+ .relationships(spaceQuotaRelationships)
+ .build());
+ }
+
private static Flux requestListSpaceQuotas(
CloudFoundryClient cloudFoundryClient, String spaceName) {
return PaginationUtils.requestClientV3Resources(
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 0000000000..e3dbab91cb
--- /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());
+ }
+}
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 fb9026d6fd..7dde64ffee 100644
--- a/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java
+++ b/integration-test/src/test/java/org/cloudfoundry/operations/BuildpacksTest.java
@@ -77,7 +77,7 @@ public void createFromDirectory() throws IOException {
.filter(buildpack -> buildpackName.equals(buildpack.getName()))
.map(Buildpack::getFilename)
.as(StepVerifier::create)
- .expectNext("test-buildpack.zip")
+ .expectNextMatches(filename -> filename.matches(".*test-buildpack.*\\.zip"))
.expectComplete()
.verify(Duration.ofMinutes(5));
}
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 87fd783ebf..b7b16566ae 100644
--- a/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java
+++ b/integration-test/src/test/java/org/cloudfoundry/operations/DomainsTest.java
@@ -17,6 +17,7 @@
package org.cloudfoundry.operations;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.InstanceOfAssertFactories.type;
import static org.cloudfoundry.operations.domains.Status.OWNED;
import static org.cloudfoundry.operations.domains.Status.SHARED;
@@ -24,7 +25,8 @@
import org.cloudfoundry.AbstractIntegrationTest;
import org.cloudfoundry.RequiresV2Api;
import org.cloudfoundry.client.CloudFoundryClient;
-import org.cloudfoundry.client.v2.ClientV2Exception;
+import org.cloudfoundry.client.v3.ClientV3Exception;
+import org.cloudfoundry.client.v3.Error;
import org.cloudfoundry.client.v3.domains.GetDomainRequest;
import org.cloudfoundry.operations.domains.CreateDomainRequest;
import org.cloudfoundry.operations.domains.CreateSharedDomainRequest;
@@ -62,10 +64,20 @@ public void createInvalidDomain() {
.consumeErrorWith(
t ->
assertThat(t)
- .isInstanceOf(ClientV2Exception.class)
- .hasMessageMatching(
- "CF-DomainInvalid\\([0-9]+\\): The domain is"
- + " invalid.*"))
+ .asInstanceOf(type(ClientV3Exception.class))
+ .extracting(ClientV3Exception::getErrors)
+ .asList()
+ .hasSize(1)
+ .first()
+ .isEqualTo(
+ Error.builder()
+ .title("CF-UnprocessableEntity")
+ .code(10008)
+ .detail(
+ "Name does not comply with RFC 1035"
+ + " standards, Name must"
+ + " contain at least one \".\"")
+ .build()))
.verify(Duration.ofMinutes(5));
}
diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java
index 8b13789179..7db823dd95 100644
--- a/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java
+++ b/integration-test/src/test/java/org/cloudfoundry/operations/StacksTest.java
@@ -1 +1,44 @@
+package org.cloudfoundry.operations;
+import java.time.Duration;
+import org.cloudfoundry.AbstractIntegrationTest;
+import org.cloudfoundry.operations.stacks.GetStackRequest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+class StacksTest extends AbstractIntegrationTest {
+ @Autowired private CloudFoundryOperations cloudFoundryOperations;
+
+ @Autowired private Mono stackName;
+
+ @Test
+ public void create() {
+ this.stackName
+ .flatMap(
+ name ->
+ this.cloudFoundryOperations
+ .stacks()
+ .get(GetStackRequest.builder().name(name).build()))
+ .as(StepVerifier::create)
+ .expectNextMatches(
+ s -> s.getDescription().contains("Cloud Foundry Linux-based filesystem"))
+ .expectComplete()
+ .verify(Duration.ofMinutes(5));
+ }
+
+ @Test
+ public void list() {
+ String stackName = this.stackName.block();
+ this.cloudFoundryOperations
+ .stacks()
+ .list()
+ .filter(s -> s.getName().equals(stackName))
+ .as(StepVerifier::create)
+ .expectNextMatches(
+ s -> s.getDescription().startsWith("Cloud Foundry Linux-based filesystem"))
+ .expectComplete()
+ .verify(Duration.ofMinutes(5));
+ }
+}
diff --git a/integration-test/src/test/java/org/cloudfoundry/uaa/RatelimitTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/uaa/RatelimitTestConfiguration.java
new file mode 100644
index 0000000000..e162955e4d
--- /dev/null
+++ b/integration-test/src/test/java/org/cloudfoundry/uaa/RatelimitTestConfiguration.java
@@ -0,0 +1,73 @@
+package org.cloudfoundry.uaa;
+
+import java.time.Duration;
+import org.cloudfoundry.IntegrationTestConfiguration.FailingDeserializationProblemHandler;
+import org.cloudfoundry.ThrottlingUaaClient;
+import org.cloudfoundry.reactor.ConnectionContext;
+import org.cloudfoundry.reactor.DefaultConnectionContext;
+import org.cloudfoundry.reactor.ProxyConfiguration;
+import org.cloudfoundry.reactor.tokenprovider.ClientCredentialsGrantTokenProvider;
+import org.cloudfoundry.reactor.uaa.ReactorUaaClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+@Configuration
+@EnableAutoConfiguration
+public class RatelimitTestConfiguration {
+
+ @Bean
+ UaaClient adminUaaClient(
+ ConnectionContext connectionContext,
+ @Value("${test.admin.clientId}") String clientId,
+ @Value("${test.admin.clientSecret}") String clientSecret,
+ @Value("${uaa.api.request.limit:0}") int uaaLimit) {
+ ReactorUaaClient client =
+ ReactorUaaClient.builder()
+ .connectionContext(connectionContext)
+ .tokenProvider(
+ ClientCredentialsGrantTokenProvider.builder()
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .build())
+ .build();
+ if (uaaLimit > 0) {
+ return new ThrottlingUaaClient(client, uaaLimit);
+ }
+ return client;
+ }
+
+ @Bean
+ DefaultConnectionContext connectionContext(
+ @Value("${test.apiHost}") String apiHost,
+ @Value("${test.proxy.host:}") String proxyHost,
+ @Value("${test.proxy.password:}") String proxyPassword,
+ @Value("${test.proxy.port:8080}") Integer proxyPort,
+ @Value("${test.proxy.username:}") String proxyUsername,
+ @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 (StringUtils.hasText(proxyHost)) {
+ ProxyConfiguration.Builder proxyConfiguration =
+ ProxyConfiguration.builder().host(proxyHost).port(proxyPort);
+
+ if (StringUtils.hasText(proxyUsername)) {
+ proxyConfiguration.password(proxyPassword).username(proxyUsername);
+ }
+
+ connectionContext.proxyConfiguration(proxyConfiguration.build());
+ }
+
+ return connectionContext.build();
+ }
+}
diff --git a/integration-test/src/test/java/org/cloudfoundry/uaa/UaaRatelimitTest.java b/integration-test/src/test/java/org/cloudfoundry/uaa/UaaRatelimitTest.java
new file mode 100644
index 0000000000..116b20338d
--- /dev/null
+++ b/integration-test/src/test/java/org/cloudfoundry/uaa/UaaRatelimitTest.java
@@ -0,0 +1,72 @@
+package org.cloudfoundry.uaa;
+
+import java.time.Duration;
+import org.cloudfoundry.ThrottlingUaaClient;
+import org.cloudfoundry.uaa.ratelimit.Current;
+import org.cloudfoundry.uaa.ratelimit.RatelimitRequest;
+import org.cloudfoundry.uaa.ratelimit.RatelimitResponse;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+@SpringJUnitConfig(classes = RatelimitTestConfiguration.class)
+public class UaaRatelimitTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(UaaRatelimitTest.class);
+
+ @Autowired private UaaClient adminUaaClient;
+
+ @Test
+ public void getRatelimit() {
+ int envRatelimit;
+ if (adminUaaClient instanceof ThrottlingUaaClient) {
+ ThrottlingUaaClient throttlingClient = (ThrottlingUaaClient) adminUaaClient;
+ envRatelimit = throttlingClient.getMaxRequestsPerSecond();
+ } else {
+ envRatelimit = 0;
+ }
+ Mono tmp =
+ adminUaaClient
+ .rateLimit()
+ .getRatelimit(RatelimitRequest.builder().build())
+ .map(response -> getServerRatelimit(response, envRatelimit))
+ .timeout(Duration.ofSeconds(5))
+ .onErrorResume(
+ ex -> {
+ LOGGER.error(
+ "Warning: could not fetch UAA rate limit, using default"
+ + " "
+ + 0
+ + ". Cause: "
+ + ex);
+ return Mono.just(false);
+ });
+ StepVerifier.create(tmp.materialize()).expectNextCount(1).verifyComplete();
+ }
+
+ private Boolean getServerRatelimit(RatelimitResponse response, int maxRequestsPerSecond) {
+ Current curr = response.getCurrentData();
+ if (!"ACTIVE".equals(curr.getStatus())) {
+ LOGGER.debug(
+ "UaaRatelimitInitializer server ratelimit is not 'ACTIVE', but "
+ + curr.getStatus()
+ + ". Ignoring server value for ratelimit.");
+ return false;
+ }
+ Integer result = curr.getLimiterMappings();
+ LOGGER.info(
+ "Server uses uaa rate limiting. There are "
+ + result
+ + " mappings declared in file "
+ + response.getFromSource());
+ if (maxRequestsPerSecond == 0) {
+ LOGGER.warn(
+ "Ratelimiting is not set locally, set variable 'UAA_API_REQUEST_LIMIT' to a"
+ + " save value or you might experience http 429 responses.");
+ }
+ return true;
+ }
+}
diff --git a/integration-test/src/test/resources/logback-test.xml b/integration-test/src/test/resources/logback-test.xml
index 4d9869fc6e..3c774460d3 100644
--- a/integration-test/src/test/resources/logback-test.xml
+++ b/integration-test/src/test/resources/logback-test.xml
@@ -36,6 +36,7 @@
+
diff --git a/pom.xml b/pom.xml
index 0e154f8cf6..df2a45ed6e 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
+ 6.0.0-SNAPSHOT
pom
https://github.com/cloudfoundry/cf-java-client
@@ -74,6 +74,7 @@
3.0.2
2.44.4
+ 1.7.0
diff --git a/test-log-cache/pom.xml b/test-log-cache/pom.xml
index 2e1fa1f4f2..65cc77ed82 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
+ 6.0.0-SNAPSHOT
test-log-cache
diff --git a/test-service-broker/pom.xml b/test-service-broker/pom.xml
index bbbc50202c..2326a92d2c 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
+ 6.0.0-SNAPSHOT
test-service-broker