Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1ecdbb4
Support skipping integration tests for unavailable CF features via en…
jorbaum Apr 29, 2026
17ad44d
Update default org quotas in integration tests
Kehrlann Apr 30, 2026
5390ef5
Update maven wrapper to 3.9.15
Kehrlann May 7, 2026
48cad87
5.x.x app features (#1351)
aegliv May 18, 2026
9426f1e
tests: enable ApplicationsTest#pushManifestV3WithFeature
Kehrlann May 18, 2026
ee2531e
Use v3 version when v2 unavailable (#1359)
Kehrlann May 19, 2026
1b7cda3
Remove signature from release script
Kehrlann May 19, 2026
411b722
v5.18.0.BUILD-SNAPSHOT Development
Kehrlann May 19, 2026
1b41db6
update dependencies, still supporting java8
Lokowandtg Jul 23, 2026
aa5b772
fix #1370, update to latest CAPI version
Lokowandtg Jul 30, 2026
fe4f3e5
Merge pull request #1371 from Lokowandtg/5.x.x_Capi1.240.0
Lokowandtg Sep 14, 2026
b78efe0
Merge pull request #1368 from Lokowandtg/5.x.x.dependencies
Lokowandtg Sep 17, 2026
3703e93
Backport #1333
SaifuddinMerchant Jan 5, 2026
3d0bfee
Add attribute allowpublic to uaa Client resource (#1366)
damzog Sep 22, 2026
acdbd2a
Create/Update route options
theghost5800 Mar 12, 2026
9fad3c1
Remove var usage from RoutesTest
Kehrlann Sep 22, 2026
c25df0e
Add env-var to ignore unknown properties (#1369)
Lokowandtg Sep 23, 2026
e22c772
Fix missing apache commons-lang3 dependency
Kehrlann Sep 30, 2026
d8ef659
Implement Service Route binding from v3 API(#1378)
Lokowandtg Oct 1, 2026
372b8d2
Minor fixes to some integration tests (#1364)
Lokowandtg Oct 1, 2026
e4fd964
Fix UAA Authorizations accept headers (#1379)
Lokowandtg Oct 2, 2026
042177d
Refactor authorization request accept header management
Kehrlann Oct 2, 2026
d615933
Fix integration test compilation for JDK8
Kehrlann Oct 5, 2026
a4ad3db
v5.19.0.BUILD-SNAPSHOT Development
Kehrlann Oct 5, 2026
6ed09b7
enable validation in branch 5.x.x (#1377)
Lokowandtg Oct 1, 2026
a96d89c
ci: Add GitHub workflow for integration tests on multiple Java versio…
ZPascal Oct 13, 2026
fb07b2f
minor corrections to integration tests (#1387)
Lokowandtg Oct 13, 2026
4e8ad2e
feat: Adapt the client and the integration tests to the KinD based CA…
ZPascal Oct 16, 2026
0a9891d
Merge branch '5.x.x'
Kehrlann Oct 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/scripts/resolve-capi-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# Prints the newest capi-release version whose CC API (v2) version equals SUPPORTED_API_VERSION
# from CloudFoundryClient.java. Needs only git and curl, no GitHub API and so no token.
#
# A capi-release tag pins cloud_controller_ng as a submodule, and that repo records its v2 API
# version in config/version_v2. The release notes aren't reliable for this. The v2 version only
# grows with the tags, so a binary search over the sorted tags is enough.
set -euo pipefail

source_file="$(dirname "$0")/../../cloudfoundry-client/src/main/java/org/cloudfoundry/client/CloudFoundryClient.java"
target="$(sed -n 's/.*String SUPPORTED_API_VERSION = "\([0-9.]*\)";.*/\1/p' "$source_file")"
[ -n "$target" ] || { echo "SUPPORTED_API_VERSION not found in $source_file" >&2; exit 1; }

workdir="$(mktemp -d)"
trap 'rm -rf "$workdir"' EXIT
# commits and trees only, which is all it takes to read the submodule commit of a tag
git clone --quiet --bare --filter=blob:none https://github.com/cloudfoundry/capi-release.git "$workdir/capi-release"

mapfile -t tags < <(git -C "$workdir/capi-release" tag -l | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V)

v2_version() {
local sha
sha="$(git -C "$workdir/capi-release" ls-tree "$1" src/cloud_controller_ng | awk '{print $3}')"
curl -fsSL --retry 3 "https://raw.githubusercontent.com/cloudfoundry/cloud_controller_ng/$sha/config/version_v2" | tr -d '[:space:]'
}

# highest index whose v2 version is <= target
lo=0
hi=$((${#tags[@]} - 1))
found=-1
while [ "$lo" -le "$hi" ]; do
mid=$(((lo + hi) / 2))
v="$(v2_version "${tags[$mid]}")"
if [ "$(printf '%s\n%s\n' "$v" "$target" | sort -V | tail -1)" = "$target" ]; then
found=$mid
lo=$((mid + 1))
else
hi=$((mid - 1))
fi
done

if [ "$found" -lt 0 ] || [ "$(v2_version "${tags[$found]}")" != "$target" ]; then
echo "No capi-release found for CC API version $target" >&2
exit 1
fi

echo "CC API $target -> capi-release ${tags[$found]}" >&2
echo "${tags[$found]}"
6 changes: 6 additions & 0 deletions .github/workflows/ci-java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ jobs:
name: Check style with Spotless
run: ./mvnw spotless:check -Pintegration-test

integration-test:
needs: build
uses: ./.github/workflows/integration-test.yml
with:
java-versions: '[21]'

125 changes: 125 additions & 0 deletions .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
name: Integration Tests

on:
workflow_dispatch:
inputs:
java-versions:
description: 'JSON array of Java versions to test'
type: string
required: false
default: '[8, 11, 17, 21]'
preview:
description: 'Deploy the latest cf-deployment versions instead of the default ones'
type: boolean
required: false
default: false
schedule:
- cron: '0 3 * * 1-5'
workflow_call:
inputs:
java-versions:
description: 'JSON array of Java versions to test'
type: string
required: false
default: '[8, 11, 17, 21]'
preview:
description: 'Deploy the latest cf-deployment versions instead of the default ones'
type: boolean
required: false
default: false

permissions:
contents: read

# A new push supersedes the running test. The group name differs from the caller's workflow name on
# purpose: a called workflow shares github.workflow with its caller, which would cancel the caller.
concurrency:
group: integration-test-${{ github.event_name == 'schedule' && 'schedule' || github.ref }}
cancel-in-progress: true

env:
CF_API_HOST: api.cf.127-0-0-1.nip.io
CF_UAA_HOST: uaa.cf.127-0-0-1.nip.io

jobs:
integration-test:
if: ${{ github.event_name != 'schedule' || github.repository == 'cloudfoundry/cf-java-client' }}
runs-on: ubuntu-latest
timeout-minutes: 120
strategy:
fail-fast: false
matrix:
java: ${{ fromJSON(inputs.java-versions || (github.event_name == 'schedule' && '[21]' || '[8, 11, 17, 21]')) }}
name: Java ${{ matrix.java }} integration test${{ (inputs.preview || github.event_name == 'schedule') && ' (preview)' || '' }}
steps:
- uses: actions/checkout@v7
with:
submodules: recursive
# Validation runs deploy the capi-release that matches SUPPORTED_API_VERSION. Preview runs
# (scheduled, or started by hand with `preview`) keep the latest versions.
- name: Resolve CAPI version
id: capi
if: ${{ !(inputs.preview || github.event_name == 'schedule') }}
run: |
# separate assignment, so that a failing resolver fails the step
version="$(.github/scripts/resolve-capi-version.sh)"
echo "version=$version" >> "$GITHUB_OUTPUT"
# All components share one Postgres, which runs out of its default 100 connections during a full
# test run. Written first, the CAPI override below appends to the same file.
- name: Write PostgreSQL values override
run: |
cat > "$RUNNER_TEMP/cf-values.yaml" <<EOF
postgresql:
primary:
extendedConfiguration: |
max_connections = 500
EOF
# Overrides the capi chart version of kind-deployment (merged last by Helmfile).
# kind-deployment's helmfile passes cloudController.appDomains, which the capi chart only
# accepts from 1.241.0 on (1.240.0 fails its values schema), so older matches are raised to it.
- name: Write CAPI values override
if: ${{ steps.capi.outputs.version != '' }}
env:
MIN_CAPI_VERSION: 1.241.0
MATCHED_CAPI_VERSION: ${{ steps.capi.outputs.version }}
run: |
version="$(printf '%s\n%s\n' "$MIN_CAPI_VERSION" "$MATCHED_CAPI_VERSION" | sort -V | tail -1)"
if [ "$version" != "$MATCHED_CAPI_VERSION" ]; then
echo "::warning::capi-release $MATCHED_CAPI_VERSION matches SUPPORTED_API_VERSION but is too old for kind-deployment, deploying $version"
fi
cat >> "$RUNNER_TEMP/cf-values.yaml" <<EOF
charts:
capi:
version: $version
EOF
# Every job gets its own Cloud Foundry on KinD, so the matrix can run in parallel.
- name: Set up Cloud Foundry
uses: cloudfoundry/kind-deployment/.github/actions/setup-cf@main
with:
use-latest-versions: ${{ (inputs.preview || github.event_name == 'schedule') }}
github-token: ${{ github.token }}
additional-values-files: ${{ runner.temp }}/cf-values.yaml
- name: Set up Java
uses: actions/setup-java@v5
with:
distribution: liberica
java-version: ${{ matrix.java }}
- name: Cache Maven packages
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Run integration tests
# CC_ADMIN_PASSWORD and UAA_ADMIN_SECRET are exported by setup-cf
env:
TEST_APIHOST: ${{ env.CF_API_HOST }}
TEST_ADMIN_USERNAME: ccadmin
TEST_ADMIN_PASSWORD: ${{ env.CC_ADMIN_PASSWORD }}
TEST_ADMIN_CLIENTID: admin
TEST_ADMIN_CLIENTSECRET: ${{ env.UAA_ADMIN_SECRET }}
TEST_SKIPSSLVALIDATION: 'true'
TEST_QUOTAS_ROUTES_RESERVED_PORTS: '50'
# kind-deployment has no metric registrar, which the log-cache metric tests need
SKIP_METRIC_REGISTRAR_TESTS: 'true'
run: ./mvnw -B -Pintegration-test test -Dgpg.skip
2 changes: 1 addition & 1 deletion .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
# under the License.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.15/apache-maven-3.9.15-bin.zip
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,12 @@ Name | Description
`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`.
`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).
`TEST_FAIL_ON_UNKNOWN_PROPERTIES` | _(Optional)_ If "true", unknown values received from CF result in a parsing error. This usually happens when client and server versions do not match. When "false", unknown properties will be ignored allowing the execution of integration-tests. Use with care! Defaults to `true`.
`SKIP_BROWSER_AUTH_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require browser-based authentication (e.g., SSO tests). Useful when the Cloud Foundry instance's UAA does not have browser-based authentication configured. Defaults to `false`.
`SKIP_METRIC_REGISTRAR_TESTS` | _(Optional)_ Set to `true` to skip integration tests that require the Metric Registrar service (e.g., MetricRegistrarTest). Useful when the Cloud Foundry instance does not have the Metric Registrar service available. Defaults to `false`.
`SKIP_TCP_ROUTING_TESTS` | _(Optional)_ Set to `true` to skip TCP routing integration tests (TcpRoutesTest, RouterGroupsTest). Useful when the Cloud Foundry instance does not have TCP routing configured (i.e., no `routing_endpoint` in the info payload). Defaults to `false`.
`TEST_QUOTAS_ROUTES_RESERVED_PORTS` | _(Optional)_ When set to a positive integer, sets reserved TCP route ports (`total_reserved_ports`) on the **default** organization quota to that value during integration test bootstrap. When set to -1, sets the reserved ports to _unlimited_. Use this when the platform leaves that limit at `0` on the default quota but you still run TCP routing tests (`SKIP_TCP_ROUTING_TESTS` unset or `false`). If unset, no quota change is applied. Maps to the Spring property `test.quotas.routes.reserved-ports`.
`SKIP_V2_TESTS` | _(Optional)_ Set to `true` to skip all V2 API integration tests. Useful when the Cloud Foundry V2 API is rate-limited or unavailable. When enabled, tests annotated with `@RequiresV2Api` will be skipped, including V3 tests that depend on V2 API calls (e.g., service broker creation). Defaults to `false`.

If you do not have access to a CloudFoundry instance with admin access, you can run one locally using [bosh-deployment](https://github.com/cloudfoundry/bosh-deployment) & [cf-deployment](https://github.com/cloudfoundry/cf-deployment/) and Virtualbox.

Expand Down
2 changes: 1 addition & 1 deletion ci/create-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ SNAPSHOT=$2
./mvnw versions:set -DnewVersion=$RELEASE -DgenerateBackupPoms=false
git add .
git commit --message "v$RELEASE Release"
git tag -s v$RELEASE -m "v$RELEASE"
git tag v$RELEASE -m "v$RELEASE"

git reset --hard HEAD^1
./mvnw versions:set -DnewVersion=$SNAPSHOT -DgenerateBackupPoms=false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Tuple2<Version, Version>> v2 = checkV2();
v2.switchIfEmpty(checkV3())
.subscribe(
consumer(
(server, supported) ->
Expand All @@ -51,11 +54,41 @@ void check() {
t));
}

private Mono<Tuple2<Version, Version>> 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<Tuple2<Version, Version>> 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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> root,
TokenProvider tokenProvider,
Map<String, String> requestTags) {
super(connectionContext, root, tokenProvider, requestTags);
}

@Override
public Mono<GetRootResponse> get(GetRootRequest request) {
return get(request, GetRootResponse.class, builder -> builder.pathSegment("")).checkpoint();
}
}
Loading
Loading