Skip to content

feat: detect dependent resource API version changes - #3536

Open
hej090224 wants to merge 2 commits into
operator-framework:nextfrom
hej090224:feat/2644-dependent-api-version-update
Open

feat: detect dependent resource API version changes#3536
hej090224 wants to merge 2 commits into
operator-framework:nextfrom
hej090224:feat/2644-dependent-api-version-update

Conversation

@hej090224

Copy link
Copy Markdown
Contributor

Summary

  • Add an opt-in, experimental detectApiVersionChange option on @KubernetesDependent to detect when a dependent resource's API version has changed since the operator last applied it, and request a one-time update in that case.
  • Record the API version the operator applies in the javaoperatorsdk.io/last-applied-api-version annotation, following the same pattern as the existing javaoperatorsdk.io/previous annotation.
  • Reuse the existing matching machinery (both SSA-based and non-SSA) to compare the marker instead of adding special-case matcher logic, so behavior is consistent across both paths.
  • Default behavior is unchanged: the feature is disabled by default and no marker annotation is ever added or read unless explicitly enabled.

Motivation

When a dependent resource's CRD gains a new API version and the operator is upgraded to target it, comparing actualResource.getApiVersion() with the desired resource's API version is not a reliable way to detect resources that still need to be updated: the Kubernetes API server serves a resource using the requested, served API version regardless of what it is actually stored as, so this comparison would always trivially match. This is why KubernetesDependentResource already ignores apiVersion entirely in both matchers.

Instead of trying to infer the actual stored/storage version (which JOSDK cannot reliably observe, and which tools like StorageVersionMigration exist to address), this PR lets JOSDK track what the operator itself last applied, using a persistent annotation marker, discussed in #2644.

When detectApiVersionChange is enabled:

  • On every create/update/match, the target (desired) resource is marked with the API version the operator is currently using.
  • The existing matcher (SSA or non-SSA) naturally detects a mismatch when the actual resource's recorded marker differs from - or is missing relative to - the desired marker, since it's just another annotation diff.
  • Once updated, the actual resource's marker matches the desired one again, so no further updates are triggered until the API version changes again.
  • This includes resources created before the feature was enabled: a missing marker is treated as a mismatch, causing a one-time update, after which matching succeeds normally.

This is explicitly not a replacement for Kubernetes' StorageVersionMigration and does not attempt to read or infer the actual stored representation of the resource.

Public API

  • New @KubernetesDependent(detectApiVersionChange = true) annotation attribute (default false), marked @Experimental.
  • New KubernetesDependentResourceConfig#detectApiVersionChange() and a new (additive) constructor overload; existing constructors are unchanged.
  • New KubernetesDependentResourceConfigBuilder#withDetectApiVersionChange(boolean).
  • New KubernetesDependentResource.LAST_APPLIED_API_VERSION_ANNOTATION_KEY constant (javaoperatorsdk.io/last-applied-api-version).

No breaking changes.

