From 8259fd357389528bed56ec42e38b07a49156dfaf Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Tue, 5 Aug 2025 12:14:11 +0000 Subject: [PATCH 01/39] Set new SNAPSHOT version into pom files. --- bootstrapper-maven-plugin/pom.xml | 2 +- caffeine-bounded-cache-support/pom.xml | 2 +- micrometer-support/pom.xml | 2 +- operator-framework-bom/pom.xml | 2 +- operator-framework-core/pom.xml | 2 +- operator-framework-junit5/pom.xml | 2 +- operator-framework/pom.xml | 2 +- pom.xml | 2 +- sample-operators/controller-namespace-deletion/pom.xml | 2 +- sample-operators/leader-election/pom.xml | 2 +- sample-operators/mysql-schema/pom.xml | 2 +- sample-operators/pom.xml | 2 +- sample-operators/tomcat-operator/pom.xml | 2 +- sample-operators/webpage/pom.xml | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml index 742c47adc9..3e292e810b 100644 --- a/bootstrapper-maven-plugin/pom.xml +++ b/bootstrapper-maven-plugin/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT bootstrapper diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml index c45e56386d..14e19dd85e 100644 --- a/caffeine-bounded-cache-support/pom.xml +++ b/caffeine-bounded-cache-support/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT caffeine-bounded-cache-support diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml index 73ed6ff77e..ea18d07ce7 100644 --- a/micrometer-support/pom.xml +++ b/micrometer-support/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT micrometer-support diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index fad8f0e164..4b314d5719 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk operator-framework-bom - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT pom Operator SDK - Bill of Materials Java SDK for implementing Kubernetes operators diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml index 69c02d6f01..c99b609113 100644 --- a/operator-framework-core/pom.xml +++ b/operator-framework-core/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT ../pom.xml diff --git a/operator-framework-junit5/pom.xml b/operator-framework-junit5/pom.xml index 07bfa9cd1c..8c8a349af0 100644 --- a/operator-framework-junit5/pom.xml +++ b/operator-framework-junit5/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT operator-framework-junit-5 diff --git a/operator-framework/pom.xml b/operator-framework/pom.xml index 18cbda43cf..9324f16835 100644 --- a/operator-framework/pom.xml +++ b/operator-framework/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT operator-framework diff --git a/pom.xml b/pom.xml index 88283de27c..0282fccb0a 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT pom Operator SDK for Java Java SDK for implementing Kubernetes operators diff --git a/sample-operators/controller-namespace-deletion/pom.xml b/sample-operators/controller-namespace-deletion/pom.xml index 9a87338da5..312e2fb199 100644 --- a/sample-operators/controller-namespace-deletion/pom.xml +++ b/sample-operators/controller-namespace-deletion/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-controller-namespace-deletion diff --git a/sample-operators/leader-election/pom.xml b/sample-operators/leader-election/pom.xml index ca74158ae6..f01406b132 100644 --- a/sample-operators/leader-election/pom.xml +++ b/sample-operators/leader-election/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-leader-election diff --git a/sample-operators/mysql-schema/pom.xml b/sample-operators/mysql-schema/pom.xml index 94b2f93769..cf1be19cbb 100644 --- a/sample-operators/mysql-schema/pom.xml +++ b/sample-operators/mysql-schema/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-mysql-schema-operator diff --git a/sample-operators/pom.xml b/sample-operators/pom.xml index 2f1c9c1645..7763767a1f 100644 --- a/sample-operators/pom.xml +++ b/sample-operators/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk java-operator-sdk - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-operators diff --git a/sample-operators/tomcat-operator/pom.xml b/sample-operators/tomcat-operator/pom.xml index 38d6b4ec0c..3a9b640db8 100644 --- a/sample-operators/tomcat-operator/pom.xml +++ b/sample-operators/tomcat-operator/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-tomcat-operator diff --git a/sample-operators/webpage/pom.xml b/sample-operators/webpage/pom.xml index 7f118be1bb..d3a691a93a 100644 --- a/sample-operators/webpage/pom.xml +++ b/sample-operators/webpage/pom.xml @@ -5,7 +5,7 @@ io.javaoperatorsdk sample-operators - 5.1.2-SNAPSHOT + 5.1.3-SNAPSHOT sample-webpage-operator From 4bb58aab24911e26be95e1c3ad58d74706335cf9 Mon Sep 17 00:00:00 2001 From: ds-akloskowski Date: Thu, 7 Aug 2025 17:38:13 +0200 Subject: [PATCH 02/39] fix: Reorder setting visited flag and readyPostcondition result (#2886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adrian Kłoskowski --- .../dependent/workflow/WorkflowReconcileExecutor.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java index 065e790ba4..9e29305b51 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/WorkflowReconcileExecutor.java @@ -144,9 +144,11 @@ protected void doRun(DependentResourceNode dependentResourceNode) { log.debug("Reconciling for primary: {} node: {} ", primaryID, dependentResourceNode); ReconcileResult reconcileResult = dependentResource.reconcile(primary, context); final var detailBuilder = createOrGetResultFor(dependentResourceNode); - detailBuilder.withReconcileResult(reconcileResult).markAsVisited(); - if (isConditionMet(dependentResourceNode.getReadyPostcondition(), dependentResourceNode)) { + boolean isReadyPostconditionMet = + isConditionMet(dependentResourceNode.getReadyPostcondition(), dependentResourceNode); + detailBuilder.withReconcileResult(reconcileResult).markAsVisited(); + if (isReadyPostconditionMet) { log.debug( "Setting already reconciled for: {} primaryID: {}", dependentResourceNode, primaryID); handleDependentsReconcile(dependentResourceNode); From 3723b1c8b72258583e260a40be298bce148c4daf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:01:04 +0200 Subject: [PATCH 03/39] chore(deps): bump org.assertj:assertj-core from 3.27.3 to 3.27.4 (#2888) Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.3 to 3.27.4. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.4) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0282fccb0a..961c227a8a 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ 3.18.0 0.21.0 1.13.0 - 3.27.3 + 3.27.4 4.3.0 2.7.3 1.15.2 From 03a0949437526f9486578151ec7a31dc31a19974 Mon Sep 17 00:00:00 2001 From: Antonio <122279781+afalhambra-hivemq@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:02:35 +0200 Subject: [PATCH 04/39] doc: Update UpdateControl#patchStatus JavaDoc (#2889) Signed-off-by: Antonio Fernandez Alhambra --- .../javaoperatorsdk/operator/api/reconciler/UpdateControl.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java index 1b5eefd7ff..1bd98c12d6 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java @@ -21,8 +21,7 @@ private UpdateControl(P resource, boolean patchResource, boolean patchStatus) { } /** - * Preferred way to update the status. It does not do optimistic locking. Uses JSON Patch to patch - * the resource. + * Preferred way to update the status. Uses JSON Patch to patch the resource. * *

Note that this does not work, if the {@link CustomResource#initStatus()} is implemented, * since it breaks the diffing process. Don't implement it if using this method. There is also an From 680600aa30718b42cbb03193c140fb8fad672460 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:04:00 +0200 Subject: [PATCH 05/39] chore(deps): bump io.micrometer:micrometer-core from 1.15.2 to 1.15.3 (#2896) Bumps [io.micrometer:micrometer-core](https://github.com/micrometer-metrics/micrometer) from 1.15.2 to 1.15.3. - [Release notes](https://github.com/micrometer-metrics/micrometer/releases) - [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.15.2...v1.15.3) --- updated-dependencies: - dependency-name: io.micrometer:micrometer-core dependency-version: 1.15.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 961c227a8a..0dbd0879d8 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 3.27.4 4.3.0 2.7.3 - 1.15.2 + 1.15.3 3.2.2 0.9.14 2.20.0 From 660ee937622c896b24bf1f71f97470ef04512fb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:37:02 +0200 Subject: [PATCH 06/39] chore(deps): bump actions/checkout from 4 to 5 (#2897) Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/e2e-test.yml | 2 +- .github/workflows/hugo.yaml | 2 +- .github/workflows/integration-tests.yml | 2 +- .github/workflows/pr.yml | 2 +- .github/workflows/release-project-in-dir.yml | 4 ++-- .github/workflows/snapshot-releases.yml | 4 ++-- .github/workflows/sonar.yml | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25562aa1c9..ebc3588a2e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,7 @@ jobs: matrix: java: [ 17, 21, 24 ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven uses: actions/setup-java@v4 with: diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 408e694a4c..c7e91915b6 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -27,7 +27,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Minikube-Kubernetes uses: manusa/actions-setup-minikube@v2.14.0 diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index 511f10a8e0..c1b967616d 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -41,7 +41,7 @@ jobs: - name: Install Dart Sass run: sudo snap install dart-sass - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive fetch-depth: 0 diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 614624242a..fb4196be9c 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -29,7 +29,7 @@ jobs: continue-on-error: ${{ inputs.experimental }} timeout-minutes: 40 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.checkout-ref }} - name: Set up Java and Maven diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 27742bf7c2..b4f045a2ef 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -17,7 +17,7 @@ jobs: check_format_and_unit_tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven uses: actions/setup-java@v4 with: diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml index 02280d5a1f..7b0d732a56 100644 --- a/.github/workflows/release-project-in-dir.yml +++ b/.github/workflows/release-project-in-dir.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout "${{inputs.version_branch}}" branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: "${{inputs.version_branch}}" @@ -56,7 +56,7 @@ jobs: if: "!contains(github.event.release.tag_name, 'RC')" # not sure we should keep this the RC part steps: - name: Checkout "${{inputs.version_branch}}" branch - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: "${{inputs.version_branch}}" diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml index f9219d1278..be2a219d37 100644 --- a/.github/workflows/snapshot-releases.yml +++ b/.github/workflows/snapshot-releases.yml @@ -16,7 +16,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven uses: actions/setup-java@v4 with: @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest needs: test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven uses: actions/setup-java@v4 with: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b7a96edef6..b95d5175c2 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest if: ${{ ( github.event_name == 'push' ) || ( github.event_name == 'pull_request' && github.event.pull_request.head.repo.owner.login == 'java-operator-sdk' ) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Java and Maven uses: actions/setup-java@v4 with: From 60216a465ef923fb2cb6c2c1b14649bdd1357f88 Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Wed, 13 Aug 2025 17:53:04 +0200 Subject: [PATCH 07/39] fix: do not output warning when resolving a configuration (#2892) * fix: do not output warning when resolving a configuration * fix: remove test code that shouldn't have been included * fix: remove duplicated line --------- Signed-off-by: Chris Laprun --- .../api/config/BaseConfigurationService.java | 23 +++++++++++++++---- .../api/config/ConfigurationService.java | 1 - 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java index 438f7d91a9..891f199dbe 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/BaseConfigurationService.java @@ -30,6 +30,12 @@ import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER; +/** + * A default {@link ConfigurationService} implementation, resolving {@link Reconciler}s + * configuration when it has already been resolved before. If this behavior is not adequate, please + * use {@link AbstractConfigurationService} instead as a base for your {@code ConfigurationService} + * implementation. + */ public class BaseConfigurationService extends AbstractConfigurationService { private static final String LOGGER_NAME = "Default ConfigurationService implementation"; @@ -149,10 +155,12 @@ private static void configureFromAnnotatedReconciler( @Override protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) { - logger.warn( - "Configuration for reconciler '{}' was not found. {}", - reconcilerKey, - reconcilersNameMessage); + if (!createIfNeeded()) { + logger.warn( + "Configuration for reconciler '{}' was not found. {}", + reconcilerKey, + reconcilersNameMessage); + } } @SuppressWarnings("unused") @@ -318,6 +326,13 @@ private

ResolvedControllerConfiguration

controllerCon informerConfig); } + /** + * @deprecated This method was meant to allow subclasses to prevent automatic creation of the + * configuration when not found. This functionality is now removed, if you want to be able to + * prevent automated, on-demand creation of a reconciler's configuration, please use the + * {@link AbstractConfigurationService} implementation instead as base for your extension. + */ + @Deprecated(forRemoval = true) protected boolean createIfNeeded() { return true; } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java index 864b65c3f7..41134e64ac 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java @@ -485,7 +485,6 @@ default Set> withPreviousAnnotationForDependentReso * * @return if resource version should be parsed (as integer) * @since 4.5.0 - * @return if resource version should be parsed (as integer) */ default boolean parseResourceVersionsForEventFilteringAndCaching() { return false; From ed89fa4cc9fd3ae40f1af47d7c705f8e3469782c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:01:05 +0200 Subject: [PATCH 08/39] chore(deps): bump org.mockito:mockito-core from 5.18.0 to 5.19.0 (#2901) Bumps [org.mockito:mockito-core](https://github.com/mockito/mockito) from 5.18.0 to 5.19.0. - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.18.0...v5.19.0) --- updated-dependencies: - dependency-name: org.mockito:mockito-core dependency-version: 5.19.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0dbd0879d8..bb5adcfa10 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 7.3.1 2.0.17 2.25.1 - 5.18.0 + 5.19.0 3.18.0 0.21.0 1.13.0 From 4c42385c559d5c38a45a0abba857fe39a664d623 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:01:44 +0200 Subject: [PATCH 09/39] chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin (#2900) Bumps [org.apache.maven.plugins:maven-javadoc-plugin](https://github.com/apache/maven-javadoc-plugin) from 3.11.2 to 3.11.3. - [Release notes](https://github.com/apache/maven-javadoc-plugin/releases) - [Commits](https://github.com/apache/maven-javadoc-plugin/compare/maven-javadoc-plugin-3.11.2...maven-javadoc-plugin-3.11.3) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-javadoc-plugin dependency-version: 3.11.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator-framework-bom/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 4b314d5719..5809eed20f 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -36,7 +36,7 @@ 3.2.8 3.3.1 - 3.11.2 + 3.11.3 2.44.3 0.8.0 diff --git a/pom.xml b/pom.xml index bb5adcfa10..4a91625c2c 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 3.14.0 3.5.3 0.8.0 - 3.11.2 + 3.11.3 3.3.1 3.3.1 3.4.2 From 38fd3fcbf82d6baabdaca86717d9d929ae4b0092 Mon Sep 17 00:00:00 2001 From: Martin Stefanko Date: Tue, 19 Aug 2025 07:13:40 +0200 Subject: [PATCH 10/39] fix: RBAC typos in testsuite (#2902) --- .../InformerRelatedBehaviorITS.java | 15 +++++++-------- ...-role.yaml => rbac-test-full-access-role.yaml} | 0 ...ss.yaml => rbac-test-no-configmap-access.yaml} | 0 ...cr-access.yaml => rbac-test-no-cr-access.yaml} | 0 ...=> rbac-test-only-main-ns-access-binding.yaml} | 0 ...ss.yaml => rbac-test-only-main-ns-access.yaml} | 0 ...e-binding.yaml => rbac-test-role-binding.yaml} | 0 7 files changed, 7 insertions(+), 8 deletions(-) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-full-access-role.yaml => rbac-test-full-access-role.yaml} (100%) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-no-configmap-access.yaml => rbac-test-no-configmap-access.yaml} (100%) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-no-cr-access.yaml => rbac-test-no-cr-access.yaml} (100%) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-only-main-ns-access-binding.yaml => rbac-test-only-main-ns-access-binding.yaml} (100%) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-only-main-ns-access.yaml => rbac-test-only-main-ns-access.yaml} (100%) rename operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/{rback-test-role-binding.yaml => rbac-test-role-binding.yaml} (100%) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java index 8686d6f33b..ec50e058b9 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/InformerRelatedBehaviorITS.java @@ -368,35 +368,34 @@ Operator startOperator( } private void setNoConfigMapAccess() { - applyClusterRole("rback-test-no-configmap-access.yaml"); + applyClusterRole("rbac-test-no-configmap-access.yaml"); applyClusterRoleBinding(); } private void setNoCustomResourceAccess() { - applyClusterRole("rback-test-no-cr-access.yaml"); + applyClusterRole("rbac-test-no-cr-access.yaml"); applyClusterRoleBinding(); } private void setFullResourcesAccess() { - applyClusterRole("rback-test-full-access-role.yaml"); + applyClusterRole("rbac-test-full-access-role.yaml"); applyClusterRoleBinding(); } private void addRoleBindingsToTestNamespaces() { var role = - ReconcilerUtils.loadYaml( - Role.class, this.getClass(), "rback-test-only-main-ns-access.yaml"); + ReconcilerUtils.loadYaml(Role.class, this.getClass(), "rbac-test-only-main-ns-access.yaml"); adminClient.resource(role).inNamespace(actualNamespace).createOrReplace(); var roleBinding = ReconcilerUtils.loadYaml( - RoleBinding.class, this.getClass(), "rback-test-only-main-ns-access-binding.yaml"); + RoleBinding.class, this.getClass(), "rbac-test-only-main-ns-access-binding.yaml"); adminClient.resource(roleBinding).inNamespace(actualNamespace).createOrReplace(); } private void applyClusterRoleBinding() { var clusterRoleBinding = ReconcilerUtils.loadYaml( - ClusterRoleBinding.class, this.getClass(), "rback-test-role-binding.yaml"); + ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml"); adminClient.resource(clusterRoleBinding).createOrReplace(); } @@ -418,7 +417,7 @@ private Namespace namespace(String name) { private void removeClusterRoleBinding() { var clusterRoleBinding = ReconcilerUtils.loadYaml( - ClusterRoleBinding.class, this.getClass(), "rback-test-role-binding.yaml"); + ClusterRoleBinding.class, this.getClass(), "rbac-test-role-binding.yaml"); adminClient.resource(clusterRoleBinding).delete(); } } diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-full-access-role.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-full-access-role.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-full-access-role.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-full-access-role.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-configmap-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-configmap-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-configmap-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-configmap-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-cr-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-cr-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-no-cr-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-no-cr-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access-binding.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access-binding.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access-binding.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access-binding.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-only-main-ns-access.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-only-main-ns-access.yaml diff --git a/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-role-binding.yaml b/operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-role-binding.yaml similarity index 100% rename from operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rback-test-role-binding.yaml rename to operator-framework/src/test/resources/io/javaoperatorsdk/operator/dependent/informerrelatedbehavior/rbac-test-role-binding.yaml From a32f4c74d1b09e6c9cde17a9e3eb486538d70d70 Mon Sep 17 00:00:00 2001 From: Michael Koepf <47541996+michaelkoepf@users.noreply.github.com> Date: Wed, 20 Aug 2025 22:29:31 +0200 Subject: [PATCH 11/39] docs: Fix broken links (#2903) Signed-off-by: Michael Koepf <47541996+michaelkoepf@users.noreply.github.com> --- .../dependent-resources.md | 4 ++-- .../dependent-resource-and-workflows/workflows.md | 8 ++++---- docs/content/en/docs/documentation/reconciler.md | 2 +- docs/content/en/docs/migration/v3-1-migration.md | 2 +- docs/content/en/docs/migration/v4-4-migration.md | 2 +- docs/content/en/docs/migration/v4-5-migration.md | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md index 43b7f37364..7416949869 100644 --- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md +++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/dependent-resources.md @@ -177,7 +177,7 @@ usually limited to status handling based on the state of the secondary resources resources are not dependent on each other. As an alternative, you can also invoke reconciliation explicitly, event for managed workflows. -See [Workflows](https://javaoperatorsdk.io/docs/workflows) for more details on how the dependent +See [Workflows](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/workflows/) for more details on how the dependent resources are reconciled. This behavior and automated handling is referred to as "managed" because the `DependentResource` @@ -220,7 +220,7 @@ It is also possible to wire dependent resources programmatically. In practice th developer is responsible for initializing and managing the dependent resources as well as calling their `reconcile` method. However, this makes it possible for developers to fully customize the reconciliation process. Standalone dependent resources should be used in cases when the managed use -case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing +case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/workflows/) when managing resources programmatically. You can see a commented example of how to do diff --git a/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md b/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md index 4b1bea6790..c5ee83a446 100644 --- a/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md +++ b/docs/content/en/docs/documentation/dependent-resource-and-workflows/workflows.md @@ -12,12 +12,12 @@ depends on the state of other resources or cannot be processed until these other a given state or some condition holds true for them. Dealing with such scenarios are therefore rather common for operators and the purpose of the workflow feature of the Java Operator SDK (JOSDK) is to simplify supporting such cases in a declarative way. Workflows build on top of the -[dependent resources](https://javaoperatorsdk.io/docs/dependent-resources) feature. +[dependent resources](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/) feature. While dependent resources focus on how a given secondary resource should be reconciled, workflows focus on orchestrating how these dependent resources should be reconciled. Workflows describe how as a set of -[dependent resources](https://javaoperatorsdk.io/docs/dependent-resources) (DR) depend on one +[dependent resources](https://javaoperatorsdk.io/docs/documentation/dependent-resource-and-workflows/dependent-resources/) (DR) depend on one another, along with the conditions that need to hold true at certain stages of the reconciliation process. @@ -135,7 +135,7 @@ public class SampleWorkflowReconciler implements Reconciler Date: Fri, 22 Aug 2025 08:33:57 +0200 Subject: [PATCH 12/39] chore(deps): bump actions/setup-java from 4 to 5 (#2907) Bumps [actions/setup-java](https://github.com/actions/setup-java) from 4 to 5. - [Release notes](https://github.com/actions/setup-java/releases) - [Commits](https://github.com/actions/setup-java/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/e2e-test.yml | 2 +- .github/workflows/integration-tests.yml | 2 +- .github/workflows/pr.yml | 2 +- .github/workflows/release-project-in-dir.yml | 4 ++-- .github/workflows/snapshot-releases.yml | 4 ++-- .github/workflows/sonar.yml | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebc3588a2e..e37ab5d8d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ matrix.java }} diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index c7e91915b6..f72a0af43b 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -41,7 +41,7 @@ jobs: driver: docker - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index fb4196be9c..fdb8897c07 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -33,7 +33,7 @@ jobs: with: ref: ${{ inputs.checkout-ref }} - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: ${{ inputs.java-version }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b4f045a2ef..df5819f33d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml index 7b0d732a56..0683b01b1f 100644 --- a/.github/workflows/release-project-in-dir.yml +++ b/.github/workflows/release-project-in-dir.yml @@ -24,7 +24,7 @@ jobs: ref: "${{inputs.version_branch}}" - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin @@ -61,7 +61,7 @@ jobs: ref: "${{inputs.version_branch}}" - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml index be2a219d37..a12d9aaed5 100644 --- a/.github/workflows/snapshot-releases.yml +++ b/.github/workflows/snapshot-releases.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 17 @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b95d5175c2..370f36303c 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -25,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 17 From ab2ef4ccfaab3d02dc38d6b7a1c137786d8e3f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Fri, 22 Aug 2025 14:57:06 +0200 Subject: [PATCH 13/39] fix: create state only on resource event (#2899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: create state only on resource event In general we cleanup caches on delete event, so although in practice this probably not causing issues in theory it can happen that we receive events where there is no related custom resource. In that case we would create state, what can lead to a memory leak. Signed-off-by: Attila Mészáros --- .../processing/event/EventProcessor.java | 10 ++++++- .../event/ResourceStateManager.java | 18 +++++++++++++ .../processing/event/EventProcessorTest.java | 26 ++++++++++++++++++- .../event/ResourceStateManagerTest.java | 26 +++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java index bdaf575814..e029e287a0 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java @@ -102,8 +102,16 @@ public synchronized void handleEvent(Event event) { try { log.debug("Received event: {}", event); + final var optionalState = resourceStateManager.getOrCreateOnResourceEvent(event); + if (optionalState.isEmpty()) { + log.debug( + "Skipping event, since no state present and it is not a resource event. Resource ID:" + + " {}", + event.getRelatedCustomResourceID()); + return; + } + var state = optionalState.orElseThrow(); final var resourceID = event.getRelatedCustomResourceID(); - final var state = resourceStateManager.getOrCreate(event.getRelatedCustomResourceID()); MDCUtils.addResourceIDInfo(resourceID); metrics.receivedEvent(event, metricsMetadata); handleEventMarking(event, state); diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java index 6932e1ca5e..481fd317ff 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManager.java @@ -2,15 +2,33 @@ import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + class ResourceStateManager { // maybe we should have a way for users to specify a hint on the amount of CRs their reconciler // will process to avoid under- or over-sizing the state maps and avoid too many resizing that // take time and memory? private final Map states = new ConcurrentHashMap<>(100); + public Optional getOrCreateOnResourceEvent(Event event) { + var resourceId = event.getRelatedCustomResourceID(); + var state = states.get(event.getRelatedCustomResourceID()); + if (state != null) { + return Optional.of(state); + } + if (event instanceof ResourceEvent) { + state = new ResourceState(resourceId); + states.put(resourceId, state); + return Optional.of(state); + } else { + return Optional.empty(); + } + } + public ResourceState getOrCreate(ResourceID resourceID) { return states.computeIfAbsent(resourceID, ResourceState::new); } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java index fe2e6e9514..9819eb7ee9 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/EventProcessorTest.java @@ -276,6 +276,30 @@ void cancelScheduleOnceEventsOnSuccessfulExecution() { verify(retryTimerEventSourceMock, times(1)).cancelOnceSchedule(eq(crID)); } + @Test + void skipsGenericEventIfNoResourceEventReceivedBefore() { + var crID = new ResourceID("test-cr", TEST_NAMESPACE); + eventProcessor = + spy( + new EventProcessor( + controllerConfiguration(null, LinearRateLimiter.deactivatedRateLimiter()), + reconciliationDispatcherMock, + eventSourceManagerMock, + metricsMock)); + + verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); + + eventProcessor.start(); + eventProcessor.handleEvent(new Event(crID)); + + await() + .pollDelay(Duration.ofMillis(100)) + .untilAsserted( + () -> { + verify(reconciliationDispatcherMock, never()).handleExecution(any()); + }); + } + @Test void startProcessedMarkedEventReceivedBefore() { var crID = new ResourceID("test-cr", TEST_NAMESPACE); @@ -287,7 +311,7 @@ void startProcessedMarkedEventReceivedBefore() { eventSourceManagerMock, metricsMock)); when(controllerEventSourceMock.get(eq(crID))).thenReturn(Optional.of(testCustomResource())); - eventProcessor.handleEvent(new Event(crID)); + eventProcessor.handleEvent(new ResourceEvent(ResourceAction.ADDED, crID, testCustomResource())); verify(reconciliationDispatcherMock, timeout(100).times(0)).handleExecution(any()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java index 2c4d9fa4f3..487ba25885 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ResourceStateManagerTest.java @@ -4,6 +4,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import io.javaoperatorsdk.operator.TestUtils; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceAction; +import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEvent; + import static org.assertj.core.api.Assertions.assertThat; class ResourceStateManagerTest { @@ -87,4 +91,26 @@ public void listsResourceIDSWithEventsPresent() { assertThat(res).hasSize(1); assertThat(res.get(0).getId()).isEqualTo(sampleResourceID2); } + + @Test + void createStateOnlyOnResourceEvent() { + var state = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + + assertThat(state).isEmpty(); + + state = + manager.getOrCreateOnResourceEvent( + new ResourceEvent( + ResourceAction.ADDED, new ResourceID("newEvent"), TestUtils.testCustomResource())); + + assertThat(state).isNotNull(); + } + + @Test + void createsOnlyResourceEventReturnsPreviouslyCreatedState() { + manager.getOrCreate(new ResourceID("newEvent")); + + var res = manager.getOrCreateOnResourceEvent(new Event(new ResourceID("newEvent"))); + assertThat(res).isNotNull(); + } } From af29934943b42b0dca0e444cfc62463fc7760926 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 08:51:20 +0200 Subject: [PATCH 14/39] chore(deps): bump actions/upload-pages-artifact from 3 to 4 (#2908) --- .github/workflows/hugo.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml index c1b967616d..2c0a63d50d 100644 --- a/.github/workflows/hugo.yaml +++ b/.github/workflows/hugo.yaml @@ -68,7 +68,7 @@ jobs: --minify \ --baseURL "${{ steps.pages.outputs.base_url }}/" - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: ./docs/public From ecb7513d635a69c58c50e3eadb28c7c7967535a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 26 Aug 2025 13:58:45 +0200 Subject: [PATCH 15/39] fix: hash code for mysql sample (#2910) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../java/io/javaoperatorsdk/operator/sample/schema/Schema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java index 08fe3295b5..3ec6d8f008 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/schema/Schema.java @@ -31,7 +31,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(name, characterSet); + return Objects.hash(name); } @Override From 6eb8fdc89367af817e06326e8cb40ec64ce692dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 26 Aug 2025 16:35:07 +0200 Subject: [PATCH 16/39] improve: add aider to git ignore (#2911) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6f4bbe6a01..638e4a93f2 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ target/ .mvn/wrapper/maven-wrapper.jar -.java-version \ No newline at end of file +.java-version +.aider* From 2bd9c35340cdae2906040706af59d758bc34261f Mon Sep 17 00:00:00 2001 From: Martin Stefanko Date: Tue, 26 Aug 2025 19:06:06 +0200 Subject: [PATCH 17/39] improve: add information about SSA use to the debug logs (#2914) Signed-off-by: xstefank --- .../dependent/kubernetes/KubernetesDependentResource.java | 8 +++++--- .../processing/event/ReconciliationDispatcher.java | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index ebd6089aa7..69d145866d 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -82,15 +82,17 @@ public R create(R desired, P primary, Context

context) { } public R update(R actual, R desired, P primary, Context

context) { + boolean useSSA = useSSA(context); if (log.isDebugEnabled()) { log.debug( - "Updating actual resource: {} version: {}", + "Updating actual resource: {} version: {}; SSA: {}", ResourceID.fromResource(actual), - actual.getMetadata().getResourceVersion()); + actual.getMetadata().getResourceVersion(), + useSSA); } R updatedResource; addMetadata(false, actual, desired, primary, context); - if (useSSA(context)) { + if (useSSA) { updatedResource = prepare(context, desired, primary, "Updating") .fieldManager(context.getControllerConfiguration().fieldManager()) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java index c4b161ef27..41d7a4f493 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java @@ -341,7 +341,11 @@ private P updateCustomResourceWithFinalizer(P resourceForExecution, P originalRe } private P patchResource(P resource, P originalResource) { - log.debug("Updating resource: {} with version: {}", getUID(resource), getVersion(resource)); + log.debug( + "Updating resource: {} with version: {}; SSA: {}", + getUID(resource), + getVersion(resource), + useSSA); log.trace("Resource before update: {}", resource); final var finalizerName = configuration().getFinalizerName(); From feec0012f8928075127c95d21d5707f97d7f30a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Sep 2025 11:15:56 +0200 Subject: [PATCH 18/39] docs: wording improvements (#2913) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs: wording and readability improvements Signed-off-by: Attila Mészáros --- docs/CONTRIBUTING.md | 67 ++++-- docs/README.md | 196 ++++------------- docs/content/en/docs/_index.md | 2 - docs/content/en/docs/contributing/_index.md | 96 ++++----- docs/content/en/docs/documentation/_index.md | 22 +- .../en/docs/documentation/architecture.md | 68 ++---- .../en/docs/documentation/configuration.md | 46 ++-- .../documentation/error-handling-retries.md | 90 +++++--- .../content/en/docs/documentation/features.md | 46 ++-- .../en/docs/documentation/reconciler.md | 54 ++--- docs/content/en/docs/faq/_index.md | 185 ++++++++-------- .../getting-started/bootstrap-and-samples.md | 97 +++++++-- .../getting-started/intro-to-operators.md | 39 ++-- .../patterns-best-practices.md | 202 ++++++++---------- docs/content/en/docs/glossary/_index.md | 26 +-- 15 files changed, 570 insertions(+), 666 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index db177d4ac7..5ea571c69d 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,28 +1,57 @@ -# How to Contribute +# Contributing to Java Operator SDK Documentation -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. +Thank you for your interest in improving the Java Operator SDK documentation! We welcome contributions from the community and appreciate your help in making our documentation better. -## Contributor License Agreement +## How to Contribute -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to to see -your current agreements on file or to sign a new one. +### Getting Started -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. +1. **Fork the repository** and clone your fork locally +2. **Create a new branch** for your changes +3. **Make your improvements** to the documentation +4. **Test your changes** locally using `hugo server` +5. **Submit a pull request** with a clear description of your changes -## Code reviews +### Types of Contributions -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. +We welcome various types of contributions: + +- **Content improvements**: Fix typos, clarify explanations, add examples +- **New documentation**: Add missing sections or entirely new guides +- **Structural improvements**: Better organization, navigation, or formatting +- **Translation**: Help translate documentation to other languages + +## Guidelines + +### Writing Style + +- Use clear, concise language +- Write in active voice when possible +- Define technical terms when first used +- Include practical examples where helpful +- Keep sentences and paragraphs reasonably short + +### Technical Requirements + +- Test all code examples to ensure they work +- Use proper markdown formatting +- Follow existing documentation structure and conventions +- Ensure links work and point to current resources + +## Legal Requirements + +### Contributor License Agreement + +All contributions must be accompanied by a Contributor License Agreement (CLA). You (or your employer) retain the copyright to your contribution; the CLA simply gives us permission to use and redistribute your contributions as part of the project. + +Visit to see your current agreements on file or to sign a new one. + +You generally only need to submit a CLA once, so if you've already submitted one (even for a different project), you probably don't need to do it again. + +### Code Review Process + +All submissions, including those by project members, require review. We use GitHub pull requests for this purpose. Please consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ## Community Guidelines -This project follows -[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). +This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). diff --git a/docs/README.md b/docs/README.md index f9d6ce7183..14f675b53b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,190 +1,82 @@ -# JOSDK comments: +# Java Operator SDK Documentation -see: sample github action: https://gohugo.io/hosting-and-deployment/hosting-on-github/ +This repository contains the documentation website for the Java Operator SDK (JOSDK), built using Hugo and the Docsy theme. -currently use hugo version v0.125.7 +## About Java Operator SDK -# Docsy Example +Java Operator SDK is a framework that makes it easy to build Kubernetes operators in Java. It provides APIs designed to feel natural to Java developers and handles common operator challenges automatically, allowing you to focus on your business logic. -[Docsy][] is a [Hugo theme module][] for technical documentation sites, providing easy -site navigation, structure, and more. This **Docsy Example Project** uses the Docsy -theme component as a hugo module and provides a skeleton documentation structure for you to use. -You can clone/copy this project and edit it with your own content, or use it as an example. +## Development Setup -In this project, the Docsy theme is pulled in as a Hugo module, together with -its dependencies: +This documentation site uses Hugo v0.125.7 with the Docsy theme. -```console -$ hugo mod graph -... -``` - -For Docsy documentation, see [Docsy user guide][]. - -This Docsy Example Project is hosted on [Netlify][] at [example.docsy.dev][]. -You can view deploy logs from the [deploy section of the project's Netlify -dashboard][deploys], or this [alternate dashboard][]. - -This is not an officially supported Google product. This project is currently maintained. - -## Using the Docsy Example Project as a template - -A simple way to get started is to use this project as a template, which gives you a site project that is set up and ready to use. To do this: - -1. Use the dropdown for switching branches/tags to change to the **latest** released tag. - -2. Click **Use this template**. - -3. Select a name for your new project and click **Create repository from template**. - -4. Make your own local working copy of your new repo using git clone, replacing https://github.com/me/example.git with your repo’s web URL: - -```bash -git clone --depth 1 https://github.com/me/example.git -``` +## Prerequisites -You can now edit your own versions of the site’s source files. +- Hugo v0.125.7 or later (extended version required) +- Node.js and npm (for PostCSS processing) +- Git -If you want to do SCSS edits and want to publish these, you need to install `PostCSS` +## Local Development -```bash -npm install -``` - -## Running the website locally - -Building and running the site locally requires a recent `extended` version of [Hugo](https://gohugo.io). -You can find out more about how to install Hugo for your environment in our -[Getting started](https://www.docsy.dev/docs/getting-started/#prerequisites-and-installation) guide. - -Once you've made your working copy of the site repo, from the repo root folder, run: - -```bash -hugo server -``` +### Quick Start -## Running a container locally +1. Clone this repository +2. Install dependencies: + ```bash + npm install + ``` +3. Start the development server: + ```bash + hugo server + ``` +4. Open your browser to `http://localhost:1313` -You can run docsy-example inside a [Docker](https://docs.docker.com/) -container, the container runs with a volume bound to the `docsy-example` -folder. This approach doesn't require you to install any dependencies other -than [Docker Desktop](https://www.docker.com/products/docker-desktop) on -Windows and Mac, and [Docker Compose](https://docs.docker.com/compose/install/) -on Linux. +### Using Docker -1. Build the docker image +You can also run the documentation site using Docker: +1. Build the container: ```bash docker-compose build ``` - -1. Run the built image - +2. Run the container: ```bash docker-compose up ``` + > **Note**: You can combine both commands with `docker-compose up --build` - > NOTE: You can run both commands at once with `docker-compose up --build`. +3. Access the site at `http://localhost:1313` -1. Verify that the service is working. - - Open your web browser and type `http://localhost:1313` in your navigation bar, - This opens a local instance of the docsy-example homepage. You can now make - changes to the docsy example and those changes will immediately show up in your - browser after you save. - -### Cleanup - -To stop Docker Compose, on your terminal window, press **Ctrl + C**. - -To remove the produced images run: +To stop the container, press **Ctrl + C** in your terminal. +To clean up Docker resources: ```bash docker-compose rm ``` -For more information see the [Docker Compose documentation][]. - -## Using a local Docsy clone - -Make sure your installed go version is `1.18` or higher. - -Clone the latest version of the docsy theme into the parent folder of your project. The newly created repo should now reside in a sibling folder of your site's root folder. - -```shell -cd root-of-your-site -git clone --branch v0.7.2 https://github.com/google/docsy.git ../docsy -``` - -Now run: - -```shell -HUGO_MODULE_WORKSPACE=docsy.work hugo server --ignoreVendorPaths "**" -``` - -or, when using npm, prepend `local` to the script you want to invoke, e.g.: - -```shell -npm run local serve -``` - -By using the `HUGO_MODULE_WORKSPACE` directive (either directly or via prefix `local` when using npm), the server now watches all files and directories inside the sibling directory `../docsy` , too. Any changes inside the local `docsy` theme clone are now immediately picked up (hot reload), you can instantly see the effect of your local edits. -In the command above, we used the environment variable `HUGO_MODULE_WORKSPACE` to tell hugo about the local workspace file `docsy.work`. Alternatively, you can declare the workspace file inside your settings file `hugo.toml`: - -```toml -[module] - workspace = "docsy.work" -``` +## Contributing -Your project's `hugo.toml` file already contains these lines, the directive for workspace assignment is commented out, however. Remove the two trailing comment characters '//' so that this line takes effect. +We welcome contributions to improve the documentation! Please see our [contribution guidelines](CONTRIBUTING.md) for details on how to get started. ## Troubleshooting -As you run the website locally, you may run into the following error: - -```console -$ hugo server -WARN 2023/06/27 16:59:06 Module "project" is not compatible with this Hugo version; run "hugo mod graph" for more information. -Start building sites … -hugo v0.101.0-466fa43c16709b4483689930a4f9ac8add5c9f66+extended windows/amd64 BuildDate=2022-06-16T07:09:16Z VendorInfo=gohugoio -Error: Error building site: "C:\Users\foo\path\to\docsy-example\content\en\_index.md:5:1": failed to extract shortcode: template for shortcode "blocks/cover" not found -Built in 27 ms -``` - -This error occurs if you are running an outdated version of Hugo. As of docsy theme version `v0.7.0`, hugo version `0.110.0` or higher is required. -See this [section](https://www.docsy.dev/docs/get-started/docsy-as-module/installation-prerequisites/#install-hugo) of the user guide for instructions on how to install Hugo. - -Or you may be confronted with the following error: - +### Module Compatibility Error +If you see an error about module compatibility, ensure you're using Hugo v0.110.0 or higher: ```console -$ hugo server - -INFO 2021/01/21 21:07:55 Using config file: -Building sites … INFO 2021/01/21 21:07:55 syncing static files to / -Built in 288 ms -Error: Error building site: TOCSS: failed to transform "scss/main.scss" (text/x-scss): resource "scss/scss/main.scss_9fadf33d895a46083cdd64396b57ef68" not found in file cache +Error: Error building site: failed to extract shortcode: template for shortcode "blocks/cover" not found ``` -This error occurs if you have not installed the extended version of Hugo. -See this [section](https://www.docsy.dev/docs/get-started/docsy-as-module/installation-prerequisites/#install-hugo) of the user guide for instructions on how to install Hugo. - -Or you may encounter the following error: - +### SCSS Processing Error +If you encounter SCSS-related errors, make sure you have the extended version of Hugo installed: ```console -$ hugo server - -Error: failed to download modules: binary with name "go" not found +Error: TOCSS: failed to transform "scss/main.scss" ``` -This error occurs if you have not installed the `go` programming language on your system. -See this [section](https://www.docsy.dev/docs/get-started/docsy-as-module/installation-prerequisites/#install-go-language) of the user guide for instructions on how to install `go`. +### Go Binary Not Found +If you see "binary with name 'go' not found", install the Go programming language from [golang.org](https://golang.org). +## Links -[alternate dashboard]: https://app.netlify.com/sites/goldydocs/deploys -[deploys]: https://app.netlify.com/sites/docsy-example/deploys -[Docsy user guide]: https://docsy.dev/docs -[Docsy]: https://github.com/google/docsy -[example.docsy.dev]: https://example.docsy.dev -[Hugo theme module]: https://gohugo.io/hugo-modules/use-modules/#use-a-module-for-a-theme -[Netlify]: https://netlify.com -[Docker Compose documentation]: https://docs.docker.com/compose/gettingstarted/ +- [Hugo Documentation](https://gohugo.io/documentation/) +- [Docsy Theme Documentation](https://www.docsy.dev/docs/) +- [Java Operator SDK GitHub Repository](https://github.com/operator-framework/java-operator-sdk) diff --git a/docs/content/en/docs/_index.md b/docs/content/en/docs/_index.md index 7118464154..5c7b74ab4b 100755 --- a/docs/content/en/docs/_index.md +++ b/docs/content/en/docs/_index.md @@ -4,5 +4,3 @@ linkTitle: Docs menu: {main: {weight: 1}} weight: 1 --- - - diff --git a/docs/content/en/docs/contributing/_index.md b/docs/content/en/docs/contributing/_index.md index 4cea1f0e5d..064e0c1f4f 100644 --- a/docs/content/en/docs/contributing/_index.md +++ b/docs/content/en/docs/contributing/_index.md @@ -3,82 +3,66 @@ title: Contributing To Java Operator SDK weight: 110 --- -First of all, we'd like to thank you for considering contributing to the project! We really -hope to create a vibrant community around this project but this won't happen without help from -people like you! +Thank you for considering contributing to the Java Operator SDK project! We're building a vibrant community and need help from people like you to make it happen. ## Code of Conduct -We are serious about making this a welcoming, happy project. We will not tolerate discrimination, -aggressive or insulting behaviour. +We're committed to making this a welcoming, inclusive project. We do not tolerate discrimination, aggressive or insulting behavior. -To this end, the project and everyone participating in it is bound by the [Code of -Conduct]({{baseurl}}/coc). By participating, you are expected to uphold this code. Please report -unacceptable behaviour to any of the project admins. +This project and all participants are bound by our [Code of Conduct]({{baseurl}}/coc). By participating, you're expected to uphold this code. Please report unacceptable behavior to any project admin. -## Bugs +## Reporting Bugs -If you find a bug, -please [open an issue](https://github.com/java-operator-sdk/java-operator-sdk/issues)! Do try -to include all the details needed to recreate your problem. This is likely to include: +Found a bug? Please [open an issue](https://github.com/java-operator-sdk/java-operator-sdk/issues)! Include all details needed to recreate the problem: -- The version of the Operator SDK being used -- The exact platform and version of the platform that you're running on -- The steps taken to cause the bug -- Reproducer code is also very welcome to help us diagnose the issue and fix it quickly +- Operator SDK version being used +- Exact platform and version you're running on +- Steps to reproduce the bug +- Reproducer code (very helpful for quick diagnosis and fixes) -## Building Features and Documentation +## Contributing Features and Documentation -If you're looking for something to work on, take look at the issue tracker, in particular any items -labelled [good first issue](https://github.com/java-operator-sdk/java-operator-sdk/labels/good%20first%20issue) -. -Please leave a comment on the issue to mention that you have started work, in order to avoid -multiple people working on the same issue. +Looking for something to work on? Check the issue tracker, especially items labeled [good first issue](https://github.com/java-operator-sdk/java-operator-sdk/labels/good%20first%20issue). Please comment on the issue when you start work to avoid duplicated effort. -If you have an idea for a feature - whether or not you have time to work on it - please also open an -issue describing your feature and label it "enhancement". We can then discuss it as a community and -see what can be done. Please be aware that some features may not align with the project goals and -might therefore be closed. In particular, please don't start work on a new feature without -discussing it first to avoid wasting effort. We do commit to listening to all proposals and will do -our best to work something out! +### Feature Ideas -Once you've got the go ahead to work on a feature, you can start work. Feel free to communicate with -team via updates on the issue tracker or the [Discord channel](https://discord.gg/DacEhAy) and ask -for feedback, pointers etc. Once you're happy with your code, go ahead and open a Pull Request. +Have a feature idea? Open an issue labeled "enhancement" even if you can't work on it immediately. We'll discuss it as a community and see what's possible. -## Pull Request Process +**Important**: Some features may not align with project goals. Please discuss new features before starting work to avoid wasted effort. We commit to listening to all proposals and working something out when possible. + +### Development Process -First, please format your commit messages so that they follow -the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) format. +Once you have approval to work on a feature: +1. Communicate progress via issue updates or our [Discord channel](https://discord.gg/DacEhAy) +2. Ask for feedback and pointers as needed +3. Open a Pull Request when ready + +## Pull Request Process -On opening a PR, a GitHub action will execute the test suite against the new code. All code is -required to pass the tests, and new code must be accompanied by new tests. +### Commit Messages +Format commit messages following [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) format. -All PRs have to be reviewed and signed off by another developer before being merged. This review -will likely ask for some changes to the code - please don't be alarmed or upset -at this; it is expected that all PRs will need tweaks and a normal part of the process. +### Testing and Review +- GitHub Actions will run the test suite on your PR +- All code must pass tests +- New code must include new tests +- All PRs require review and sign-off from another developer +- Expect requests for changes - this is normal and part of the process +- PRs must comply with Java Google code style -The PRs are checked to be compliant with the Java Google code style. +### Licensing +All Operator SDK code is released under the [Apache 2.0 licence](LICENSE). -Be aware that all Operator SDK code is released under the [Apache 2.0 licence](LICENSE). +## Development Environment Setup -## Development environment setup +### Code Style -### Code style +SDK modules and samples follow Java Google code style. Code gets formatted automatically on every `compile`, but to avoid PR rejections due to style issues, set up your IDE: -The SDK modules and samples are formatted to follow the Java Google code style. -On every `compile` the code gets formatted automatically, however, to make things simpler (i.e. -avoid getting a PR rejected simply because of code style issues), you can import one of the -following code style schemes based on the IDE you use: +**IntelliJ IDEA**: Install the [google-java-format](https://plugins.jetbrains.com/plugin/8527-google-java-format) plugin -- for *Intellij IDEA* - install [google-java-format](https://plugins.jetbrains.com/plugin/8527-google-java-format) plugin -- for *Eclipse* - follow [these intructions](https://github.com/google/google-java-format?tab=readme-ov-file#eclipse) +**Eclipse**: Follow [these instructions](https://github.com/google/google-java-format?tab=readme-ov-file#eclipse) -## Thanks +## Acknowledgments -These guidelines were based on several sources, including -[Atom](https://github.com/atom/atom/blob/master/CONTRIBUTING.md), [PurpleBooth's -advice](https://gist.github.com/PurpleBooth/b24679402957c63ec426) and the [Contributor -Covenant](https://www.contributor-covenant.org/). +These guidelines were inspired by [Atom](https://github.com/atom/atom/blob/master/CONTRIBUTING.md), [PurpleBooth's advice](https://gist.github.com/PurpleBooth/b24679402957c63ec426), and the [Contributor Covenant](https://www.contributor-covenant.org/). diff --git a/docs/content/en/docs/documentation/_index.md b/docs/content/en/docs/documentation/_index.md index cc7fc50a57..54ca17f68c 100644 --- a/docs/content/en/docs/documentation/_index.md +++ b/docs/content/en/docs/documentation/_index.md @@ -1,4 +1,24 @@ --- title: Documentation weight: 40 ---- \ No newline at end of file +--- + +# JOSDK Documentation + +This section contains detailed documentation for all Java Operator SDK features and concepts. Whether you're building your first operator or need advanced configuration options, you'll find comprehensive guides here. + +## Core Concepts + +- **[Implementing a Reconciler](reconciler/)** - The heart of any operator +- **[Architecture](architecture/)** - How JOSDK works under the hood +- **[Dependent Resources & Workflows](dependent-resource-and-workflows/)** - Managing resource relationships +- **[Configuration](configuration/)** - Customizing operator behavior +- **[Error Handling & Retries](error-handling-retries/)** - Managing failures gracefully + +## Advanced Features + +- **[Eventing](eventing/)** - Understanding the event-driven model +- **[Observability](observability/)** - Monitoring and debugging your operators +- **[Other Features](features/)** - Additional capabilities and integrations + +Each guide includes practical examples and best practices to help you build robust, production-ready operators. diff --git a/docs/content/en/docs/documentation/architecture.md b/docs/content/en/docs/documentation/architecture.md index 8663b64d67..4108849c04 100644 --- a/docs/content/en/docs/documentation/architecture.md +++ b/docs/content/en/docs/documentation/architecture.md @@ -3,62 +3,34 @@ title: Architecture and Internals weight: 85 --- -This document gives an overview of the internal structure and components of Java Operator SDK core, -in order to make it easier for developers to understand and contribute to it. This document is -not intended to be a comprehensive reference, rather an introduction to the core concepts and we -hope that the other parts should be fairly easy to understand. We will evolve this document -based on the community's feedback. +This document provides an overview of the Java Operator SDK's internal structure and components to help developers understand and contribute to the project. While not a comprehensive reference, it introduces core concepts that should make other components easier to understand. ## The Big Picture and Core Components ![JOSDK architecture](/images/architecture.svg) -An [Operator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java) -is a set of -independent [controllers](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java) -. -The `Controller` class, however, is an internal class managed by the framework itself and -usually shouldn't interacted with directly by end users. It -manages all the processing units involved with reconciling a single type of Kubernetes resource. +An [Operator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java) is a set of independent [controllers](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/Controller.java). -Other components include: +The `Controller` class is an internal class managed by the framework and typically shouldn't be interacted with directly. It manages all processing units involved with reconciling a single type of Kubernetes resource. -- [Reconciler](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) - is the primary entry-point for the developers of the framework to implement the reconciliation - logic. -- [EventSource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java) - represents a source of events that might eventually trigger a reconciliation. -- [EventSourceManager](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java) - aggregates all the event sources associated with a controller. Manages the event sources' - lifecycle. -- [ControllerResourceEventSource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java) - is a central event source that watches the resources associated with the controller (also - called primary resources) for changes, propagates events and caches the related state. -- [EventProcessor](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java) - processes the incoming events and makes sure they are executed in a sequential manner, that is - making sure that the events are processed in the order they are received for a given resource, - despite requests being processed concurrently overall. The `EventProcessor` also takes care of - re-scheduling or retrying requests as needed. -- [ReconcilerDispatcher](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java) - is responsible for dispatching requests to the appropriate `Reconciler` method and handling - the reconciliation results, making the instructed Kubernetes API calls. +### Core Components + +- **[Reconciler](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java)** - The primary entry point for developers to implement reconciliation logic +- **[EventSource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java)** - Represents a source of events that might trigger reconciliation +- **[EventSourceManager](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceManager.java)** - Aggregates all event sources for a controller and manages their lifecycle +- **[ControllerResourceEventSource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSource.java)** - Central event source that watches primary resources associated with a given controller for changes, propagates events and caches state +- **[EventProcessor](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventProcessor.java)** - Processes incoming events sequentially per resource while allowing concurrent overall processing. Handles rescheduling and retrying +- **[ReconcilerDispatcher](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java)** - Dispatches requests to appropriate `Reconciler` methods and handles reconciliation results, making necessary Kubernetes API calls ## Typical Workflow -A typical workflows looks like following: +A typical workflow follows these steps: -1. An `EventSource` produces an event, that is propagated to the `EventProcessor`. -2. The resource associated with the event is read from the internal cache. -3. If the resource is not already being processed, a reconciliation request is - submitted to the executor service to be executed in a different thread, encapsulated in a - `ControllerExecution` instance. -4. This, in turns, calls the `ReconcilerDispatcher` which dispatches the call to the appropriate - `Reconciler` method, passing along all the required information. -5. Once the `Reconciler` is done, what happens depends on the result returned by the - `Reconciler`. If needed, the `ReconcilerDispatcher` will make the appropriate calls to the - Kubernetes API server. -6. Once the `Reconciler` is done, the `EventProcessor` is called back to finalize the - execution and update the controller's state. -7. The `EventProcessor` checks if the request needs to be rescheduled or retried and if there are no - subsequent events received for the same resource. -8. When none of this happens, the processing of the event is finished. +1. **Event Generation**: An `EventSource` produces an event and propagates it to the `EventProcessor` +2. **Resource Reading**: The resource associated with the event is read from the internal cache +3. **Reconciliation Submission**: If the resource isn't already being processed, a reconciliation request is submitted to the executor service in a different thread (encapsulated in a `ControllerExecution` instance) +4. **Dispatching**: The `ReconcilerDispatcher` is called, which dispatches the call to the appropriate `Reconciler` method with all required information +5. **Reconciler Execution**: Once the `Reconciler` completes, the `ReconcilerDispatcher` makes appropriate Kubernetes API server calls based on the returned result +6. **Finalization**: The `EventProcessor` is called back to finalize execution and update the controller's state +7. **Rescheduling Check**: The `EventProcessor` checks if the request needs rescheduling or retrying, and whether subsequent events were received for the same resource +8. **Completion**: When no further action is needed, event processing is finished diff --git a/docs/content/en/docs/documentation/configuration.md b/docs/content/en/docs/documentation/configuration.md index 06eda5f2a2..888804628f 100644 --- a/docs/content/en/docs/documentation/configuration.md +++ b/docs/content/en/docs/documentation/configuration.md @@ -3,29 +3,19 @@ title: Configurations weight: 55 --- -The Java Operator SDK (JOSDK) provides several abstractions that work great out of the -box. However, while we strive to cover the most common cases with the default behavior, we also -recognize that that default behavior is not always what any given user might want for their -operator. Numerous configuration options are therefore provided to help people tailor the -framework to their needs. - -Configuration options act at several levels, depending on which behavior you wish to act upon: -- `Operator`-level using `ConfigurationService` -- `Reconciler`-level using `ControllerConfiguration` -- `DependentResouce`-level using the `DependentResourceConfigurator` interface -- `EventSource`-level: some event sources, such as `InformerEventSource`, might need to be - fine-tuned to properly identify which events will trigger the associated reconciler. - -## Operator-level configuration - -Configuration that impacts the whole operator is performed via the `ConfigurationService` class. -`ConfigurationService` is an abstract class, and the implementation can be different based -on which flavor of the framework is used. For example Quarkus Operator SDK replaces the -default implementation. Configurations are initialized with sensible defaults, but can -be changed during initialization. - -For instance, if you wish to not validate that the CRDs are present on your cluster when the -operator starts and configure leader election, you would do something similar to: +The Java Operator SDK (JOSDK) provides abstractions that work great out of the box. However, we recognize that default behavior isn't always suitable for every use case. Numerous configuration options help you tailor the framework to your specific needs. + +Configuration options operate at several levels: +- **Operator-level** using `ConfigurationService` +- **Reconciler-level** using `ControllerConfiguration` +- **DependentResource-level** using the `DependentResourceConfigurator` interface +- **EventSource-level** where some event sources (like `InformerEventSource`) need fine-tuning to identify which events trigger the associated reconciler + +## Operator-Level Configuration + +Configuration that impacts the entire operator is performed via the `ConfigurationService` class. `ConfigurationService` is an abstract class with different implementations based on which framework flavor you use (e.g., Quarkus Operator SDK replaces the default implementation). Configurations initialize with sensible defaults but can be changed during initialization. + +For example, to disable CRD validation on startup and configure leader election: ```java Operator operator = new Operator( override -> override @@ -33,13 +23,11 @@ Operator operator = new Operator( override -> override .withLeaderElectionConfiguration(new LeaderElectionConfiguration("bar", "barNS"))); ``` -## Reconciler-level configuration +## Reconciler-Level Configuration -While reconcilers are typically configured using the `@ControllerConfiguration` annotation, it -is also possible to override the configuration at runtime, when the reconciler is registered -with the operator instance, either by passing it a completely new `ControllerConfiguration` -instance or by preferably overriding some aspects of the current configuration using a -`ControllerConfigurationOverrider` `Consumer`: +While reconcilers are typically configured using the `@ControllerConfiguration` annotation, you can also override configuration at runtime when registering the reconciler with the operator. You can either: +- Pass a completely new `ControllerConfiguration` instance +- Override specific aspects using a `ControllerConfigurationOverrider` `Consumer` (preferred) ```java Operator operator; diff --git a/docs/content/en/docs/documentation/error-handling-retries.md b/docs/content/en/docs/documentation/error-handling-retries.md index a36c46f08e..136c643cc4 100644 --- a/docs/content/en/docs/documentation/error-handling-retries.md +++ b/docs/content/en/docs/documentation/error-handling-retries.md @@ -3,41 +3,71 @@ title: Error handling and retries weight: 46 --- -## Automatic Retries on Error +## How Automatic Retries Work -JOSDK will schedule an automatic retry of the reconciliation whenever an exception is thrown by -your `Reconciler`. The retry behavior is configurable, but a default implementation is provided -covering most of the typical use-cases, see -[GenericRetry](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java) -. +JOSDK automatically schedules retries whenever your `Reconciler` throws an exception. This robust retry mechanism helps handle transient issues like network problems or temporary resource unavailability. + +### Default Retry Behavior + +The default retry implementation covers most typical use cases with exponential backoff: + +```java +GenericRetry.defaultLimitedExponentialRetry() + .setInitialInterval(5000) // Start with 5-second delay + .setIntervalMultiplier(1.5D) // Increase delay by 1.5x each retry + .setMaxAttempts(5); // Maximum 5 attempts +``` + +### Configuration Options + +**Using the `@GradualRetry` annotation:** + +```java +@ControllerConfiguration +@GradualRetry(maxAttempts = 3, initialInterval = 2000) +public class MyReconciler implements Reconciler { + // reconciler implementation +} +``` + +**Custom retry implementation:** + +Specify a custom retry class in the `@ControllerConfiguration` annotation: ```java - GenericRetry.defaultLimitedExponentialRetry() - .setInitialInterval(5000) - .setIntervalMultiplier(1.5D) - .setMaxAttempts(5); +@ControllerConfiguration(retry = MyCustomRetry.class) +public class MyReconciler implements Reconciler { + // reconciler implementation +} ``` -You can also configure the default retry behavior using the `@GradualRetry` annotation. - -It is possible to provide a custom implementation using the `retry` field of the -`@ControllerConfiguration` annotation and specifying the class of your custom implementation. -Note that this class must provide an accessible no-arg constructor for automated -instantiation. Additionally, your implementation can be automatically configured from an -annotation that you can provide by having your `Retry` implementation implement the -`AnnotationConfigurable` interface, parameterized with your annotation type. See the -`GenericRetry` implementation for more details. - -Information about the current retry state is accessible from -the [Context](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Context.java) -object. Of note, particularly interesting is the `isLastAttempt` method, which could allow your -`Reconciler` to implement a different behavior based on this status, by setting an error message -in your resource status, for example, when attempting a last retry. - -Note, though, that reaching the retry limit won't prevent new events to be processed. New -reconciliations will happen for new events as usual. However, if an error also occurs that -would trigger a retry, the SDK won't schedule one at this point since the retry limit -has already been reached. +Your custom retry class must: +- Provide a no-argument constructor for automatic instantiation +- Optionally implement `AnnotationConfigurable` for configuration from annotations + +### Accessing Retry Information + +The [Context](https://github.com/java-operator-sdk/java-operator-sdk/blob/master/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Context.java) object provides retry state information: + +```java +@Override +public UpdateControl reconcile(MyResource resource, Context context) { + if (context.isLastAttempt()) { + // Handle final retry attempt differently + resource.getStatus().setErrorMessage("Failed after all retry attempts"); + return UpdateControl.patchStatus(resource); + } + + // Normal reconciliation logic + // ... +} +``` + +### Important Retry Behavior Notes + +- **Retry limits don't block new events**: When retry limits are reached, new reconciliations still occur for new events +- **No retry on limit reached**: If an error occurs after reaching the retry limit, no additional retries are scheduled until new events arrive +- **Event-driven recovery**: Fresh events can restart the retry cycle, allowing recovery from previously failed states A successful execution resets the retry state. diff --git a/docs/content/en/docs/documentation/features.md b/docs/content/en/docs/documentation/features.md index c39dece4e3..8c8909c8b2 100644 --- a/docs/content/en/docs/documentation/features.md +++ b/docs/content/en/docs/documentation/features.md @@ -3,19 +3,13 @@ title: Other Features weight: 57 --- -The Java Operator SDK (JOSDK) is a high level framework and related tooling aimed at -facilitating the implementation of Kubernetes operators. The features are by default following -the best practices in an opinionated way. However, feature flags and other configuration options -are provided to fine tune or turn off these features. +The Java Operator SDK (JOSDK) is a high-level framework and tooling suite for implementing Kubernetes operators. By default, features follow best practices in an opinionated way. However, configuration options and feature flags are available to fine-tune or disable these features. -## Support for Well Known (non-custom) Kubernetes Resources +## Support for Well-Known Kubernetes Resources -A Controller can be registered for a non-custom resource, so well known Kubernetes resources like ( -`Ingress`, `Deployment`,...). +Controllers can be registered for standard Kubernetes resources (not just custom resources), such as `Ingress`, `Deployment`, and others. -See -the [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment) -for reconciling deployments. +See the [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/deployment) for an example of reconciling deployments. ```java public class DeploymentReconciler @@ -31,25 +25,17 @@ public class DeploymentReconciler ## Leader Election -Operators are generally deployed with a single running or active instance. However, it is -possible to deploy multiple instances in such a way that only one, called the "leader", processes the -events. This is achieved via a mechanism called "leader election". While all the instances are -running, and even start their event sources to populate the caches, only the leader will process -the events. This means that should the leader change for any reason, for example because it -crashed, the other instances are already warmed up and ready to pick up where the previous -leader left off should one of them become elected leader. +Operators are typically deployed with a single active instance. However, you can deploy multiple instances where only one (the "leader") processes events. This is achieved through "leader election." -See sample configuration in -the [E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/8865302ac0346ee31f2d7b348997ec2913d5922b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java#L21-L23) -. +While all instances run and start their event sources to populate caches, only the leader processes events. If the leader crashes, other instances are already warmed up and ready to take over when a new leader is elected. -## Automatic Generation of CRDs +See sample configuration in the [E2E test](https://github.com/java-operator-sdk/java-operator-sdk/blob/8865302ac0346ee31f2d7b348997ec2913d5922b/sample-operators/leader-election/src/main/java/io/javaoperatorsdk/operator/sample/LeaderElectionTestOperator.java#L21-L23). -Note that this feature is provided by the -[Fabric8 Kubernetes Client](https://github.com/fabric8io/kubernetes-client), not JOSDK itself. +## Automatic CRD Generation -To automatically generate CRD manifests from your annotated Custom Resource classes, you only need -to add the following dependencies to your project: +**Note:** This feature is provided by the [Fabric8 Kubernetes Client](https://github.com/fabric8io/kubernetes-client), not JOSDK itself. + +To automatically generate CRD manifests from your annotated Custom Resource classes, add this dependency to your project: ```xml @@ -60,14 +46,10 @@ to add the following dependencies to your project: ``` -The CRD will be generated in `target/classes/META-INF/fabric8` (or -in `target/test-classes/META-INF/fabric8`, if you use the `test` scope) with the CRD name -suffixed by the generated spec version. For example, a CR using the `java-operator-sdk.io` group -with a `mycrs` plural form will result in 2 files: +The CRD will be generated in `target/classes/META-INF/fabric8` (or `target/test-classes/META-INF/fabric8` for test scope) with the CRD name suffixed by the generated spec version. +For example, a CR using the `java-operator-sdk.io` group with a `mycrs` plural form will result in these files: - `mycrs.java-operator-sdk.io-v1.yml` - `mycrs.java-operator-sdk.io-v1beta1.yml` -**NOTE:** -> Quarkus users using the `quarkus-operator-sdk` extension do not need to add any extra dependency -> to get their CRD generated as this is handled by the extension itself. +**Note for Quarkus users:** If you're using the `quarkus-operator-sdk` extension, you don't need to add any extra dependency for CRD generation - the extension handles this automatically. diff --git a/docs/content/en/docs/documentation/reconciler.md b/docs/content/en/docs/documentation/reconciler.md index e7cf85923e..3ea09cf167 100644 --- a/docs/content/en/docs/documentation/reconciler.md +++ b/docs/content/en/docs/documentation/reconciler.md @@ -3,53 +3,43 @@ title: Implementing a reconciler weight: 45 --- -## Reconciliation Execution in a Nutshell +## How Reconciliation Works -An event always triggers reconciliation execution. Events typically come from a -primary resource, usually a custom resource, triggered by changes made to that resource -on the server (e.g. a resource is created, updated, or deleted) or from secondary resources for which there is a registered event source. -Reconciler implementations are associated with a given resource type and listen for such events from the Kubernetes API server -so that they can appropriately react to them. It is, however, possible for secondary sources to -trigger the reconciliation process. This occurs via -the [event source](#handling-related-events-with-event-sources) mechanism. +The reconciliation process is event-driven and follows this flow: -When we receive an event, it triggers the reconciliation unless a reconciliation is already -underway for this particular resource. In other words, the framework guarantees that no concurrent reconciliation happens for a resource. +1. **Event Reception**: Events trigger reconciliation from: + - **Primary resources** (usually custom resources) when created, updated, or deleted + - **Secondary resources** through registered event sources -Once the reconciliation is done, the framework checks if: +2. **Reconciliation Execution**: Each reconciler handles a specific resource type and listens for events from the Kubernetes API server. When an event arrives, it triggers reconciliation unless one is already running for that resource. The framework ensures no concurrent reconciliation occurs for the same resource. -- an exception was thrown during execution, and if yes, schedules a retry. -- new events were received during the controller execution; if yes, schedule a new reconciliation. -- the reconciler results explicitly re-scheduled (`UpdateControl.rescheduleAfter(..)`) a reconciliation with a time delay, if yes, - schedules a timer event with the specific delay. -- if none of the above applies, the reconciliation is finished. +3. **Post-Reconciliation Processing**: After reconciliation completes, the framework: + - Schedules a retry if an exception was thrown + - Schedules new reconciliation if events were received during execution + - Schedules a timer event if rescheduling was requested (`UpdateControl.rescheduleAfter(..)`) + - Finishes reconciliation if none of the above apply -In summary, the core of the SDK is implemented as an eventing system where events trigger -reconciliation requests. +The SDK core implements an event-driven system where events trigger reconciliation requests. -## Implementing a Reconciler and Cleaner interfaces +## Implementing Reconciler and Cleaner Interfaces -To implement a reconciler, you always have to implement the [`Reconciler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) interface. +To implement a reconciler, you must implement the [`Reconciler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) interface. -The lifecycle of a Kubernetes resource can be separated into two phases depending on whether the resource has already been marked for deletion or not. +A Kubernetes resource lifecycle has two phases depending on whether the resource is marked for deletion: -The framework out of the box supports this logic, it will always -call the `reconcile` method unless the custom resource is -[marked from deletion](https://kubernetes.io/docs/concepts/overview/working-with-objects/finalizers/#how-finalizers-work). +**Normal Phase**: The framework calls the `reconcile` method for regular resource operations. -On the other hand, if the resource is marked from deletion and if the `Reconciler` implements the -[`Cleaner`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java) interface, only the `cleanup` method is called. By implementing this interface -the framework will automatically handle (add/remove) the finalizers for you. +**Deletion Phase**: If the resource is marked for deletion and your `Reconciler` implements the [`Cleaner`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Cleaner.java) interface, only the `cleanup` method is called. The framework automatically handles finalizers for you. -In short, if you need to provide explicit cleanup logic, you always want to use finalizers; for a more detailed explanation, see [Finalizer support](#finalizer-support) for more details. +If you need explicit cleanup logic, always use finalizers. See [Finalizer support](#finalizer-support) for details. ### Using `UpdateControl` and `DeleteControl` -These two classes control the outcome or the desired behavior after the reconciliation. +These classes control the behavior after reconciliation completes. -The [`UpdateControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java) -can instruct the framework to update the status sub-resource of the resource -and/or re-schedule a reconciliation with a desired time delay: +[`UpdateControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java) can instruct the framework to: +- Update the status sub-resource +- Reschedule reconciliation with a time delay ```java @Override diff --git a/docs/content/en/docs/faq/_index.md b/docs/content/en/docs/faq/_index.md index 1c3d82fe35..31840965ae 100644 --- a/docs/content/en/docs/faq/_index.md +++ b/docs/content/en/docs/faq/_index.md @@ -1,145 +1,129 @@ --- -title: FAQ +title: Frequently Asked Questions weight: 90 --- -### How can I access the events which triggered the Reconciliation? +## Events and Reconciliation -In the v1.* version events were exposed to `Reconciler` (which was called `ResourceController` -then). This included events (Create, Update) of the custom resource, but also events produced by -Event Sources. After long discussions also with developers of golang version (controller-runtime), -we decided to remove access to these events. We already advocated to not use events in the -reconciliation logic, since events can be lost. Instead, reconcile all the resources on every -execution of reconciliation. On first this might sound a little opinionated, but there is a -sound agreement between the developers that this is the way to go. +### How can I access the events that triggered reconciliation? -Note that this is also consistent with Kubernetes -[level based](https://cloud.redhat.com/blog/kubernetes-operators-best-practices) reconciliation approach. +In v1.* versions, events were exposed to `Reconciler` (then called `ResourceController`). This included custom resource events (Create, Update) and events from Event Sources. After extensive discussions with golang controller-runtime developers, we decided to remove event access. -### Can I re-schedule a reconciliation, possibly with a specific delay? +**Why this change was made:** +- Events can be lost in distributed systems +- Best practice is to reconcile all resources on every execution +- Aligns with Kubernetes [level-based](https://cloud.redhat.com/blog/kubernetes-operators-best-practices) reconciliation approach -Yes, this can be done -using [`UpdateControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java) -and [`DeleteControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DeleteControl.java) -, see: +**Recommendation**: Always reconcile all resources instead of relying on specific events. +### Can I reschedule a reconciliation with a specific delay? + +Yes, you can reschedule reconciliation using [`UpdateControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java) and [`DeleteControl`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/DeleteControl.java). + +**With status update:** ```java - @Override - public UpdateControl reconcile( - EventSourceTestCustomResource resource, Context context) { - ... - return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS); - } +@Override +public UpdateControl reconcile( + EventSourceTestCustomResource resource, Context context) { + // ... reconciliation logic + return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS); +} ``` -without an update: - +**Without an update:** ```java - @Override - public UpdateControl reconcile( - EventSourceTestCustomResource resource, Context context) { - ... - return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS); - } +@Override +public UpdateControl reconcile( + EventSourceTestCustomResource resource, Context context) { + // ... reconciliation logic + return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS); +} +``` + +**Note**: Consider using `EventSources` for smarter reconciliation triggering instead of time-based scheduling. + +### How can I make status updates trigger reconciliation? + +By default, the framework filters out events that don't increase the `generation` field of the primary resource's metadata. Since `generation` typically only increases when the `.spec` field changes, status-only changes won't trigger reconciliation. + +To change this behavior, set [`generationAwareEventProcessing`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L43) to `false`: + +```java +@ControllerConfiguration(generationAwareEventProcessing = false) +static class TestCustomReconciler implements Reconciler { + @Override + public UpdateControl reconcile( + TestCustomResource resource, Context context) { + // reconciliation logic + } +} ``` -Although you might consider using `EventSources`, to handle reconciliation triggering in a smarter -way. +For secondary resources, every change should trigger reconciliation by default, except when you add explicit filters or use dependent resource implementations that filter out self-triggered changes. See [related docs](../documentation/dependent-resource-and-workflows/dependent-resources.md#caching-and-event-handling-in-kubernetesdependentresource). + +## Permissions and Access Control -### How can I run an operator without cluster scope rights? +### How can I run an operator without cluster-scope rights? -By default, JOSDK requires access to CRs at cluster scope. You may not be granted such -rights and you will see some error at startup that looks like: +By default, JOSDK requires cluster-scope access to custom resources. Without these rights, you'll see startup errors like: ```plain io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://kubernetes.local.svc/apis/mygroup/v1alpha1/mycr. Message: Forbidden! Configured service account doesn't have access. Service account may have been revoked. mycrs.mygroup is forbidden: User "system:serviceaccount:ns:sa" cannot list resource "mycrs" in API group "mygroup" at the cluster scope. ``` -To restrict the operator to a set of namespaces, you may override which namespaces are watched by a reconciler -at [Reconciler-level configuration](../configuration.md#reconciler-level-configuration): +**Solution 1: Restrict to specific namespaces** + +Override watched namespaces using [Reconciler-level configuration](../configuration.md#reconciler-level-configuration): ```java Operator operator; Reconciler reconciler; -... +// ... operator.register(reconciler, configOverrider -> configOverrider.settingNamespace("mynamespace")); ``` -Note that configuring the watched namespaces can also be done using the `@ControllerConfiguration` annotation. -Furthermore, you may not be able to list CRDs at startup which is required when `checkingCRDAndValidateLocalModel` -is `true` (`false` by default). To disable, set it to `false` at [Operator-level configuration](../configuration#operator-level-configuration): +**Note**: You can also configure watched namespaces using the `@ControllerConfiguration` annotation. + +**Solution 2: Disable CRD validation** + +If you can't list CRDs at startup (required when `checkingCRDAndValidateLocalModel` is `true`), disable it using [Operator-level configuration](../configuration#operator-level-configuration): ```java -Operator operator = new Operator( override -> override.checkingCRDAndValidateLocalModel(false)); +Operator operator = new Operator(override -> override.checkingCRDAndValidateLocalModel(false)); ``` -### I'm managing an external resource that has a generated ID, where should I store that? +## State Management -It is common that a non-Kubernetes or external resource is managed from a controller. Those external resources might -have a generated ID, so are not simply addressable based on the spec of a custom resources. Therefore, the -generated ID needs to be stored somewhere in order to address the resource during the subsequent reconciliations. +### Where should I store generated IDs for external resources? -Usually there are two options you can consider to store the ID: +When managing external (non-Kubernetes) resources, they often have generated IDs that aren't simply addressable based on your custom resource spec. You need to store these IDs for subsequent reconciliations. -1. Create a separate resource (usually ConfigMap, Secret or dedicated CustomResource) where you store the ID. -2. Store the ID in the status of the custom resource. +**Storage Options:** +1. **Separate resource** (usually ConfigMap, Secret, or dedicated CustomResource) +2. **Custom resource status field** -Note that both approaches are a bit tricky, since you have to guarantee the resources are cached for the next -reconciliation. For example if you patch the status at the end of the reconciliation (`UpdateControl.patchStatus(...)`) -it is not guaranteed that during the next reconciliation you will see the fresh resource. Therefore, controllers -which do this, usually cache the updated status in memory to make sure it is present for next reconciliation. +**Important considerations:** -From version 5.1 you can use [this utility](../documentation/reconciler.md#making-sure-the-primary-resource-is-up-to-date-for-the-next-reconciliation) -to make sure an updated status is present for the next reconciliation. +Both approaches require guaranteeing resources are cached for the next reconciliation. If you patch status at the end of reconciliation (`UpdateControl.patchStatus(...)`), the fresh resource isn't guaranteed to be available during the next reconciliation. Controllers typically cache updated status in memory to ensure availability. -Dependent Resources feature supports the [first approach](../documentation/dependent-resource-and-workflows/dependent-resources.md#external-state-tracking-dependent-resources). - -### How can I make the status update of my custom resource trigger a reconciliation? +**Modern solution**: From version 5.1, use [this utility](../documentation/reconciler.md#making-sure-the-primary-resource-is-up-to-date-for-the-next-reconciliation) to ensure updated status is available for the next reconciliation. -The framework checks, by default, when an event occurs, that could trigger a reconciliation, if the event increased the -`generation` field of the primary resource's metadata and filters out the event if it did not. `generation` is typically -only increased when the `.spec` field of a resource is changed. As a result, a change in the `.status` field would not -normally trigger a reconciliation. +**Dependent Resources**: This feature supports [the first approach](../documentation/dependent-resource-and-workflows/dependent-resources.md#external-state-tracking-dependent-resources) natively. -To change this behavior, you can set the [ -`generationAwareEventProcessing`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L43) -to `false`: - -```java - -@ControllerConfiguration(generationAwareEventProcessing = false) -static class TestCustomReconciler implements Reconciler { - - @Override - public UpdateControl reconcile(TestCustomResource resource, Context context) { - // code omitted - } -} -``` - -For secondary resources, every change should trigger a reconciliation by default, except when you add explicit filters -or use dependent resource implementations that filter out changes they trigger themselves by default, -see [related docs](../documentation/dependent-resource-and-workflows/dependent-resources.md#caching-and-event-handling-in-kubernetesdependentresource). +## Advanced Use Cases ### How can I skip the reconciliation of a dependent resource? -Skipping workflow reconciliation altogether is possible with the explicit invocation feature since v5. -You can read more about this in [v5 release notes](https://javaoperatorsdk.io/blog/2025/01/06/version-5-released/#explicit-workflow-invocation). +Skipping workflow reconciliation altogether is possible with the explicit invocation feature since v5. You can read more about this in [v5 release notes](https://javaoperatorsdk.io/blog/2025/01/06/version-5-released/#explicit-workflow-invocation). + +However, what if you want to avoid reconciling a single dependent resource based on some state? First, remember that the dependent resource won't be modified if the desired state and actual state match. Moreover, it's generally good practice to reconcile all your resources, with JOSDK taking care of only processing resources whose state doesn't match the desired one. -However, what if you want to avoid reconciling a single dependent resource based on some state? -First of all, remember that the dependent resource won't be modified if the desired state and the actual state match. -Moreover, it is generally a good practice to reconcile all your resources, JOSDK taking care of only processing the -resources which state doesn't match the desired one. -However, in some corner cases (for example, if it is expensive to compute the desired state or compare it to the actual -state), it is somtimes useful to be able to only skip the reconcilation of some resources but not all, if it is known -that they don't need to be processed based for example on the status of the custom resource. +However, in some corner cases (for example, if it's expensive to compute the desired state or compare it to the actual state), it's sometimes useful to skip the reconciliation of some resources but not all, if it's known that they don't need processing based on the status of the custom resource. -A common mistake is to use `ReconcilePrecondition`, if the condition does not hold it will delete the resources. -This is by design (although it's true that the name of this condition might be misleading), but not what we want in this -case. +A common mistake is to use `ReconcilePrecondition`. If the condition doesn't hold, it will delete the resources. This is by design (although the name might be misleading), but not what we want in this case. -The way to go is to override the matcher in the dependent resource: +The correct approach is to override the matcher in the dependent resource: ```java public Result match(R actualResource, R desired, P primary, Context

context) { @@ -151,11 +135,13 @@ public Result match(R actualResource, R desired, P primary, Context

contex } ``` -This will make sure that the dependent resource is not updated if the primary resource is in certain state. +This ensures the dependent resource isn't updated if the primary resource is in a certain state. -### How to fix `sun.security.provider.certpath.SunCertPathBuilderException` on Rancher Desktop and k3d/k3s Kubernetes +## Troubleshooting -It's a common issue when using k3d and the fabric8 client tries to connect to the cluster an exception is thrown: +### How to fix SSL certificate issues with Rancher Desktop and k3d/k3s + +This is a common issue when using k3d and the fabric8 client tries to connect to the cluster: ``` Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target @@ -164,8 +150,9 @@ Caused by: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.s at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:295) ``` -The cause is that fabric8 kubernetes client does not handle elliptical curve encryption by default. To fix this, add -the following dependency on the classpath: +**Cause**: The fabric8 kubernetes client doesn't handle elliptical curve encryption by default. + +**Solution**: Add the following dependency to your classpath: ```xml diff --git a/docs/content/en/docs/getting-started/bootstrap-and-samples.md b/docs/content/en/docs/getting-started/bootstrap-and-samples.md index 597ed3477e..d0d94f860d 100644 --- a/docs/content/en/docs/getting-started/bootstrap-and-samples.md +++ b/docs/content/en/docs/getting-started/bootstrap-and-samples.md @@ -3,36 +3,93 @@ title: Bootstrapping and samples weight: 20 --- -## Generating Project Skeleton +## Creating a New Operator Project -Project includes a maven plugin to generate a skeleton project: +### Using the Maven Plugin + +The simplest way to start a new operator project is using the provided Maven plugin, which generates a complete project skeleton: ```shell -mvn io.javaoperatorsdk:bootstrapper:[version]:create -DprojectGroupId=org.acme -DprojectArtifactId=getting-started +mvn io.javaoperatorsdk:bootstrapper:[version]:create \ + -DprojectGroupId=org.acme \ + -DprojectArtifactId=getting-started ``` -You can build this project with maven, -the build will generate also the [CustomResourceDefinition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) -for you. +This command creates a new Maven project with: +- A basic operator implementation +- Maven configuration with required dependencies +- Generated [CustomResourceDefinition](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) (CRD) + +### Building Your Project + +Build the generated project with Maven: +```shell +mvn clean install +``` + +The build process automatically generates the CustomResourceDefinition YAML file that you'll need to apply to your Kubernetes cluster. + +## Exploring Sample Operators + +The [sample-operators](https://github.com/java-operator-sdk/java-operator-sdk/tree/master/sample-operators) directory contains real-world examples demonstrating different JOSDK features and patterns: + +### Available Samples + +**[webpage](https://github.com/java-operator-sdk/java-operator-sdk/tree/master/sample-operators/webpage)** +- **Purpose**: Creates NGINX webservers from Custom Resources containing HTML code +- **Key Features**: Multiple implementation approaches using both low-level APIs and higher-level abstractions +- **Good for**: Understanding basic operator concepts and API usage patterns + +**[mysql-schema](https://github.com/java-operator-sdk/java-operator-sdk/tree/master/sample-operators/mysql-schema)** +- **Purpose**: Manages database schemas in MySQL instances +- **Key Features**: Demonstrates managing non-Kubernetes resources (external systems) +- **Good for**: Learning how to integrate with external services and manage state outside Kubernetes + +**[tomcat](https://github.com/java-operator-sdk/java-operator-sdk/tree/master/sample-operators/tomcat)** +- **Purpose**: Manages Tomcat instances and web applications +- **Key Features**: Multiple controllers managing related custom resources +- **Good for**: Understanding complex operators with multiple resource types and relationships + +## Running the Samples + +### Prerequisites + +The easiest way to try samples is using a local Kubernetes cluster: +- [minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) +- [kind](https://kind.sigs.k8s.io/) +- [Docker Desktop with Kubernetes](https://docs.docker.com/desktop/kubernetes/) + +### Step-by-Step Instructions + +1. **Apply the CustomResourceDefinition**: + ```shell + kubectl apply -f target/classes/META-INF/fabric8/[resource-name]-v1.yml + ``` + +2. **Run the operator**: + ```shell + mvn exec:java -Dexec.mainClass="your.main.ClassName" + ``` + Or run your main class directly from your IDE. -## Getting started with samples +3. **Create custom resources**: + The operator will automatically detect and reconcile custom resources when you create them: + ```shell + kubectl apply -f examples/sample-resource.yaml + ``` -You can find examples under [sample-operators](https://github.com/java-operator-sdk/java-operator-sdk/tree/master/sample-operators) -directory which are intended to demonstrate the usage of different components in different scenarios, but mainly are more real world -examples: +### Detailed Examples -* *webpage*: Simple example creating an NGINX webserver from a Custom Resource containing HTML code. We provide more - flavors of implementation, both with the low level APIs and higher level abstractions. -* *mysql-schema*: Operator managing schemas in a MySQL database. Shows how to manage non Kubernetes resources. -* *tomcat*: Operator with two controllers, managing Tomcat instances and Webapps running in Tomcat. The intention - with this example to show how to manage multiple related custom resources and/or more controllers. +For comprehensive setup instructions and examples, see: +- [MySQL Schema sample README](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/mysql-schema/README.md) +- Individual sample directories for specific setup requirements -The easiest way to run / try out is to run one of the samples on -[minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/) or [kind](https://kind.sigs.k8s.io/). -After applying the generated CRD, you can simply run your main class. The controller will automatically -start communicate with you local Kubernetes cluster and reconcile custom resource after you create one. +## Next Steps -See also detailed instructions under [`samples/mysql-schema/README.md`](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/mysql-schema/README.md). +After exploring the samples: +1. Review the [patterns and best practices](../patterns-best-practices) guide +2. Learn about [implementing reconcilers](../../documentation/reconciler) +3. Explore [dependent resources and workflows](../../documentation/dependent-resource-and-workflows) for advanced use cases diff --git a/docs/content/en/docs/getting-started/intro-to-operators.md b/docs/content/en/docs/getting-started/intro-to-operators.md index 6247bef288..2879f00db9 100644 --- a/docs/content/en/docs/getting-started/intro-to-operators.md +++ b/docs/content/en/docs/getting-started/intro-to-operators.md @@ -3,30 +3,31 @@ title: Introduction to Kubernetes operators weight: 15 --- -## Introduction & Resources +## What are Kubernetes Operators? -Operators manage both cluster and non-cluster resources on behalf of Kubernetes. Java -Operator SDK (JOSDK) aims to make it as easy as possible to implement a Kubernetes operators in Java. -The APIs are designed to feel natural to Java developers. In addition the framework tries to -handle common problem out of the box, so you don't have to worry about generic sub-problems. +Kubernetes operators are software extensions that manage both cluster and non-cluster resources on behalf of Kubernetes. The Java Operator SDK (JOSDK) makes it easy to implement Kubernetes operators in Java, with APIs designed to feel natural to Java developers and framework handling of common problems so you can focus on your business logic. -For an introduction on operators, please see this -[blog post](https://blog.container-solutions.com/kubernetes-operators-explained). +## Why Use Java Operator SDK? -For introductions to JOSDK see [this talk](https://www.youtube.com/watch?v=CvftaV-xrB4). +JOSDK provides several key advantages: -You can read about the common problems JOSDK is solving for you -[here](https://blog.container-solutions.com/a-deep-dive-into-the-java-operator-sdk). +- **Java-native APIs** that feel familiar to Java developers +- **Automatic handling** of common operator challenges (caching, event handling, retries) +- **Production-ready features** like observability, metrics, and error handling +- **Simplified development** so you can focus on business logic instead of Kubernetes complexities -You can also refer to the -[Writing Kubernetes operators using JOSDK blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk). +## Learning Resources +### Getting Started +- [Introduction to Kubernetes operators](https://blog.container-solutions.com/kubernetes-operators-explained) - Core concepts explained +- [Implementing Kubernetes Operators in Java](https://www.youtube.com/watch?v=CvftaV-xrB4) - Introduction talk +- [Kubernetes operator pattern documentation](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) - Official Kubernetes docs -## Operators in General - - [Implementing Kubernetes Operators in Java talk](https://www.youtube.com/watch?v=CvftaV-xrB4) - - [Introduction of the concept of Kubernetes Operators](https://blog.container-solutions.com/kubernetes-operators-explained) - - [Operator pattern explained in Kubernetes documentation](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) - - [An explanation why Java Operators makes sense](https://blog.container-solutions.com/cloud-native-java-infrastructure-automation-with-kubernetes-operators) - - [What are the problems an operator framework is solving](https://csviri.medium.com/deep-dive-building-a-kubernetes-operator-sdk-for-java-developers-5008218822cb) - - [Writing Kubernetes operators using JOSDK blog series](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk) +### Deep Dives +- [Problems JOSDK solves](https://blog.container-solutions.com/a-deep-dive-into-the-java-operator-sdk) - Technical deep dive +- [Why Java operators make sense](https://blog.container-solutions.com/cloud-native-java-infrastructure-automation-with-kubernetes-operators) - Java in cloud-native infrastructure +- [Building a Kubernetes operator SDK for Java](https://csviri.medium.com/deep-dive-building-a-kubernetes-operator-sdk-for-java-developers-5008218822cb) - Framework design principles + +### Tutorials +- [Writing Kubernetes operators using JOSDK](https://developers.redhat.com/articles/2022/02/15/write-kubernetes-java-java-operator-sdk) - Step-by-step blog series diff --git a/docs/content/en/docs/getting-started/patterns-best-practices.md b/docs/content/en/docs/getting-started/patterns-best-practices.md index 575c82af72..7451124df8 100644 --- a/docs/content/en/docs/getting-started/patterns-best-practices.md +++ b/docs/content/en/docs/getting-started/patterns-best-practices.md @@ -3,127 +3,113 @@ title: Patterns and best practices weight: 25 --- -This document describes patterns and best practices, to build and run operators, and how to -implement them in terms of the Java Operator SDK (JOSDK). +This document describes patterns and best practices for building and running operators, and how to implement them using the Java Operator SDK (JOSDK). -See also best practices -in [Operator SDK](https://sdk.operatorframework.io/docs/best-practices/best-practices/). +See also best practices in the [Operator SDK](https://sdk.operatorframework.io/docs/best-practices/best-practices/). ## Implementing a Reconciler -### Reconcile All The Resources All the Time - -The reconciliation can be triggered by events from multiple sources. It could be tempting to check -the events and reconcile just the related resource or subset of resources that the controller -manages. However, this is **considered an anti-pattern** for operators because the distributed -nature of Kubernetes makes it difficult to ensure that all events are always received. If, for -some reason, your operator doesn't receive some events, if you do not reconcile the whole state, -you might be operating with improper assumptions about the state of the cluster. This is why it -is important to always reconcile all the resources, no matter how tempting it might be to only -consider a subset. Luckily, JOSDK tries to make it as easy and efficient as possible by -providing smart caches to avoid unduly accessing the Kubernetes API server and by making sure -your reconciler is only triggered when needed. - -Since there is a consensus regarding this topic in the industry, JOSDK does not provide -event access from `Reconciler` implementations anymore starting with version 2 of the framework. - -### EventSources and Caching - -As mentioned above during a reconciliation best practice is to reconcile all the dependent resources -managed by the controller. This means that we want to compare a desired state with the actual -state of the cluster. Reading the actual state of a resource from the Kubernetes API Server -directly all the time would mean a significant load. Therefore, it's a common practice to -instead create a watch for the dependent resources and cache their latest state. This is done -following the Informer pattern. In Java Operator SDK, informers are wrapped into an `EventSource`, -to integrate it with the eventing system of the framework. This is implemented by the -`InformerEventSource` class. - -A new event that triggers the reconciliation is only propagated to the `Reconciler` when the actual -resource is already in cache. `Reconciler` implementations therefore only need to compare the -desired state with the observed one provided by the cached resource. If the resource cannot be -found in the cache, it therefore needs to be created. If the actual state doesn't match the -desired state, the resource needs to be updated. +### Always Reconcile All Resources + +Reconciliation can be triggered by events from multiple sources. It might be tempting to check the events and only reconcile the related resource or subset of resources that the controller manages. However, this is **considered an anti-pattern** for operators. + +**Why this is problematic:** +- Kubernetes' distributed nature makes it difficult to ensure all events are received +- If your operator misses some events and doesn't reconcile the complete state, it might operate with incorrect assumptions about the cluster state +- Always reconcile all resources, regardless of the triggering event + +JOSDK makes this efficient by providing smart caches to avoid unnecessary Kubernetes API server access and ensuring your reconciler is triggered only when needed. + +Since there's industry consensus on this topic, JOSDK no longer provides event access from `Reconciler` implementations starting with version 2. + +### Event Sources and Caching + +During reconciliation, best practice is to reconcile all dependent resources managed by the controller. This means comparing the desired state with the actual cluster state. + +**The Challenge**: Reading the actual state directly from the Kubernetes API Server every time would create significant load. + +**The Solution**: Create a watch for dependent resources and cache their latest state using the Informer pattern. In JOSDK, informers are wrapped into `EventSource` to integrate with the framework's eventing system via the `InformerEventSource` class. + +**How it works**: +- New events trigger reconciliation only when the resource is already cached +- Reconciler implementations compare desired state with cached observed state +- If a resource isn't in cache, it needs to be created +- If actual state doesn't match desired state, the resource needs updating ### Idempotency -Since all resources should be reconciled when your `Reconciler` is triggered and reconciliations -can be triggered multiple times for any given resource, especially when retry policies are in -place, it is especially important that `Reconciler` implementations be idempotent, meaning that -the same observed state should result in exactly the same outcome. This also means that -operators should generally operate in stateless fashion. Luckily, since operators are usually -managing declarative resources, ensuring idempotency is usually not difficult. - -### Sync or Async Way of Resource Handling - -Depending on your use case, it's possible that your reconciliation logic needs to wait a -non-insignificant amount of time while the operator waits for resources to reach their desired -state. For example, you `Reconciler` might need to wait for a `Pod` to get ready before -performing additional actions. This problem can be approached either synchronously or -asynchronously. - -The asynchronous way is to just exit the reconciliation logic as soon as the `Reconciler` -determines that it cannot complete its full logic at this point in time. This frees resources to -process other primary resource events. However, this requires that adequate event sources are -put in place to monitor state changes of all the resources the operator waits for. When this is -done properly, any state change will trigger the `Reconciler` again and it will get the -opportunity to finish its processing - -The synchronous way would be to periodically poll the resources' state until they reach their -desired state. If this is done in the context of the `reconcile` method of your `Reconciler` -implementation, this would block the current thread for possibly a long time. It's therefore -usually recommended to use the asynchronous processing fashion. - -## Why have Automatic Retries? - -Automatic retries are in place by default and can be configured to your needs. It is also -possible to completely deactivate the feature, though we advise against it. The main reason -configure automatic retries for your `Reconciler` is due to the fact that errors occur quite -often due to the distributed nature of Kubernetes: transient network errors can be easily dealt -with by automatic retries. Similarly, resources can be modified by different actors at the same -time, so it's not unheard of to get conflicts when working with Kubernetes resources. Such -conflicts can usually be quite naturally resolved by reconciling the resource again. If it's -done automatically, the whole process can be completely transparent. +Since all resources should be reconciled when your `Reconciler` is triggered, and reconciliations can be triggered multiple times for any given resource (especially with retry policies), it's crucial that `Reconciler` implementations be **idempotent**. + +**Idempotency means**: The same observed state should always result in exactly the same outcome. + +**Key implications**: +- Operators should generally operate in a stateless fashion +- Since operators usually manage declarative resources, ensuring idempotency is typically straightforward + +### Synchronous vs Asynchronous Resource Handling + +Sometimes your reconciliation logic needs to wait for resources to reach their desired state (e.g., waiting for a `Pod` to become ready). You can approach this either synchronously or asynchronously. + +#### Asynchronous Approach (Recommended) + +Exit the reconciliation logic as soon as the `Reconciler` determines it cannot complete at this point. This frees resources to process other events. + +**Requirements**: Set up adequate event sources to monitor state changes of all resources the operator waits for. When state changes occur, the `Reconciler` is triggered again and can finish processing. + +#### Synchronous Approach + +Periodically poll resources' state until they reach the desired state. If done within the `reconcile` method, this blocks the current thread for potentially long periods. + +**Recommendation**: Use the asynchronous approach for better resource utilization. + +## Why Use Automatic Retries? + +Automatic retries are enabled by default and configurable. While you can deactivate this feature, we advise against it. + +**Why retries are important**: +- **Transient network errors**: Common in Kubernetes' distributed environment, easily resolved with retries +- **Resource conflicts**: When multiple actors modify resources simultaneously, conflicts can be resolved by reconciling again +- **Transparency**: Automatic retries make error handling completely transparent when successful ## Managing State -Thanks to the declarative nature of Kubernetes resources, operators that deal only with -Kubernetes resources can operate in a stateless fashion, i.e. they do not need to maintain -information about the state of these resources, as it should be possible to completely rebuild -the resource state from its representation (that's what declarative means, after all). -However, this usually doesn't hold true anymore when dealing with external resources, and it -might be necessary for the operator to keep track of this external state so that it is available -when another reconciliation occurs. While such state could be put in the primary resource's -status sub-resource, this could become quickly difficult to manage if a lot of state needs to be -tracked. It also goes against the best practice that a resource's status should represent the -actual resource state, when its spec represents the desired state. Putting state that doesn't -strictly represent the resource's actual state is therefore discouraged. Instead, it's -advised to put such state into a separate resource meant for this purpose such as a -Kubernetes Secret or ConfigMap or even a dedicated Custom Resource, which structure can be more -easily validated. - -## Stopping (or not) Operator in case of Informer Errors and Cache Sync Timeouts - -It can -be [configured](https://github.com/java-operator-sdk/java-operator-sdk/blob/2cb616c4c4fd0094ee6e3a0ef2a0ea82173372bf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L168-L168) -if the operator should stop in case of any informer error happens on startup. By default, if there ia an error on -startup and the informer for example has no permissions list the target resources (both the primary resource or -secondary resources) the operator will stop instantly. This behavior can be altered by setting the mentioned flag -to `false`, so operator will start even some informers are not started. In this case - same as in case when an informer -is started at first but experienced problems later - will continuously retry the connection indefinitely with an -exponential backoff. The operator will just stop if there is a fatal -error, [currently](https://github.com/java-operator-sdk/java-operator-sdk/blob/0e55c640bf8be418bc004e51a6ae2dcf7134c688/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerWrapper.java#L64-L66) -that is when a resource cannot be deserialized. The typical use case for changing this flag is when a list of namespaces -is watched by a controller. In is better to start up the operator, so it can handle other namespaces while there -might be a permission issue for some resources in another namespace. - -The `stopOnInformerErrorDuringStartup` has implication on [cache sync timeout](https://github.com/java-operator-sdk/java-operator-sdk/blob/114c4312c32b34688811df8dd7cea275878c9e73/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L177-L179) -behavior. If true operator will stop on cache sync timeout. if `false`, after the timeout the controller will start -reconcile resources even if one or more event source caches did not sync yet. +Thanks to Kubernetes resources' declarative nature, operators dealing only with Kubernetes resources can operate statelessly. They don't need to maintain resource state information since it should be possible to rebuild the complete resource state from its representation. + +### When State Management Becomes Necessary + +This stateless approach typically breaks down when dealing with external resources. You might need to track external state for future reconciliations. + +**Anti-pattern**: Putting state in the primary resource's status sub-resource +- Becomes difficult to manage with large amounts of state +- Violates best practice: status should represent actual resource state, while spec represents desired state + +**Recommended approach**: Store state in separate resources designed for this purpose: +- Kubernetes Secret or ConfigMap +- Dedicated Custom Resource with validated structure + +## Handling Informer Errors and Cache Sync Timeouts + +You can [configure](https://github.com/java-operator-sdk/java-operator-sdk/blob/2cb616c4c4fd0094ee6e3a0ef2a0ea82173372bf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L168-L168) whether the operator should stop when informer errors occur on startup. + +### Default Behavior +By default, if there's a startup error (e.g., the informer lacks permissions to list target resources for primary or secondary resources), the operator stops immediately. + +### Alternative Configuration +Set the flag to `false` to start the operator even when some informers fail to start. In this case: +- The operator continuously retries connection with exponential backoff +- This applies both to startup failures and runtime problems +- The operator only stops for fatal errors (currently when a resource cannot be deserialized) + +**Use case**: When watching multiple namespaces, it's better to start the operator so it can handle other namespaces while resolving permission issues in specific namespaces. + +### Cache Sync Timeout Impact +The `stopOnInformerErrorDuringStartup` setting affects [cache sync timeout](https://github.com/java-operator-sdk/java-operator-sdk/blob/114c4312c32b34688811df8dd7cea275878c9e73/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L177-L179) behavior: +- **If `true`**: Operator stops on cache sync timeout +- **If `false`**: After timeout, the controller starts reconciling resources even if some event source caches haven't synced yet ## Graceful Shutdown -You can provide sufficient time for the reconciler to process and complete the currently ongoing events before shutting down. -The configuration is simple. You just need to set an appropriate duration value for `reconciliationTerminationTimeout` using `ConfigurationServiceOverrider`. +You can provide sufficient time for the reconciler to process and complete ongoing events before shutting down. Simply set an appropriate duration value for `reconciliationTerminationTimeout` using `ConfigurationServiceOverrider`. ```java final var overridden = new ConfigurationServiceOverrider(config) diff --git a/docs/content/en/docs/glossary/_index.md b/docs/content/en/docs/glossary/_index.md index 1523f68a23..282a98d4df 100644 --- a/docs/content/en/docs/glossary/_index.md +++ b/docs/content/en/docs/glossary/_index.md @@ -3,22 +3,10 @@ title: Glossary weight: 100 --- -- **Primary Resource** - the resource that represents the desired state that the controller is - working to achieve. While this is often a Custom Resource, it can be also be a Kubernetes native - resource (Deployment, ConfigMap,...). -- **Secondary Resource** - any resource that the controller needs to manage the reach the desired - state represented by the primary resource. These resources can be created, updated, deleted or - simply read depending on the use case. For example, the `Deployment` controller manages - `ReplicaSet` instances when trying to realize the state represented by the `Deployment`. In - this scenario, the `Deployment` is the primary resource while `ReplicaSet` is one of the - secondary resources managed by the `Deployment` controller. -- **Dependent Resource** - a feature of JOSDK, to make it easier to manage secondary resources. A - dependent resource represents a secondary resource with related reconciliation logic. -- **Low-level API** - refers to the SDK APIs that don't use any of features (such as Dependent - Resources or Workflows) outside of the core -- [`Reconciler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) - interface. See - the [WebPage sample](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java) - . The same logic - is also implemented using - [Dependent Resource and Workflows](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java) \ No newline at end of file +- **Primary Resource** - The resource representing the desired state that the controller works to achieve. While often a Custom Resource, it can also be a native Kubernetes resource (Deployment, ConfigMap, etc.). + +- **Secondary Resource** - Any resource the controller needs to manage to reach the desired state represented by the primary resource. These can be created, updated, deleted, or simply read depending on the use case. For example, the `Deployment` controller manages `ReplicaSet` instances to realize the state represented by the `Deployment`. Here, `Deployment` is the primary resource while `ReplicaSet` is a secondary resource. + +- **Dependent Resource** - A JOSDK feature that makes managing secondary resources easier. A dependent resource represents a secondary resource with associated reconciliation logic. + +- **Low-level API** - SDK APIs that don't use features beyond the core [`Reconciler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java) interface (such as Dependent Resources or Workflows). See the [WebPage sample](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageReconciler.java). The same logic is also implemented using [Dependent Resource and Workflows](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java). \ No newline at end of file From 40ca71347fbf42e5b6a2538d7787be2e9e7fec07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Mon, 1 Sep 2025 18:15:45 +0200 Subject: [PATCH 19/39] docs: improve details (#2925) --- docs/content/en/docs/contributing/_index.md | 2 +- docs/content/en/docs/documentation/error-handling-retries.md | 3 ++- docs/content/en/docs/faq/_index.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/en/docs/contributing/_index.md b/docs/content/en/docs/contributing/_index.md index 064e0c1f4f..0ab40d55b1 100644 --- a/docs/content/en/docs/contributing/_index.md +++ b/docs/content/en/docs/contributing/_index.md @@ -1,5 +1,5 @@ --- -title: Contributing To Java Operator SDK +title: Contributing weight: 110 --- diff --git a/docs/content/en/docs/documentation/error-handling-retries.md b/docs/content/en/docs/documentation/error-handling-retries.md index 136c643cc4..eeecf54751 100644 --- a/docs/content/en/docs/documentation/error-handling-retries.md +++ b/docs/content/en/docs/documentation/error-handling-retries.md @@ -43,7 +43,8 @@ public class MyReconciler implements Reconciler { Your custom retry class must: - Provide a no-argument constructor for automatic instantiation -- Optionally implement `AnnotationConfigurable` for configuration from annotations +- Optionally implement `AnnotationConfigurable` for configuration from annotations. See [`GenericRetry`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java#) + implementation for more details. ### Accessing Retry Information diff --git a/docs/content/en/docs/faq/_index.md b/docs/content/en/docs/faq/_index.md index 31840965ae..977a725b0d 100644 --- a/docs/content/en/docs/faq/_index.md +++ b/docs/content/en/docs/faq/_index.md @@ -1,5 +1,5 @@ --- -title: Frequently Asked Questions +title: FAQ weight: 90 --- From 1c2ac1981659c9f741b6fffe0933aff459f6438d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Tue, 2 Sep 2025 16:20:19 +0200 Subject: [PATCH 20/39] fix: CreateOnlyIfNotExistingDependentWithSSAIT actually use SSA for the test (#2921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../ConfigMapDependentResource.java | 4 +++- .../CreateOnlyIfNotExistingDependentWithSSAIT.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java index 67532ee159..2e37413766 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/ConfigMapDependentResource.java @@ -11,6 +11,8 @@ public class ConfigMapDependentResource extends CRUDKubernetesDependentResource< ConfigMap, CreateOnlyIfNotExistingDependentWithSSACustomResource> { + public static final String DRKEY = "drkey"; + @Override protected ConfigMap desired( CreateOnlyIfNotExistingDependentWithSSACustomResource primary, @@ -21,7 +23,7 @@ protected ConfigMap desired( .withName(primary.getMetadata().getName()) .withNamespace(primary.getMetadata().getNamespace()) .build()); - configMap.setData(Map.of("drkey", "v")); + configMap.setData(Map.of(DRKEY, "v")); return configMap; } } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java index 5c1923fa55..dd54979305 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java @@ -2,6 +2,7 @@ import java.time.Duration; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -22,6 +23,7 @@ class CreateOnlyIfNotExistingDependentWithSSAIT { @RegisterExtension LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder() + .withConfigurationService(o -> o.withDefaultNonSSAResource(Set.of())) .withReconciler(new CreateOnlyIfNotExistingDependentWithSSAReconciler()) .build(); @@ -41,7 +43,7 @@ void createsResourceOnlyIfNotExisting() { .untilAsserted( () -> { var currentCM = extension.get(ConfigMap.class, TEST_RESOURCE_NAME); - assertThat(currentCM.getData()).containsKey(KEY); + assertThat(currentCM.getData()).containsOnlyKeys(KEY); }); } From 9598c123af2e45e705b8c13a52ca1eaf2cca1200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 3 Sep 2025 14:00:38 +0200 Subject: [PATCH 21/39] docs: comment to clarify SSA usage in CreateOnlyIfNotExistingDependentWithSSAIT (#2930) --- .../CreateOnlyIfNotExistingDependentWithSSAIT.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java index dd54979305..3c41bae977 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/createonlyifnotexistsdependentwithssa/CreateOnlyIfNotExistingDependentWithSSAIT.java @@ -23,6 +23,8 @@ class CreateOnlyIfNotExistingDependentWithSSAIT { @RegisterExtension LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder() + // for the sake of this test, we allow to manage ConfigMaps with SSA + // by removing it from the non SSA resources (it is not managed with SSA by default) .withConfigurationService(o -> o.withDefaultNonSSAResource(Set.of())) .withReconciler(new CreateOnlyIfNotExistingDependentWithSSAReconciler()) .build(); From bf54b664edab4b09b24df55e0582b2f56fb72ae1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Sep 2025 14:44:53 +0200 Subject: [PATCH 22/39] chore(deps-dev): bump com.google.testing.compile:compile-testing (#2931) Bumps [com.google.testing.compile:compile-testing](https://github.com/google/compile-testing) from 0.21.0 to 0.22.0. - [Release notes](https://github.com/google/compile-testing/releases) - [Commits](https://github.com/google/compile-testing/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: com.google.testing.compile:compile-testing dependency-version: 0.22.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4a91625c2c..21542965c0 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 2.25.1 5.19.0 3.18.0 - 0.21.0 + 0.22.0 1.13.0 3.27.4 4.3.0 From f22f663d68b5c35ca02ea844a36598ec9d373a9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sat, 6 Sep 2025 09:33:17 +0200 Subject: [PATCH 23/39] improve: remove empty file (#2933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../operator/processing/event/NamedEventSource.java | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/NamedEventSource.java deleted file mode 100644 index e69de29bb2..0000000000 From 26d972c89fa829e3d9061fc61719ae0247d39454 Mon Sep 17 00:00:00 2001 From: Martin Stefanko Date: Mon, 8 Sep 2025 16:10:12 +0200 Subject: [PATCH 24/39] fix: typo in eventing docs (#2936) Signed-off-by: xstefank --- docs/content/en/docs/documentation/eventing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/en/docs/documentation/eventing.md b/docs/content/en/docs/documentation/eventing.md index 2591ab19c9..77daeb6fa3 100644 --- a/docs/content/en/docs/documentation/eventing.md +++ b/docs/content/en/docs/documentation/eventing.md @@ -106,7 +106,7 @@ single namespace (i.e. you cannot have an owner reference to a resource in a dif and are, by essence, limited to Kubernetes resources so you're out of luck if your secondary resources live outside of a cluster. -This is why JOSDK provides the `SecondayToPrimaryMapper` interface so that you can provide +This is why JOSDK provides the `SecondaryToPrimaryMapper` interface so that you can provide alternative ways for the SDK to identify which primary resource needs to be reconciled when something occurs to your secondary resources. We even provide some of these alternatives in the [Mappers](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java) From 40350ff0202be35403dd2cb9ff31079e8ea98ec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:19:10 +0200 Subject: [PATCH 25/39] chore(deps): bump io.micrometer:micrometer-core from 1.15.3 to 1.15.4 (#2939) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 21542965c0..649dbb951f 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ 3.27.4 4.3.0 2.7.3 - 1.15.3 + 1.15.4 3.2.2 0.9.14 2.20.0 From e8c093dcc6614ed4f96573c3597cc02c445b6fe8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 08:39:25 +0200 Subject: [PATCH 26/39] chore(deps-dev): bump com.google.testing.compile:compile-testing (#2940) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 649dbb951f..4214ccf6a0 100644 --- a/pom.xml +++ b/pom.xml @@ -58,7 +58,7 @@ 2.25.1 5.19.0 3.18.0 - 0.22.0 + 0.23.0 1.13.0 3.27.4 4.3.0 From 2dcb231b22959a17ced8b3dc6b65c82cecabf40f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:55:43 +0200 Subject: [PATCH 27/39] chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin (#2942) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 4214ccf6a0..e2610a1442 100644 --- a/pom.xml +++ b/pom.xml @@ -71,7 +71,7 @@ 2.11 3.14.0 - 3.5.3 + 3.5.4 0.8.0 3.11.3 3.3.1 From 4901d00e6e525dbc5e5f1a3b9a2ac731fb1756e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 17 Sep 2025 08:59:56 +0200 Subject: [PATCH 28/39] docs: fix reconiliation termination override snippet (#2946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../en/docs/getting-started/patterns-best-practices.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/content/en/docs/getting-started/patterns-best-practices.md b/docs/content/en/docs/getting-started/patterns-best-practices.md index 7451124df8..8ff36114a8 100644 --- a/docs/content/en/docs/getting-started/patterns-best-practices.md +++ b/docs/content/en/docs/getting-started/patterns-best-practices.md @@ -112,8 +112,5 @@ The `stopOnInformerErrorDuringStartup` setting affects [cache sync timeout](http You can provide sufficient time for the reconciler to process and complete ongoing events before shutting down. Simply set an appropriate duration value for `reconciliationTerminationTimeout` using `ConfigurationServiceOverrider`. ```java -final var overridden = new ConfigurationServiceOverrider(config) - .withReconciliationTerminationTimeout(Duration.ofSeconds(5)); - -final var operator = new Operator(overridden); +final var operator = new Operator(override -> override.withReconciliationTerminationTimeout(Duration.ofSeconds(5))); ``` From 86603d9048c8e8e34dd26f5c0262b7e65e9b0cb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 17 Sep 2025 09:08:33 +0200 Subject: [PATCH 29/39] docs: best practices for state management (#2945) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../docs/getting-started/patterns-best-practices.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/content/en/docs/getting-started/patterns-best-practices.md b/docs/content/en/docs/getting-started/patterns-best-practices.md index 8ff36114a8..f092d48971 100644 --- a/docs/content/en/docs/getting-started/patterns-best-practices.md +++ b/docs/content/en/docs/getting-started/patterns-best-practices.md @@ -77,13 +77,15 @@ Thanks to Kubernetes resources' declarative nature, operators dealing only with ### When State Management Becomes Necessary -This stateless approach typically breaks down when dealing with external resources. You might need to track external state for future reconciliations. +This stateless approach typically breaks down when dealing with external resources. You might need to track external state or allocated +values for future reconciliations. There are multiple options: -**Anti-pattern**: Putting state in the primary resource's status sub-resource -- Becomes difficult to manage with large amounts of state -- Violates best practice: status should represent actual resource state, while spec represents desired state -**Recommended approach**: Store state in separate resources designed for this purpose: +1. Putting state in the primary resource's status sub-resource. This is a bit more complex that might seem at the first look. + Refer to the [documentation](../documentation/reconciler.md#making-sure-the-primary-resource-is-up-to-date-for-the-next-reconciliation) + for further details. + +2. Store state in separate resources designed for this purpose: - Kubernetes Secret or ConfigMap - Dedicated Custom Resource with validated structure From aad964b030e0f9a7dd68c60370df9d71c690537d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:29:07 +0200 Subject: [PATCH 30/39] chore(deps): bump org.assertj:assertj-core from 3.27.4 to 3.27.5 (#2952) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e2610a1442..2afcd8448a 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ 3.18.0 0.23.0 1.13.0 - 3.27.4 + 3.27.5 4.3.0 2.7.3 1.15.4 From 1ab7e4353572b4b39a77322f37536bdfb0cf96ca Mon Sep 17 00:00:00 2001 From: Chris Laprun Date: Sat, 20 Sep 2025 13:24:24 +0200 Subject: [PATCH 31/39] refactor: avoid unneeded initializations in injection contexts (#2950) * refactor: avoid unneeded initializations in injection contexts Signed-off-by: Chris Laprun * docs: added javadoc and tests Signed-off-by: Chris Laprun --------- Signed-off-by: Chris Laprun --- .../io/javaoperatorsdk/operator/Operator.java | 78 +++++++++++++++---- .../operator/OperatorTest.java | 54 +++++++++---- 2 files changed, 103 insertions(+), 29 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java index 22072bb696..f65f6ae022 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/Operator.java @@ -24,17 +24,17 @@ public class Operator implements LifecycleAware { private static final Logger log = LoggerFactory.getLogger(Operator.class); - private final ControllerManager controllerManager; - private final LeaderElectionManager leaderElectionManager; - private final ConfigurationService configurationService; + private ControllerManager controllerManager; + private LeaderElectionManager leaderElectionManager; + private ConfigurationService configurationService; private volatile boolean started = false; public Operator() { - this((KubernetesClient) null); + init(initConfigurationService(null, null), true); } Operator(KubernetesClient kubernetesClient) { - this(initConfigurationService(kubernetesClient, null)); + init(initConfigurationService(kubernetesClient, null), false); } /** @@ -46,12 +46,7 @@ public Operator() { * operator */ public Operator(ConfigurationService configurationService) { - this.configurationService = configurationService; - - final var executorServiceManager = configurationService.getExecutorServiceManager(); - controllerManager = new ControllerManager(executorServiceManager); - - leaderElectionManager = new LeaderElectionManager(controllerManager, configurationService); + init(configurationService, false); } /** @@ -62,10 +57,55 @@ public Operator(ConfigurationService configurationService) { * {@link ConfigurationService} values */ public Operator(Consumer overrider) { - this(initConfigurationService(null, overrider)); + init(initConfigurationService(null, overrider), false); + } + + /** + * In a deferred initialization scenario, the default constructor will typically be called to + * create a proxy instance, usually to be replaced at some later time when the dependents (in this + * case the ConfigurationService instance) are available. In this situation, we want to make it + * possible to not perform the initialization steps directly so this implementation makes it + * possible to not crash when a null ConfigurationService is passed only if deferred + * initialization is allowed + * + * @param configurationService the potentially {@code null} {@link ConfigurationService} to use + * for this operator + * @param allowDeferredInit whether or not deferred initialization of the configuration service is + * allowed + * @throws IllegalStateException if the specified configuration service is {@code null} but + * deferred initialization is not allowed + */ + private void init(ConfigurationService configurationService, boolean allowDeferredInit) { + if (configurationService == null) { + if (!allowDeferredInit) { + throw new IllegalStateException( + "Deferred initialization of ConfigurationService is not allowed"); + } + } else { + this.configurationService = configurationService; + + final var executorServiceManager = configurationService.getExecutorServiceManager(); + controllerManager = new ControllerManager(executorServiceManager); + + leaderElectionManager = new LeaderElectionManager(controllerManager, configurationService); + } } - private static ConfigurationService initConfigurationService( + /** + * Overridable by subclasses to enable deferred configuration, useful to avoid unneeded processing + * in injection scenarios, typically returning {@code null} here instead of performing any + * configuration + * + * @param client a potentially {@code null} {@link KubernetesClient} to initialize the operator's + * {@link ConfigurationService} with + * @param overrider a potentially {@code null} {@link ConfigurationServiceOverrider} consumer to + * override the default {@link ConfigurationService} with + * @return a ready to use {@link ConfigurationService} using values provided by the specified + * overrides and kubernetes client, if provided or {@code null} in case deferred + * initialization is possible, in which case it is up to the extension to ensure that the + * {@link ConfigurationService} is properly set before the operator instance is used + */ + protected ConfigurationService initConfigurationService( KubernetesClient client, Consumer overrider) { // initialize the client if the user didn't provide one if (client == null) { @@ -232,8 +272,8 @@ public

RegisteredController

register( * * @param reconciler part of the reconciler to register * @param configOverrider consumer to use to change config values - * @return registered controller * @param

the {@code HasMetadata} type associated with the reconciler + * @return registered controller */ public

RegisteredController

register( Reconciler

reconciler, Consumer> configOverrider) { @@ -266,4 +306,14 @@ boolean isStarted() { public ConfigurationService getConfigurationService() { return configurationService; } + + /** + * Make it possible for extensions to set the {@link ConfigurationService} after the operator has + * been initialized + * + * @param configurationService the {@link ConfigurationService} to use for this operator + */ + protected void setConfigurationService(ConfigurationService configurationService) { + init(configurationService, false); + } } diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java index 9bdd54ca8d..39fc98f6b0 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/OperatorTest.java @@ -1,31 +1,24 @@ package io.javaoperatorsdk.operator; -import org.junit.jupiter.api.BeforeEach; +import java.util.function.Consumer; + import org.junit.jupiter.api.Test; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; +import io.javaoperatorsdk.operator.api.config.ConfigurationService; +import io.javaoperatorsdk.operator.api.config.ConfigurationServiceOverrider; import io.javaoperatorsdk.operator.api.reconciler.Context; -import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration; import io.javaoperatorsdk.operator.api.reconciler.Reconciler; import io.javaoperatorsdk.operator.api.reconciler.UpdateControl; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("rawtypes") class OperatorTest { - - private final KubernetesClient kubernetesClient = MockKubernetesClient.client(ConfigMap.class); - private Operator operator; - - @BeforeEach - void initOperator() { - operator = new Operator(kubernetesClient); - } - @Test void shouldBePossibleToRetrieveNumberOfRegisteredControllers() { + final var operator = new Operator(); assertEquals(0, operator.getRegisteredControllersNumber()); operator.register(new FooReconciler()); @@ -34,6 +27,7 @@ void shouldBePossibleToRetrieveNumberOfRegisteredControllers() { @Test void shouldBePossibleToRetrieveRegisteredControllerByName() { + final var operator = new Operator(); final var reconciler = new FooReconciler(); final var name = ReconcilerUtils.getNameFor(reconciler); @@ -51,12 +45,42 @@ void shouldBePossibleToRetrieveRegisteredControllerByName() { assertEquals(maybeController.get(), registeredControllers.stream().findFirst().orElseThrow()); } - @ControllerConfiguration - private static class FooReconciler implements Reconciler { + @Test + void shouldThrowExceptionIf() { + final var operator = new OperatorExtension(); + assertNotNull(operator); + operator.setConfigurationService(ConfigurationService.newOverriddenConfigurationService(null)); + assertNotNull(operator.getConfigurationService()); + + // should fail because the implementation is not providing a valid configuration service when + // constructing the operator + assertThrows( + IllegalStateException.class, + () -> new OperatorExtension(MockKubernetesClient.client(ConfigMap.class))); + } + private static class FooReconciler implements Reconciler { @Override public UpdateControl reconcile(ConfigMap resource, Context context) { return UpdateControl.noUpdate(); } } + + private static class OperatorExtension extends Operator { + public OperatorExtension() {} + + public OperatorExtension(KubernetesClient client) { + super(client); + } + + /** + * Overridden to mimic deferred initialization (or rather the fact that we don't want to do that + * processing at this time so return null). + */ + @Override + protected ConfigurationService initConfigurationService( + KubernetesClient client, Consumer overrider) { + return null; + } + } } From b35600953db20e7e19aa1075c92eb6ee108c9e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:02:35 +0200 Subject: [PATCH 32/39] chore(deps): bump org.mockito:mockito-core from 5.19.0 to 5.20.0 (#2955) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2afcd8448a..7772449ac7 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ 7.3.1 2.0.17 2.25.1 - 5.19.0 + 5.20.0 3.18.0 0.23.0 1.13.0 From 81120d772ef195bd33f7c8aadc90d9f88f22ab32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:03:06 +0200 Subject: [PATCH 33/39] chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin (#2956) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7772449ac7..7166244977 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 4.16 2.11 - 3.14.0 + 3.14.1 3.5.4 0.8.0 3.11.3 From 70da6322283a991bf9b9b7517b8689489be53c05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:03:39 +0200 Subject: [PATCH 34/39] chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin (#2957) --- operator-framework-bom/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 5809eed20f..5712a6bc0d 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -36,7 +36,7 @@ 3.2.8 3.3.1 - 3.11.3 + 3.12.0 2.44.3 0.8.0 diff --git a/pom.xml b/pom.xml index 7166244977..59f72b316e 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ 3.14.1 3.5.4 0.8.0 - 3.11.3 + 3.12.0 3.3.1 3.3.1 3.4.2 From b01409e881f037ab4a7dbb0f43c27db9e27cc303 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:40:15 +0200 Subject: [PATCH 35/39] chore(deps): bump org.assertj:assertj-core from 3.27.5 to 3.27.6 (#2959) Bumps [org.assertj:assertj-core](https://github.com/assertj/assertj) from 3.27.5 to 3.27.6. - [Release notes](https://github.com/assertj/assertj/releases) - [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.5...assertj-build-3.27.6) --- updated-dependencies: - dependency-name: org.assertj:assertj-core dependency-version: 3.27.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59f72b316e..f6cabf91c9 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,7 @@ 3.18.0 0.23.0 1.13.0 - 3.27.5 + 3.27.6 4.3.0 2.7.3 1.15.4 From 1a15b4b9e69ecb4c6acb18856c43a91f1fce4aae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:40:33 +0200 Subject: [PATCH 36/39] chore(deps): bump log4j.version from 2.25.1 to 2.25.2 (#2958) Bumps `log4j.version` from 2.25.1 to 2.25.2. Updates `org.apache.logging.log4j:log4j-slf4j2-impl` from 2.25.1 to 2.25.2 Updates `org.apache.logging.log4j:log4j-core` from 2.25.1 to 2.25.2 Updates `org.apache.logging.log4j:log4j2-core` from 2.25.1 to 2.25.2 --- updated-dependencies: - dependency-name: org.apache.logging.log4j:log4j-slf4j2-impl dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j-core dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.logging.log4j:log4j2-core dependency-version: 2.25.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f6cabf91c9..35602ad65a 100644 --- a/pom.xml +++ b/pom.xml @@ -55,7 +55,7 @@ 5.13.4 7.3.1 2.0.17 - 2.25.1 + 2.25.2 5.20.0 3.18.0 0.23.0 From 14cc06c9abc509ef985a225f2f599035dc10decc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Sep 2025 08:12:24 +0200 Subject: [PATCH 37/39] chore(deps): bump org.sonatype.central:central-publishing-maven-plugin (#2960) Bumps [org.sonatype.central:central-publishing-maven-plugin](https://github.com/sonatype/central-publishing-maven-plugin) from 0.8.0 to 0.9.0. - [Commits](https://github.com/sonatype/central-publishing-maven-plugin/commits) --- updated-dependencies: - dependency-name: org.sonatype.central:central-publishing-maven-plugin dependency-version: 0.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- operator-framework-bom/pom.xml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml index 5712a6bc0d..6b2525137a 100644 --- a/operator-framework-bom/pom.xml +++ b/operator-framework-bom/pom.xml @@ -38,7 +38,7 @@ 3.3.1 3.12.0 2.44.3 - 0.8.0 + 0.9.0 diff --git a/pom.xml b/pom.xml index 35602ad65a..93a0e2d711 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ 2.11 3.14.1 3.5.4 - 0.8.0 + 0.9.0 3.12.0 3.3.1 3.3.1 From 7397be67bd28314bd56b696b3f53b372955c4c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Wed, 24 Sep 2025 08:28:38 +0200 Subject: [PATCH 38/39] improve: PrimaryToSecondayMapper test improvements (#2951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../baseapi/primarytosecondary/Cluster.java | 2 +- .../primarytosecondary/ClusterSpec.java | 14 +++++++ .../baseapi/primarytosecondary/Job.java | 2 +- .../primarytosecondary/JobReconciler.java | 35 ++++++++++++------ .../baseapi/primarytosecondary/JobStatus.java | 14 +++++++ .../PrimaryToSecondaryIT.java | 37 +++++++++++++++---- 6 files changed, 82 insertions(+), 22 deletions(-) create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/ClusterSpec.java create mode 100644 operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobStatus.java diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Cluster.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Cluster.java index d0be7738a6..1d154cd6a8 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Cluster.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Cluster.java @@ -9,4 +9,4 @@ @Group("sample.javaoperatorsdk") @Version("v1") @ShortNames("clu") -public class Cluster extends CustomResource implements Namespaced {} +public class Cluster extends CustomResource implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/ClusterSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/ClusterSpec.java new file mode 100644 index 0000000000..b948bea6b4 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/ClusterSpec.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.baseapi.primarytosecondary; + +public class ClusterSpec { + + private String clusterValue; + + public String getClusterValue() { + return clusterValue; + } + + public void setClusterValue(String clusterValue) { + this.clusterValue = clusterValue; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Job.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Job.java index 611898bd52..bec3598e73 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Job.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/Job.java @@ -9,4 +9,4 @@ @Group("sample.javaoperatorsdk") @Version("v1") @ShortNames("cjo") -public class Job extends CustomResource implements Namespaced {} +public class Job extends CustomResource implements Namespaced {} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobReconciler.java index 1855f89b77..6c51a06b1c 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobReconciler.java @@ -17,7 +17,7 @@ * needed, and to show the use cases when some mechanisms would not work without that. It's not * intended to be a reusable code as it is, rather serves for deeper understanding of the problem. */ -@ControllerConfiguration() +@ControllerConfiguration public class JobReconciler implements Reconciler { private static final String JOB_CLUSTER_INDEX = "job-cluster-index"; @@ -38,26 +38,37 @@ public JobReconciler(boolean addPrimaryToSecondaryMapper) { @Override public UpdateControl reconcile(Job resource, Context context) { - + Cluster cluster; if (!getResourceDirectlyFromCache) { // this is only possible when there is primary to secondary mapper - context - .getSecondaryResource(Cluster.class) - .orElseThrow(() -> new IllegalStateException("Secondary resource should be present")); + cluster = + context + .getSecondaryResource(Cluster.class) + .orElseThrow(() -> new IllegalStateException("Secondary resource should be present")); } else { // reading the resource from cache as alternative, works without primary to secondary mapper var informerEventSource = (InformerEventSource) context.eventSourceRetriever().getEventSourceFor(Cluster.class); - informerEventSource - .get( - new ResourceID( - resource.getSpec().getClusterName(), resource.getMetadata().getNamespace())) - .orElseThrow( - () -> new IllegalStateException("Secondary resource cannot be read from cache")); + cluster = + informerEventSource + .get( + new ResourceID( + resource.getSpec().getClusterName(), resource.getMetadata().getNamespace())) + .orElseThrow( + () -> new IllegalStateException("Secondary resource cannot be read from cache")); + } + if (resource.getStatus() == null) { + resource.setStatus(new JobStatus()); } numberOfExecutions.addAndGet(1); - return UpdateControl.noUpdate(); + // copy a value to job status, to we can test triggering + if (!cluster.getSpec().getClusterValue().equals(resource.getStatus().getValueFromCluster())) { + resource.getStatus().setValueFromCluster(cluster.getSpec().getClusterValue()); + return UpdateControl.patchStatus(resource); + } else { + return UpdateControl.noUpdate(); + } } @Override diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobStatus.java new file mode 100644 index 0000000000..d59634b295 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/JobStatus.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.baseapi.primarytosecondary; + +public class JobStatus { + + private String valueFromCluster; + + public String getValueFromCluster() { + return valueFromCluster; + } + + public void setValueFromCluster(String valueFromCluster) { + this.valueFromCluster = valueFromCluster; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java index 9344cc787f..d82c24a55f 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java @@ -16,8 +16,12 @@ class PrimaryToSecondaryIT { public static final String CLUSTER_NAME = "cluster1"; public static final int MIN_DELAY = 150; + public static final String CLUSTER_VALUE = "clusterValue"; + public static final String JOB_1 = "job1"; + public static final String CHANGED_VALUE = "CHANGED_VALUE"; + @RegisterExtension - LocallyRunOperatorExtension operator = + LocallyRunOperatorExtension extension = LocallyRunOperatorExtension.builder() .withAdditionalCustomResourceDefinition(Cluster.class) .withReconciler(new JobReconciler()) @@ -25,22 +29,37 @@ class PrimaryToSecondaryIT { @Test void readsSecondaryInManyToOneCases() throws InterruptedException { - operator.create(cluster()); + var cluster = extension.create(cluster()); Thread.sleep(MIN_DELAY); - operator.create(job()); + extension.create(job()); + + await() + .pollDelay(Duration.ofMillis(300)) + .untilAsserted( + () -> { + assertThat(extension.getReconcilerOfType(JobReconciler.class).getNumberOfExecutions()) + .isEqualTo(1); + var job = extension.get(Job.class, JOB_1); + assertThat(job.getStatus()).isNotNull(); + assertThat(job.getStatus().getValueFromCluster()).isEqualTo(CLUSTER_VALUE); + }); + + cluster.getSpec().setClusterValue(CHANGED_VALUE); + extension.replace(cluster); + // cluster change triggers job reconciliations await() .pollDelay(Duration.ofMillis(300)) .untilAsserted( - () -> - assertThat( - operator.getReconcilerOfType(JobReconciler.class).getNumberOfExecutions()) - .isEqualTo(1)); + () -> { + var job = extension.get(Job.class, JOB_1); + assertThat(job.getStatus().getValueFromCluster()).isEqualTo(CHANGED_VALUE); + }); } public static Job job() { var job = new Job(); - job.setMetadata(new ObjectMetaBuilder().withName("job1").build()); + job.setMetadata(new ObjectMetaBuilder().withName(JOB_1).build()); job.setSpec(new JobSpec()); job.getSpec().setClusterName(CLUSTER_NAME); return job; @@ -49,6 +68,8 @@ public static Job job() { public static Cluster cluster() { Cluster cluster = new Cluster(); cluster.setMetadata(new ObjectMetaBuilder().withName(CLUSTER_NAME).build()); + cluster.setSpec(new ClusterSpec()); + cluster.getSpec().setClusterValue(CLUSTER_VALUE); return cluster; } } From e181b0fc091da38b91b15919b78d2ae731564d36 Mon Sep 17 00:00:00 2001 From: Martin Stefanko Date: Wed, 24 Sep 2025 21:11:44 +0200 Subject: [PATCH 39/39] feat: allow DependentResourceNode creation override (#2961) Signed-off-by: xstefank --- .../reconciler/dependent/DependentResourceFactory.java | 10 ++++++++++ .../dependent/workflow/DefaultManagedWorkflow.java | 10 ++++------ .../dependent/workflow/DependentResourceNode.java | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java index 8803c15b8c..d6a2971515 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/DependentResourceFactory.java @@ -4,6 +4,7 @@ import io.javaoperatorsdk.operator.api.config.Utils; import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec; import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ConfiguredDependentResource; +import io.javaoperatorsdk.operator.processing.dependent.workflow.DependentResourceNode; @SuppressWarnings({"rawtypes", "unchecked"}) public interface DependentResourceFactory< @@ -36,4 +37,13 @@ default Class associatedResourceType(D spec) { dependentResourceClass, DependentResource.class, null, null); return dr != null ? dr.resourceType() : null; } + + default DependentResourceNode createNodeFrom(D spec, DependentResource dependentResource) { + return new DependentResourceNode( + spec.getReconcileCondition(), + spec.getDeletePostCondition(), + spec.getReadyCondition(), + spec.getActivationCondition(), + dependentResource); + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java index 587c7fbdc8..ed02ef8f4e 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DefaultManagedWorkflow.java @@ -77,12 +77,10 @@ public Workflow

resolve(KubernetesClient client, ControllerConfiguration

c for (DependentResourceSpec spec : orderedSpecs) { final var dependentResource = resolve(spec, client, configuration); final var node = - new DependentResourceNode( - spec.getReconcileCondition(), - spec.getDeletePostCondition(), - spec.getReadyCondition(), - spec.getActivationCondition(), - dependentResource); + configuration + .getConfigurationService() + .dependentResourceFactory() + .createNodeFrom(spec, dependentResource); alreadyResolved.put(dependentResource.name(), node); spec.getDependsOn().forEach(depend -> node.addDependsOnRelation(alreadyResolved.get(depend))); } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java index 87646a56d9..c456b44ef2 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/DependentResourceNode.java @@ -8,7 +8,7 @@ import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource; @SuppressWarnings("rawtypes") -class DependentResourceNode { +public class DependentResourceNode { private final List dependsOn = new LinkedList<>(); private final List parents = new LinkedList<>();