Skip to content

Commit 686ba2d

Browse files
authored
Merge branch 'main' into generator-bot-19666845203/iaas
2 parents 45bcb0b + 4d7bd44 commit 686ba2d

File tree

249 files changed

+57096
-69
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

249 files changed

+57096
-69
lines changed

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,12 @@ updates:
44
directory: "/"
55
schedule:
66
interval: "daily"
7+
cooldown:
8+
default-days: 7
9+
- package-ecosystem: "gradle"
10+
directory: "/"
11+
schedule:
12+
interval: "daily"
13+
cooldown:
14+
default-days: 7
15+
exclude: ["cloud.stackit*"]

CHANGELOG.md

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,58 @@
1-
## Release (2025-xx-xx)
2-
- `iaas`: [v1.0.0](services/iaas/CHANGELOG.md#v100)
3-
- **Breaking Change:** The region must be passed as a parameter to any region-specific request.
4-
- **Feature:** Add new methods to manage routing tables: `addRoutingTableToArea`, `deleteRoutingTableFromArea`, `getRoutingTableOfArea`, `listRoutingTablesOfArea`, `updateRoutingTableOfArea`
5-
- **Feature:** Add new methods to manage routes in routing tables: `addRoutesToRoutingTable`, `deleteRouteFromRoutingTable`, `getRouteOfRoutingTable`, `listRoutesOfRoutingTable`, `updateRouteOfRoutingTable`
6-
- **Breaking Change:** Add new method to manage network area regions: `createNetworkAreaRegion`, `deleteNetworkAreaRegion`, `getNetworkAreaRegion`, `listNetworkAreaRegions`, `updateNetworkAreaRegion`
7-
- **Feature:** Add new field `Encrypted` to `Backup` model, which indicates if a backup is encrypted
8-
- **Feature:** Add new field `ImportProgress` to `Image` model, which indicates the import progress of an image
9-
- **Breaking Change:** Remove field `addressFamily` in `CreateNetworkAreaPayload` model
10-
- **Breaking Change:** `Network` model has changed:
11-
- Rename `networkId` to `id`
12-
- Rename `state` to `status`
13-
- Move fields `gateway`, `nameservers`, `prefixes` and `publicIp` to new model `NetworkIPv4`, and can be accessed in the new field `ipv4`
14-
- Move fields `gatewayv6`, `nameserversv6` and `prefixesv6` to new model `NetworkIPv6`, and can be accessed in the new field `ipv6`
15-
- Add new field `routingTabledId`
16-
- **Breaking Change:** `NetworkArea` model has changed:
17-
- Rename `areaId` to `id`
18-
- Remove field `ipv4`
19-
- **Breaking Change:** Rename `networkRangeId` to `id` in `NetworkRange` model
20-
- **Breaking Change:** `CreateServerPayload` model has changed:
21-
- Model `CreateServerPayloadBootVolume` of `BootVolume` property changed to `ServerBootVolume`
22-
- Property `Networking` in `CreateServerPayload` is required now
1+
## Release (2026-MM-DD)
2+
- `core`: [v0.4.1](core/CHANGELOG.md/#v041)
3+
- **Bugfix:** Add check in `KeyFlowAuthenticator` to prevent endless loops
4+
- `iaas`:
5+
- [v1.0.0](services/iaas/CHANGELOG.md#v100)
6+
- **Breaking Change:** The region must be passed as a parameter to any region-specific request.
7+
- **Feature:** Add new methods to manage routing tables: `addRoutingTableToArea`, `deleteRoutingTableFromArea`, `getRoutingTableOfArea`, `listRoutingTablesOfArea`, `updateRoutingTableOfArea`
8+
- **Feature:** Add new methods to manage routes in routing tables: `addRoutesToRoutingTable`, `deleteRouteFromRoutingTable`, `getRouteOfRoutingTable`, `listRoutesOfRoutingTable`, `updateRouteOfRoutingTable`
9+
- **Breaking Change:** Add new method to manage network area regions: `createNetworkAreaRegion`, `deleteNetworkAreaRegion`, `getNetworkAreaRegion`, `listNetworkAreaRegions`, `updateNetworkAreaRegion`
10+
- **Feature:** Add new field `Encrypted` to `Backup` model, which indicates if a backup is encrypted
11+
- **Feature:** Add new field `ImportProgress` to `Image` model, which indicates the import progress of an image
12+
- **Breaking Change:** Remove field `addressFamily` in `CreateNetworkAreaPayload` model
13+
- **Breaking Change:** `Network` model has changed:
14+
- Rename `networkId` to `id`
15+
- Rename `state` to `status`
16+
- Move fields `gateway`, `nameservers`, `prefixes` and `publicIp` to new model `NetworkIPv4`, and can be accessed in the new field `ipv4`
17+
- Move fields `gatewayv6`, `nameserversv6` and `prefixesv6` to new model `NetworkIPv6`, and can be accessed in the new field `ipv6`
18+
- Add new field `routingTabledId`
19+
- **Breaking Change:** `NetworkArea` model has changed:
20+
- Rename `areaId` to `id`
21+
- Remove field `ipv4`
22+
- **Breaking Change:** Rename `networkRangeId` to `id` in `NetworkRange` model
23+
- **Breaking Change:** `CreateServerPayload` model has changed:
24+
- Model `CreateServerPayloadBootVolume` of `BootVolume` property changed to `ServerBootVolume`
25+
- Property `Networking` in `CreateServerPayload` is required now
26+
- [v0.3.1](services/iaas/CHANGELOG.md#v031)
27+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
28+
- `resourcemanager`: [v0.4.1](services/resourcemanager/CHANGELOG.md#v041)
29+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
30+
- `loadbalancer`:
31+
- [v0.1.1](services/loadbalancer/CHANGELOG.md#v011)
32+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
33+
- [v0.1.0](services/loadbalancer/CHANGELOG.md#v010)
34+
- Initial onboarding of STACKIT Java SDK for Load balancer service
35+
- `alb`:
36+
- [v0.2.0](services/alb/CHANGELOG.md#v020)
37+
- **Feature:** Switch from `v2beta` API version to `v2` version.
38+
- **Feature:** `MaxCredentials` field added to `GetQuotaResponse`
39+
- **Breaking change:** added `version` to LoadBalancer constructor
40+
- **Breaking change:** renamed `exact` to `exactMatch` in Path model
41+
- **Breaking change:** removed `pathPrefix` from Rule model
42+
- [v0.1.1](services/alb/CHANGELOG.md#v011)
43+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
44+
- [v0.1.0](services/alb/CHANGELOG.md#v010)
45+
- Initial onboarding of STACKIT Java SDK for Application load balancer service
46+
- `objectstorage`:
47+
- [v0.1.1](services/objectstorage/CHANGELOG.md#v011)
48+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
49+
- [v0.1.0](services/objectstorage/CHANGELOG.md#v010)
50+
- Initial onboarding of STACKIT Java SDK for Object storage service
51+
- `serverupdate`:
52+
- [v0.1.1](services/serverupdate/CHANGELOG.md#v011)
53+
- Bump dependency `cloud.stackit.sdk.core` to v0.4.1
54+
- [v0.1.0](services/serverupdate/CHANGELOG.md#v010)
55+
- Initial onboarding of STACKIT Java SDK for Server Update service
2356

2457
## Release (2025-10-29)
2558
- `core`:

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ fmt:
66

77
lint:
88
@./gradlew pmdMain
9+
@./gradlew lintProjectVersion
910

1011
test:
1112
@./gradlew test

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Examples on services, configuration and authentication possibilities can be foun
6666
6767
## Authorization
6868

69-
To authenticate to the SDK, you will need a [service account](https://docs.stackit.cloud/stackit/en/service-accounts-134415819.html). Create it in the STACKIT Portal and assign it the necessary permissions, e.g. `project.owner`.
69+
To authenticate to the SDK, you will need a [service account](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/). Create it in the STACKIT Portal and assign it the necessary permissions, e.g. `project.owner`.
7070

7171
The Java SDK supports only Key flow for authentication.
7272

@@ -97,12 +97,12 @@ When creating the service account key, a new pair can be created automatically,
9797
This will make it much easier to configure the key flow authentication in the SDK, by just providing the service account key.
9898

9999
> **Optionally**, you can provide your own private key when creating the service account key, which will then require you to also provide it explicitly to the SDK, additionally to the service account key.
100-
> Check the STACKIT Knowledge Base for an [example of how to create your own key-pair](https://docs.stackit.cloud/stackit/en/usage-of-the-service-account-keys-in-stackit-175112464.html#UsageoftheserviceaccountkeysinSTACKIT-CreatinganRSAkey-pair).
100+
> Check the STACKIT Docs for an [example of how to create your own key-pair](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-keys/).
101101
102102
To configure the key flow, follow this steps:
103103

104104
1. Create a service account key:
105-
- Use the STACKIT Portal: go to the `Service Accounts` tab, choose a `Service Account` and go to `Service Account Keys` to create a key. For more details, see [Create a service account key](https://docs.stackit.cloud/stackit/en/create-a-service-account-key-175112456.html).
105+
- Use the STACKIT Portal: go to the `Service Accounts` tab, choose a `Service Account` and go to `Service Account Keys` to create a key. For more details, see [Create a service account key](https://docs.stackit.cloud/platform/access-and-identity/service-accounts/how-tos/manage-service-account-keys/).
106106
2. Save the content of the service account key by copying it and saving it in a JSON file. The expected format of the service account key is **JSON** with the following structure:
107107

108108
```json

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ subprojects {
113113
logger.warn("VERSION file not found in project '${project.path}'. Skipping version setting.")
114114
}
115115

116+
tasks.register('lintProjectVersion', VersionCheckTask) {}
117+
116118
java {
117119
withSourcesJar()
118120
withJavadocJar()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import org.gradle.api.DefaultTask
2+
import org.gradle.api.tasks.TaskAction
3+
import org.gradle.api.tasks.Input
4+
import org.gradle.api.GradleException
5+
6+
class VersionCheckTask extends DefaultTask {
7+
@Input
8+
def versionRegex = ~/^[0-1]\.\d+\.\d+$/
9+
10+
@TaskAction
11+
def checkVersion() {
12+
File versionFile = project.file('VERSION')
13+
14+
if (!versionFile.exists()) {
15+
logger.quiet("VERSION file not found in subproject: ${project.name}")
16+
return
17+
}
18+
19+
def version = versionFile.text.trim()
20+
if (!(version =~ versionRegex)) {
21+
throw new GradleException("Version '${version}' in project '${project.name}' does not match regex: ${versionRegex}")
22+
}
23+
}
24+
}

core/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## v0.4.1
2+
- **Bugfix:** Add check in `KeyFlowAuthenticator` to prevent endless loops
3+
14
## v0.4.0
25
- **Feature:** Added core wait handler structure which can be used by every service waiter implementation.
36

core/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.4.0
1+
0.4.1

core/src/main/java/cloud/stackit/sdk/core/KeyFlowAuthenticator.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public class KeyFlowAuthenticator implements Authenticator {
5252
/**
5353
* Creates the initial service account and refreshes expired access token.
5454
*
55+
* <p>NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior.
56+
* The first request is always attempted without the authenticator and in case the response is
57+
* Unauthorized(=401), OkHttp reattempt the request with the authenticator. See <a
58+
* href="https://square.github.io/okhttp/recipes/#handling-authentication-kt-java">OkHttp
59+
* Docs</a>
60+
*
5561
* @deprecated use constructor with OkHttpClient instead to prevent resource leaks. Will be
5662
* removed in April 2026.
5763
* @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
@@ -65,6 +71,12 @@ public KeyFlowAuthenticator(CoreConfiguration cfg, ServiceAccountKey saKey) {
6571
/**
6672
* Creates the initial service account and refreshes expired access token.
6773
*
74+
* <p>NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior.
75+
* The first request is always attempted without the authenticator and in case the response is
76+
* Unauthorized(=401), OkHttp reattempt the request with the authenticator. See <a
77+
* href="https://square.github.io/okhttp/recipes/#handling-authentication-kt-java">OkHttp
78+
* Docs</a>
79+
*
6880
* @deprecated use constructor with OkHttpClient instead to prevent resource leaks. Will be
6981
* removed in April 2026.
7082
* @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
@@ -81,6 +93,12 @@ public KeyFlowAuthenticator(
8193
/**
8294
* Creates the initial service account and refreshes expired access token.
8395
*
96+
* <p>NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior.
97+
* The first request is always attempted without the authenticator and in case the response is
98+
* Unauthorized(=401), OkHttp reattempt the request with the authenticator. See <a
99+
* href="https://square.github.io/okhttp/recipes/#handling-authentication-kt-java">OkHttp
100+
* Docs</a>
101+
*
84102
* @param httpClient OkHttpClient object
85103
* @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
86104
*/
@@ -91,6 +109,12 @@ public KeyFlowAuthenticator(OkHttpClient httpClient, CoreConfiguration cfg) thro
91109
/**
92110
* Creates the initial service account and refreshes expired access token.
93111
*
112+
* <p>NOTE: It's normal that 2 requests are sent, it's regular OkHttp Authenticator behavior.
113+
* The first request is always attempted without the authenticator and in case the response is
114+
* Unauthorized(=401), OkHttp reattempt the request with the authenticator. See <a
115+
* href="https://square.github.io/okhttp/recipes/#handling-authentication-kt-java">OkHttp
116+
* Docs</a>
117+
*
94118
* @param httpClient OkHttpClient object
95119
* @param cfg Configuration to set a custom token endpoint and the token expiration leeway.
96120
* @param saKey Service Account Key, which should be used for the authentication
@@ -129,6 +153,9 @@ protected KeyFlowAuthenticator(
129153

130154
@Override
131155
public Request authenticate(Route route, @NotNull Response response) throws IOException {
156+
if (response.request().header("Authorization") != null) {
157+
return null; // Give up, we've already attempted to authenticate.
158+
}
132159
String accessToken;
133160
try {
134161
accessToken = getAccessToken();

core/src/test/java/cloud/stackit/sdk/core/KeyFlowAuthenticatorTest.java

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
import java.security.spec.InvalidKeySpecException;
1717
import java.time.temporal.ChronoUnit;
1818
import java.util.Date;
19-
import okhttp3.HttpUrl;
20-
import okhttp3.OkHttpClient;
19+
import okhttp3.*;
2120
import okhttp3.mockwebserver.MockResponse;
2221
import okhttp3.mockwebserver.MockWebServer;
2322
import org.junit.jupiter.api.AfterEach;
@@ -62,6 +61,9 @@ class KeyFlowAuthenticatorTest {
6261
+ "h/9afEtu5aUE/m+1vGBoH8z1\n"
6362
+ "-----END PRIVATE KEY-----\n";
6463

64+
private static final Request mockRequest =
65+
new Request.Builder().url("https://stackit.com").get().build();
66+
6567
private ServiceAccountKey createDummyServiceAccount() {
6668
ServiceAccountCredentials credentials =
6769
new ServiceAccountCredentials("aud", "iss", "kid", PRIVATE_KEY, "sub");
@@ -270,4 +272,92 @@ void createAccessTokenWithRefreshTokenResponse200WithEmptyBodyThrowsException()
270272
assertThrows(
271273
JsonSyntaxException.class, keyFlowAuthenticator::createAccessTokenWithRefreshToken);
272274
}
275+
276+
@Test
277+
@DisplayName("authenticator sets Authorization header")
278+
void authenticatorSetsAuthorizationHeaderIfNotAuthenticated()
279+
throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
280+
// Setup mockServer
281+
final String authorizationHeader = "Authorization";
282+
KeyFlowAuthenticator.KeyFlowTokenResponse mockResponse = mockResponseBody(false);
283+
// mock response for KeyFlow authentication with mocked access token
284+
MockResponse mockedResponse =
285+
new MockResponse()
286+
.setResponseCode(200)
287+
.setBody(new Gson().toJson(mockResponse))
288+
.addHeader("Content-type", "application/json");
289+
mockWebServer.enqueue(mockedResponse);
290+
HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH);
291+
292+
// Set unauthorized request
293+
Response unauthorizedRequest =
294+
new Response.Builder()
295+
.request(mockRequest)
296+
.code(401)
297+
.message("Unauthorized")
298+
.protocol(Protocol.HTTP_2)
299+
.build();
300+
301+
// Config
302+
CoreConfiguration cfg =
303+
new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer
304+
305+
// Check if "Authorization" header is unset
306+
assertNull(unauthorizedRequest.request().header(authorizationHeader));
307+
308+
// Prepare keyFlowAuthenticator
309+
KeyFlowAuthenticator keyFlowAuthenticator =
310+
new KeyFlowAuthenticator(httpClient, cfg, createDummyServiceAccount());
311+
// authenticator creates new access token and sets it the Authorization header
312+
Request newRequest = keyFlowAuthenticator.authenticate(null, unauthorizedRequest);
313+
314+
// Check if new request is not null
315+
assertNotNull(newRequest);
316+
// Check if the "Authorization" Header is set
317+
assertNotNull(newRequest.header(authorizationHeader));
318+
}
319+
320+
@Test
321+
@DisplayName("Authenticator returns null when already authenticated")
322+
void authenticatorReturnsNullWhenAlreadyAuthenticated()
323+
throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
324+
// Setup mockServer
325+
final String authorizationHeader = "Authorization";
326+
KeyFlowAuthenticator.KeyFlowTokenResponse mockResponse = mockResponseBody(false);
327+
// mock response for KeyFlow authentication with mocked access token
328+
MockResponse mockedResponse =
329+
new MockResponse()
330+
.setResponseCode(200)
331+
.setBody(new Gson().toJson(mockResponse))
332+
.addHeader("Content-type", "application/json");
333+
mockWebServer.enqueue(mockedResponse);
334+
HttpUrl url = mockWebServer.url(MOCK_WEBSERVER_PATH);
335+
336+
// Set unauthorized request
337+
Response unauthorizedRequest =
338+
new Response.Builder()
339+
.request(
340+
mockRequest
341+
.newBuilder()
342+
.addHeader(authorizationHeader, "<my-access-token>")
343+
.build())
344+
.code(401)
345+
.message("Unauthorized")
346+
.protocol(Protocol.HTTP_2)
347+
.build(); // Unauthorized request
348+
349+
// Config
350+
CoreConfiguration cfg =
351+
new CoreConfiguration().tokenCustomUrl(url.toString()); // Use mockWebServer
352+
353+
// Check if "Authorization" header is set
354+
assertNotNull(unauthorizedRequest.request().header(authorizationHeader));
355+
356+
// Prepare keyFlowAuthenticator
357+
KeyFlowAuthenticator keyFlowAuthenticator =
358+
new KeyFlowAuthenticator(httpClient, cfg, createDummyServiceAccount());
359+
360+
// Authenticator returns no new request, because "Authorization" header was already set
361+
assertNull(keyFlowAuthenticator.authenticate(null, unauthorizedRequest));
362+
}
273363
}

0 commit comments

Comments
 (0)