Testing

  • ./mvnw -pl operator-framework-core -am test -Dtest='KubernetesDependentResourceApiVersionChangeTest,KubernetesDependentConverterTest'
  • ./mvnw -pl operator-framework-core -am test -Dtest='GenericKubernetesResourceMatcherTest,SSABasedGenericKubernetesResourceMatcherTest,KubernetesDependentResourceTest,DependentResourceConfigurationResolverTest'
  • ./mvnw -pl operator-framework-core -am test (full core module, 706 tests)
  • ./mvnw spotless:apply / ./mvnw spotless:check
  • ./mvnw clean install -pl '!migration' (the migration module's OpenRewrite-based tests fail in this environment due to a pre-existing JDK 25 / OpenRewrite javac-internals incompatibility, unrelated to this change and not touched by it)

Fixes #2644

Copilot AI review requested due to automatic review settings August 3, 2026 17:23
@openshift-ci
openshift-ci Bot requested review from csviri and metacosm August 3, 2026 17:23
Add an opt-in, experimental detectApiVersionChange option on
@KubernetesDependent that records the API version the operator applies
in the javaoperatorsdk.io/last-applied-api-version annotation. The
regular matcher then detects a mismatch when that marker differs from
(or is missing relative to) the currently applied API version, causing
a one-time update without triggering repeated reconciliations once the
resource is up-to-date. Disabled by default, so existing behavior and
matching are unaffected unless explicitly enabled.

Fixes operator-framework#2644

Signed-off-by: hej090224 <fc49854985@gmail.com>
@hej090224
hej090224 force-pushed the feat/2644-dependent-api-version-update branch from d322648 to f0530e1 Compare August 3, 2026 17:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds an opt-in, experimental mechanism for dependent resources to detect API version changes by persisting the operator-applied API version in an annotation, so the existing matcher logic can trigger a one-time update when that marker changes.

Changes:

  • Introduces detectApiVersionChange on @KubernetesDependent, plus config + builder wiring to propagate the flag.
  • Adds javaoperatorsdk.io/last-applied-api-version marker annotation support in KubernetesDependentResource.
  • Adds focused unit tests and documentation covering both SSA and non-SSA matcher behavior.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java Adds marker annotation constant and writes marker when feature enabled.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfig.java Adds config flag, default, constructor overload, and getter.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceConfigBuilder.java Adds builder support for detectApiVersionChange.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverter.java Wires annotation attribute into config creation.
operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java Adds experimental annotation attribute and documentation.
operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResourceApiVersionChangeTest.java New tests validating marker-driven mismatch behavior for SSA and non-SSA.
operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentConverterTest.java New focused test for annotation→config wiring for the new flag.
docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md Documents the new experimental feature and its behavior/limitations.

Comment on lines +195 to +207
private void addLastAppliedApiVersion(R target) {
if (kubernetesDependentResourceConfig == null
|| !kubernetesDependentResourceConfig.detectApiVersionChange()) {
return;
}
var apiVersion = target.getApiVersion();
if (apiVersion != null) {
target
.getMetadata()
.getAnnotations()
.put(LAST_APPLIED_API_VERSION_ANNOTATION_KEY, apiVersion);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aacbb4b: the desired resource's annotations map is now defensively copied into a mutable LinkedHashMap (via ensureMutableAnnotations) whenever detectApiVersionChange is enabled, before this method or the pre-existing previous-annotation bookkeeping write to it. This is scoped to the opt-in feature so default behavior for everyone else is unchanged. Added a regression test (nonSSA_preservesExistingAnnotationsWhenMarkingEvenIfImmutable) that sets an immutable Map.of(...) on the desired resource and asserts both the existing entry and the new marker survive.

Comment on lines +195 to +207
private void addLastAppliedApiVersion(R target) {
if (kubernetesDependentResourceConfig == null
|| !kubernetesDependentResourceConfig.detectApiVersionChange()) {
return;
}
var apiVersion = target.getApiVersion();
if (apiVersion != null) {
target
.getMetadata()
.getAnnotations()
.put(LAST_APPLIED_API_VERSION_ANNOTATION_KEY, apiVersion);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added nonSSA_preservesExistingAnnotationsWhenMarkingEvenIfImmutable in aacbb4b, which sets an immutable Map.of("user.example.com/owner", "team-a") as the desired resource's annotations, then asserts the marker is added and the pre-existing entry is preserved. This also caught that the pre-existing previous-annotation handling code had the same latent assumption, which is now fixed alongside it (see the other reply).

Comment on lines +313 to +318
private static class ConfigMapDependentResourceForTest
extends KubernetesDependentResource<GenericKubernetesResource, HasMetadata> {
public ConfigMapDependentResourceForTest() {
super(GenericKubernetesResource.class, null);
}
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to WidgetDependentResourceForTest in aacbb4b — good catch, it does manage a GenericKubernetesResource (a synthetic "Widget" test resource), not a ConfigMap.

Comment on lines +178 to +182
var result = dr.match(actual, desired, primary(), context);

assertThat(result.matched()).isNotNull();
assertThat(desired.getMetadata().getAnnotations())
.doesNotContainKey(KubernetesDependentResource.LAST_APPLIED_API_VERSION_ANNOTATION_KEY);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aacbb4b — replaced with assertThat(result.matched()).withFailMessage(...).isTrue(), asserting the concrete expected outcome (a null desired API version just skips marking, and matching proceeds normally) instead of the no-op null check.

Comment on lines +75 to +77
* <p>When enabled, JOSDK records the API version it applies in the {@code
* javaoperatorsdk.io/last-applied-api-version} annotation. On subsequent reconciliations, the
* resource is considered mismatched (and thus updated) if that recorded marker differs from the

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in aacbb4b — the Javadoc now references {@value KubernetesDependentResource#LAST_APPLIED_API_VERSION_ANNOTATION_KEY} instead of duplicating the string literal.

Guard against a null or immutable annotations map (e.g. Map.of(...))
on the desired resource when detectApiVersionChange is enabled, since
writing the last-applied-api-version marker (and the pre-existing
previous-annotation bookkeeping that runs alongside it) requires a
mutable map. Also rename a misleadingly-named test helper and replace
a no-op assertion on a primitive boolean with a concrete expectation.

Addresses Copilot review feedback on PR operator-framework#3536.

Signed-off-by: hej090224 <fc49854985@gmail.com>
@csviri

csviri commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

@xstefank @metacosm could you review this one pls, I'm quite busy next couple of days.

@csviri
csviri requested a review from xstefank August 4, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants