From 98a3de24e1f3d4c55587df406e26d0589767c4a5 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Tue, 28 Apr 2026 14:12:07 +0200 Subject: [PATCH 1/2] make compatibilityChecker robust if v2 api is disabled --- ...loudFoundryClientCompatibilityChecker.java | 45 +++- .../reactor/client/ReactorRoot.java | 53 +++++ .../client/_ReactorCloudFoundryClient.java | 10 +- .../client/CloudFoundryClient.java | 7 + .../java/org/cloudfoundry/client/Root.java | 33 +++ .../cloudfoundry/client/_GetRootRequest.java | 27 +++ .../cloudfoundry/client/_GetRootResponse.java | 218 ++++++++++++++++++ .../IntegrationTestConfiguration.java | 32 ++- .../org/cloudfoundry/client/v2/InfoTest.java | 10 +- 9 files changed, 423 insertions(+), 12 deletions(-) create mode 100644 cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java create mode 100644 cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java index e2aa39459b..d540e1f70c 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/CloudFoundryClientCompatibilityChecker.java @@ -20,27 +20,30 @@ import com.github.zafarkhaja.semver.Version; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.GetRootRequest; +import org.cloudfoundry.client.Root; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.info.Info; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; +import reactor.util.function.Tuple2; final class CloudFoundryClientCompatibilityChecker { private final Logger logger = LoggerFactory.getLogger("cloudfoundry-client.compatibility"); private final Info info; + private final Root root; - CloudFoundryClientCompatibilityChecker(Info info) { + CloudFoundryClientCompatibilityChecker(Info info, Root root) { this.info = info; + this.root = root; } void check() { - this.info - .get(GetInfoRequest.builder().build()) - .map(response -> Version.valueOf(response.getApiVersion())) - .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION))) + Mono> v2 = checkV2(); + v2.switchIfEmpty(checkV3()) .subscribe( consumer( (server, supported) -> @@ -51,11 +54,41 @@ void check() { t)); } + private Mono> checkV2() { + return this.info + .get(GetInfoRequest.builder().build()) + .flatMap( + response -> { + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + if ("CF API v2 is disabled".equals(response.getSupport())) { + this.logger.warn( + "calling v2 info endpoint but CF API v2 is disabled", + response); + } + return Mono.empty(); + } else { + return Mono.just(Version.valueOf(version)); + } + }) + .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION))); + } + + private Mono> checkV3() { + return this.root + .get(GetRootRequest.builder().build()) + .map( + response -> { + String versionV3 = response.getApiVersionV3(); + return Version.valueOf(versionV3); + }) + .zipWith(Mono.just(Version.valueOf(CloudFoundryClient.SUPPORTED_API_VERSION_V3))); + } + private static void logCompatibility(Version server, Version supported, Logger logger) { String message = "Client supports API version {} and is connected to server with API version {}." + " Things may not work as expected."; - if (server.greaterThan(supported)) { logger.info(message, supported, server); } else if (server.lessThan(supported)) { diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java new file mode 100644 index 0000000000..7cf690307a --- /dev/null +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/ReactorRoot.java @@ -0,0 +1,53 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.reactor.client; + +import java.util.Map; +import org.cloudfoundry.client.GetRootRequest; +import org.cloudfoundry.client.GetRootResponse; +import org.cloudfoundry.client.Root; +import org.cloudfoundry.reactor.ConnectionContext; +import org.cloudfoundry.reactor.TokenProvider; +import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; +import reactor.core.publisher.Mono; + +/** + * The Reactor-based implementation of {@link Root} + */ +public class ReactorRoot extends AbstractClientV3Operations implements Root { + + /** + * Creates an instance + * + * @param connectionContext the {@link ConnectionContext} to use when communicating with the server + * @param root the root URI of the server. Typically something like {@code https://api.run.pivotal.io}. + * @param tokenProvider the {@link TokenProvider} to use when communicating with the server + * @param requestTags map with custom http headers which will be added to web request + */ + public ReactorRoot( + ConnectionContext connectionContext, + Mono root, + TokenProvider tokenProvider, + Map requestTags) { + super(connectionContext, root, tokenProvider, requestTags); + } + + @Override + public Mono get(GetRootRequest request) { + return get(request, GetRootResponse.class, builder -> builder.pathSegment("")).checkpoint(); + } +} diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java index d131ea8a49..d646300f28 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/client/_ReactorCloudFoundryClient.java @@ -18,6 +18,7 @@ import jakarta.annotation.PostConstruct; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.Root; import org.cloudfoundry.client.v2.applications.ApplicationsV2; import org.cloudfoundry.client.v2.applicationusageevents.ApplicationUsageEvents; import org.cloudfoundry.client.v2.blobstores.Blobstores; @@ -205,7 +206,7 @@ public Builds builds() { @PostConstruct public void checkCompatibility() { - new CloudFoundryClientCompatibilityChecker(info()).check(); + new CloudFoundryClientCompatibilityChecker(info(), rootEndpoint()).check(); } @Override @@ -257,6 +258,13 @@ public Info info() { return new ReactorInfo(getConnectionContext(), getRootV2(), getTokenProvider(), getRequestTags()); } + @Override + @Value.Derived + public Root rootEndpoint() { + Mono root = getConnectionContext().getRootProvider().getRoot(getConnectionContext()); + return new ReactorRoot(getConnectionContext(), root, getTokenProvider(), getRequestTags()); + } + @Override @Value.Derived public IsolationSegments isolationSegments() { diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java index bfd4f1bd2a..171dd5d284 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java @@ -85,6 +85,8 @@ public interface CloudFoundryClient { */ String SUPPORTED_API_VERSION = "2.272.0"; + String SUPPORTED_API_VERSION_V3 = "3.216.0"; + /** * Main entry point to the Cloud Foundry Application Usage Events Client API */ @@ -170,6 +172,11 @@ public interface CloudFoundryClient { */ Info info(); + /** + * Main entry point to the Cloud Foundry root endpoint + */ + Root rootEndpoint(); + /** * Main entry point to the Cloud Foundry Isolation Segments API */ diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java new file mode 100644 index 0000000000..37c59a4d7e --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/Root.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.client; + +import reactor.core.publisher.Mono; + +/** + * Main entry point to the Cloud Foundry RootV3 Client API + */ +public interface Root { + + /** + * Makes the Get root request + * + * @param request the Get Root request + * @return the response from the Get Root request + */ + Mono get(GetRootRequest request); +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java new file mode 100644 index 0000000000..a0aab3e5f1 --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootRequest.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.client; + +import org.immutables.value.Value; + +/** + * The request payload for the Get root "/" operation + */ +@Value.Immutable +abstract class _GetRootRequest { + +} diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java new file mode 100644 index 0000000000..f56a890a5c --- /dev/null +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/client/_GetRootResponse.java @@ -0,0 +1,218 @@ +/* + * Copyright 2013-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.cloudfoundry.client; + +import com.fasterxml.jackson.core.JacksonException; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; + +import java.io.IOException; + +import org.cloudfoundry.Nullable; +import org.immutables.value.Value; + +/** + * The response payload for the get root operation. See V3 API Root + */ +@JsonDeserialize(using = org.cloudfoundry.client._GetRootResponse.RootDeserializer.class) +@Value.Immutable() +public interface _GetRootResponse { + + /** + * The root of the cloud controller Api version 3 + */ + @Value.Parameter + @Nullable + public abstract String getApiV3(); + + /** + * The version of the cloud controller Api version 3 + */ + @Value.Parameter + @Nullable + public abstract String getApiVersionV3(); + + /** + * The root of the cloud controller Api version 2 + */ + @Value.Parameter + @Nullable + public abstract String getApiV2(); + + /** + * The version of the cloud controller Api version 2 (if available) + */ + @Value.Parameter + @Nullable + public abstract String getApiVersion(); + + /** + * The network policy v0 endpoint + */ + @Value.Parameter + @Nullable + public abstract String getNetworkPolicyV0Endpoint(); + + /** + * The network policy v1 endpoint + */ + @Value.Parameter + @Nullable + public abstract String getNetworkPolicyV1Endpoint(); + + /** + * The login endpoint + */ + @Value.Parameter + @Nullable + public abstract String getLoginEndpoint(); + + /** + * The uaa endpoint + */ + @Value.Parameter + @Nullable + public abstract String getUaaEndpoint(); + + /** + * The credhub endpoint + */ + @Value.Parameter + @Nullable + public abstract String getCredhubEndpoint(); + + /** + * The routing endpoint + */ + @Value.Parameter + @Nullable + public abstract String getRoutingEndpoint(); + + /** + * The loggin encpoint + */ + @Value.Parameter + @Nullable + public abstract String getLoggingEndpoint(); + + /** + * The log cache url + */ + @Value.Parameter + @Nullable + public abstract String getLogCacheEndpoint(); + + /** + * The log stream url + */ + @Value.Parameter + @Nullable + public abstract String getLogStreamEndpoint(); + + /** + * The ssh endpoint for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshEndpoint(); + + /** + * The ssh host key fingerprint for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshHostKeyFingerprint(); + + /** + * The ssh oauth client for apps. + */ + @Value.Parameter + @Nullable + public abstract String getAppSshOauthClient(); + + /** + * The self url + */ + @Value.Parameter + @Nullable + public abstract String getSelf(); + + public class RootDeserializer extends StdDeserializer<_GetRootResponse>{ + private static final long serialVersionUID = 1L; + + protected RootDeserializer() { + super(GetRootResponse.class); + } + @Override + public _GetRootResponse deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JacksonException { + JsonNode productNode = jp.getCodec().readTree(jp); + String apiV3Endpoint = getEndpoint("cloud_controller_v3",productNode); + + JsonNode tmp = productNode.get("links").get("cloud_controller_v3"); + String apiVersionV3 = null; + if(tmp!=null) { + apiVersionV3 = tmp.get("meta").get("version").textValue(); + } + + String apiV2Endpoint = getEndpoint("cloud_controller_v2",productNode); + tmp = productNode.get("links").get("cloud_controller_v2"); + String apiVersion = null; + if(tmp!=null) { + apiVersion = tmp.get("meta").get("version").textValue(); + } + String networkPolicyV0Endpoint = getEndpoint("network_policy_v0",productNode); + String networkPolicyV1Endpoint = getEndpoint("network_policy_v1",productNode); + String loginEndpoint = getEndpoint("login",productNode); + String uaaEndpoint = getEndpoint("uaa",productNode); + String credhubEndpoint = getEndpoint("credhub",productNode); + String routingEndpoint = getEndpoint("routing",productNode); + String loggingEndpoint = getEndpoint("logging",productNode); + String logCacheEndpoint = getEndpoint("log_cache",productNode); + String logStreamEndpoint = getEndpoint("log_stream",productNode); + String appSshEndpoint = getEndpoint("app_ssh",productNode); + tmp = productNode.get("links").get("app_ssh"); + String appSshHostKeyFingerprint = null; + if(tmp!=null) { + appSshHostKeyFingerprint = tmp.get("meta").get("host_key_fingerprint").textValue(); + } + tmp = productNode.get("links").get("app_ssh"); + String appSshOauthClient = null; + if(tmp!=null) { + appSshOauthClient = tmp.get("meta").get("oauth_client").textValue(); + } + + String self = getEndpoint("self",productNode); + return GetRootResponse.of(apiV3Endpoint,apiVersionV3,apiV2Endpoint, + apiVersion,networkPolicyV0Endpoint, networkPolicyV1Endpoint, loginEndpoint,uaaEndpoint,credhubEndpoint, + routingEndpoint,loggingEndpoint, logCacheEndpoint, logStreamEndpoint,appSshEndpoint, appSshHostKeyFingerprint, appSshOauthClient,self); + } + + // null safe access to href-endpoints + private String getEndpoint(String name,JsonNode productNode) { + String result = null; + JsonNode tmp = productNode.get("links").get(name); + if(tmp!=null&& !tmp.isNull()) { + result = tmp.get("href").textValue(); + } + return result; + } + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index bfb6d9bdf1..1c0eaba5e7 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import org.cloudfoundry.client.CloudFoundryClient; +import org.cloudfoundry.client.GetRootRequest; import org.cloudfoundry.client.v2.info.GetInfoRequest; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.client.v3.Relationship; @@ -462,15 +463,38 @@ RoutingClient routingClient(ConnectionContext connectionContext, TokenProvider t @Bean Version serverVersion(@Qualifier("admin") CloudFoundryClient cloudFoundryClient) { - return cloudFoundryClient - .info() - .get(GetInfoRequest.builder().build()) - .map(response -> Version.valueOf(response.getApiVersion())) + return serverVersionV2(cloudFoundryClient) + .switchIfEmpty(serverVersionV3(cloudFoundryClient)) .doOnSubscribe(s -> this.logger.debug(">> CLOUD FOUNDRY VERSION <<")) .doOnSuccess(r -> this.logger.debug("<< CLOUD FOUNDRY VERSION >>")) .block(); } + private Mono serverVersionV2( + @Qualifier("admin") CloudFoundryClient cloudFoundryClient) { + return cloudFoundryClient + .info() + .get(GetInfoRequest.builder().build()) + .flatMap( + response -> { + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + this.logger.warn( + "calling v2 info endpoint but CF API v2 is disabled"); + return Mono.empty(); + } + return Mono.just(Version.valueOf(version)); + }); + } + + private Mono serverVersionV3( + @Qualifier("admin") CloudFoundryClient cloudFoundryClient) { + return cloudFoundryClient + .rootEndpoint() + .get(GetRootRequest.builder().build()) + .map(response -> Version.valueOf(response.getApiVersionV3())); + } + @Lazy @Bean(initMethod = "block") @DependsOn("cloudFoundryCleaner") diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java index a4395079a5..5c1a1d4d4f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java @@ -43,7 +43,15 @@ public void info() { .consumeNextWith( response -> { Version expected = Version.valueOf(SUPPORTED_API_VERSION); - Version actual = Version.valueOf(response.getApiVersion()); + Version actual; + String version = response.getApiVersion(); + if (version == null || version.isEmpty()) { + assertThat("CF API v2 is disabled") + .isEqualTo(response.getSupport()); + actual = Version.of(0, 0, 0); + } else { + actual = Version.valueOf(version); + } assertThat(actual).isLessThanOrEqualTo(expected); }) From 35d76267e6cfdc02f8361506b5e7bd63ed33b0a7 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Tue, 19 May 2026 10:32:11 +0200 Subject: [PATCH 2/2] Polish #1356 --- .../ApplicationManifestUtilsCommon.java | 13 ++++----- .../ApplicationManifestUtilsV3.java | 26 +++++++---------- .../ApplicationManifestUtilsV3Test.java | 9 +++--- .../IntegrationTestConfiguration.java | 8 ++---- .../cloudfoundry/client/RootEndpointTest.java | 28 +++++++++++++++++++ .../org/cloudfoundry/client/v2/InfoTest.java | 10 +------ .../operations/ApplicationsTest.java | 27 ++++++++---------- 7 files changed, 63 insertions(+), 58 deletions(-) create mode 100644 integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java index 3baca9f415..79b50ab809 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsCommon.java @@ -16,10 +16,7 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.util.tuple.Consumer2; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; +import static java.util.Collections.emptyMap; import java.io.IOException; import java.io.InputStream; @@ -39,8 +36,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; - -import static java.util.Collections.emptyMap; +import org.cloudfoundry.util.tuple.Consumer2; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; /** * Common base class for dealing with manifests @@ -322,7 +321,7 @@ static Map getNamedObject(List array, String name) { value -> value instanceof Map && name.equals( - ((Map) value).get("name"))) + ((Map) value).get("name"))) .findFirst() .orElseGet(() -> getEmptyNamedObject(array, name)); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java index b085234c0d..5b585b810c 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3.java @@ -16,12 +16,8 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.HealthCheckType; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.yaml.snakeyaml.DumperOptions; -import org.yaml.snakeyaml.Yaml; -import reactor.core.Exceptions; +import static java.util.Collections.emptyMap; +import static java.util.stream.Collectors.toMap; import java.io.IOException; import java.io.OutputStream; @@ -37,9 +33,12 @@ import java.util.TreeMap; import java.util.regex.Pattern; import java.util.stream.Stream; - -import static java.util.Collections.emptyMap; -import static java.util.stream.Collectors.toMap; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; +import reactor.core.Exceptions; /** * Utilities for dealing with {@link ManifestV3}s. Includes the functionality to transform to and from standard CLI YAML files. @@ -162,8 +161,7 @@ private static ManifestV3Application.Builder toApplicationManifest( "features", variables, String::valueOf, - (k,v) -> builder.feature(k, Boolean.valueOf(v)) - ); + (k, v) -> builder.feature(k, Boolean.valueOf(v))); asList( application, "processes", @@ -307,11 +305,7 @@ private static Map toYaml(ManifestV3 manifest) { private static Map toApplicationYaml(ManifestV3Application application) { Map yaml = ApplicationManifestUtilsCommon.toApplicationYaml(application); - putIfPresent( - yaml, - "features", - application.getFeatures() - ); + putIfPresent(yaml, "features", application.getFeatures()); putIfPresent( yaml, "processes", diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java index fac85f0377..3e05995f57 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/ApplicationManifestUtilsV3Test.java @@ -1,15 +1,14 @@ package org.cloudfoundry.operations.applications; -import org.cloudfoundry.client.v3.Metadata; -import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; -import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.cloudfoundry.client.v3.Metadata; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.junit.jupiter.api.Test; class ApplicationManifestUtilsV3Test { @Test diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 1c0eaba5e7..6de4016e46 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -470,8 +470,7 @@ Version serverVersion(@Qualifier("admin") CloudFoundryClient cloudFoundryClient) .block(); } - private Mono serverVersionV2( - @Qualifier("admin") CloudFoundryClient cloudFoundryClient) { + private static Mono serverVersionV2(CloudFoundryClient cloudFoundryClient) { return cloudFoundryClient .info() .get(GetInfoRequest.builder().build()) @@ -479,16 +478,13 @@ private Mono serverVersionV2( response -> { String version = response.getApiVersion(); if (version == null || version.isEmpty()) { - this.logger.warn( - "calling v2 info endpoint but CF API v2 is disabled"); return Mono.empty(); } return Mono.just(Version.valueOf(version)); }); } - private Mono serverVersionV3( - @Qualifier("admin") CloudFoundryClient cloudFoundryClient) { + private static Mono serverVersionV3(CloudFoundryClient cloudFoundryClient) { return cloudFoundryClient .rootEndpoint() .get(GetRootRequest.builder().build()) diff --git a/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java b/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java new file mode 100644 index 0000000000..84b0479420 --- /dev/null +++ b/integration-test/src/test/java/org/cloudfoundry/client/RootEndpointTest.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.client; + +import com.github.zafarkhaja.semver.Version; +import java.time.Duration; +import org.cloudfoundry.AbstractIntegrationTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import reactor.test.StepVerifier; + +class RootEndpointTest extends AbstractIntegrationTest { + + @Qualifier("cloudFoundryClient") + @Autowired + CloudFoundryClient client; + + @Test + void rootVersion() { + client.rootEndpoint() + .get(GetRootRequest.builder().build()) + .map(GetRootResponse::getApiVersionV3) + .map(Version::parse) + .as(StepVerifier::create) + .consumeNextWith(v -> v.isHigherThan(Version.of(3))) + .expectComplete() + .verify(Duration.ofMinutes(1)); + } +} diff --git a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java index 5c1a1d4d4f..a4395079a5 100644 --- a/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/client/v2/InfoTest.java @@ -43,15 +43,7 @@ public void info() { .consumeNextWith( response -> { Version expected = Version.valueOf(SUPPORTED_API_VERSION); - Version actual; - String version = response.getApiVersion(); - if (version == null || version.isEmpty()) { - assertThat("CF API v2 is disabled") - .isEqualTo(response.getSupport()); - actual = Version.of(0, 0, 0); - } else { - actual = Version.valueOf(version); - } + Version actual = Version.valueOf(response.getApiVersion()); assertThat(actual).isLessThanOrEqualTo(expected); }) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 936542c24f..13b9fc1d3b 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -16,6 +16,15 @@ package org.cloudfoundry.operations; +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; @@ -83,16 +92,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import java.io.IOException; -import java.nio.file.Path; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - @CleanupCloudFoundryAfterClass @RequiresV2Api public final class ApplicationsTest extends AbstractIntegrationTest { @@ -755,9 +754,7 @@ public void pushManifestV3() throws IOException { } @Test - @IfCloudFoundryVersion( - greaterThanOrEqualTo = - CloudFoundryVersion.PCF_4_v2) + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_4_v2) public void pushManifestV3WithFeature() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -787,7 +784,6 @@ public void pushManifestV3WithFeature() throws IOException { this.cloudFoundryOperations .applications() .get(GetApplicationRequest.builder().name(applicationName).build())) - .map(ApplicationDetail::getId) .flatMapMany( applicationId -> @@ -798,7 +794,8 @@ public void pushManifestV3WithFeature() throws IOException { .listFeatures( ListApplicationFeaturesRequest .builder() - .applicationId(applicationId) + .applicationId( + applicationId) .page(page) .build()))) .filter(feature -> featureKey.equals(feature.getName()))