Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ on:
paths-ignore:
- 'docs/**'
- 'adr/**'
branches: [ main, next ]
branches: [ main, next, v5.3 ]
push:
paths-ignore:
- 'docs/**'
- 'adr/**'
branches:
- main
- next
- v5.3

jobs:
sample_operators_tests:
Expand Down
2 changes: 1 addition & 1 deletion bootstrapper-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.2.3-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>

<artifactId>bootstrapper</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<artifactId>operator-framework-junit</artifactId>
<version>${josdk.version}</version>
<scope>test</scope>
</dependency>
Expand Down
4 changes: 2 additions & 2 deletions caffeine-bounded-cache-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.2.3-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>

<artifactId>caffeine-bounded-cache-support</artifactId>
Expand All @@ -43,7 +43,7 @@
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<artifactId>operator-framework-junit</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
Expand Down
29 changes: 29 additions & 0 deletions docs/content/en/docs/migration/v5-3-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Migrating from v5.2 to v5.3
description: Migrating from v5.2 to v5.3
---


## Renamed JUnit Module

If you use JUnit extension in your test just rename it from:

```
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<version>5.2.x<version>
<scope>test</scope>
</dependency>
```

to

```
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit</artifactId>
<version>5.3.0<version>
<scope>test</scope>
</dependency>
```
4 changes: 2 additions & 2 deletions micrometer-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.2.3-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>

<artifactId>micrometer-support</artifactId>
Expand Down Expand Up @@ -58,7 +58,7 @@
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<artifactId>operator-framework-junit</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
Expand Down
4 changes: 2 additions & 2 deletions operator-framework-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-bom</artifactId>
<version>5.2.3-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Operator SDK - Bill of Materials</name>
<description>Java SDK for implementing Kubernetes operators</description>
Expand Down Expand Up @@ -77,7 +77,7 @@
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<artifactId>operator-framework-junit</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Expand Down
2 changes: 1 addition & 1 deletion operator-framework-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>5.2.3-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ public <P extends HasMetadata> RegisteredController<P> register(
"Cannot register reconciler with name "
+ reconciler.getClass().getCanonicalName()
+ " reconciler named "
+ ReconcilerUtils.getNameFor(reconciler)
+ ReconcilerUtilsInternal.getNameFor(reconciler)
+ " because its configuration cannot be found.\n"
+ " Known reconcilers are: "
+ configurationService.getKnownReconcilerNames());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
import io.fabric8.kubernetes.client.utils.Serialization;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.NonComparableResourceVersionException;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;

@SuppressWarnings("rawtypes")
public class ReconcilerUtils {
public class ReconcilerUtilsInternal {

private static final String FINALIZER_NAME_SUFFIX = "/finalizer";
protected static final String MISSING_GROUP_SUFFIX = ".javaoperatorsdk.io";
Expand All @@ -46,7 +47,7 @@ public class ReconcilerUtils {
Pattern.compile(".*http(s?)://[^/]*/api(s?)/(\\S*).*"); // NOSONAR: input is controlled

// prevent instantiation of util class
private ReconcilerUtils() {}
private ReconcilerUtilsInternal() {}

public static boolean isFinalizerValid(String finalizer) {
return HasMetadata.validateFinalizer(finalizer);
Expand Down Expand Up @@ -241,4 +242,123 @@ private static boolean matchesResourceType(
}
return false;
}

/**
* Compares resource versions of two resources. This is a convenience method that extracts the
* resource versions from the metadata and delegates to {@link
* #validateAndCompareResourceVersions(String, String)}.
*
* @param h1 first resource
* @param h2 second resource
* @return negative if h1 is older, zero if equal, positive if h1 is newer
* @throws NonComparableResourceVersionException if either resource version is invalid
*/
public static int validateAndCompareResourceVersions(HasMetadata h1, HasMetadata h2) {
return validateAndCompareResourceVersions(
h1.getMetadata().getResourceVersion(), h2.getMetadata().getResourceVersion());
}

/**
* Compares the resource versions of two Kubernetes resources.
*
* <p>This method extracts the resource versions from the metadata of both resources and delegates
* to {@link #compareResourceVersions(String, String)} for the actual comparison.
*
* @param h1 the first resource to compare
* @param h2 the second resource to compare
* @return a negative integer if h1's version is less than h2's version, zero if they are equal,
* or a positive integer if h1's version is greater than h2's version
* @see #compareResourceVersions(String, String)
*/
public static int compareResourceVersions(HasMetadata h1, HasMetadata h2) {
return compareResourceVersions(
h1.getMetadata().getResourceVersion(), h2.getMetadata().getResourceVersion());
}

/**
* Compares two resource version strings using a length-first, then lexicographic comparison
* algorithm.
*
* <p>The comparison is performed in two steps:
*
* <ol>
* <li>First, compare the lengths of the version strings. A longer version string is considered
* greater than a shorter one. This works correctly for numeric versions because larger
* numbers have more digits (e.g., "100" > "99").
* <li>If the lengths are equal, perform a character-by-character lexicographic comparison until
* a difference is found.
* </ol>
*
* <p>This algorithm is more efficient than parsing the versions as numbers, especially for
* Kubernetes resource versions which are typically monotonically increasing numeric strings.
*
* <p><strong>Note:</strong> This method does not validate that the input strings are numeric. For
* validated numeric comparison, use {@link #validateAndCompareResourceVersions(String, String)}.
*
* @param v1 the first resource version string
* @param v2 the second resource version string
* @return a negative integer if v1 is less than v2, zero if they are equal, or a positive integer
* if v1 is greater than v2
* @see #validateAndCompareResourceVersions(String, String)
*/
public static int compareResourceVersions(String v1, String v2) {
int comparison = v1.length() - v2.length();
if (comparison != 0) {
return comparison;
}
for (int i = 0; i < v2.length(); i++) {
int comp = v1.charAt(i) - v2.charAt(i);
if (comp != 0) {
return comp;
}
}
return 0;
}

/**
* Compares two Kubernetes resource versions numerically. Kubernetes resource versions are
* expected to be numeric strings that increase monotonically. This method assumes both versions
* are valid numeric strings without leading zeros.
*
* @param v1 first resource version
* @param v2 second resource version
* @return negative if v1 is older, zero if equal, positive if v1 is newer
* @throws NonComparableResourceVersionException if either resource version is empty, has leading
* zeros, or contains non-numeric characters
*/
public static int validateAndCompareResourceVersions(String v1, String v2) {
int v1Length = validateResourceVersion(v1);
int v2Length = validateResourceVersion(v2);
int comparison = v1Length - v2Length;
if (comparison != 0) {
return comparison;
}
for (int i = 0; i < v2Length; i++) {
int comp = v1.charAt(i) - v2.charAt(i);
if (comp != 0) {
return comp;
}
}
return 0;
}

private static int validateResourceVersion(String v1) {
int v1Length = v1.length();
if (v1Length == 0) {
throw new NonComparableResourceVersionException("Resource version is empty");
}
for (int i = 0; i < v1Length; i++) {
char char1 = v1.charAt(i);
if (char1 == '0') {
if (i == 0) {
throw new NonComparableResourceVersionException(
"Resource version cannot begin with 0: " + v1);
}
} else if (char1 < '0' || char1 > '9') {
throw new NonComparableResourceVersionException(
"Non numeric characters in resource version: " + v1);
}
}
return v1Length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;

/**
Expand Down Expand Up @@ -145,7 +145,7 @@ private String getReconcilersNameMessage() {
}

protected <R extends HasMetadata> String keyFor(Reconciler<R> reconciler) {
return ReconcilerUtils.getNameFor(reconciler);
return ReconcilerUtilsInternal.getNameFor(reconciler);
}

@SuppressWarnings("unused")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.ReconcilerUtilsInternal;
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
Expand Down Expand Up @@ -265,7 +265,7 @@ private <P extends HasMetadata> ResolvedControllerConfiguration<P> controllerCon
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass);

final var name = ReconcilerUtils.getNameFor(reconcilerClass);
final var name = ReconcilerUtilsInternal.getNameFor(reconcilerClass);
final var generationAware =
valueOrDefaultFromAnnotation(
annotation,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.CustomResource;
Expand Down Expand Up @@ -447,64 +445,6 @@ default Set<Class<? extends HasMetadata>> defaultNonSSAResource() {
return defaultNonSSAResources();
}

/**
* If a javaoperatorsdk.io/previous annotation should be used so that the operator sdk can detect
* events from its own updates of dependent resources and then filter them.
*
* <p>Disable this if you want to react to your own dependent resource updates
*
* @return if special annotation should be used for dependent resource to filter events
* @since 4.5.0
*/
default boolean previousAnnotationForDependentResourcesEventFiltering() {
return true;
}

/**
* For dependent resources, the framework can add an annotation to filter out events resulting
* directly from the framework's operation. There are, however, some resources that do not follow
* the Kubernetes API conventions that changes in metadata should not increase the generation of
* the resource (as recorded in the {@code generation} field of the resource's {@code metadata}).
* For these resources, this convention is not respected and results in a new event for the
* framework to process. If that particular case is not handled correctly in the resource matcher,
* the framework will consider that the resource doesn't match the desired state and therefore
* triggers an update, which in turn, will re-add the annotation, thus starting the loop again,
* infinitely.
*
* <p>As a workaround, we automatically skip adding previous annotation for those well-known
* resources. Note that if you are sure that the matcher works for your use case, and it should in
* most instances, you can remove the resource type from the blocklist.
*
* <p>The consequence of adding a resource type to the set is that the framework will not use
* event filtering to prevent events, initiated by changes made by the framework itself as a
* result of its processing of dependent resources, to trigger the associated reconciler again.
*
* <p>Note that this method only takes effect if annotating dependent resources to prevent
* dependent resources events from triggering the associated reconciler again is activated as
* controlled by {@link #previousAnnotationForDependentResourcesEventFiltering()}
*
* @return a Set of resource classes where the previous version annotation won't be used.
*/
default Set<Class<? extends HasMetadata>> withPreviousAnnotationForDependentResourcesBlocklist() {
return Set.of(Deployment.class, StatefulSet.class);
}

/**
* If the event logic should parse the resourceVersion to determine the ordering of dependent
* resource events. This is typically not needed.
*
* <p>Disabled by default as Kubernetes does not support, and discourages, this interpretation of
* resourceVersions. Enable only if your api server event processing seems to lag the operator
* logic, and you want to further minimize the amount of work done / updates issued by the
* operator.
*
* @return if resource version should be parsed (as integer)
* @since 4.5.0
*/
default boolean parseResourceVersionsForEventFilteringAndCaching() {
return false;
}

/**
* {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} patch resource or status can
* either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for
Expand Down
Loading