diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000..bdee8b743a
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,49 @@
+name: Build
+
+env:
+ MAVEN_ARGS: -V -ntp -e
+
+on:
+ workflow_call:
+
+jobs:
+ integration_tests:
+ strategy:
+ matrix:
+ java: [ 17, 21 ]
+ kubernetes: [ 'v1.29.11','1.30.7', '1.31.3' ]
+ it-category: [ 'baseapi', 'dependent', 'workflow' ]
+ uses: ./.github/workflows/integration-tests.yml
+ with:
+ java-version: ${{ matrix.java }}
+ kube-version: ${{ matrix.kubernetes }}
+ it-category: ${{ matrix.it-category }}
+
+ http_client_tests:
+ strategy:
+ matrix:
+ java: [ 17, 21 ]
+ kubernetes: [ 'v1.29.11','1.30.7', '1.31.3' ]
+ it-category: [ 'baseapi' ]
+ httpclient: [ 'vertx', 'jdk', 'jetty' ]
+ uses: ./.github/workflows/integration-tests.yml
+ with:
+ java-version: ${{ matrix.java }}
+ kube-version: ${{ matrix.kubernetes }}
+ it-category: ${{ matrix.it-category }}
+ http-client: ${{ matrix.httpclient }}
+
+ special_integration_tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ java: [ 17, 21 ]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Java and Maven
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java }}
+ - name: Run Special Integration Tests
+ run: ./mvnw ${MAVEN_ARGS} -B package -P minimal-watch-timeout-dependent-it --file pom.xml
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index 5d6ec78c5f..01aedf91aa 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -19,7 +19,7 @@ jobs:
sample_operators_tests:
strategy:
matrix:
- sample_dir:
+ sample:
- "sample-operators/mysql-schema"
- "sample-operators/tomcat-operator"
- "sample-operators/webpage"
@@ -33,7 +33,7 @@ jobs:
uses: manusa/actions-setup-minikube@v2.13.0
with:
minikube version: v1.33.0
- kubernetes version: v1.31.0
+ kubernetes version: v1.31.3
github token: ${{ secrets.GITHUB_TOKEN }}
driver: docker
@@ -48,12 +48,10 @@ jobs:
run: mvn install -DskipTests
- name: Run integration tests in local mode
- working-directory: ${{ matrix.sample_dir }}
run: |
- mvn test -P end-to-end-tests
+ mvn test -P end-to-end-tests -pl ${{ matrix.sample }}
- name: Run E2E tests as a deployment
- working-directory: ${{ matrix.sample_dir }}
run: |
eval $(minikube -p minikube docker-env)
- mvn jib:dockerBuild test -P end-to-end-tests -Dtest.deployment=remote
+ mvn jib:dockerBuild test -P end-to-end-tests -Dtest.deployment=remote -pl ${{ matrix.sample }}
diff --git a/.github/workflows/fabric8-next-version-schedule.yml b/.github/workflows/fabric8-next-version-schedule.yml
index b0bc57ea75..64d2042135 100644
--- a/.github/workflows/fabric8-next-version-schedule.yml
+++ b/.github/workflows/fabric8-next-version-schedule.yml
@@ -23,46 +23,8 @@ jobs:
with:
distribution: temurin
java-version: 17
- cache: 'maven'
- name: Run unit tests
run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml
- integration_tests:
- strategy:
- matrix:
- java: [ 11, 17 ]
- kubernetes: ['v1.28.12', 'v1.29.7','1.30.3', '1.31.0']
- uses: ./.github/workflows/integration-tests.yml
- with:
- java-version: ${{ matrix.java }}
- kube-version: ${{ matrix.kubernetes }}
-
- httpclient-tests:
- strategy:
- matrix:
- httpclient: [ 'vertx', 'jdk', 'jetty' ]
- uses: ./.github/workflows/integration-tests.yml
- with:
- java-version: 17
- kube-version: 'v1.29.1'
- http-client: ${{ matrix.httpclient }}
- experimental: true
- checkout-ref: 'fabric8-next-version'
-
- special_integration_tests:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- java: [ 11, 17 ]
- steps:
- - uses: actions/checkout@v4
- with:
- ref: 'fabric8-next-version'
- - name: Set up Java and Maven
- uses: actions/setup-java@v4
- with:
- distribution: temurin
- java-version: ${{ matrix.java }}
- cache: 'maven'
- - name: Run Special Integration Tests
- run: ./mvnw ${MAVEN_ARGS} -B package -P minimal-watch-timeout-dependent-it --file pom.xml
\ No newline at end of file
+ build:
+ uses: ./.github/workflows/build.yml
\ No newline at end of file
diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml
index 2a5ee0d64e..1d91c85ef3 100644
--- a/.github/workflows/hugo.yaml
+++ b/.github/workflows/hugo.yaml
@@ -49,7 +49,7 @@ jobs:
id: pages
uses: actions/configure-pages@v5
- name: Install Node.js dependencies
- working-directory: ./docsy
+ working-directory: ./docs
run: |
[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true
npm install -D autoprefixer
@@ -61,7 +61,7 @@ jobs:
HUGO_ENVIRONMENT: production
HUGO_ENV: production
TZ: America/Los_Angeles
- working-directory: ./docsy
+ working-directory: ./docs
run: |
hugo \
--gc \
@@ -70,7 +70,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
- path: ./docsy/public
+ path: ./docs/public
# Deployment job
deploy:
diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index be811d309e..e36add39c2 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -12,7 +12,7 @@ on:
http-client:
type: string
required: false
- default: 'okhttp'
+ default: 'vertx'
experimental:
type: boolean
required: false
@@ -21,6 +21,10 @@ on:
type: string
required: false
default: ''
+ it-category:
+ type: string
+ required: false
+ default: ''
jobs:
integration_tests:
@@ -42,9 +46,17 @@ jobs:
- name: Set up Minikube
uses: manusa/actions-setup-minikube@v2.13.0
with:
- minikube version: v1.33.0
- kubernetes version: ${{ inputs.kube-version }}
+ minikube version: 'v1.33.0'
+ kubernetes version: '${{ inputs.kube-version }}'
driver: 'docker'
github token: ${{ secrets.GITHUB_TOKEN }}
- - name: Run integration tests
- run: ./mvnw ${MAVEN_ARGS} -B package -P no-unit-tests -Dfabric8-httpclient-impl.name=${{inputs.http-client}} --file pom.xml
\ No newline at end of file
+ - name: "${{inputs.it-category}} integration tests (kube: ${{ inputs.kube-version }} / java: ${{ inputs.java-version }} / client: ${{ inputs.http-client }})"
+ run: |
+ if [ -z "${{inputs.it-category}}" ]; then
+ it_profile="integration-tests"
+ else
+ it_profile="integration-tests-${{inputs.it-category}}"
+ fi
+ echo "Using profile: ${it_profile}"
+ ./mvnw ${MAVEN_ARGS} -T1C -B install -DskipTests -Pno-apt --file pom.xml
+ ./mvnw ${MAVEN_ARGS} -T1C -B package -P${it_profile} -Dfabric8-httpclient-impl.name=${{inputs.http-client}} --file pom.xml
\ No newline at end of file
diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 15342af1b1..facd6be13a 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -26,44 +26,9 @@ jobs:
cache: 'maven'
- name: Check code format
run: |
- ./mvnw ${MAVEN_ARGS} formatter:validate -Dconfigfile=$PWD/contributing/eclipse-google-style.xml -pl '!operator-framework-bom' --file pom.xml
- ./mvnw ${MAVEN_ARGS} impsort:check -pl '!operator-framework-bom' --file pom.xml
+ ./mvnw ${MAVEN_ARGS} spotless:check --file pom.xml
- name: Run unit tests
- run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml
+ run: ./mvnw ${MAVEN_ARGS} clean install -Pno-apt --file pom.xml
- integration_tests:
- strategy:
- matrix:
- java: [ 11, 17 ]
- kubernetes: [ 'v1.28.12', 'v1.29.7','1.30.3', '1.31.0' ]
- uses: ./.github/workflows/integration-tests.yml
- with:
- java-version: ${{ matrix.java }}
- kube-version: ${{ matrix.kubernetes }}
-
- httpclient-tests:
- strategy:
- matrix:
- httpclient: [ 'vertx', 'jdk', 'jetty' ]
- uses: ./.github/workflows/integration-tests.yml
- with:
- java-version: 17
- kube-version: 'v1.29.1'
- http-client: ${{ matrix.httpclient }}
- experimental: true
-
- special_integration_tests:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- java: [ 11, 17 ]
- steps:
- - uses: actions/checkout@v4
- - name: Set up Java and Maven
- uses: actions/setup-java@v4
- with:
- distribution: temurin
- java-version: ${{ matrix.java }}
- cache: 'maven'
- - name: Run Special Integration Tests
- run: ./mvnw ${MAVEN_ARGS} -B package -P minimal-watch-timeout-dependent-it --file pom.xml
\ No newline at end of file
+ build:
+ uses: ./.github/workflows/build.yml
\ No newline at end of file
diff --git a/.github/workflows/release-project-in-dir.yml b/.github/workflows/release-project-in-dir.yml
index b271f05f02..dc79b6f6c2 100644
--- a/.github/workflows/release-project-in-dir.yml
+++ b/.github/workflows/release-project-in-dir.yml
@@ -26,7 +26,7 @@ jobs:
- name: Set up Java and Maven
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: temurin
cache: 'maven'
@@ -61,7 +61,7 @@ jobs:
- name: Set up Java and Maven
uses: actions/setup-java@v4
with:
- java-version: 11
+ java-version: 17
distribution: temurin
cache: 'maven'
diff --git a/.github/workflows/snapshot-releases.yml b/.github/workflows/snapshot-releases.yml
index e5aff55e62..66fe9d25a3 100644
--- a/.github/workflows/snapshot-releases.yml
+++ b/.github/workflows/snapshot-releases.yml
@@ -21,7 +21,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
- java-version: 11
+ java-version: 17
cache: 'maven'
- name: Build and test project
run: ./mvnw ${MAVEN_ARGS} clean install --file pom.xml
@@ -34,7 +34,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: temurin
- java-version: 11
+ java-version: 17
cache: 'maven'
- name: Release Maven package
uses: samuelmeuli/action-maven-publish@v1
diff --git a/README.md b/README.md
index b932fe1e96..933dacb54b 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# 
+# 

[](https://kubernetes.slack.com/archives/CAW0GV7A5 "get invite here: https://communityinviter.com/apps/kubernetes/community" )
@@ -40,7 +40,6 @@ It makes it easy to implement best practices and patterns for an Operator. Featu
* Handling dependent resources, related events, and caching.
* Automatic Retries
* Smart event scheduling
-* Handling Observed Generations automatically
* Easy to use Error Handling
* ... and everything that a batteries included framework needs
diff --git a/bootstrapper-maven-plugin/pom.xml b/bootstrapper-maven-plugin/pom.xml
index 5ab7398f8c..7ee2156600 100644
--- a/bootstrapper-maven-plugin/pom.xml
+++ b/bootstrapper-maven-plugin/pom.xml
@@ -1,11 +1,11 @@
-
+
+
4.0.0
- java-operator-sdk
io.javaoperatorsdk
- 4.9.8-SNAPSHOT
+ java-operator-sdk
+ 5.0.0-SNAPSHOT
bootstrapper
@@ -39,7 +39,7 @@
org.apache.logging.log4j
- log4j-slf4j-impl
+ log4j-slf4j2-impl
test
@@ -72,29 +72,29 @@
-
-
- org.apache.maven.plugins
- maven-plugin-plugin
- ${maven-plugin-plugin.version}
-
- josdk-bootstrapper
-
-
-
- org.codehaus.mojo
- templating-maven-plugin
- ${templating-maven-plugin.version}
-
-
- filtering-java-templates
-
- filter-sources
-
-
-
-
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${maven-plugin-plugin.version}
+
+ josdk-bootstrapper
+
+
+
+ org.codehaus.mojo
+ templating-maven-plugin
+ ${templating-maven-plugin.version}
+
+
+ filtering-java-templates
+
+ filter-sources
+
+
+
+
-
+
diff --git a/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java b/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java
index ad7bc3cc79..ed12e7619d 100644
--- a/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java
+++ b/bootstrapper-maven-plugin/src/main/java/io/javaoperatorsdk/boostrapper/Bootstrapper.java
@@ -21,7 +21,7 @@ public class Bootstrapper {
private static final Logger log = LoggerFactory.getLogger(Bootstrapper.class);
- private MustacheFactory mustacheFactory = new DefaultMustacheFactory();
+ private final MustacheFactory mustacheFactory = new DefaultMustacheFactory();
// .gitignore gets excluded from resource, using here a prefixed version
private static final Map TOP_LEVEL_STATIC_FILES =
diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java b/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java
index 03ac06f882..f7583be4ee 100644
--- a/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java
+++ b/bootstrapper-maven-plugin/src/main/resources/templates/Reconciler.java
@@ -1,23 +1,15 @@
package {{groupId}};
-import io.fabric8.kubernetes.api.model.ConfigMap;
-import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
-import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
-import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
-import io.javaoperatorsdk.operator.api.reconciler.EventSourceInitializer;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.Context;
-import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
-import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
-import io.javaoperatorsdk.operator.processing.event.source.EventSource;
-import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
+import io.javaoperatorsdk.operator.api.reconciler.Workflow;
import java.util.Map;
import java.util.Optional;
-@ControllerConfiguration(dependents = {@Dependent(type = ConfigMapDependentResource.class)})
+@Workflow(dependents = {@Dependent(type = ConfigMapDependentResource.class)})
public class {{artifactClassId}}Reconciler implements Reconciler<{{artifactClassId}}CustomResource> {
public UpdateControl<{{artifactClassId}}CustomResource> reconcile({{artifactClassId}}CustomResource primary,
diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/Status.java b/bootstrapper-maven-plugin/src/main/resources/templates/Status.java
index 4c37b99947..52bd0fd4d2 100644
--- a/bootstrapper-maven-plugin/src/main/resources/templates/Status.java
+++ b/bootstrapper-maven-plugin/src/main/resources/templates/Status.java
@@ -1,7 +1,5 @@
package {{groupId}};
-import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
-
-public class {{artifactClassId}}Status extends ObservedGenerationAwareStatus {
+public class {{artifactClassId}}Status {
}
diff --git a/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml b/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml
index fe9d29300e..11d4288421 100644
--- a/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml
+++ b/bootstrapper-maven-plugin/src/main/resources/templates/pom.xml
@@ -11,7 +11,7 @@
jar
- 11
+ 17
${java.version}
${java.version}
{{josdkVersion}}
@@ -45,12 +45,6 @@
${josdk.version}
test
-
- io.fabric8
- crd-generator-apt
- ${fabric8-client.version}
- provided
-
org.slf4j
slf4j-api
@@ -58,7 +52,7 @@
org.apache.logging.log4j
- log4j-slf4j-impl
+ log4j-slf4j2-impl
${log4j.version}
@@ -86,7 +80,19 @@
maven-compiler-plugin
3.11.0
+
+ io.fabric8
+ crd-generator-maven-plugin
+ ${fabric8-client.version}
+
+
+
+ generate
+
+
+
+
-
\ No newline at end of file
+
diff --git a/caffeine-bounded-cache-support/pom.xml b/caffeine-bounded-cache-support/pom.xml
index 2010056cda..fccdec8aab 100644
--- a/caffeine-bounded-cache-support/pom.xml
+++ b/caffeine-bounded-cache-support/pom.xml
@@ -1,22 +1,15 @@
-
+
+ 4.0.0
- java-operator-sdk
io.javaoperatorsdk
- 4.9.8-SNAPSHOT
+ java-operator-sdk
+ 5.0.0-SNAPSHOT
- 4.0.0
caffeine-bounded-cache-support
Operator SDK - Caffeine Bounded Cache Support
-
- 11
- 11
-
-
io.javaoperatorsdk
@@ -37,14 +30,9 @@
${project.version}
test
-
- io.fabric8
- crd-generator-apt
- test
-
org.apache.logging.log4j
- log4j-slf4j-impl
+ log4j-slf4j2-impl
test
@@ -70,10 +58,10 @@
However, this is needed to compile the tests so let's disable apt just for the compile phase -->
default-compile
- compile
compile
+ compile
-proc:none
@@ -82,7 +70,24 @@
+
+ io.fabric8
+ crd-generator-maven-plugin
+ ${fabric8-client.version}
+
+
+
+ generate
+
+ process-test-classes
+
+ ${project.build.testOutputDirectory}
+ WITH_ALL_DEPENDENCIES_AND_TESTS
+
+
+
+
-
\ No newline at end of file
+
diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
index b6e3ba2c8f..9d912986e1 100644
--- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
+++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/AbstractTestReconciler.java
@@ -1,6 +1,7 @@
package io.javaoperatorsdk.operator.processing.event.source.cache.sample;
import java.time.Duration;
+import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
@@ -13,7 +14,7 @@
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
-import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
+import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.*;
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
import io.javaoperatorsdk.operator.processing.event.source.cache.BoundedItemStore;
@@ -28,7 +29,7 @@
import com.github.benmanes.caffeine.cache.Caffeine;
public abstract class AbstractTestReconciler
>
- implements Reconciler
, EventSourceInitializer
{
+ implements Reconciler
{
private static final Logger log =
LoggerFactory.getLogger(BoundedCacheClusterScopeTestReconciler.class);
@@ -69,20 +70,23 @@ protected void createConfigMap(P resource, Context
context) {
}
@Override
- public Map prepareEventSources(
+ public List> prepareEventSources(
EventSourceContext context) {
var boundedItemStore =
boundedItemStore(new KubernetesClientBuilder().build(),
ConfigMap.class, Duration.ofMinutes(1), 1); // setting max size for testing purposes
- var es = new InformerEventSource<>(InformerConfiguration.from(ConfigMap.class, context)
- .withItemStore(boundedItemStore)
- .withSecondaryToPrimaryMapper(
- Mappers.fromOwnerReference(this instanceof BoundedCacheClusterScopeTestReconciler))
- .build(), context);
+ var es = new InformerEventSource<>(
+ InformerEventSourceConfiguration.from(ConfigMap.class, primaryClass())
+ .withInformerConfiguration(c -> c.withItemStore(boundedItemStore))
+ .withSecondaryToPrimaryMapper(
+ Mappers.fromOwnerReferences(context.getPrimaryResourceClass(),
+ this instanceof BoundedCacheClusterScopeTestReconciler))
+ .build(),
+ context);
- return EventSourceInitializer.nameEventSources(es);
+ return List.of(es);
}
private void ensureStatus(P resource) {
@@ -102,4 +106,7 @@ public static BoundedItemStore boundedItemStore(
.build();
return CaffeineBoundedItemStores.boundedItemStore(client, rClass, cache);
}
+
+ protected abstract Class primaryClass();
+
}
diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/clusterscope/BoundedCacheClusterScopeTestReconciler.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/clusterscope/BoundedCacheClusterScopeTestReconciler.java
index a154659164..84448fc9d8 100644
--- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/clusterscope/BoundedCacheClusterScopeTestReconciler.java
+++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/clusterscope/BoundedCacheClusterScopeTestReconciler.java
@@ -7,4 +7,8 @@
public class BoundedCacheClusterScopeTestReconciler extends
AbstractTestReconciler {
+ @Override
+ protected Class primaryClass() {
+ return BoundedCacheClusterScopeTestCustomResource.class;
+ }
}
diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestReconciler.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestReconciler.java
index 211877b361..6b95665585 100644
--- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestReconciler.java
+++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestReconciler.java
@@ -7,4 +7,8 @@
public class BoundedCacheTestReconciler
extends AbstractTestReconciler {
+ @Override
+ protected Class primaryClass() {
+ return BoundedCacheTestCustomResource.class;
+ }
}
diff --git a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java
index 03a311529e..2bdd434d23 100644
--- a/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java
+++ b/caffeine-bounded-cache-support/src/test/java/io/javaoperatorsdk/operator/processing/event/source/cache/sample/namespacescope/BoundedCacheTestStatus.java
@@ -1,6 +1,4 @@
package io.javaoperatorsdk.operator.processing.event.source.cache.sample.namespacescope;
-import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
-
-public class BoundedCacheTestStatus extends ObservedGenerationAwareStatus {
+public class BoundedCacheTestStatus {
}
diff --git a/docsy/.gitignore b/docs/.gitignore
similarity index 100%
rename from docsy/.gitignore
rename to docs/.gitignore
diff --git a/docsy/.nvmrc b/docs/.nvmrc
similarity index 100%
rename from docsy/.nvmrc
rename to docs/.nvmrc
diff --git a/docsy/CONTRIBUTING.md b/docs/CONTRIBUTING.md
similarity index 100%
rename from docsy/CONTRIBUTING.md
rename to docs/CONTRIBUTING.md
diff --git a/docsy/Dockerfile b/docs/Dockerfile
similarity index 100%
rename from docsy/Dockerfile
rename to docs/Dockerfile
diff --git a/docsy/LICENSE b/docs/LICENSE
similarity index 100%
rename from docsy/LICENSE
rename to docs/LICENSE
diff --git a/docsy/README.md b/docs/README.md
similarity index 100%
rename from docsy/README.md
rename to docs/README.md
diff --git a/docsy/assets/icons/logo.svg b/docs/assets/icons/logo.svg
similarity index 100%
rename from docsy/assets/icons/logo.svg
rename to docs/assets/icons/logo.svg
diff --git a/docsy/assets/scss/_variables_project.scss b/docs/assets/scss/_variables_project.scss
similarity index 100%
rename from docsy/assets/scss/_variables_project.scss
rename to docs/assets/scss/_variables_project.scss
diff --git a/docsy/config.yaml b/docs/config.yaml
similarity index 100%
rename from docsy/config.yaml
rename to docs/config.yaml
diff --git a/docsy/content/en/_index.md b/docs/content/en/_index.md
similarity index 100%
rename from docsy/content/en/_index.md
rename to docs/content/en/_index.md
diff --git a/docsy/content/en/blog/_index.md b/docs/content/en/blog/_index.md
similarity index 100%
rename from docsy/content/en/blog/_index.md
rename to docs/content/en/blog/_index.md
diff --git a/docsy/content/en/blog/news/_index.md b/docs/content/en/blog/news/_index.md
similarity index 100%
rename from docsy/content/en/blog/news/_index.md
rename to docs/content/en/blog/news/_index.md
diff --git a/docsy/content/en/blog/releases/_index.md b/docs/content/en/blog/releases/_index.md
similarity index 100%
rename from docsy/content/en/blog/releases/_index.md
rename to docs/content/en/blog/releases/_index.md
diff --git a/docs/content/en/blog/releases/v5-release.md b/docs/content/en/blog/releases/v5-release.md
new file mode 100644
index 0000000000..badee98c8c
--- /dev/null
+++ b/docs/content/en/blog/releases/v5-release.md
@@ -0,0 +1,397 @@
+---
+title: Version 5 Released!
+date: 2024-09-21
+---
+
+We are excited to announce that Java Operator SDK v5 has been released. This significant effort contains
+various features and enhancements accumulated since the last major release and required changes in our APIs.
+Within this post, we will go through all the main changes and help you upgrade to this new version, and provide
+a rationale behind the changes if necessary.
+
+We will omit descriptions of changes that should only require simple code updates; please do contact
+us if you encounter issues anyway.
+
+You can see an introduction and some important changes and rationale behind them from [KubeCon](https://youtu.be/V0NYHt2yjcM?t=1238).
+
+## Various Changes
+
+- From this release, the minimal Java version is 17.
+- Various deprecated APIs are removed. Migration should be easy.
+
+## Naming changes
+
+TODO add handy diff links here
+
+## Changes in low-level APIs
+
+### Server Side Apply (SSA)
+
+[Server Side Apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/) is now a first-class citizen in
+the framework and
+the default approach for patching the status resource. This means that patching a resource or its status through
+`UpdateControl` and adding
+the finalizer in the background will both use SSA.
+
+Migration from a non-SSA based patching to an SSA based one can be problematic. Make sure you test the transition when
+you migrate from older version of the frameworks.
+To continue to use a non-SSA based on,
+set [ConfigurationService.useSSAToPatchPrimaryResource](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L462)
+to `false`.
+
+See some identified problematic migration cases and how to handle them
+in [StatusPatchSSAMigrationIT](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/statuspatchnonlocking/StatusPatchSSAMigrationIT.java).
+
+TODO using new instance to update status always,
+
+### Event Sources related changes
+
+#### Multi-cluster support in InformerEventSource
+
+`InformerEventSource` now supports watching remote clusters. You can simply pass a `KubernetesClient` instance
+initialized to connect to a different cluster from the one where the controller runs when configuring your event source.
+See [InformerEventSourceConfiguration.withKubernetesClient](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java)
+
+Such an informer behaves exactly as a regular one. Owner references won't work in this situation, though, so you have to
+specify a `SecondaryToPrimaryMapper` (probably based on labels or annotations).
+
+See related integration
+test [here](https://github.com/operator-framework/java-operator-sdk/tree/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster)
+
+#### SecondaryToPrimaryMapper now checks resource types
+
+The owner reference based mappers are now checking the type (`kind` and `apiVersion`) of the resource when resolving the
+mapping. This is important
+since a resource may have owner references to a different resource type with the same name.
+
+See implementation
+details [here](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/Mappers.java#L74-L75)
+
+#### InformerEventSource-related changes
+
+There are multiple smaller changes to `InformerEventSource` and related classes:
+
+1. `InformerConfiguration` is renamed
+ to [
+ `InformerEventSourceConfiguration`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/InformerEventSourceConfiguration.java)
+2. `InformerEventSourceConfiguration` doesn't require `EventSourceContext` to be initialized anymore.
+
+#### All EventSource are now ResourceEventSources
+
+The [
+`EventSource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java)
+abstraction is now always aware of the resources and
+handles accessing (the cached) resources, filtering, and additional capabilities. Before v5, such capabilities were
+present only in a sub-class called `ResourceEventSource`,
+but we decided to merge and remove `ResourceEventSource` since this has a nice impact on other parts of the system in
+terms of architecture.
+
+If you still need to create an `EventSource` that only supports triggering of your reconciler,
+see [
+`TimerEventSource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/timer/TimerEventSource.java)
+for an example of how this can be accomplished.
+
+#### Naming event sources
+
+[
+`EventSource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/EventSource.java#L45)
+are now named. This reduces the ambiguity that might have existed when trying to refer to an `EventSource`.
+
+### ControllerConfiguration annotation related changes
+
+You no longer have to annotate the reconciler with `@ControllerConfiguration` annotation.
+This annotation is (one) way to override the default properties of a controller.
+If the annotation is not present, the default values from the annotation are used.
+
+PR: https://github.com/operator-framework/java-operator-sdk/pull/2203
+
+In addition to that, the informer-related configurations are now extracted into
+a separate [
+`@Informer`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/informer/Informer.java)
+annotation within [
+`@ControllerConfiguration`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java#L24).
+Hopefully this explicits which part of the configuration affects the informer associated with primary resource.
+Similarly, the same `@Informer` annotation is used when configuring the informer associated with a managed
+`KubernetesDependentResource` via the
+[
+`KubernetesDependent`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependent.java#L33)
+annotation.
+
+### EventSourceInitializer and ErrorStatusHandler are removed
+
+Both the `EventSourceInitializer` and `ErrorStatusHandler` interfaces are removed, and their methods moved directly
+under [
+`Reconciler`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Reconciler.java#L30-L56).
+
+If possible, we try to avoid such marker interfaces since it is hard to deduce related usage just by looking at the
+source code.
+You can now simply override those methods when implementing the `Reconciler` interface.
+
+### Cloning accessing secondary resources
+
+When accessing the secondary resources using [
+`Context.getSecondaryResource(s)(...)`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java#L19-L29),
+the resources are no longer cloned by default, since
+cloning could have an impact on performance. This means that you now need to ensure that these any changes
+are now made directly to the underlying cached resource. This should be avoided since the same resource instance may be
+present for other reconciliation cycles and would
+no longer represent the state on the server.
+
+If you want to still clone resources by default,
+set [
+`ConfigurationService.cloneSecondaryResourcesWhenGettingFromCache`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L484)
+to `true`.
+
+### Removed automated observed generation handling
+
+The automatic observed generation handling feature was removed since it is easy to implement inside the reconciler, but
+it made
+the implementation much more complex, especially if the framework would have to support it both for served side apply
+and client side apply.
+
+You can check a sample implementation how to do it manually in
+this [integration test](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/manualobservedgeneration/).
+
+## Dependent Resource related changes
+
+### ResourceDiscriminator is removed and related changes
+
+The primary reason `ResourceDiscriminator` was introduced was to cover the case when there are
+more than one dependent resources of a given type associated with a given primary resource. In this situation, JOSDK
+needed a generic mechanism to
+identify which resources on the cluster should be associated with which dependent resource implementation.
+We improved this association mechanism, thus rendering `ResourceDiscriminator` obsolete.
+
+As a replacement, the dependent resource will select the target resource based on the desired state.
+See the generic implementation in [
+`AbstractDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/AbstractDependentResource.java#L135-L144).
+Calculating the desired state can be costly and might depend on other resources. For `KubernetesDependentResource`
+it is usually enough to provide the name and namespace (if namespace-scoped) of the target resource, which is what the
+`KubernetesDependentResource` implementation does by default. If you can determine which secondary to target without
+computing the desired state via its associated `ResourceID`, then we encourage you to override the
+[
+`ResourceID targetSecondaryResourceID()`](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L234-L244)
+method as shown
+in [this example](https://github.com/operator-framework/java-operator-sdk/blob/c7901303c5304e6017d050f05cbb3d4930bdfe44/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator/MultipleManagedDependentNoDiscriminatorConfigMap1.java#L24-L35)
+
+### Read-only bulk dependent resources
+
+Read-only bulk dependent resources are now supported; this was a request from multiple users, but it required changes to
+the underlying APIs.
+Please check the documentation for further details.
+
+See also the
+related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/readonly).
+
+### Multiple Dependents with Activation Condition
+
+Until now, activation conditions had a limitation that only one condition was allowed for a specific resource type.
+For example, two `ConfigMap` dependent resources were not allowed, both with activation conditions. The underlying issue
+was with the informer registration process. When an activation condition is evaluated as "met" in the background,
+the informer is registered dynamically for the target resource type. However, we need to avoid registering multiple
+informers of the same kind. To prevent this the dependent resource must specify
+the [name of the informer](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation/ConfigMapDependentResource2.java#L12).
+
+See the complete
+example [here](https://github.com/operator-framework/java-operator-sdk/blob/1635c9ea338f8e89bacc547808d2b409de8734cf/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/multipledependentwithactivation).
+
+### `getSecondaryResource` is Activation condition aware
+
+When an activation condition for a resource type is not met, no associated informer might be registered for that
+resource type. However, in this situation, calling `Context.getSecondaryResource`
+and its alternatives would previously throw an exception. This was, however, rather confusing and a better user
+experience would be to return an empty value instead of throwing an error. We changed this behavior in v5 to make it
+more user-friendly and attempting to retrieve a secondary resource that is gated by an activation condition will now
+return an empty value as if the associated informer existed.
+
+See related [issue](https://github.com/operator-framework/java-operator-sdk/issues/2198) for details.
+
+## Workflow related changes
+
+### `@Workflow` annotation
+
+The managed workflow definition is now a separate `@Workflow` annotation; it is no longer part of
+`@ControllerConfiguration`.
+
+See sample
+usage [here](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageManagedDependentsReconciler.java#L14-L20)
+
+### Explicit workflow invocation
+
+Before v5, the managed dependents part of a workflow would always be reconciled before the primary `Reconciler`
+`reconcile` or `cleanup` methods were called. It is now possible to explictly ask for a workflow reconciliation in your
+primary `Reconciler`, thus allowing you to control when the workflow is reconciled. This mean you can perform all kind
+of operations - typically validations - before executing the workflow, as shown in the sample below:
+
+```java
+
+@Workflow(explicitInvocation = true,
+ dependents = @Dependent(type = ConfigMapDependent.class))
+@ControllerConfiguration
+public class WorkflowExplicitCleanupReconciler
+ implements Reconciler,
+ Cleaner {
+
+ @Override
+ public UpdateControl reconcile(
+ WorkflowExplicitCleanupCustomResource resource,
+ Context context) {
+
+ context.managedWorkflowAndDependentResourceContext().reconcileManagedWorkflow();
+
+ return UpdateControl.noUpdate();
+ }
+
+ @Override
+ public DeleteControl cleanup(WorkflowExplicitCleanupCustomResource resource,
+ Context context) {
+
+ context.managedWorkflowAndDependentResourceContext().cleanupManageWorkflow();
+ // this can be checked
+ // context.managedWorkflowAndDependentResourceContext().getWorkflowCleanupResult()
+ return DeleteControl.defaultDelete();
+ }
+}
+```
+
+To turn on this mode of execution, set [
+`explicitInvocation`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java#L26)
+flag to `true` in the managed workflow definition.
+
+See the following integration tests
+for [
+`invocation`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation)
+and [
+`cleanup`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup).
+
+### Explicit exception handling
+
+If an exception happens during a workflow reconciliation, the framework automatically throws it further.
+You can now set [
+`handleExceptionsInReconciler`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Workflow.java#L40)
+to true for a workflow and check the thrown exceptions explicitly
+in the execution results.
+
+```java
+
+@Workflow(handleExceptionsInReconciler = true,
+ dependents = @Dependent(type = ConfigMapDependent.class))
+@ControllerConfiguration
+public class HandleWorkflowExceptionsInReconcilerReconciler
+ implements Reconciler,
+ Cleaner {
+
+ private volatile boolean errorsFoundInReconcilerResult = false;
+ private volatile boolean errorsFoundInCleanupResult = false;
+
+ @Override
+ public UpdateControl reconcile(
+ HandleWorkflowExceptionsInReconcilerCustomResource resource,
+ Context context) {
+
+ errorsFoundInReconcilerResult = context.managedWorkflowAndDependentResourceContext()
+ .getWorkflowReconcileResult().erroredDependentsExist();
+
+ // check errors here:
+ Map errors = context.getErroredDependents();
+
+ return UpdateControl.noUpdate();
+ }
+}
+```
+
+See integration
+test [here](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowsilentexceptionhandling).
+
+### CRDPresentActivationCondition
+
+Activation conditions are typically used to check if the cluster has specific capabilities (e.g., is cert-manager
+available).
+Such a check can be done by verifying if a particular custom resource definition (CRD) is present on the cluster. You
+can now use the generic [
+`CRDPresentActivationCondition`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java)
+for this
+purpose, it will check if the CRD of a target resource type of a dependent resource exists on the cluster.
+
+See usage in integration
+test [here](https://github.com/operator-framework/java-operator-sdk/blob/refs/heads/next/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation).
+
+## Fabric8 client updated to 7.0
+
+The Fabric8 client has been updated to version 7.0.0. This is a new major version which implies that some API might have
+changed. Please take a look at the [Fabric8 client 7.0.0 migration guide](https://github.com/fabric8io/kubernetes-client/blob/main/doc/MIGRATION-v7.md).
+
+### CRD generator changes
+
+Starting with v5.0 (in accordance with changes made to the Fabric8 client in version 7.0.0), the CRD generator will use the maven plugin instead of the annotation processor as was previously the case.
+In many instances, you can simply configure the plugin by adding the following stanza to your project's POM build configuration:
+
+```xml
+
+ io.fabric8
+ crd-generator-maven-plugin
+ ${fabric8-client.version}
+
+
+
+ generate
+
+
+
+
+
+```
+*NOTE*: If you use the SDK's JUnit extension for your tests, you might also need to configure the CRD generator plugin to access your test `CustomResource` implementations as follows:
+```xml
+
+
+ io.fabric8
+ crd-generator-maven-plugin
+ ${fabric8-client.version}
+
+
+
+ generate
+
+ process-test-classes
+
+ ${project.build.testOutputDirectory}
+ WITH_ALL_DEPENDENCIES_AND_TESTS
+
+
+
+
+
+```
+
+Please refer to the [CRD generator documentation](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CRD-generator.md) for more details.
+
+
+## Experimental
+
+### Check if the following reconciliation is imminent
+
+You can now check if the subsequent reconciliation will happen right after the current one because the SDK has already
+received an event that will trigger a new reconciliation
+This information is available from
+the [
+`Context`](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Context.java#L69).
+
+Note that this could be useful, for example, in situations when a heavy task would be repeated in the follow-up
+reconciliation. In the current
+reconciliation, you can check this flag and return to avoid unneeded processing. Note that this is a semi-experimental
+feature, so please let us know
+if you found this helpful.
+
+```java
+
+@Override
+public UpdateControl reconcile(MyCustomResource resource, Context context) {
+
+ if (context.isNextReconciliationImminent()) {
+ // your logic, maybe return?
+ }
+}
+```
+
+See
+related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/664cb7109fe62f9822997d578ae7f57f17ef8c26/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/nextreconciliationimminent).
\ No newline at end of file
diff --git a/docsy/content/en/community/_index.md b/docs/content/en/community/_index.md
similarity index 100%
rename from docsy/content/en/community/_index.md
rename to docs/content/en/community/_index.md
diff --git a/docsy/content/en/docs/_index.md b/docs/content/en/docs/_index.md
similarity index 100%
rename from docsy/content/en/docs/_index.md
rename to docs/content/en/docs/_index.md
diff --git a/docsy/content/en/docs/architecture/_index.md b/docs/content/en/docs/architecture/_index.md
similarity index 100%
rename from docsy/content/en/docs/architecture/_index.md
rename to docs/content/en/docs/architecture/_index.md
diff --git a/docsy/content/en/docs/configuration/_index.md b/docs/content/en/docs/configuration/_index.md
similarity index 100%
rename from docsy/content/en/docs/configuration/_index.md
rename to docs/content/en/docs/configuration/_index.md
diff --git a/docsy/content/en/docs/contributing/_index.md b/docs/content/en/docs/contributing/_index.md
similarity index 100%
rename from docsy/content/en/docs/contributing/_index.md
rename to docs/content/en/docs/contributing/_index.md
diff --git a/docsy/content/en/docs/dependent-resources/_index.md b/docs/content/en/docs/dependent-resources/_index.md
similarity index 71%
rename from docsy/content/en/docs/dependent-resources/_index.md
rename to docs/content/en/docs/dependent-resources/_index.md
index be67ec6ab6..205c03ac1c 100644
--- a/docsy/content/en/docs/dependent-resources/_index.md
+++ b/docs/content/en/docs/dependent-resources/_index.md
@@ -3,6 +3,8 @@ title: Dependent Resources
weight: 60
---
+# Dependent Resources
+
## Motivations and Goals
Most operators need to deal with secondary resources when trying to realize the desired state
@@ -96,13 +98,13 @@ and labels, which are ignored by default:
```java
public class MyDependentResource extends KubernetesDependentResource
- implements Matcher {
- // your implementation
+ implements Matcher {
+ // your implementation
- public Result match(MyDependent actualResource, MyPrimary primary,
- Context context) {
- return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
- }
+ public Result match(MyDependent actualResource, MyPrimary primary,
+ Context context) {
+ return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
+ }
}
```
@@ -136,24 +138,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
class DeploymentDependentResource extends CRUDKubernetesDependentResource {
- public DeploymentDependentResource() {
- super(Deployment.class);
- }
-
- @Override
- protected Deployment desired(WebPage webPage, Context context) {
- var deploymentName = deploymentName(webPage);
- Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
- deployment.getMetadata().setName(deploymentName);
- deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
- deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
-
- deployment.getSpec().getTemplate().getMetadata().getLabels()
- .put("app", deploymentName);
- deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
- .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
- return deployment;
- }
+ public DeploymentDependentResource() {
+ super(Deployment.class);
+ }
+
+ @Override
+ protected Deployment desired(WebPage webPage, Context context) {
+ var deploymentName = deploymentName(webPage);
+ Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
+ deployment.getMetadata().setName(deploymentName);
+ deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
+ deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
+
+ deployment.getSpec().getTemplate().getMetadata().getLabels()
+ .put("app", deploymentName);
+ deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
+ .setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
+ return deployment;
+ }
}
```
@@ -189,25 +191,25 @@ instances are managed by JOSDK, an example of which can be seen below:
```java
@ControllerConfiguration(
- labelSelector = SELECTOR,
- dependents = {
- @Dependent(type = ConfigMapDependentResource.class),
- @Dependent(type = DeploymentDependentResource.class),
- @Dependent(type = ServiceDependentResource.class)
- })
+ labelSelector = SELECTOR,
+ dependents = {
+ @Dependent(type = ConfigMapDependentResource.class),
+ @Dependent(type = DeploymentDependentResource.class),
+ @Dependent(type = ServiceDependentResource.class)
+ })
public class WebPageManagedDependentsReconciler
- implements Reconciler, ErrorStatusHandler {
+ implements Reconciler, ErrorStatusHandler {
- // omitted code
+ // omitted code
- @Override
- public UpdateControl reconcile(WebPage webPage, Context context) {
+ @Override
+ public UpdateControl reconcile(WebPage webPage, Context context) {
- final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
- .getMetadata().getName();
- webPage.setStatus(createStatus(name));
- return UpdateControl.patchStatus(webPage);
- }
+ final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
+ .getMetadata().getName();
+ webPage.setStatus(createStatus(name));
+ return UpdateControl.patchStatus(webPage);
+ }
}
```
@@ -222,104 +224,11 @@ 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.
-
-Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone
-resources.
-
-The following sample is similar to the one above, simply performing additional checks, and
-conditionally creating an `Ingress`:
-
-```java
-
-@ControllerConfiguration
-public class WebPageStandaloneDependentsReconciler
- implements Reconciler, ErrorStatusHandler,
- EventSourceInitializer {
-
- private KubernetesDependentResource configMapDR;
- private KubernetesDependentResource deploymentDR;
- private KubernetesDependentResource serviceDR;
- private KubernetesDependentResource ingressDR;
-
- public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) {
- // 1.
- createDependentResources(kubernetesClient);
- }
-
- @Override
- public List prepareEventSources(EventSourceContext context) {
- // 2.
- return List.of(
- configMapDR.initEventSource(context),
- deploymentDR.initEventSource(context),
- serviceDR.initEventSource(context));
- }
-
- @Override
- public UpdateControl reconcile(WebPage webPage, Context context) {
-
- // 3.
- if (!isValidHtml(webPage.getHtml())) {
- return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage));
- }
-
- // 4.
- configMapDR.reconcile(webPage, context);
- deploymentDR.reconcile(webPage, context);
- serviceDR.reconcile(webPage, context);
-
- // 5.
- if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
- ingressDR.reconcile(webPage, context);
- } else {
- ingressDR.delete(webPage, context);
- }
+case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing
+resources programmatically.
- // 6.
- webPage.setStatus(
- createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()));
- return UpdateControl.patchStatus(webPage);
- }
-
- private void createDependentResources(KubernetesClient client) {
- this.configMapDR = new ConfigMapDependentResource();
- this.deploymentDR = new DeploymentDependentResource();
- this.serviceDR = new ServiceDependentResource();
- this.ingressDR = new IngressDependentResource();
-
- Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> {
- dr.setKubernetesClient(client);
- dr.configureWith(new KubernetesDependentResourceConfig()
- .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR));
- });
- }
-
- // omitted code
-}
-```
-
-There are multiple things happening here:
-
-1. Dependent resources are explicitly created and can be access later by reference.
-2. Event sources are produced by the dependent resources, but needs to be explicitly registered in
- this case by implementing
- the [`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java)
- interface.
-3. The input html is validated, and error message is set in case it is invalid.
-4. Reconciliation of dependent resources is called explicitly, but here the workflow
- customization is fully in the hand of the developer.
-5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to
- delete it if not.
-6. Status is set in a different way, this is just an alternative way to show, that the actual state
- can be read using the reference. This could be written in a same way as in the managed example.
-
-See the full source code of
-sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java)
-.
-
-Note also the Workflows feature makes it possible to also support this conditional creation use
-case in managed dependent resources.
+You can see a commented example of how to do
+so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java).
## Creating/Updating Kubernetes Resources
@@ -352,17 +261,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t
these implementations. See
in [ConfigurationService](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L332-L358).
-It is, however, important to note that these implementations are default, generic
-implementations that the framework can provide expected behavior out of the box. In many
-situations, these will work just fine but it is also possible to provide matching algorithms
+It is, however, important to note that these implementations are default, generic
+implementations that the framework can provide expected behavior out of the box. In many
+situations, these will work just fine but it is also possible to provide matching algorithms
optimized for specific use cases. This is easily done by simply overriding
-the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).
+the `match(...)` [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java#L156-L156).
-It is also possible to bypass the matching logic altogether to simply rely on the server-side
+It is also possible to bypass the matching logic altogether to simply rely on the server-side
apply mechanism if always sending potentially unchanged resources to the cluster is not an issue.
JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API
-server. To bypass the matching feature completely, simply override the `match` method to always
-return `false`, thus telling JOSDK that the actual state never matches the desired one, making
+server. To bypass the matching feature completely, simply override the `match` method to always
+return `false`, thus telling JOSDK that the actual state never matches the desired one, making
it always update the resources using SSA.
WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update
@@ -382,27 +291,33 @@ customized by implementing
by the dependent resource.
See sample in one of the integration
-tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java#L25-L25)
+tests [here](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primaryindexer)
.
## Multiple Dependent Resources of Same Type
When dealing with multiple dependent resources of same type, the dependent resource implementation
needs to know which specific resource should be targeted when reconciling a given dependent
-resource, since there will be multiple instances of that type which could possibly be used, each
-associated with the same primary resource. In order to do this, JOSDK relies on the
-[resource discriminator](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ResourceDiscriminator.java)
-concept. Resource discriminators uniquely identify the target resource of a dependent resource.
-In the managed Kubernetes dependent resources case, the discriminator can be declaratively set
-using the `@KubernetesDependent` annotation:
-
-```java
-
-@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class)
-public class MultipleManagedDependentResourceConfigMap1 {
-//...
-}
-```
+resource, since there could be multiple instances of that type which could possibly be used, each
+associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary
+resource matching the desired state associated with the primary resource. This makes sense because the desired
+state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how
+they should be reconciled.
+
+There might be cases, though, where it might be problematic to call the `desired` method several times (for example, because it is costly to do so),
+it is always possible to override this automated discrimination using several means (consider in this priority order):
+
+- Override the `targetSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`,
+ where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be
+ the easiest solution if you're working with Kubernetes resources.
+- Override the `selectTargetSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`.
+ This should be relatively simple to override this method to optimize the matching to your needs. You can see an
+ example of such an implementation in
+ the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalWithStateDependentResource.java)
+ class.
+- As last resort, you can implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch.
+
+### Sharing an Event Source Between Dependent Resources
Dependent resources usually also provide event sources. When dealing with multiple dependents of
the same type, one needs to decide whether these dependent resources should track the same
@@ -418,10 +333,10 @@ would look as follows:
useEventSourceWithName = "configMapSource")
```
-A sample is provided as an integration test both
-for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java)
-and
-for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
+A sample is provided as an integration test both:
+for [managed](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledrsametypenodiscriminator)
+
+For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/multipledependentresource)
cases.
## Bulk Dependent Resources
@@ -438,11 +353,11 @@ implement the
interface.
Various examples are provided
-as [integration tests](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent)
+as [integration tests](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent)
.
To see how bulk dependent resources interact with workflow conditions, please refer to this
-[integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/bulkdependent/BulkDependentWithConditionIT.java).
+[integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/bulkdependent/conidition).
## External State Tracking Dependent Resources
@@ -463,11 +378,11 @@ interface. Note that most of the JOSDK-provided dependent resource implementatio
`PollingDependentResource` or `PerResourcePollingDependentResource` already extends
`AbstractExternalDependentResource`, thus supporting external state tracking out of the box.
-See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateDependentIT.java)
+See [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateDependentIT.java)
as a sample.
For a better understanding it might be worth to study
-a [sample implementation](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalStateReconciler.java)
+a [sample implementation](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/ExternalStateReconciler.java)
without dependent resources.
Please also refer to the [docs](/docs/patterns-and-best-practices#managing-state) for managing state in
@@ -481,18 +396,21 @@ created. For example, if three bulk dependent resources associated with external
three associated `ConfigMaps` (assuming `ConfigMaps` are used as a state-tracking resource) will
also be created, one per dependent resource.
-See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java)
+See [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/externalstate/externalstatebulkdependent)
as a sample.
-
## GenericKubernetesResource based Dependent Resources
-In rare circumstances resource handling where there is no class representation or just typeless handling might be needed.
-Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
-to support that.
+In rare circumstances resource handling where there is no class representation or just typeless handling might be
+needed.
+Fabric8 Client
+provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
+to support that.
-For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
-. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
+For dependent resource this is supported
+by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
+. See
+samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
## Other Dependent Resource Features
@@ -518,13 +436,13 @@ For dependent resource this is supported by [GenericKubernetesDependentResource]
practice in general) - so for example if there are two config map dependents, either
there should be a shared event source between them, or a label selector on the event sources
to select only the relevant events, see
- in [related integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/orderedmanageddependent/ConfigMapDependentResource1.java)
+ in [related integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/orderedmanageddependent/ConfigMapDependentResource2.java)
.
## "Read-only" Dependent Resources vs. Event Source
See Integration test for a read-only
-dependent [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/249b41f3c68c4d0e9c77c41eca647a69a24347b0/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java).
+dependent [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/dependent/primarytosecondaydependent/ConfigMapDependent.java).
Some secondary resources only exist as input for the reconciliation process and are never
updated *by a controller* (they might, and actually usually do, get updated by users interacting
diff --git a/docsy/content/en/docs/faq/_index.md b/docs/content/en/docs/faq/_index.md
similarity index 74%
rename from docsy/content/en/docs/faq/_index.md
rename to docs/content/en/docs/faq/_index.md
index 79264332ef..5e4975a385 100644
--- a/docsy/content/en/docs/faq/_index.md
+++ b/docs/content/en/docs/faq/_index.md
@@ -56,7 +56,7 @@ io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET a
```
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):
+at [Reconciler-level configuration](../configuration.md#reconciler-level-configuration):
```java
Operator operator;
@@ -68,12 +68,30 @@ operator.register(reconciler, configOverrider ->
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.md#operator-level-configuration):
+is `true` (`false` by default). To disable, set it to `false` at [Operator-level configuration](../configuration#operator-level-configuration):
```java
Operator operator = new Operator( override -> override.checkingCRDAndValidateLocalModel(false));
```
+### Q: I'm managing an external resource that has a generated ID, where should I store that?
+
+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.
+
+Usually there are two options you can consider to store the ID:
+
+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.
+
+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.
+
+Dependent Resources feature supports the [first approach](../dependent-resources/_index.md#external-state-tracking-dependent-resources).
+
### Q: How to fix `sun.security.provider.certpath.SunCertPathBuilderException` on Rancher Desktop and k3d/k3s Kubernetes
It's a common issue when using k3d and the fabric8 client tries to connect to the cluster an exception is thrown:
diff --git a/docsy/content/en/docs/features/_index.md b/docs/content/en/docs/features/_index.md
similarity index 90%
rename from docsy/content/en/docs/features/_index.md
rename to docs/content/en/docs/features/_index.md
index 428747f17d..a10da73412 100644
--- a/docsy/content/en/docs/features/_index.md
+++ b/docs/content/en/docs/features/_index.md
@@ -3,6 +3,8 @@ title: Features
weight: 50
---
+# Features
+
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
@@ -62,7 +64,8 @@ and/or re-schedule a reconciliation with a desired time delay:
@Override
public UpdateControl reconcile(
EventSourceTestCustomResource resource, Context context) {
- ...
+ // omitted code
+
return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
}
```
@@ -73,7 +76,8 @@ without an update:
@Override
public UpdateControl reconcile(
EventSourceTestCustomResource resource, Context context) {
- ...
+ // omitted code
+
return UpdateControl.noUpdate().rescheduleAfter(10, TimeUnit.SECONDS);
}
```
@@ -85,17 +89,31 @@ Those are the typical use cases of resource updates, however in some cases there
the controller wants to update the resource itself (for example to add annotations) or not perform
any updates, which is also supported.
-It is also possible to update both the status and the resource with the
-`updateResourceAndStatus` method. In this case, the resource is updated first followed by the
-status, using two separate requests to the Kubernetes API.
-
-You should always state your intent using `UpdateControl` and let the SDK deal with the actual
-updates instead of performing these updates yourself using the actual Kubernetes client so that
-the SDK can update its internal state accordingly.
-
-Resource updates are protected using optimistic version control, to make sure that other updates
-that might have occurred in the mean time on the server are not overwritten. This is ensured by
-setting the `resourceVersion` field on the processed resources.
+It is also possible to update both the status and the resource with the `patchResourceAndStatus` method. In this case,
+the resource is updated first followed by the status, using two separate requests to the Kubernetes API.
+
+From v5 `UpdateControl` only supports patching the resources, by default
+using [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/).
+It is important to understand how SSA works in Kubernetes. Mainly, resources applied using SSA
+should contain only the fields identifying the resource and those the user is interested in (a 'fully specified intent'
+in Kubernetes parlance), thus usually using a resource created from scratch, see
+[sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourcewithssa).
+To contrast, see the same sample, this time [without SSA](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java).
+
+Non-SSA based patch is still supported.
+You can control whether or not to use SSA
+using [`ConfigurationServcice.useSSAToPatchPrimaryResource()`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationService.java#L385-L385)
+and the related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource` method.
+Related integration test can be
+found [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/patchresourceandstatusnossa).
+
+Handling resources directly using the client, instead of delegating these updates operations to JOSDK by returning
+an `UpdateControl` at the end of your reconciliation, should work appropriately. However, we do recommend to
+use `UpdateControl` instead since JOSDK makes sure that the operations are handled properly, since there are subtleties
+to be aware of. For example, if you are using a finalizer, JOSDK makes sure to include it in your fully specified intent
+so that it is not unintentionally removed from the resource (which would happen if you omit it, since your controller is
+the designated manager for that field and Kubernetes interprets the finalizer being gone from the specified intent as a
+request for removal).
[`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)
typically instructs the framework to remove the finalizer after the dependent
@@ -104,7 +122,8 @@ resource are cleaned up in `cleanup` implementation.
```java
public DeleteControl cleanup(MyCustomResource customResource,Context context){
- ...
+ // omitted code
+
return DeleteControl.defaultDelete();
}
@@ -163,56 +182,7 @@ You can specify the name of the finalizer to use for your `Reconciler` using the
[`@ControllerConfiguration`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java)
annotation. If you do not specify a finalizer name, one will be automatically generated for you.
-## Automatic Observed Generation Handling
-
-Having an `.observedGeneration` value on your resources' status is a best practice to
-indicate the last generation of the resource which was successfully reconciled by the controller.
-This helps users / administrators diagnose potential issues.
-
-In order to have this feature working:
-
-- the **status class** (not the resource itself) must implement the
- [`ObservedGenerationAware`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java)
- interface. See also
- the [`ObservedGenerationAwareStatus`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java)
- convenience implementation that you can extend in your own status class implementations.
-- The other condition is that the `CustomResource.getStatus()` method should not return `null`.
- So the status should be instantiated when the object is returned using the `UpdateControl`.
-
-If these conditions are fulfilled and generation awareness is activated, the observed generation
-is automatically set by the framework after the `reconcile` method is called. Note that the
-observed generation is also updated even when `UpdateControl.noUpdate()` is returned from the
-reconciler. See this feature at work in
-the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5)
-.
-
-```java
-public class WebPageStatus extends ObservedGenerationAwareStatus {
-
- private String htmlConfigMap;
-
- ...
-}
-```
-
-Initializing status automatically on custom resource could be done by overriding the `initStatus` method
-of `CustomResource`. However, this is NOT advised, since breaks the status patching if you use:
-`UpdateControl.patchStatus`. See
-also [javadocs](https://github.com/java-operator-sdk/java-operator-sdk/blob/3994f5ffc1fb000af81aa198abf72a5f75fd3e97/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/UpdateControl.java#L41-L42)
-.
-
-```java
-@Group("sample.javaoperatorsdk")
-@Version("v1")
-public class WebPage extends CustomResource
- implements Namespaced {
-
- @Override
- protected WebPageStatus initStatus() {
- return new WebPageStatus();
- }
-}
-```
+From v5 by default finalizer is added using Served Side Apply. See also UpdateControl in docs.
## Generation Awareness and Event Filtering
@@ -231,23 +201,22 @@ To turn off this feature, set `generationAwareEventProcessing` to `false` for th
## Support for Well Known (non-custom) Kubernetes Resources
A Controller can be registered for a non-custom resource, so well known Kubernetes resources like (
-`Ingress`, `Deployment`,...). Note that automatic observed generation handling is not supported
-for these resources, though, in this case, the handling of the observed generation is probably
-handled by the primary controller.
+`Ingress`, `Deployment`,...).
See
-the [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/deployment/DeploymentReconciler.java)
+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.
```java
public class DeploymentReconciler
implements Reconciler, TestExecutionInfoProvider {
- @Override
- public UpdateControl reconcile(
- Deployment resource, Context context) {
- ...
- }
+ @Override
+ public UpdateControl reconcile(
+ Deployment resource, Context context) {
+ // omitted code
+ }
+}
```
## Max Interval Between Reconciliations
@@ -265,6 +234,7 @@ standard annotation:
@ControllerConfiguration(maxReconciliationInterval = @MaxReconciliationInterval(
interval = 50,
timeUnit = TimeUnit.MILLISECONDS))
+public class MyReconciler implements Reconciler {}
```
The event is not propagated at a fixed rate, rather it's scheduled after each reconciliation. So the
@@ -487,9 +457,8 @@ related [method](https://github.com/java-operator-sdk/java-operator-sdk/blob/mai
### Registering Event Sources
-To register event sources, your `Reconciler` has to implement the
-[`EventSourceInitializer`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceInitializer.java)
-interface and initialize a list of event sources to register. One way to see this in action is
+To register event sources, your `Reconciler` has to override the `prepareEventSources` and return
+list of event sources to register. One way to see this in action is
to look at the
[tomcat example](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/sample-operators/tomcat-operator/src/main/java/io/javaoperatorsdk/operator/sample/WebappReconciler.java)
(irrelevant details omitted):
@@ -499,8 +468,9 @@ to look at the
@ControllerConfiguration
public class WebappReconciler
implements Reconciler, Cleaner, EventSourceInitializer {
-
- @Override
+ // ommitted code
+
+ @Override
public Map prepareEventSources(EventSourceContext context) {
InformerConfiguration configuration =
InformerConfiguration.from(Tomcat.class, context)
@@ -512,7 +482,7 @@ public class WebappReconciler
return EventSourceInitializer
.nameEventSources(new InformerEventSource<>(configuration, context));
}
- ...
+
}
```
@@ -561,7 +531,7 @@ between a primary resource and its associated secondary resources using an imple
`PrimaryToSecondaryMapper` interface. This is typically needed when there are many-to-one or
many-to-many relationships between primary and secondary resources, e.g. when the primary resource
is referencing secondary resources.
-See [PrimaryToSecondaryIT](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryIT.java)
+See [PrimaryToSecondaryIT](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/primarytosecondary/PrimaryToSecondaryIT.java)
integration test for a sample.
### Built-in EventSources
@@ -638,6 +608,25 @@ parts of reconciliation logic and during the execution of the controller:
For more information about MDC see this [link](https://www.baeldung.com/mdc-in-log4j-2-logback).
+## InformerEventSource Multi-Cluster Support
+
+It is possible to handle resources for remote cluster with `InformerEventSource`. To do so,
+simply set a client that connects to a remote cluster:
+
+```java
+
+InformerEventSourceConfiguration configuration =
+ InformerEventSourceConfiguration.from(SecondaryResource.class, PrimaryResource.class)
+ .withKubernetesClient(remoteClusterClient)
+ .withSecondaryToPrimaryMapper(Mappers.fromDefaultAnnotations());
+
+```
+
+You will also need to specify a `SecondaryToPrimaryMapper`, since the default one
+is based on owner references and won't work across cluster instances. You could, for example, use the provided implementation that relies on annotations added to the secondary resources to identify the associated primary resource.
+
+See related [integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/informerremotecluster).
+
## Dynamically Changing Target Namespaces
A controller can be configured to watch a specific set of namespaces in addition of the
@@ -656,15 +645,15 @@ registering an associated `Informer` and then calling the `changeNamespaces` met
```java
-public static void main(String[]args)throws IOException{
- KubernetesClient client=new DefaultKubernetesClient();
- Operator operator=new Operator(client);
- RegisteredController registeredController=operator.register(new WebPageReconciler(client));
- operator.installShutdownHook();
- operator.start();
+public static void main(String[] args) {
+ KubernetesClient client = new DefaultKubernetesClient();
+ Operator operator = new Operator(client);
+ RegisteredController registeredController = operator.register(new WebPageReconciler(client));
+ operator.installShutdownHook();
+ operator.start();
- // call registeredController further while operator is running
- }
+ // call registeredController further while operator is running
+}
```
@@ -676,8 +665,7 @@ configured appropriately so that the `followControllerNamespaceChanges` method r
```java
@ControllerConfiguration
-public class MyReconciler
- implements Reconciler, EventSourceInitializer {
+public class MyReconciler implements Reconciler {
@Override
public Map prepareEventSources(
@@ -688,7 +676,7 @@ public class MyReconciler
.withNamespacesInheritedFromController(context)
.build(), context);
- return EventSourceInitializer.nameEventSources(configMapES);
+ return EventSourceUtils.nameEventSources(configMapES);
}
}
@@ -698,7 +686,7 @@ As seen in the above code snippet, the informer will have the initial namespaces
controller, but also will adjust the target namespaces if it changes for the controller.
See also
-the [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/ec37025a15046d8f409c77616110024bf32c3416/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/changenamespace/ChangeNamespaceTestReconciler.java)
+the [integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/changenamespace)
for this feature.
## Leader Election
@@ -767,8 +755,8 @@ You can use a different implementation by overriding the default one provided by
follows:
```java
-Metrics metrics= …;
-Operator operator = new Operator(client, o -> o.withMetrics());
+Metrics metrics; // initialize your metrics implementation
+Operator operator = new Operator(client, o -> o.withMetrics(metrics));
```
### Micrometer implementation
@@ -784,8 +772,8 @@ To create a `MicrometerMetrics` implementation that behaves how it has historica
instance via:
```java
-MeterRegistry registry= …;
-Metrics metrics=new MicrometerMetrics(registry)
+MeterRegistry registry; // initialize your registry implementation
+Metrics metrics = new MicrometerMetrics(registry);
```
Note, however, that this constructor is deprecated and we encourage you to use the factory methods instead, which either
@@ -801,7 +789,7 @@ basis, deleting the associated meters after 5 seconds when a resource is deleted
MicrometerMetrics.newPerResourceCollectingMicrometerMetricsBuilder(registry)
.withCleanUpDelayInSeconds(5)
.withCleaningThreadNumber(2)
- .build()
+ .build();
```
### Operator SDK metrics
diff --git a/docsy/content/en/docs/getting-started/_index.md b/docs/content/en/docs/getting-started/_index.md
similarity index 100%
rename from docsy/content/en/docs/getting-started/_index.md
rename to docs/content/en/docs/getting-started/_index.md
diff --git a/docsy/content/en/docs/glossary/_index.md b/docs/content/en/docs/glossary/_index.md
similarity index 100%
rename from docsy/content/en/docs/glossary/_index.md
rename to docs/content/en/docs/glossary/_index.md
diff --git a/docsy/content/en/docs/intro-to-operators/_index.md b/docs/content/en/docs/intro-to-operators/_index.md
similarity index 100%
rename from docsy/content/en/docs/intro-to-operators/_index.md
rename to docs/content/en/docs/intro-to-operators/_index.md
diff --git a/docsy/content/en/docs/migration/_index.md b/docs/content/en/docs/migration/_index.md
similarity index 100%
rename from docsy/content/en/docs/migration/_index.md
rename to docs/content/en/docs/migration/_index.md
diff --git a/docsy/content/en/docs/migration/v2-migration.md b/docs/content/en/docs/migration/v2-migration.md
similarity index 98%
rename from docsy/content/en/docs/migration/v2-migration.md
rename to docs/content/en/docs/migration/v2-migration.md
index 4500672308..5b0ef31c45 100644
--- a/docsy/content/en/docs/migration/v2-migration.md
+++ b/docs/content/en/docs/migration/v2-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v1 to v2
-description: Migrating from v1 to v2
layout: docs
permalink: /docs/v2-migration
---
-# Migrating from v1 to v2
-
Version 2 of the framework introduces improvements, features and breaking changes for the APIs both
internal and user facing ones. The migration should be however trivial in most of the cases. For
detailed overview of all major issues until the release of
diff --git a/docsy/content/en/docs/migration/v3-1-migration.md b/docs/content/en/docs/migration/v3-1-migration.md
similarity index 97%
rename from docsy/content/en/docs/migration/v3-1-migration.md
rename to docs/content/en/docs/migration/v3-1-migration.md
index 9a7d9f7a9f..e42c4a206a 100644
--- a/docsy/content/en/docs/migration/v3-1-migration.md
+++ b/docs/content/en/docs/migration/v3-1-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v3 to v3.1
-description: Migrating from v3 to v3.1
layout: docs
permalink: /docs/v3-1-migration
---
-# Migrating from v3 to v3.1
-
## ReconciliationMaxInterval Annotation has been renamed to MaxReconciliationInterval
Associated methods on both the `ControllerConfiguration` class and annotation have also been
diff --git a/docsy/content/en/docs/migration/v3-migration.md b/docs/content/en/docs/migration/v3-migration.md
similarity index 98%
rename from docsy/content/en/docs/migration/v3-migration.md
rename to docs/content/en/docs/migration/v3-migration.md
index 97e844904e..462ab26f9f 100644
--- a/docsy/content/en/docs/migration/v3-migration.md
+++ b/docs/content/en/docs/migration/v3-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v2 to v3
-description: Migrating from v2 to v3
layout: docs
permalink: /docs/v3-migration
---
-# Migrating from v2 to v3
-
Version 3 introduces some breaking changes to APIs, however the migration to these changes should be trivial.
## Reconciler
diff --git a/docsy/content/en/docs/migration/v4-3-migration.md b/docs/content/en/docs/migration/v4-3-migration.md
similarity index 96%
rename from docsy/content/en/docs/migration/v4-3-migration.md
rename to docs/content/en/docs/migration/v4-3-migration.md
index 17d3be70b4..e9fd58c5f8 100644
--- a/docsy/content/en/docs/migration/v4-3-migration.md
+++ b/docs/content/en/docs/migration/v4-3-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v4.2 to v4.3
-description: Migrating from v4.2 to v4.3
layout: docs
permalink: /docs/v4-3-migration
---
-# Migrating from v4.2 to v4.3
-
## Condition API Change
In Workflows the target of the condition was the managed resource itself, not the target dependent resource.
diff --git a/docsy/content/en/docs/migration/v4-4-migration.md b/docs/content/en/docs/migration/v4-4-migration.md
similarity index 98%
rename from docsy/content/en/docs/migration/v4-4-migration.md
rename to docs/content/en/docs/migration/v4-4-migration.md
index c198871f3f..998e6ddf9a 100644
--- a/docsy/content/en/docs/migration/v4-4-migration.md
+++ b/docs/content/en/docs/migration/v4-4-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v4.3 to v4.4
-description: Migrating from v4.3 to v4.4
layout: docs
permalink: /docs/v4-4-migration
---
-# Migrating from v4.3 to v4.4
-
## API changes
### ConfigurationService
diff --git a/docsy/content/en/docs/migration/v4-5-migration.md b/docs/content/en/docs/migration/v4-5-migration.md
similarity index 96%
rename from docsy/content/en/docs/migration/v4-5-migration.md
rename to docs/content/en/docs/migration/v4-5-migration.md
index 0ff08eef13..eff1581d87 100644
--- a/docsy/content/en/docs/migration/v4-5-migration.md
+++ b/docs/content/en/docs/migration/v4-5-migration.md
@@ -1,12 +1,9 @@
---
title: Migrating from v4.4 to v4.5
-description: Migrating from v4.4 to v4.5
layout: docs
permalink: /docs/v4-5-migration
---
-# Migrating from v4.4 to v4.5
-
Version 4.5 introduces improvements related to event handling for Dependent Resources, more precisely the
[caching and event handling](https://javaoperatorsdk.io/docs/dependent-resources#caching-and-event-handling-in-kubernetesdependentresource)
features. As a result the Kubernetes resources managed using
diff --git a/docs/content/en/docs/migration/v5-0-migration.md b/docs/content/en/docs/migration/v5-0-migration.md
new file mode 100644
index 0000000000..31b3c2ca22
--- /dev/null
+++ b/docs/content/en/docs/migration/v5-0-migration.md
@@ -0,0 +1,6 @@
+---
+title: Migrating from v4.7 to v5.0
+description: Migrating from v4.7 to v5.0
+---
+
+For migration to v5 see [this blogpost](../../blog/releases/v5-release.md).
\ No newline at end of file
diff --git a/docsy/content/en/docs/patterns-and-best-practices/_index.md b/docs/content/en/docs/patterns-and-best-practices/_index.md
similarity index 94%
rename from docsy/content/en/docs/patterns-and-best-practices/_index.md
rename to docs/content/en/docs/patterns-and-best-practices/_index.md
index 422f3f7bfe..a2b3b716b6 100644
--- a/docsy/content/en/docs/patterns-and-best-practices/_index.md
+++ b/docs/content/en/docs/patterns-and-best-practices/_index.md
@@ -120,3 +120,15 @@ 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.
+
+## 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`.
+
+```java
+final var overridden = new ConfigurationServiceOverrider(config)
+ .withReconciliationTerminationTimeout(Duration.ofSeconds(5));
+
+final var operator = new Operator(overridden);
+```
diff --git a/docsy/content/en/docs/using-samples/_index.md b/docs/content/en/docs/using-samples/_index.md
similarity index 100%
rename from docsy/content/en/docs/using-samples/_index.md
rename to docs/content/en/docs/using-samples/_index.md
diff --git a/docsy/content/en/docs/workflows/_index.md b/docs/content/en/docs/workflows/_index.md
similarity index 75%
rename from docsy/content/en/docs/workflows/_index.md
rename to docs/content/en/docs/workflows/_index.md
index 5f275c2b9a..19b5fab104 100644
--- a/docsy/content/en/docs/workflows/_index.md
+++ b/docs/content/en/docs/workflows/_index.md
@@ -41,10 +41,33 @@ reconciliation process.
condition holds or not. This is a very useful feature when your operator needs to handle different flavors of the
platform (e.g. OpenShift vs plain Kubernetes) and/or change its behavior based on the availability of optional
resources / features (e.g. CertManager, a specific Ingress controller, etc.).
-
- Activation condition is semi-experimental at the moment, and it has its limitations.
- For example event sources cannot be shared between multiple managed dependent resources which use activation condition.
- The intention is to further improve and explore the possibilities with this approach.
+
+ A generic activation condition is provided out of the box, called
+ [CRDPresentActivationCondition](https://github.com/operator-framework/java-operator-sdk/blob/ba5e33527bf9e3ea0bd33025ccb35e677f9d44b4/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/CRDPresentActivationCondition.java)
+ that will prevent the associated dependent resource from being activated if the Custom Resource Definition associated
+ with the dependent's resource type is not present on the cluster.
+ See related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/crdpresentactivation).
+
+ To have multiple resources of same type with an activation condition is a bit tricky, since you
+ don't want to have multiple `InformerEventSource` for the same type, you have to explicitly
+ name the informer for the Dependent Resource (`@KubernetesDependent(informerConfig = @InformerConfig(name = "configMapInformer"))`)
+ for all resource of same type with activation condition. This will make sure that only one is registered.
+ See details at [low level api](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/EventSourceRetriever.java#L20-L52).
+
+### Result conditions
+
+While simple conditions are usually enough, it might happen you want to convey extra information as a result of the
+evaluation of the conditions (e.g., to report error messages or because the result of the condition evaluation might be
+interesting for other purposes). In this situation, you should implement `DetailedCondition` instead of `Condition` and
+provide an implementation of the `detailedIsMet` method, which allows you to return a more detailed `Result` object via
+which you can provide extra information. The `DetailedCondition.Result` interface provides factory method for your
+convenience but you can also provide your own implementation if required.
+
+You can access the results for conditions from the `WorkflowResult` instance that is returned whenever a workflow is
+evaluated. You can access that result from the `ManagedWorkflowAndDependentResourceContext` accessible from the
+reconciliation `Context`. You can then access individual condition results using the `
+getDependentConditionResult` methods. You can see an example of this
+in [this integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowallfeature/WorkflowAllFeatureReconciler.java).
## Defining Workflows
@@ -70,15 +93,16 @@ will only consider the `ConfigMap` deleted until that post-condition becomes `tr
```java
-@ControllerConfiguration(dependents = {
- @Dependent(name = DEPLOYMENT_NAME, type = DeploymentDependentResource.class,
- readyPostcondition = DeploymentReadyCondition.class),
- @Dependent(type = ConfigMapDependentResource.class,
- reconcilePrecondition = ConfigMapReconcileCondition.class,
- deletePostcondition = ConfigMapDeletePostCondition.class,
- activationCondition = ConfigMapActivationCondition.class,
- dependsOn = DEPLOYMENT_NAME)
+@Workflow(dependents = {
+ @Dependent(name = DEPLOYMENT_NAME, type = DeploymentDependentResource.class,
+ readyPostcondition = DeploymentReadyCondition.class),
+ @Dependent(type = ConfigMapDependentResource.class,
+ reconcilePrecondition = ConfigMapReconcileCondition.class,
+ deletePostcondition = ConfigMapDeletePostCondition.class,
+ activationCondition = ConfigMapActivationCondition.class,
+ dependsOn = DEPLOYMENT_NAME)
})
+@ControllerConfiguration
public class SampleWorkflowReconciler implements Reconciler,
Cleaner {
@@ -120,7 +144,7 @@ page sample):
@ControllerConfiguration(
labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR)
public class WebPageDependentsWorkflowReconciler
- implements Reconciler, ErrorStatusHandler, EventSourceInitializer {
+ implements Reconciler, ErrorStatusHandler {
public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level";
private static final Logger log =
@@ -131,7 +155,7 @@ public class WebPageDependentsWorkflowReconciler
private KubernetesDependentResource serviceDR;
private KubernetesDependentResource ingressDR;
- private Workflow workflow;
+ private final Workflow workflow;
public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) {
initDependentResources(kubernetesClient);
@@ -145,7 +169,7 @@ public class WebPageDependentsWorkflowReconciler
@Override
public Map prepareEventSources(EventSourceContext context) {
- return EventSourceInitializer.nameEventSources(
+ return EventSourceUtils.nameEventSources(
configMapDR.initEventSource(context),
deploymentDR.initEventSource(context),
serviceDR.initEventSource(context),
@@ -198,7 +222,7 @@ demonstrated using examples:
2. Root nodes, i.e. nodes in the graph that do not depend on other nodes are reconciled first,
in a parallel manner.
3. A DR is reconciled if it does not depend on any other DRs, or *ALL* the DRs it depends on are
- reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition,
+ reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition,
then these condition must become `true` before the DR is reconciled.
4. A DR is considered *ready* if it got successfully reconciled and any ready post-condition it
might define is `true`.
@@ -326,12 +350,40 @@ checks that the resource is actually removed or that it, at least, doesn't have
provides such a delete post-condition implementation in the form of
[`KubernetesResourceDeletedCondition`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/KubernetesResourceDeletedCondition.java)
-Also, check usage in an [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java).
+Also, check usage in an [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java).
-In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource`
+In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource`
and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage Collector would delete the resources.
In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement
-`GargageCollected` interface, otherwise the deletion order won't be guaranteed.
+`GargageCollected` interface, otherwise the deletion order won't be guaranteed.
+
+
+## Explicit Managed Workflow Invocation
+
+Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled
+before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration)
+will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are,
+however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for
+example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit
+invocation of managed workflow was therefore introduced to solve these issues.
+
+To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then
+call the `reconcileManagedWorkflow` method from the `
+ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary
+resource reconciler `reconcile` method arguments.
+
+See
+related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitinvocation)
+for more details.
+
+For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly.
+However, if `Cleaner` interface is not implemented, it will be called implicitly.
+See
+related [integration test](https://github.com/operator-framework/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/workflow/workflowexplicitcleanup).
+
+While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do
+so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource
+reconciler, the workflow won't be reconciled at all.
## Notes and Caveats
diff --git a/docsy/content/en/featured-background.jpg b/docs/content/en/featured-background.jpg
similarity index 100%
rename from docsy/content/en/featured-background.jpg
rename to docs/content/en/featured-background.jpg
diff --git a/docsy/content/en/search.md b/docs/content/en/search.md
similarity index 100%
rename from docsy/content/en/search.md
rename to docs/content/en/search.md
diff --git a/docsy/content/fileList.txt b/docs/content/fileList.txt
similarity index 100%
rename from docsy/content/fileList.txt
rename to docs/content/fileList.txt
diff --git a/docsy/docker-compose.yaml b/docs/docker-compose.yaml
similarity index 100%
rename from docsy/docker-compose.yaml
rename to docs/docker-compose.yaml
diff --git a/docsy/docsy.work b/docs/docsy.work
similarity index 100%
rename from docsy/docsy.work
rename to docs/docsy.work
diff --git a/docsy/docsy.work.sum b/docs/docsy.work.sum
similarity index 100%
rename from docsy/docsy.work.sum
rename to docs/docsy.work.sum
diff --git a/docsy/go.mod b/docs/go.mod
similarity index 100%
rename from docsy/go.mod
rename to docs/go.mod
diff --git a/docsy/go.sum b/docs/go.sum
similarity index 100%
rename from docsy/go.sum
rename to docs/go.sum
diff --git a/docsy/hugo.toml b/docs/hugo.toml
similarity index 99%
rename from docsy/hugo.toml
rename to docs/hugo.toml
index 520a1cb358..b4535c08af 100644
--- a/docsy/hugo.toml
+++ b/docs/hugo.toml
@@ -110,7 +110,7 @@ github_repo = "https://github.com/operator-framework/java-operator-sdk/"
# github_project_repo = "https://github.com/operator-framework/java-operator-sdk"
# Specify a value here if your content directory is not in your repo's root directory
-github_subdir = "docsy"
+github_subdir = "docs"
# Uncomment this if your GitHub repo does not have "main" as the default branch,
# or specify a new value if you want to reference another branch in your GitHub links
diff --git a/docsy/layouts/404.html b/docs/layouts/404.html
similarity index 100%
rename from docsy/layouts/404.html
rename to docs/layouts/404.html
diff --git a/docsy/layouts/_default/_markup/render-heading.html b/docs/layouts/_default/_markup/render-heading.html
similarity index 100%
rename from docsy/layouts/_default/_markup/render-heading.html
rename to docs/layouts/_default/_markup/render-heading.html
diff --git a/docsy/netlify.toml b/docs/netlify.toml
similarity index 100%
rename from docsy/netlify.toml
rename to docs/netlify.toml
diff --git a/docsy/package.json b/docs/package.json
similarity index 100%
rename from docsy/package.json
rename to docs/package.json
diff --git a/docsy/static/favicons/favicon.ico b/docs/static/favicons/favicon.ico
similarity index 100%
rename from docsy/static/favicons/favicon.ico
rename to docs/static/favicons/favicon.ico
diff --git a/docsy/static/images/architecture.svg b/docs/static/images/architecture.svg
similarity index 100%
rename from docsy/static/images/architecture.svg
rename to docs/static/images/architecture.svg
diff --git a/docsy/static/images/cncf_logo.png b/docs/static/images/cncf_logo.png
similarity index 100%
rename from docsy/static/images/cncf_logo.png
rename to docs/static/images/cncf_logo.png
diff --git a/docsy/static/images/cs-logo.svg b/docs/static/images/cs-logo.svg
similarity index 100%
rename from docsy/static/images/cs-logo.svg
rename to docs/static/images/cs-logo.svg
diff --git a/docsy/static/images/full-logo-white.svg b/docs/static/images/full-logo-white.svg
similarity index 100%
rename from docsy/static/images/full-logo-white.svg
rename to docs/static/images/full-logo-white.svg
diff --git a/docsy/static/images/full_logo.png b/docs/static/images/full_logo.png
similarity index 100%
rename from docsy/static/images/full_logo.png
rename to docs/static/images/full_logo.png
diff --git a/docsy/static/images/red-hat.webp b/docs/static/images/red-hat.webp
similarity index 100%
rename from docsy/static/images/red-hat.webp
rename to docs/static/images/red-hat.webp
diff --git a/micrometer-support/pom.xml b/micrometer-support/pom.xml
index b97535ef63..a24acef29e 100644
--- a/micrometer-support/pom.xml
+++ b/micrometer-support/pom.xml
@@ -1,22 +1,15 @@
-
+
+ 4.0.0
- java-operator-sdk
io.javaoperatorsdk
- 4.9.8-SNAPSHOT
+ java-operator-sdk
+ 5.0.0-SNAPSHOT
- 4.0.0
micrometer-support
Operator SDK - Micrometer Support
-
- 11
- 11
-
-
io.micrometer
@@ -42,9 +35,9 @@
test
- org.awaitility
- awaitility
- test
+ org.awaitility
+ awaitility
+ test
io.javaoperatorsdk
@@ -59,4 +52,4 @@
-
\ No newline at end of file
+
diff --git a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java
index b819bd0ca3..07106d9b3c 100644
--- a/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java
+++ b/micrometer-support/src/main/java/io/javaoperatorsdk/operator/monitoring/micrometer/MicrometerMetrics.java
@@ -59,20 +59,6 @@ public class MicrometerMetrics implements Metrics {
private final Map gauges = new ConcurrentHashMap<>();
private final Cleaner cleaner;
- /**
- * Creates a default micrometer-based Metrics implementation, collecting metrics on a per resource
- * basis and not dealing with cleaning these after these resources are deleted. Note that this
- * probably will change in a future release. If you want more control over what the implementation
- * actually does, please use the static factory methods instead.
- *
- * @param registry the {@link MeterRegistry} instance to use for metrics recording
- * @deprecated Use the factory methods / builders instead
- */
- @Deprecated
- public MicrometerMetrics(MeterRegistry registry) {
- this(registry, Cleaner.NOOP, true);
- }
-
/**
* Creates a MicrometerMetrics instance configured to not collect per-resource metrics, just
* aggregates per resource **type**
diff --git a/operator-framework-bom/pom.xml b/operator-framework-bom/pom.xml
index 9999dfca1f..35d8da7cd2 100644
--- a/operator-framework-bom/pom.xml
+++ b/operator-framework-bom/pom.xml
@@ -1,142 +1,170 @@
-
- 4.0.0
+
+ 4.0.0
- io.javaoperatorsdk
- operator-framework-bom
- 4.9.8-SNAPSHOT
- Operator SDK - Bill of Materials
- pom
- Java SDK for implementing Kubernetes operators
- https://github.com/operator-framework/java-operator-sdk
+ io.javaoperatorsdk
+ operator-framework-bom
+ 5.0.0-SNAPSHOT
+ pom
+ Operator SDK - Bill of Materials
+ Java SDK for implementing Kubernetes operators
+ https://github.com/operator-framework/java-operator-sdk
-
-
- Apache 2 License
- https://www.apache.org/licenses/LICENSE-2.0.html
-
-
-
-
- Attila Meszaros
- csviri@gmail.com
-
-
- Christophe Laprun
- claprun@redhat.com
-
-
+
+
+ Apache 2 License
+ https://www.apache.org/licenses/LICENSE-2.0.html
+
+
+
+
+ Attila Meszaros
+ csviri@gmail.com
+
+
+ Christophe Laprun
+ claprun@redhat.com
+
+
-
- scm:git:git://github.com/java-operator-sdk/java-operator-sdk.git
- scm:git:git@github.com/java-operator-sdk/java-operator-sdk.git
- https://github.com/operator-framework/java-operator-sdk/tree/master
-
+
+ scm:git:git://github.com/java-operator-sdk/java-operator-sdk.git
+ scm:git:git@github.com/java-operator-sdk/java-operator-sdk.git
+ https://github.com/operator-framework/java-operator-sdk/tree/master
+
-
-
-
- io.javaoperatorsdk
- operator-framework-core
- ${project.version}
-
-
- io.javaoperatorsdk
- operator-framework
- ${project.version}
-
-
- io.javaoperatorsdk
- micrometer-support
- ${project.version}
-
-
- io.javaoperatorsdk
- operator-framework-junit-5
- ${project.version}
-
-
-
+
+
+ ossrh
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
-
- 1.7.0
- 3.2.7
- 3.3.1
- 3.11.1
-
+
+ 1.7.0
+ 3.2.7
+ 3.3.1
+ 3.11.1
+ 2.43.0
+
-
-
- release
-
-
-
- org.apache.maven.plugins
- maven-javadoc-plugin
- ${maven-javadoc-plugin.version}
-
-
- attach-javadocs
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-source-plugin
- ${maven-source-plugin.version}
-
-
- attach-sources
-
- jar
-
-
-
-
-
- org.apache.maven.plugins
- maven-gpg-plugin
- ${maven-gpg-plugin.version}
-
-
- sign-artifacts
- verify
-
- sign
-
-
-
- --pinentry-mode
- loopback
-
-
-
-
-
-
- org.sonatype.plugins
- nexus-staging-maven-plugin
- ${nexus-staging-maven-plugin.version}
- true
-
- ossrh
- https://oss.sonatype.org/
- true
-
-
-
-
-
-
+
+
+
+ io.javaoperatorsdk
+ operator-framework-core
+ ${project.version}
+
+
+ io.javaoperatorsdk
+ operator-framework
+ ${project.version}
+
+
+ io.javaoperatorsdk
+ micrometer-support
+ ${project.version}
+
+
+ io.javaoperatorsdk
+ operator-framework-junit-5
+ ${project.version}
+
+
+
-
-
- ossrh
- https://oss.sonatype.org/content/repositories/snapshots
-
-
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ ${spotless.version}
+
+
+
+ pom.xml
+ ./**/pom.xml
+
+
+
+
+
+ contributing/eclipse-google-style.xml
+
+
+ contributing/eclipse.importorder
+
+
+
+
+
+
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ ${maven-gpg-plugin.version}
+
+
+ sign-artifacts
+
+ sign
+
+ verify
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ ${nexus-staging-maven-plugin.version}
+ true
+
+ ossrh
+ https://oss.sonatype.org/
+ true
+
+
+
+
+
+
diff --git a/operator-framework-core/pom.xml b/operator-framework-core/pom.xml
index 1c48abc3f3..76a6169a3f 100644
--- a/operator-framework-core/pom.xml
+++ b/operator-framework-core/pom.xml
@@ -1,70 +1,23 @@
-
+
4.0.0
io.javaoperatorsdk
java-operator-sdk
- 4.9.8-SNAPSHOT
+ 5.0.0-SNAPSHOT
../pom.xml
operator-framework-core
+ jar
Operator SDK - Framework - Core
Core framework for implementing Kubernetes operators
- jar
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
-
- io.github.git-commit-id
- git-commit-id-maven-plugin
- ${git-commit-id-maven-plugin.version}
-
-
- get-the-git-infos
-
- revision
-
- initialize
-
-
-
- true
- ${project.build.outputDirectory}/version.properties
-
-
- ^git.build.time$
- ^git.commit.id.(abbrev|full)$
- git.branch
-
- full
-
-
-
- org.codehaus.mojo
- templating-maven-plugin
- 3.0.0
-
-
- filtering-java-templates
-
- filter-sources
-
-
-
-
-
-
-
+
+ io.github.java-diff-utils
+ java-diff-utils
+
io.fabric8
kubernetes-client
@@ -81,7 +34,7 @@
org.apache.logging.log4j
- log4j-slf4j-impl
+ log4j-slf4j2-impl
test
@@ -127,4 +80,51 @@
test
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+
+ io.github.git-commit-id
+ git-commit-id-maven-plugin
+ ${git-commit-id-maven-plugin.version}
+
+ true
+ ${project.build.outputDirectory}/version.properties
+
+ ^git.build.time$
+ ^git.commit.id.(abbrev|full)$
+ git.branch
+
+ full
+
+
+
+ get-the-git-infos
+
+ revision
+
+ initialize
+
+
+
+
+ org.codehaus.mojo
+ templating-maven-plugin
+ 3.0.0
+
+
+ filtering-java-templates
+
+ filter-sources
+
+
+
+
+
+
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java
index 1cb46bafab..8f33036b74 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/BuilderUtils.java
@@ -10,7 +10,7 @@ public final class BuilderUtils {
// prevent instantiation of util class
private BuilderUtils() {}
- public static final B newBuilder(Class builderType, T item) {
+ public static B newBuilder(Class builderType, T item) {
Class builderTargetType = builderTargetType(builderType);
try {
Constructor constructor = builderType.getDeclaredConstructor(builderTargetType);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java
index e90070d3c2..a755e4db7e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ControllerManager.java
@@ -89,4 +89,3 @@ synchronized int size() {
return controllers.size();
}
}
-
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/LeaderElectionManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/LeaderElectionManager.java
index d0af5ee441..d6ee17a383 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/LeaderElectionManager.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/LeaderElectionManager.java
@@ -102,11 +102,15 @@ private void startLeading() {
}
private void stopLeading() {
- log.info("Stopped leading for identity: {}. Exiting.", identity);
- // When leader stops leading the process ends immediately to prevent multiple reconciliations
- // running parallel.
- // Note that some reconciliations might run for a very long time.
- System.exit(1);
+ if (configurationService.getLeaderElectionConfiguration().orElseThrow().isExitOnStopLeading()) {
+ log.info("Stopped leading for identity: {}. Exiting.", identity);
+ // When leader stops leading the process ends immediately to prevent multiple reconciliations
+ // running parallel.
+ // Note that some reconciliations might run for a very long time.
+ System.exit(1);
+ } else {
+ log.info("Stopped leading, configured not to exit");
+ }
}
private String identity(LeaderElectionConfiguration config) {
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 3fc01e1f64..9680bc7e8d 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
@@ -35,7 +35,7 @@ public Operator() {
}
Operator(KubernetesClient kubernetesClient) {
- this(kubernetesClient, null);
+ this(initConfigurationService(kubernetesClient, null));
}
/**
@@ -63,19 +63,7 @@ public Operator(ConfigurationService configurationService) {
* {@link ConfigurationService} values
*/
public Operator(Consumer overrider) {
- this(null, overrider);
- }
-
- /**
- * @param client client to use to all Kubernetes related operations
- * @param overrider a {@link ConfigurationServiceOverrider} consumer used to override the default
- * {@link ConfigurationService} values
- * @deprecated Use {@link Operator#Operator(Consumer)} instead, passing your custom client with
- * {@link ConfigurationServiceOverrider#withKubernetesClient(KubernetesClient)}
- */
- @Deprecated(since = "4.4.0")
- public Operator(KubernetesClient client, Consumer overrider) {
- this(initConfigurationService(client, overrider));
+ this(initConfigurationService(null, overrider));
}
private static ConfigurationService initConfigurationService(KubernetesClient client,
@@ -98,17 +86,6 @@ private static ConfigurationService initConfigurationService(KubernetesClient cl
return ConfigurationService.newOverriddenConfigurationService(overrider);
}
- /**
- * Uses {@link ConfigurationService#getTerminationTimeoutSeconds()} for graceful shutdown timeout
- *
- * @deprecated use the overloaded version with graceful shutdown timeout parameter.
- *
- */
- @Deprecated(forRemoval = true)
- public void installShutdownHook() {
- installShutdownHook(Duration.ofSeconds(configurationService.getTerminationTimeoutSeconds()));
- }
-
/**
* Adds a shutdown hook that automatically calls {@link #stop()} when the app shuts down. Note
* that graceful shutdown is usually not needed, but some {@link Reconciler} implementations might
@@ -120,9 +97,10 @@ public void installShutdownHook() {
* @param gracefulShutdownTimeout timeout to wait for executor threads to complete actual
* reconciliations
*/
+ @SuppressWarnings("unused")
public void installShutdownHook(Duration gracefulShutdownTimeout) {
if (!leaderElectionManager.isLeaderElectionEnabled()) {
- Runtime.getRuntime().addShutdownHook(new Thread(() -> stop(gracefulShutdownTimeout)));
+ Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
} else {
log.warn("Leader election is on, shutdown hook will not be installed.");
}
@@ -167,15 +145,18 @@ public synchronized void start() {
}
}
- public void stop(Duration gracefulShutdownTimeout) throws OperatorException {
+ @Override
+ public void stop() throws OperatorException {
+ Duration reconciliationTerminationTimeout =
+ configurationService.reconciliationTerminationTimeout();
if (!started) {
return;
}
- log.info(
- "Operator SDK {} is shutting down...", configurationService.getVersion().getSdkVersion());
+ log.info("Operator SDK {} is shutting down...",
+ configurationService.getVersion().getSdkVersion());
controllerManager.stop();
- configurationService.getExecutorServiceManager().stop(gracefulShutdownTimeout);
+ configurationService.getExecutorServiceManager().stop(reconciliationTerminationTimeout);
leaderElectionManager.stop();
if (configurationService.closeClientOnStop()) {
getKubernetesClient().close();
@@ -184,11 +165,6 @@ public void stop(Duration gracefulShutdownTimeout) throws OperatorException {
started = false;
}
- @Override
- public void stop() throws OperatorException {
- stop(Duration.ZERO);
- }
-
/**
* Add a registration requests for the specified reconciler with this operator. The effective
* registration of the reconciler is delayed till the operator is started.
@@ -237,8 +213,9 @@ public RegisteredController
register(Reconciler
re
controllerManager.add(controller);
- final var watchedNS = configuration.watchAllNamespaces() ? "[all namespaces]"
- : configuration.getEffectiveNamespaces();
+ final var informerConfig = configuration.getInformerConfig();
+ final var watchedNS = informerConfig.watchAllNamespaces() ? "[all namespaces]"
+ : informerConfig.getEffectiveNamespaces(configuration);
log.info(
"Registered reconciler: '{}' for resource: '{}' for namespace(s): {}",
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
index fec5c4e61e..c2241e4bbb 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java
@@ -9,7 +9,6 @@
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.builder.Builder;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
@@ -126,8 +125,7 @@ public static boolean specsEqual(HasMetadata r1, HasMetadata r2) {
// will be replaced with: https://github.com/fabric8io/kubernetes-client/issues/3816
public static Object getSpec(HasMetadata resource) {
// optimize CustomResource case
- if (resource instanceof CustomResource) {
- CustomResource cr = (CustomResource) resource;
+ if (resource instanceof CustomResource cr) {
return cr.getSpec();
}
@@ -142,8 +140,7 @@ public static Object getSpec(HasMetadata resource) {
@SuppressWarnings("unchecked")
public static Object setSpec(HasMetadata resource, Object spec) {
// optimize CustomResource case
- if (resource instanceof CustomResource) {
- CustomResource cr = (CustomResource) resource;
+ if (resource instanceof CustomResource cr) {
cr.setSpec(spec);
return null;
}
@@ -191,8 +188,7 @@ public static void handleKubernetesClientException(Exception e, String resourceT
throw ((MissingCRDException) e);
}
- if (e instanceof KubernetesClientException) {
- KubernetesClientException ke = (KubernetesClientException) e;
+ if (e instanceof KubernetesClientException ke) {
// only throw MissingCRDException if the 404 error occurs on the target CRD
if (404 == ke.getCode() &&
(resourceTypeName.equals(ke.getFullResourceName())
@@ -217,7 +213,7 @@ private static boolean matchesResourceType(String resourceTypeName,
group = group.substring(0, group.length() - 1);
}
final var segments = Arrays.stream(group.split("/")).filter(Predicate.not(String::isEmpty))
- .collect(Collectors.toUnmodifiableList());
+ .toList();
if (segments.size() != 3) {
return false;
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
index 961e519d62..ee2f4d447e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/RuntimeInfo.java
@@ -7,6 +7,7 @@
import io.javaoperatorsdk.operator.health.EventSourceHealthIndicator;
import io.javaoperatorsdk.operator.health.InformerWrappingEventSourceHealthIndicator;
+import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
/**
* RuntimeInfo in general is available when operator is fully started. You can use "isStarted" to
@@ -64,9 +65,7 @@ public Map> unhealthyEventSource
/**
* @return Aggregated Map with controller related event sources that wraps an informer. Thus,
- * either a
- * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource}
- * or an
+ * either a {@link ControllerEventSource} or an
* {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource}.
*/
public Map> unhealthyInformerWrappingEventSourceHealthIndicator() {
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java
deleted file mode 100644
index 069953a32d..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package io.javaoperatorsdk.operator.api;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.client.CustomResource;
-import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
-
-/**
- * If the custom resource's status implements this interface, the observed generation will be
- * automatically handled. The last observed generation will be updated on status.
- *
- * In order for this automatic handling to work the status object returned by
- * {@link CustomResource#getStatus()} should not be null.
- *
- * The observed generation is updated even when {@link UpdateControl#noUpdate()} or
- * {@link UpdateControl#updateResource(HasMetadata)} is called. Although those results call normally
- * does not result in a status update, there will be a subsequent status update Kubernetes API call
- * in this case.
- *
- * @see ObservedGenerationAwareStatus
- */
-public interface ObservedGenerationAware {
-
- void setObservedGeneration(Long generation);
-
- Long getObservedGeneration();
-
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java
deleted file mode 100644
index d2048c9513..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package io.javaoperatorsdk.operator.api;
-
-/**
- * A helper base class for status sub-resources classes to extend to support generate awareness.
- */
-public class ObservedGenerationAwareStatus implements ObservedGenerationAware {
-
- private Long observedGeneration;
-
- @Override
- public void setObservedGeneration(Long generation) {
- this.observedGeneration = generation;
- }
-
- @Override
- public Long getObservedGeneration() {
- return observedGeneration;
- }
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
index 1a54dbafc7..f7ed42c577 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractConfigurationService.java
@@ -10,6 +10,9 @@
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+/**
+ * An abstract implementation of {@link ConfigurationService} meant to ease custom implementations
+ */
@SuppressWarnings("rawtypes")
public class AbstractConfigurationService implements ConfigurationService {
private final Map configurations = new ConcurrentHashMap<>();
@@ -18,11 +21,11 @@ public class AbstractConfigurationService implements ConfigurationService {
private Cloner cloner;
private ExecutorServiceManager executorServiceManager;
- public AbstractConfigurationService(Version version) {
+ protected AbstractConfigurationService(Version version) {
this(version, null);
}
- public AbstractConfigurationService(Version version, Cloner cloner) {
+ protected AbstractConfigurationService(Version version, Cloner cloner) {
this(version, cloner, null, null);
}
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 d908f52ebe..4204cb6faf 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
@@ -7,38 +7,34 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
-import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.KubernetesClient;
-import io.fabric8.kubernetes.client.informers.cache.ItemStore;
-import io.javaoperatorsdk.operator.OperatorException;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.Utils.Configurator;
+import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationResolver;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
+import io.javaoperatorsdk.operator.api.reconciler.Workflow;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.retry.Retry;
import static io.javaoperatorsdk.operator.api.config.ControllerConfiguration.CONTROLLER_NAME_AS_FIELD_MANAGER;
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
public class BaseConfigurationService extends AbstractConfigurationService {
private static final String LOGGER_NAME = "Default ConfigurationService implementation";
private static final Logger logger = LoggerFactory.getLogger(LOGGER_NAME);
+ private static final ResourceClassResolver DEFAULT_RESOLVER = new DefaultResourceClassResolver();
public BaseConfigurationService(Version version) {
this(version, null);
@@ -56,6 +52,98 @@ public BaseConfigurationService() {
this(Utils.VERSION);
}
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static List dependentResources(
+ Workflow annotation,
+ ControllerConfiguration> controllerConfiguration) {
+ final var dependents = annotation.dependents();
+
+
+ if (dependents == null || dependents.length == 0) {
+ return Collections.emptyList();
+ }
+
+ final var specsMap = new LinkedHashMap(dependents.length);
+ for (Dependent dependent : dependents) {
+ final Class extends DependentResource> dependentType = dependent.type();
+
+ final var dependentName = getName(dependent.name(), dependentType);
+ var spec = specsMap.get(dependentName);
+ if (spec != null) {
+ throw new IllegalArgumentException(
+ "A DependentResource named '" + dependentName + "' already exists: " + spec);
+ }
+
+ final var name = controllerConfiguration.getName();
+
+ var eventSourceName = dependent.useEventSourceWithName();
+ eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
+ final var context = Utils.contextFor(name, dependentType, null);
+ spec = new DependentResourceSpec(dependentType, dependentName,
+ Set.of(dependent.dependsOn()),
+ Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
+ Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
+ Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
+ Utils.instantiate(dependent.activationCondition(), Condition.class, context),
+ eventSourceName);
+ specsMap.put(dependentName, spec);
+
+ // extract potential configuration
+ DependentResourceConfigurationResolver.configureSpecFromConfigured(spec,
+ controllerConfiguration,
+ dependentType);
+
+ specsMap.put(dependentName, spec);
+ }
+
+ return specsMap.values().stream().toList();
+ }
+
+ @SuppressWarnings("unchecked")
+ private static T valueOrDefaultFromAnnotation(
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
+ Function mapper,
+ String defaultMethodName) {
+ try {
+ if (controllerConfiguration == null) {
+ return (T) io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
+ .getDeclaredMethod(defaultMethodName).getDefaultValue();
+ } else {
+ return mapper.apply(controllerConfiguration);
+ }
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static String getName(String name, Class extends DependentResource> dependentType) {
+ if (name.isBlank()) {
+ name = DependentResource.defaultNameFor(dependentType);
+ }
+ return name;
+ }
+
+ @SuppressWarnings("unused")
+ private static Configurator configuratorFor(Class instanceType,
+ Class extends Reconciler>> reconcilerClass) {
+ return instance -> configureFromAnnotatedReconciler(instance, reconcilerClass);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static void configureFromAnnotatedReconciler(Object instance,
+ Class extends Reconciler>> reconcilerClass) {
+ if (instance instanceof AnnotationConfigurable configurable) {
+ final Class extends Annotation> configurationClass =
+ (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
+ instance.getClass(), AnnotationConfigurable.class);
+ final var configAnnotation = reconcilerClass.getAnnotation(configurationClass);
+ if (configAnnotation != null) {
+ configurable.initFrom(configAnnotation);
+ }
+ }
+ }
+
@Override
protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) {
logger.warn("Configuration for reconciler '{}' was not found. {}", reconcilerKey,
@@ -95,39 +183,84 @@ public ControllerConfiguration getConfigurationFor(
return config;
}
+ /**
+ * Override if a different class resolution is needed
+ *
+ * @return the custom {@link ResourceClassResolver} implementation to use
+ */
+ protected ResourceClassResolver getResourceClassResolver() {
+ return DEFAULT_RESOLVER;
+ }
+
@SuppressWarnings({"unchecked", "rawtypes"})
protected ControllerConfiguration
configFor(Reconciler
reconciler) {
- final var annotation = reconciler.getClass().getAnnotation(
+ final Class extends Reconciler
> reconcilerClass =
+ (Class extends Reconciler
>) reconciler.getClass();
+ final var controllerAnnotation = reconcilerClass.getAnnotation(
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class);
- if (annotation == null) {
- throw new OperatorException(
- "Missing mandatory @"
- + io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.class
- .getSimpleName()
- +
- " annotation for reconciler: " + reconciler);
+
+ ResolvedControllerConfiguration
config =
+ controllerConfiguration(reconcilerClass, controllerAnnotation);
+
+ final var workflowAnnotation = reconcilerClass.getAnnotation(
+ io.javaoperatorsdk.operator.api.reconciler.Workflow.class);
+ if (workflowAnnotation != null) {
+ final var specs = dependentResources(workflowAnnotation, config);
+ WorkflowSpec workflowSpec = new WorkflowSpec() {
+ @Override
+ public List getDependentResourceSpecs() {
+ return specs;
+ }
+
+ @Override
+ public boolean isExplicitInvocation() {
+ return workflowAnnotation.explicitInvocation();
+ }
+
+ @Override
+ public boolean handleExceptionsInReconciler() {
+ return workflowAnnotation.handleExceptionsInReconciler();
+ }
+
+ };
+ config.setWorkflowSpec(workflowSpec);
}
- Class> reconcilerClass = (Class>) reconciler.getClass();
- final var resourceClass = getResourceClassResolver().getResourceClass(reconcilerClass);
- final var name = ReconcilerUtils.getNameFor(reconciler);
- final var generationAware = valueOrDefault(
+ return config;
+ }
+
+ @SuppressWarnings({"unchecked"})
+ private ResolvedControllerConfiguration
controllerConfiguration(
+ Class extends Reconciler
> reconcilerClass,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
+ final var resourceClass = getResourceClassResolver().getPrimaryResourceClass(reconcilerClass);
+
+ final var name = ReconcilerUtils.getNameFor(reconcilerClass);
+ final var generationAware = valueOrDefaultFromAnnotation(
annotation,
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::generationAwareEventProcessing,
- true);
+ "generationAwareEventProcessing");
final var associatedReconcilerClass =
- ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconciler.getClass());
+ ResolvedControllerConfiguration.getAssociatedReconcilerClassName(reconcilerClass);
final var context = Utils.contextFor(name);
- final Class extends Retry> retryClass = annotation.retry();
+ final Class extends Retry> retryClass =
+ valueOrDefaultFromAnnotation(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::retry,
+ "retry");
final var retry = Utils.instantiateAndConfigureIfNeeded(retryClass, Retry.class,
- context, configuratorFor(Retry.class, reconciler));
+ context, configuratorFor(Retry.class, reconcilerClass));
- final Class extends RateLimiter> rateLimiterClass = annotation.rateLimiter();
+ @SuppressWarnings("rawtypes")
+ final Class extends RateLimiter> rateLimiterClass = valueOrDefaultFromAnnotation(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::rateLimiter,
+ "rateLimiter");
final var rateLimiter = Utils.instantiateAndConfigureIfNeeded(rateLimiterClass,
- RateLimiter.class, context, configuratorFor(RateLimiter.class, reconciler));
+ RateLimiter.class, context, configuratorFor(RateLimiter.class, reconcilerClass));
- final var reconciliationInterval = annotation.maxReconciliationInterval();
+ final var reconciliationInterval = valueOrDefaultFromAnnotation(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::maxReconciliationInterval,
+ "maxReconciliationInterval");
long interval = -1;
TimeUnit timeUnit = null;
if (reconciliationInterval != null && reconciliationInterval.interval() > 0) {
@@ -135,107 +268,29 @@ protected
ControllerConfiguration
configFor(Reconcile
timeUnit = reconciliationInterval.timeUnit();
}
+ var fieldManager = valueOrDefaultFromAnnotation(annotation,
+ io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::fieldManager,
+ "fieldManager");
final var dependentFieldManager =
- annotation.fieldManager().equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
- : annotation.fieldManager();
+ fieldManager.equals(CONTROLLER_NAME_AS_FIELD_MANAGER) ? name
+ : fieldManager;
- final var informerListLimit =
- annotation.informerListLimit() == Constants.NO_LONG_VALUE_SET ? null
- : annotation.informerListLimit();
+ InformerConfiguration
informerConfig = InformerConfiguration.builder(resourceClass)
+ .initFromAnnotation(annotation != null ? annotation.informer() : null, context)
+ .buildForController();
- final var config = new ResolvedControllerConfiguration
(
- resourceClass, name, generationAware,
+ return new ResolvedControllerConfiguration
(
+ name, generationAware,
associatedReconcilerClass, retry, rateLimiter,
ResolvedControllerConfiguration.getMaxReconciliationInterval(interval, timeUnit),
- Utils.instantiate(annotation.onAddFilter(), OnAddFilter.class, context),
- Utils.instantiate(annotation.onUpdateFilter(), OnUpdateFilter.class, context),
- Utils.instantiate(annotation.genericFilter(), GenericFilter.class, context),
- Set.of(valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::namespaces,
- DEFAULT_NAMESPACES_SET.toArray(String[]::new))),
- valueOrDefault(annotation,
+ valueOrDefaultFromAnnotation(annotation,
io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::finalizerName,
- Constants.NO_VALUE_SET),
- valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::labelSelector,
- Constants.NO_VALUE_SET),
+ "finalizerName"),
null,
- Utils.instantiate(annotation.itemStore(), ItemStore.class, context), dependentFieldManager,
- this, informerListLimit);
-
- ResourceEventFilter
answer = deprecatedEventFilter(annotation);
- config.setEventFilter(answer != null ? answer : ResourceEventFilters.passthrough());
-
- List specs = dependentResources(annotation, config);
- config.setDependentResources(specs);
-
- return config;
+ dependentFieldManager,
+ this, informerConfig);
}
- @SuppressWarnings("unchecked")
- private static ResourceEventFilter
deprecatedEventFilter(
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation) {
- ResourceEventFilter
answer = null;
-
- Class>[] filterTypes =
- (Class>[]) valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::eventFilters,
- new Object[] {});
- for (var filterType : filterTypes) {
- try {
- ResourceEventFilter filter = filterType.getConstructor().newInstance();
-
- if (answer == null) {
- answer = filter;
- } else {
- answer = answer.and(filter);
- }
- } catch (Exception e) {
- throw new IllegalArgumentException(e);
- }
- }
- return answer;
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static List dependentResources(
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration annotation,
- ControllerConfiguration> parent) {
- final var dependents =
- valueOrDefault(annotation,
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration::dependents,
- new Dependent[] {});
- if (dependents.length == 0) {
- return Collections.emptyList();
- }
-
- final var specsMap = new LinkedHashMap(dependents.length);
- for (Dependent dependent : dependents) {
- final Class extends DependentResource> dependentType = dependent.type();
-
- final var dependentName = getName(dependent.name(), dependentType);
- var spec = specsMap.get(dependentName);
- if (spec != null) {
- throw new IllegalArgumentException(
- "A DependentResource named '" + dependentName + "' already exists: " + spec);
- }
-
- final var name = parent.getName();
-
- var eventSourceName = dependent.useEventSourceWithName();
- eventSourceName = Constants.NO_VALUE_SET.equals(eventSourceName) ? null : eventSourceName;
- final var context = Utils.contextFor(name, dependentType, null);
- spec = new DependentResourceSpec(dependentType, dependentName,
- Set.of(dependent.dependsOn()),
- Utils.instantiate(dependent.readyPostcondition(), Condition.class, context),
- Utils.instantiate(dependent.reconcilePrecondition(), Condition.class, context),
- Utils.instantiate(dependent.deletePostcondition(), Condition.class, context),
- Utils.instantiate(dependent.activationCondition(), Condition.class, context),
- eventSourceName);
- specsMap.put(dependentName, spec);
- }
- return specsMap.values().stream().collect(Collectors.toUnmodifiableList());
- }
protected boolean createIfNeeded() {
return true;
@@ -245,43 +300,4 @@ protected boolean createIfNeeded() {
public boolean checkCRDAndValidateLocalModel() {
return Utils.shouldCheckCRDAndValidateLocalModel();
}
-
- private static T valueOrDefault(
- io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration controllerConfiguration,
- Function mapper,
- T defaultValue) {
- if (controllerConfiguration == null) {
- return defaultValue;
- } else {
- return mapper.apply(controllerConfiguration);
- }
- }
-
- @SuppressWarnings("rawtypes")
- private static String getName(String name, Class extends DependentResource> dependentType) {
- if (name.isBlank()) {
- name = DependentResource.defaultNameFor(dependentType);
- }
- return name;
- }
-
- @SuppressWarnings("unused")
- private static Configurator configuratorFor(Class instanceType,
- Reconciler> reconciler) {
- return instance -> configureFromAnnotatedReconciler(instance, reconciler);
- }
-
- @SuppressWarnings({"unchecked", "rawtypes"})
- private static void configureFromAnnotatedReconciler(Object instance, Reconciler> reconciler) {
- if (instance instanceof AnnotationConfigurable) {
- AnnotationConfigurable configurable = (AnnotationConfigurable) instance;
- final Class extends Annotation> configurationClass =
- (Class extends Annotation>) Utils.getFirstTypeArgumentFromSuperClassOrInterface(
- instance.getClass(), AnnotationConfigurable.class);
- final var configAnnotation = reconciler.getClass().getAnnotation(configurationClass);
- if (configAnnotation != null) {
- configurable.initFrom(configAnnotation);
- }
- }
- }
}
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 111ef03b1b..14a13bf0b6 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
@@ -20,6 +20,7 @@
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
@@ -27,6 +28,7 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.ResourceUpdaterMatcher;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory;
+import io.javaoperatorsdk.operator.processing.event.source.controller.ControllerEventSource;
/** An interface from which to retrieve configuration information. */
public interface ConfigurationService {
@@ -34,18 +36,79 @@ public interface ConfigurationService {
Logger log = LoggerFactory.getLogger(ConfigurationService.class);
int DEFAULT_MAX_CONCURRENT_REQUEST = 512;
+ /**
+ * The default numbers of concurrent reconciliations
+ */
+ int DEFAULT_RECONCILIATION_THREADS_NUMBER = 50;
+ /**
+ * The default number of threads used to process dependent workflows
+ */
+ int DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER = DEFAULT_RECONCILIATION_THREADS_NUMBER;
+
+ /**
+ * Creates a new {@link ConfigurationService} instance used to configure an
+ * {@link io.javaoperatorsdk.operator.Operator} instance, starting from the specified base
+ * configuration and overriding specific aspects according to the provided
+ * {@link ConfigurationServiceOverrider} instance.
+ *
+ *
+ * NOTE: This overriding mechanism should only be used before creating
+ * your Operator instance as the configuration service is set at creation time and cannot be
+ * subsequently changed. As a result, overriding values this way after the Operator has been
+ * configured will not take effect.
+ *
+ *
+ * @param baseConfiguration the {@link ConfigurationService} to start from
+ * @param overrider the {@link ConfigurationServiceOverrider} used to change the values provided
+ * by the base configuration
+ * @return a new {@link ConfigurationService} starting from the configuration provided as base but
+ * with overridden values.
+ */
+ static ConfigurationService newOverriddenConfigurationService(
+ ConfigurationService baseConfiguration,
+ Consumer overrider) {
+ if (overrider != null) {
+ final var toOverride = new ConfigurationServiceOverrider(baseConfiguration);
+ overrider.accept(toOverride);
+ return toOverride.build();
+ }
+ return baseConfiguration;
+ }
+
+ /**
+ * Creates a new {@link ConfigurationService} instance used to configure an
+ * {@link io.javaoperatorsdk.operator.Operator} instance, starting from the default configuration
+ * and overriding specific aspects according to the provided {@link ConfigurationServiceOverrider}
+ * instance.
+ *
+ *
+ * NOTE: This overriding mechanism should only be used before creating
+ * your Operator instance as the configuration service is set at creation time and cannot be
+ * subsequently changed. As a result, overriding values this way after the Operator has been
+ * configured will not take effect.
+ *
+ *
+ * @param overrider the {@link ConfigurationServiceOverrider} used to change the values provided
+ * by the default configuration
+ * @return a new {@link ConfigurationService} overriding the default values with the ones provided
+ * by the specified {@link ConfigurationServiceOverrider}
+ * @since 4.4.0
+ */
+ static ConfigurationService newOverriddenConfigurationService(
+ Consumer overrider) {
+ return newOverriddenConfigurationService(new BaseConfigurationService(), overrider);
+ }
/**
* Retrieves the configuration associated with the specified reconciler
*
* @param reconciler the reconciler we want the configuration of
* @param the {@code CustomResource} type associated with the specified reconciler
- * @return the {@link ControllerConfiguration} associated with the specified reconciler or {@code
- * null} if no configuration exists for the reconciler
+ * @return the {@link ControllerConfiguration} associated with the specified reconciler or
+ * {@code null} if no configuration exists for the reconciler
*/
ControllerConfiguration getConfigurationFor(Reconciler reconciler);
-
/**
* Used to clone custom resources.
*
@@ -129,13 +192,6 @@ default boolean checkCRDAndValidateLocalModel() {
return false;
}
- int DEFAULT_RECONCILIATION_THREADS_NUMBER = 50;
- /**
- * @deprecated Not used anymore in the default implementation
- */
- @Deprecated(forRemoval = true)
- int MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER = 10;
-
/**
* The number of threads the operator can spin out to dispatch reconciliation requests to
* reconcilers with the default executors
@@ -146,24 +202,6 @@ default int concurrentReconciliationThreads() {
return DEFAULT_RECONCILIATION_THREADS_NUMBER;
}
- /**
- * The minimum number of threads the operator starts in the thread pool for reconciliations.
- *
- * @deprecated not used anymore by default executor implementation
- * @return the minimum number of concurrent reconciliation threads
- */
- @Deprecated(forRemoval = true)
- default int minConcurrentReconciliationThreads() {
- return MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER;
- }
-
- int DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER = DEFAULT_RECONCILIATION_THREADS_NUMBER;
- /**
- * @deprecated Not used anymore in the default implementation
- */
- @Deprecated(forRemoval = true)
- int MIN_DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER = MIN_DEFAULT_RECONCILIATION_THREADS_NUMBER;
-
/**
* Number of threads the operator can spin out to be used in the workflows with the default
* executor.
@@ -175,53 +213,63 @@ default int concurrentWorkflowExecutorThreads() {
}
/**
- * The minimum number of threads the operator starts in the thread pool for workflows.
+ * Override to provide a custom {@link Metrics} implementation
*
- * @deprecated not used anymore by default executor implementation
- * @return the minimum number of concurrent workflow threads
+ * @return the {@link Metrics} implementation
*/
- @Deprecated(forRemoval = true)
- default int minConcurrentWorkflowExecutorThreads() {
- return MIN_DEFAULT_WORKFLOW_EXECUTOR_THREAD_NUMBER;
+ default Metrics getMetrics() {
+ return Metrics.NOOP;
}
- int DEFAULT_TERMINATION_TIMEOUT_SECONDS = 10;
-
/**
- * Retrieves the number of seconds the SDK waits for reconciliation threads to terminate before
- * shutting down.
- *
- * @deprecated use {@link io.javaoperatorsdk.operator.Operator#stop(Duration)} instead. Where the
- * parameter can be passed to specify graceful timeout.
+ * Override to provide a custom {@link ExecutorService} implementation to change how threads
+ * handle concurrent reconciliations
*
- * @return the number of seconds to wait before terminating reconciliation threads
+ * @return the {@link ExecutorService} implementation to use for concurrent reconciliation
+ * processing
*/
- @Deprecated(forRemoval = true)
- default int getTerminationTimeoutSeconds() {
- return DEFAULT_TERMINATION_TIMEOUT_SECONDS;
- }
-
- default Metrics getMetrics() {
- return Metrics.NOOP;
- }
-
default ExecutorService getExecutorService() {
return Executors.newFixedThreadPool(concurrentReconciliationThreads());
}
+ /**
+ * Override to provide a custom {@link ExecutorService} implementation to change how dependent
+ * workflows are processed in parallel
+ *
+ * @return the {@link ExecutorService} implementation to use for dependent workflow processing
+ */
default ExecutorService getWorkflowExecutorService() {
return Executors.newFixedThreadPool(concurrentWorkflowExecutorThreads());
}
+ /**
+ * Determines whether the associated Kubernetes client should be closed when the associated
+ * {@link io.javaoperatorsdk.operator.Operator} is stopped.
+ *
+ * @return {@code true} if the Kubernetes should be closed on stop, {@code false} otherwise
+ */
default boolean closeClientOnStop() {
return true;
}
+ /**
+ * Override to provide a custom {@link DependentResourceFactory} implementation to change how
+ * {@link io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource} are instantiated
+ *
+ * @return the custom {@link DependentResourceFactory} implementation
+ */
@SuppressWarnings("rawtypes")
default DependentResourceFactory dependentResourceFactory() {
return DependentResourceFactory.DEFAULT;
}
+ /**
+ * Retrieves the optional {@link LeaderElectionConfiguration} to specify how the associated
+ * {@link io.javaoperatorsdk.operator.Operator} handles leader election to ensure only one
+ * instance of the operator runs on the cluster at any given time
+ *
+ * @return the {@link LeaderElectionConfiguration}
+ */
default Optional getLeaderElectionConfiguration() {
return Optional.empty();
}
@@ -230,8 +278,7 @@ default Optional getLeaderElectionConfiguration() {
*
* if true, operator stops if there are some issues with informers
* {@link io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource} or
- * {@link io.javaoperatorsdk.operator.processing.event.source.controller.ControllerResourceEventSource}
- * on startup. Other event sources may also respect this flag.
+ * {@link ControllerEventSource} on startup. Other event sources may also respect this flag.
*
*
* if false, the startup will ignore recoverable errors, caused for example by RBAC issues, and
@@ -255,6 +302,17 @@ default Duration cacheSyncTimeout() {
return Duration.ofMinutes(2);
}
+ /**
+ * This is the timeout value that allows the reconciliation threads to gracefully shut down. If no
+ * value is set, the default is immediate shutdown.
+ *
+ * @return The duration of time to wait before terminating the reconciliation threads
+ * @since 5.0.0
+ */
+ default Duration reconciliationTerminationTimeout() {
+ return Duration.ZERO;
+ }
+
/**
* Handler for an informer stop. Informer stops if there is a non-recoverable error. Like received
* a resource that cannot be deserialized.
@@ -278,69 +336,23 @@ default Optional getInformerStoppedHandler() {
});
}
- @SuppressWarnings("rawtypes")
- default ManagedWorkflowFactory getWorkflowFactory() {
- return ManagedWorkflowFactory.DEFAULT;
- }
-
- default ResourceClassResolver getResourceClassResolver() {
- return new DefaultResourceClassResolver();
- }
-
/**
- * Creates a new {@link ConfigurationService} instance used to configure an
- * {@link io.javaoperatorsdk.operator.Operator} instance, starting from the specified base
- * configuration and overriding specific aspects according to the provided
- * {@link ConfigurationServiceOverrider} instance.
+ * Override to provide a custom {@link ManagedWorkflowFactory} implementation to change how
+ * {@link io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow} are
+ * instantiated
*
- *
- * NOTE: This overriding mechanism should only be used before creating
- * your Operator instance as the configuration service is set at creation time and cannot be
- * subsequently changed. As a result, overriding values this way after the Operator has been
- * configured will not take effect.
- *
- *
- * @param baseConfiguration the {@link ConfigurationService} to start from
- * @param overrider the {@link ConfigurationServiceOverrider} used to change the values provided
- * by the base configuration
- * @return a new {@link ConfigurationService} starting from the configuration provided as base but
- * with overridden values.
+ * @return the custom {@link ManagedWorkflowFactory} implementation
*/
- static ConfigurationService newOverriddenConfigurationService(
- ConfigurationService baseConfiguration,
- Consumer overrider) {
- if (overrider != null) {
- final var toOverride = new ConfigurationServiceOverrider(baseConfiguration);
- overrider.accept(toOverride);
- return toOverride.build();
- }
- return baseConfiguration;
+ @SuppressWarnings("rawtypes")
+ default ManagedWorkflowFactory getWorkflowFactory() {
+ return ManagedWorkflowFactory.DEFAULT;
}
/**
- * Creates a new {@link ConfigurationService} instance used to configure an
- * {@link io.javaoperatorsdk.operator.Operator} instance, starting from the default configuration
- * and overriding specific aspects according to the provided {@link ConfigurationServiceOverrider}
- * instance.
+ * Override to provide a custom {@link ExecutorServiceManager} implementation
*
- *
- * NOTE: This overriding mechanism should only be used before creating
- * your Operator instance as the configuration service is set at creation time and cannot be
- * subsequently changed. As a result, overriding values this way after the Operator has been
- * configured will not take effect.
- *
- *
- * @param overrider the {@link ConfigurationServiceOverrider} used to change the values provided
- * by the default configuration
- * @return a new {@link ConfigurationService} overriding the default values with the ones provided
- * by the specified {@link ConfigurationServiceOverrider}
- * @since 4.4.0
+ * @return the custom {@link ExecutorServiceManager} implementation
*/
- static ConfigurationService newOverriddenConfigurationService(
- Consumer overrider) {
- return newOverriddenConfigurationService(new BaseConfigurationService(), overrider);
- }
-
default ExecutorServiceManager getExecutorServiceManager() {
return new ExecutorServiceManager(this);
}
@@ -355,6 +367,7 @@ default ExecutorServiceManager getExecutorServiceManager() {
* SSA based create/update can be still used with the legacy matching, just overriding the match
* method of Kubernetes Dependent Resource.
*
+ * @return {@code true} if SSA should be used for dependent resources, {@code false} otherwise
* @since 4.4.0
*/
default boolean ssaBasedCreateUpdateMatchForDependentResources() {
@@ -397,7 +410,7 @@ default boolean shouldUseSSA(Class extends KubernetesDependentResource> depend
return false;
}
Boolean useSSAConfig = Optional.ofNullable(config)
- .flatMap(KubernetesDependentResourceConfig::useSSA)
+ .map(KubernetesDependentResourceConfig::useSSA)
.orElse(null);
// don't use SSA for certain resources by default, only if explicitly overridden
if (useSSAConfig == null) {
@@ -440,7 +453,10 @@ default Set> defaultNonSSAResource() {
*
* Disable this if you want to react to your own dependent resource updates
*
+ * @return if special annotation should be used for dependent resource to filter events
* @since 4.5.0
+ *
+ * @return if special annotation should be used for dependent resource to filter events
*/
default boolean previousAnnotationForDependentResourcesEventFiltering() {
return true;
@@ -452,13 +468,52 @@ default boolean previousAnnotationForDependentResourcesEventFiltering() {
*
* Disabled by default as Kubernetes does not support, and discourages, this interpretation of
* resourceVersions. Enable only if your api server event processing seems to lag the operator
- * logic and you want to further minimize the amount of work done / updates issued by the
+ * logic, and you want to further minimize the amount of work done / updates issued by the
* operator.
*
+ * @return if resource version should be parsed (as integer)
* @since 4.5.0
+ *
+ * @return if resource version should be parsed (as integer)
*/
default boolean parseResourceVersionsForEventFilteringAndCaching() {
return false;
}
+ /**
+ * {@link io.javaoperatorsdk.operator.api.reconciler.UpdateControl} patch resource or status can
+ * either use simple patches or SSA. Setting this to {@code true}, controllers will use SSA for
+ * adding finalizers, patching resources and status.
+ *
+ * @return {@code true} if Server-Side Apply (SSA) should be used when patching the primary
+ * resources, {@code false} otherwise
+ * @see ConfigurationServiceOverrider#withUseSSAToPatchPrimaryResource(boolean)
+ * @since 5.0.0
+ */
+ default boolean useSSAToPatchPrimaryResource() {
+ return true;
+ }
+
+ /**
+ *
+ * Determines whether resources retrieved from caches such as via calls to
+ * {@link Context#getSecondaryResource(Class)} should be defensively cloned first.
+ *
+ *
+ *
+ * Defensive cloning to prevent problematic cache modifications (modifying the resource would
+ * otherwise modify the stored copy in the cache) was transparently done in previous JOSDK
+ * versions. This might have performance consequences and, with the more prevalent use of
+ * Server-Side Apply, where you should create a new copy of your resource with only modified
+ * fields, such modifications of these resources are less likely to occur.
+ *
+ *
+ * @return {@code true} if resources should be defensively cloned before returning them from
+ * caches, {@code false} otherwise
+ * @since 5.0.0
+ */
+ default boolean cloneSecondaryResourcesWhenGettingFromCache() {
+ return false;
+ }
+
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
index 4996035943..7de9bcda43 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ConfigurationServiceOverrider.java
@@ -4,7 +4,7 @@
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
+import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -15,7 +15,7 @@
import io.javaoperatorsdk.operator.api.monitoring.Metrics;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResourceFactory;
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "UnusedReturnValue"})
public class ConfigurationServiceOverrider {
private static final Logger log = LoggerFactory.getLogger(ConfigurationServiceOverrider.class);
@@ -23,11 +23,8 @@ public class ConfigurationServiceOverrider {
private Metrics metrics;
private Boolean checkCR;
private Integer concurrentReconciliationThreads;
- private Integer minConcurrentReconciliationThreads;
private Integer concurrentWorkflowExecutorThreads;
- private Integer minConcurrentWorkflowExecutorThreads;
private Cloner cloner;
- private Integer timeoutSeconds;
private Boolean closeClientOnStop;
private KubernetesClient client;
private ExecutorService executorService;
@@ -36,11 +33,13 @@ public class ConfigurationServiceOverrider {
private InformerStoppedHandler informerStoppedHandler;
private Boolean stopOnInformerErrorDuringStartup;
private Duration cacheSyncTimeout;
- private ResourceClassResolver resourceClassResolver;
+ private Duration reconciliationTerminationTimeout;
private Boolean ssaBasedCreateUpdateMatchForDependentResources;
private Set> defaultNonSSAResource;
private Boolean previousAnnotationForDependentResources;
private Boolean parseResourceVersions;
+ private Boolean useSSAToPatchPrimaryResource;
+ private Boolean cloneSecondaryResourcesWhenGettingFromCache;
@SuppressWarnings("rawtypes")
private DependentResourceFactory dependentResourceFactory;
@@ -63,24 +62,6 @@ public ConfigurationServiceOverrider withConcurrentWorkflowExecutorThreads(int t
return this;
}
- private int minimumMaxValueFor(Integer minValue) {
- return minValue != null ? (minValue < 0 ? 0 : minValue) + 1 : 1;
- }
-
- public ConfigurationServiceOverrider withMinConcurrentReconciliationThreads(int threadNumber) {
- this.minConcurrentReconciliationThreads = Utils.ensureValid(threadNumber,
- "minimum reconciliation threads", ExecutorServiceManager.MIN_THREAD_NUMBER,
- original.minConcurrentReconciliationThreads());
- return this;
- }
-
- public ConfigurationServiceOverrider withMinConcurrentWorkflowExecutorThreads(int threadNumber) {
- this.minConcurrentWorkflowExecutorThreads = Utils.ensureValid(threadNumber,
- "minimum workflow execution threads", ExecutorServiceManager.MIN_THREAD_NUMBER,
- original.minConcurrentWorkflowExecutorThreads());
- return this;
- }
-
@SuppressWarnings("rawtypes")
public ConfigurationServiceOverrider withDependentResourceFactory(
DependentResourceFactory dependentResourceFactory) {
@@ -93,11 +74,6 @@ public ConfigurationServiceOverrider withResourceCloner(Cloner cloner) {
return this;
}
- public ConfigurationServiceOverrider withTerminationTimeoutSeconds(int timeoutSeconds) {
- this.timeoutSeconds = timeoutSeconds;
- return this;
- }
-
public ConfigurationServiceOverrider withMetrics(Metrics metrics) {
this.metrics = metrics;
return this;
@@ -157,9 +133,9 @@ public ConfigurationServiceOverrider withCacheSyncTimeout(Duration cacheSyncTime
return this;
}
- public ConfigurationServiceOverrider withResourceClassResolver(
- ResourceClassResolver resourceClassResolver) {
- this.resourceClassResolver = resourceClassResolver;
+ public ConfigurationServiceOverrider withReconciliationTerminationTimeout(
+ Duration reconciliationTerminationTimeout) {
+ this.reconciliationTerminationTimeout = reconciliationTerminationTimeout;
return this;
}
@@ -203,6 +179,17 @@ public ConfigurationServiceOverrider wihtParseResourceVersions(
return this;
}
+ public ConfigurationServiceOverrider withUseSSAToPatchPrimaryResource(boolean value) {
+ this.useSSAToPatchPrimaryResource = value;
+ return this;
+ }
+
+ public ConfigurationServiceOverrider withCloneSecondaryResourcesWhenGettingFromCache(
+ boolean value) {
+ this.cloneSecondaryResourcesWhenGettingFromCache = value;
+ return this;
+ }
+
public ConfigurationService build() {
return new BaseConfigurationService(original.getVersion(), cloner, client) {
@Override
@@ -210,83 +197,63 @@ public Set getKnownReconcilerNames() {
return original.getKnownReconcilerNames();
}
+ private T overriddenValueOrDefault(T value,
+ Function defaultValue) {
+ return value != null ? value : defaultValue.apply(original);
+ }
+
@Override
public boolean checkCRDAndValidateLocalModel() {
- return checkCR != null ? checkCR : original.checkCRDAndValidateLocalModel();
+ return overriddenValueOrDefault(checkCR,
+ ConfigurationService::checkCRDAndValidateLocalModel);
}
@SuppressWarnings("rawtypes")
@Override
public DependentResourceFactory dependentResourceFactory() {
- return dependentResourceFactory != null ? dependentResourceFactory
- : DependentResourceFactory.DEFAULT;
+ return overriddenValueOrDefault(dependentResourceFactory,
+ ConfigurationService::dependentResourceFactory);
}
@Override
public int concurrentReconciliationThreads() {
return Utils.ensureValid(
- concurrentReconciliationThreads != null ? concurrentReconciliationThreads
- : original.concurrentReconciliationThreads(),
+ overriddenValueOrDefault(concurrentReconciliationThreads,
+ ConfigurationService::concurrentReconciliationThreads),
"maximum reconciliation threads",
- minimumMaxValueFor(minConcurrentReconciliationThreads),
+ 1,
original.concurrentReconciliationThreads());
}
@Override
public int concurrentWorkflowExecutorThreads() {
return Utils.ensureValid(
- concurrentWorkflowExecutorThreads != null ? concurrentWorkflowExecutorThreads
- : original.concurrentWorkflowExecutorThreads(),
+ overriddenValueOrDefault(concurrentWorkflowExecutorThreads,
+ ConfigurationService::concurrentWorkflowExecutorThreads),
"maximum workflow execution threads",
- minimumMaxValueFor(minConcurrentWorkflowExecutorThreads),
+ 1,
original.concurrentWorkflowExecutorThreads());
}
- /**
- * @deprecated Not used anymore in the default implementation
- */
- @Deprecated(forRemoval = true)
- @Override
- public int minConcurrentReconciliationThreads() {
- return minConcurrentReconciliationThreads != null ? minConcurrentReconciliationThreads
- : original.minConcurrentReconciliationThreads();
- }
-
- /**
- * @deprecated Not used anymore in the default implementation
- */
- @Override
- @Deprecated(forRemoval = true)
- public int minConcurrentWorkflowExecutorThreads() {
- return minConcurrentWorkflowExecutorThreads != null ? minConcurrentWorkflowExecutorThreads
- : original.minConcurrentWorkflowExecutorThreads();
- }
-
- @Override
- public int getTerminationTimeoutSeconds() {
- return timeoutSeconds != null ? timeoutSeconds : original.getTerminationTimeoutSeconds();
- }
-
@Override
public Metrics getMetrics() {
- return metrics != null ? metrics : original.getMetrics();
+ return overriddenValueOrDefault(metrics, ConfigurationService::getMetrics);
}
@Override
public boolean closeClientOnStop() {
- return closeClientOnStop != null ? closeClientOnStop : original.closeClientOnStop();
+ return overriddenValueOrDefault(closeClientOnStop, ConfigurationService::closeClientOnStop);
}
@Override
public ExecutorService getExecutorService() {
- return executorService != null ? executorService
- : super.getExecutorService();
+ return overriddenValueOrDefault(executorService, ConfigurationService::getExecutorService);
}
@Override
public ExecutorService getWorkflowExecutorService() {
- return workflowExecutorService != null ? workflowExecutorService
- : super.getWorkflowExecutorService();
+ return overriddenValueOrDefault(workflowExecutorService,
+ ConfigurationService::getWorkflowExecutorService);
}
@Override
@@ -303,59 +270,57 @@ public Optional getInformerStoppedHandler() {
@Override
public boolean stopOnInformerErrorDuringStartup() {
- return stopOnInformerErrorDuringStartup != null ? stopOnInformerErrorDuringStartup
- : super.stopOnInformerErrorDuringStartup();
+ return overriddenValueOrDefault(stopOnInformerErrorDuringStartup,
+ ConfigurationService::stopOnInformerErrorDuringStartup);
}
@Override
public Duration cacheSyncTimeout() {
- return cacheSyncTimeout != null ? cacheSyncTimeout : super.cacheSyncTimeout();
+ return overriddenValueOrDefault(cacheSyncTimeout, ConfigurationService::cacheSyncTimeout);
}
@Override
- public ResourceClassResolver getResourceClassResolver() {
- return resourceClassResolver != null ? resourceClassResolver
- : super.getResourceClassResolver();
+ public Duration reconciliationTerminationTimeout() {
+ return overriddenValueOrDefault(reconciliationTerminationTimeout,
+ ConfigurationService::reconciliationTerminationTimeout);
}
@Override
public boolean ssaBasedCreateUpdateMatchForDependentResources() {
- return ssaBasedCreateUpdateMatchForDependentResources != null
- ? ssaBasedCreateUpdateMatchForDependentResources
- : super.ssaBasedCreateUpdateMatchForDependentResources();
+ return overriddenValueOrDefault(ssaBasedCreateUpdateMatchForDependentResources,
+ ConfigurationService::ssaBasedCreateUpdateMatchForDependentResources);
}
@Override
public Set> defaultNonSSAResources() {
- return defaultNonSSAResource != null ? defaultNonSSAResource
- : super.defaultNonSSAResources();
+ return overriddenValueOrDefault(defaultNonSSAResource,
+ ConfigurationService::defaultNonSSAResources);
}
@Override
public boolean previousAnnotationForDependentResourcesEventFiltering() {
- return previousAnnotationForDependentResources != null
- ? previousAnnotationForDependentResources
- : super.previousAnnotationForDependentResourcesEventFiltering();
+ return overriddenValueOrDefault(previousAnnotationForDependentResources,
+ ConfigurationService::previousAnnotationForDependentResourcesEventFiltering);
}
@Override
public boolean parseResourceVersionsForEventFilteringAndCaching() {
- return parseResourceVersions != null
- ? parseResourceVersions
- : super.parseResourceVersionsForEventFilteringAndCaching();
+ return overriddenValueOrDefault(parseResourceVersions,
+ ConfigurationService::parseResourceVersionsForEventFilteringAndCaching);
+ }
+
+ @Override
+ public boolean useSSAToPatchPrimaryResource() {
+ return overriddenValueOrDefault(useSSAToPatchPrimaryResource,
+ ConfigurationService::useSSAToPatchPrimaryResource);
+ }
+
+ @Override
+ public boolean cloneSecondaryResourcesWhenGettingFromCache() {
+ return overriddenValueOrDefault(cloneSecondaryResourcesWhenGettingFromCache,
+ ConfigurationService::cloneSecondaryResourcesWhenGettingFromCache);
}
};
}
- /**
- * @deprecated Use
- * {@link ConfigurationService#newOverriddenConfigurationService(ConfigurationService, Consumer)}
- * instead
- * @param original that will be overridden
- * @return current overrider
- */
- @Deprecated(since = "2.2.0")
- public static ConfigurationServiceOverrider override(ConfigurationService original) {
- return new ConfigurationServiceOverrider(original);
- }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
index 13ddd995ad..e03cf5626e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
@@ -1,24 +1,20 @@
package io.javaoperatorsdk.operator.api.config;
import java.time.Duration;
-import java.util.Collections;
-import java.util.List;
import java.util.Optional;
import java.util.Set;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.MaxReconciliationInterval;
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
-import io.javaoperatorsdk.operator.processing.retry.GradualRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
-public interface ControllerConfiguration extends ResourceConfiguration
{
+public interface ControllerConfiguration
extends Informable
{
@SuppressWarnings("rawtypes")
RateLimiter DEFAULT_RATE_LIMITER = LinearRateLimiter.deactivatedRateLimiter();
@@ -60,22 +56,7 @@ default boolean isGenerationAware() {
String getAssociatedReconcilerClassName();
default Retry getRetry() {
- final var configuration = getRetryConfiguration();
- return !RetryConfiguration.DEFAULT.equals(configuration)
- ? GenericRetry.fromConfiguration(configuration)
- : GenericRetry.DEFAULT; // NOSONAR
- }
-
- /**
- * Use {@link #getRetry()} instead.
- *
- * @return configuration for retry.
- * @deprecated provide your own {@link Retry} implementation or use the {@link GradualRetry}
- * annotation instead
- */
- @Deprecated(forRemoval = true)
- default RetryConfiguration getRetryConfiguration() {
- return RetryConfiguration.DEFAULT;
+ return GenericRetry.DEFAULT;
}
@SuppressWarnings("rawtypes")
@@ -83,50 +64,19 @@ default RateLimiter getRateLimiter() {
return DEFAULT_RATE_LIMITER;
}
- /**
- * Allow controllers to filter events before they are passed to the
- * {@link io.javaoperatorsdk.operator.processing.event.EventHandler}.
- *
- *
- * Resource event filters only applies on events of the main custom resource. Not on events from
- * other event sources nor the periodic events.
- *
- *
- * @return filter
- * @deprecated use {@link ResourceConfiguration#onAddFilter()},
- * {@link ResourceConfiguration#onUpdateFilter()} or
- * {@link ResourceConfiguration#genericFilter()} instead
- */
- @Deprecated(forRemoval = true)
- default ResourceEventFilter getEventFilter() {
- return ResourceEventFilters.passthrough();
- }
-
- @SuppressWarnings("rawtypes")
- default List getDependentResources() {
- return Collections.emptyList();
+ default Optional getWorkflowSpec() {
+ return Optional.empty();
}
default Optional maxReconciliationInterval() {
return Optional.of(Duration.ofHours(MaxReconciliationInterval.DEFAULT_INTERVAL));
}
- @SuppressWarnings("unused")
ConfigurationService getConfigurationService();
- @SuppressWarnings("unchecked")
- @Override
- default Class getResourceClass() {
- // note that this implementation at the end not used within the boundaries of the core
- // framework, should be removed in the future, (and marked as an API changed, or behavior
- // change)
- return (Class
) Utils.getFirstTypeArgumentFromSuperClassOrInterface(getClass(),
- ControllerConfiguration.class);
- }
-
@SuppressWarnings("unused")
default Set getEffectiveNamespaces() {
- return ResourceConfiguration.super.getEffectiveNamespaces(getConfigurationService());
+ return getInformerConfig().getEffectiveNamespaces(this);
}
/**
@@ -140,4 +90,5 @@ default String fieldManager() {
return getName();
}
+ C getConfigurationFor(DependentResourceSpec, P, C> spec);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
index 328d912109..3d3eef5990 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
@@ -10,55 +10,39 @@
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE_SET;
-@SuppressWarnings({"rawtypes", "unused"})
+@SuppressWarnings({"rawtypes", "unused", "UnusedReturnValue"})
public class ControllerConfigurationOverrider {
+ private final ControllerConfiguration original;
+ private String name;
private String finalizer;
private boolean generationAware;
- private Set namespaces;
private Retry retry;
- private String labelSelector;
- private ResourceEventFilter customResourcePredicate;
- private final ControllerConfiguration original;
- private Duration reconciliationMaxInterval;
- private OnAddFilter super R> onAddFilter;
- private OnUpdateFilter super R> onUpdateFilter;
- private GenericFilter super R> genericFilter;
private RateLimiter rateLimiter;
- private Map configurations;
- private ItemStore itemStore;
- private String name;
private String fieldManager;
- private Long informerListLimit;
+ private Duration reconciliationMaxInterval;
+ private Map configurations;
+ private final InformerConfiguration.Builder config;
private ControllerConfigurationOverrider(ControllerConfiguration original) {
this.finalizer = original.getFinalizerName();
this.generationAware = original.isGenerationAware();
- this.namespaces = new HashSet<>(original.getNamespaces());
+ final var informerConfig = original.getInformerConfig();
+ this.config = InformerConfiguration.builder(informerConfig);
this.retry = original.getRetry();
- this.labelSelector = original.getLabelSelector();
- this.customResourcePredicate = original.getEventFilter();
this.reconciliationMaxInterval = original.maxReconciliationInterval().orElse(null);
- this.onAddFilter = original.onAddFilter().orElse(null);
- this.onUpdateFilter = original.onUpdateFilter().orElse(null);
- this.genericFilter = original.genericFilter().orElse(null);
this.original = original;
this.rateLimiter = original.getRateLimiter();
this.name = original.getName();
this.fieldManager = original.fieldManager();
- this.informerListLimit = original.getInformerListLimit().orElse(null);
- this.itemStore = original.getItemStore().orElse(null);
}
public ControllerConfigurationOverrider withFinalizer(String finalizer) {
@@ -72,26 +56,36 @@ public ControllerConfigurationOverrider withGenerationAware(boolean generatio
}
public ControllerConfigurationOverrider watchingOnlyCurrentNamespace() {
- this.namespaces = WATCH_CURRENT_NAMESPACE_SET;
+ config.withWatchCurrentNamespace();
return this;
}
public ControllerConfigurationOverrider addingNamespaces(String... namespaces) {
- this.namespaces.addAll(List.of(namespaces));
+ if (namespaces != null && namespaces.length > 0) {
+ final var current = config.namespaces();
+ final var aggregated = new HashSet(current.size() + namespaces.length);
+ aggregated.addAll(current);
+ aggregated.addAll(Set.of(namespaces));
+ config.withNamespaces(aggregated);
+ }
return this;
}
public ControllerConfigurationOverrider removingNamespaces(String... namespaces) {
- List.of(namespaces).forEach(this.namespaces::remove);
- if (this.namespaces.isEmpty()) {
- this.namespaces = DEFAULT_NAMESPACES_SET;
+ if (namespaces != null && namespaces.length > 0) {
+ final var current = new HashSet<>(config.namespaces());
+ List.of(namespaces).forEach(current::remove);
+ if (current.isEmpty()) {
+ return watchingAllNamespaces();
+ } else {
+ config.withNamespaces(current);
+ }
}
return this;
}
public ControllerConfigurationOverrider settingNamespaces(Set newNamespaces) {
- this.namespaces.clear();
- this.namespaces.addAll(newNamespaces);
+ config.withNamespaces(newNamespaces);
return this;
}
@@ -100,24 +94,12 @@ public ControllerConfigurationOverrider settingNamespaces(String... newNamesp
}
public ControllerConfigurationOverrider settingNamespace(String namespace) {
- this.namespaces.clear();
- this.namespaces.add(namespace);
+ config.withNamespaces(Set.of(namespace));
return this;
}
public ControllerConfigurationOverrider watchingAllNamespaces() {
- this.namespaces = DEFAULT_NAMESPACES_SET;
- return this;
- }
-
- /**
- * @param retry configuration
- * @return current instance of overrider
- * @deprecated Use {@link #withRetry(Retry)} instead
- */
- @Deprecated(forRemoval = true)
- public ControllerConfigurationOverrider withRetry(RetryConfiguration retry) {
- this.retry = GenericRetry.fromConfiguration(retry);
+ config.withWatchAllNamespaces();
return this;
}
@@ -132,13 +114,7 @@ public ControllerConfigurationOverrider withRateLimiter(RateLimiter rateLimit
}
public ControllerConfigurationOverrider withLabelSelector(String labelSelector) {
- this.labelSelector = labelSelector;
- return this;
- }
-
- public ControllerConfigurationOverrider withCustomResourcePredicate(
- ResourceEventFilter customResourcePredicate) {
- this.customResourcePredicate = customResourcePredicate;
+ config.withLabelSelector(labelSelector);
return this;
}
@@ -149,27 +125,28 @@ public ControllerConfigurationOverrider withReconciliationMaxInterval(
}
public ControllerConfigurationOverrider withOnAddFilter(OnAddFilter onAddFilter) {
- this.onAddFilter = onAddFilter;
+ config.withOnAddFilter(onAddFilter);
return this;
}
public ControllerConfigurationOverrider withOnUpdateFilter(OnUpdateFilter onUpdateFilter) {
- this.onUpdateFilter = onUpdateFilter;
+ config.withOnUpdateFilter(onUpdateFilter);
return this;
}
public ControllerConfigurationOverrider withGenericFilter(GenericFilter genericFilter) {
- this.genericFilter = genericFilter;
+ config.withGenericFilter(genericFilter);
return this;
}
public ControllerConfigurationOverrider withItemStore(ItemStore itemStore) {
- this.itemStore = itemStore;
+ config.withItemStore(itemStore);
return this;
}
public ControllerConfigurationOverrider withName(String name) {
this.name = name;
+ config.withName(name);
return this;
}
@@ -189,14 +166,14 @@ public ControllerConfigurationOverrider withFieldManager(
*/
public ControllerConfigurationOverrider withInformerListLimit(
Long informerListLimit) {
- this.informerListLimit = informerListLimit;
+ config.withInformerListLimit(informerListLimit);
return this;
}
public ControllerConfigurationOverrider replacingNamedDependentResourceConfig(String name,
Object dependentResourceConfig) {
- final var specs = original.getDependentResources();
+ final var specs = original.getWorkflowSpec().orElseThrow().getDependentResourceSpecs();
final var spec = specs.stream()
.filter(drs -> drs.getName().equals(name)).findFirst()
.orElseThrow(
@@ -210,15 +187,14 @@ public ControllerConfigurationOverrider replacingNamedDependentResourceConfig
}
public ControllerConfiguration build() {
- final var overridden = new ResolvedControllerConfiguration<>(original.getResourceClass(),
+ return new ResolvedControllerConfiguration<>(
name,
generationAware, original.getAssociatedReconcilerClassName(), retry, rateLimiter,
- reconciliationMaxInterval, onAddFilter, onUpdateFilter, genericFilter,
- original.getDependentResources(),
- namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
- original.getConfigurationService(), informerListLimit);
- overridden.setEventFilter(customResourcePredicate);
- return overridden;
+ reconciliationMaxInterval,
+ finalizer, configurations, fieldManager,
+ original.getConfigurationService(),
+ config.buildForController(),
+ original.getWorkflowSpec().orElse(null));
}
public static ControllerConfigurationOverrider override(
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceClassResolver.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceClassResolver.java
index c038e7d966..cdd4c5540e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceClassResolver.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceClassResolver.java
@@ -7,7 +7,7 @@ public class DefaultResourceClassResolver implements ResourceClassResolver {
@SuppressWarnings("unchecked")
@Override
- public Class getResourceClass(
+ public Class getPrimaryResourceClass(
Class extends Reconciler> reconcilerClass) {
return (Class) Utils.getFirstTypeArgumentFromSuperClassOrInterface(reconcilerClass,
Reconciler.class);
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java
deleted file mode 100644
index f8ee9f4e84..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultResourceConfiguration.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-import java.util.Optional;
-import java.util.Set;
-
-import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.client.informers.cache.ItemStore;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-
-public class DefaultResourceConfiguration
- implements ResourceConfiguration {
-
- private final Class resourceClass;
- private final String resourceTypeName;
- private final OnAddFilter super R> onAddFilter;
- private final OnUpdateFilter super R> onUpdateFilter;
- private final GenericFilter super R> genericFilter;
- private final String labelSelector;
- private final Set namespaces;
- private final ItemStore itemStore;
- private final Long informerListLimit;
-
- protected DefaultResourceConfiguration(Class resourceClass,
- Set namespaces, String labelSelector, OnAddFilter super R> onAddFilter,
- OnUpdateFilter super R> onUpdateFilter, GenericFilter super R> genericFilter,
- ItemStore itemStore, Long informerListLimit) {
- this.resourceClass = resourceClass;
- this.resourceTypeName = resourceClass.isAssignableFrom(GenericKubernetesResource.class)
- // in general this is irrelevant now for secondary resources it is used just by controller
- // where GenericKubernetesResource now does not apply
- ? GenericKubernetesResource.class.getSimpleName()
- : ReconcilerUtils.getResourceTypeName(resourceClass);
- this.onAddFilter = onAddFilter;
- this.onUpdateFilter = onUpdateFilter;
- this.genericFilter = genericFilter;
-
- this.namespaces = ResourceConfiguration.ensureValidNamespaces(namespaces);
- this.labelSelector = ResourceConfiguration.ensureValidLabelSelector(labelSelector);
- this.itemStore = itemStore;
- this.informerListLimit = informerListLimit;
- }
-
- @Override
- public String getResourceTypeName() {
- return resourceTypeName;
- }
-
- @Override
- public String getLabelSelector() {
- return labelSelector;
- }
-
- @Override
- public Set getNamespaces() {
- return namespaces;
- }
-
- @Override
- public Class getResourceClass() {
- return resourceClass;
- }
-
- @Override
- public Optional> onAddFilter() {
- return Optional.ofNullable(onAddFilter);
- }
-
- @Override
- public Optional> onUpdateFilter() {
- return Optional.ofNullable(onUpdateFilter);
- }
-
- @Override
- public Optional> genericFilter() {
- return Optional.ofNullable(genericFilter);
- }
-
- @Override
- public Optional> getItemStore() {
- return Optional.ofNullable(itemStore);
- }
-
- @Override
- public Optional getInformerListLimit() {
- return Optional.ofNullable(informerListLimit);
- }
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java
deleted file mode 100644
index 40fbb38aa7..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultRetryConfiguration.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-public class DefaultRetryConfiguration implements RetryConfiguration {
-
-}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java
index 112ab7188a..c35281e822 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ExecutorServiceManager.java
@@ -22,7 +22,6 @@
public class ExecutorServiceManager {
private static final Logger log = LoggerFactory.getLogger(ExecutorServiceManager.class);
- public static final int MIN_THREAD_NUMBER = 0;
private ExecutorService executor;
private ExecutorService workflowExecutor;
private ExecutorService cachingExecutorService;
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Informable.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Informable.java
new file mode 100644
index 0000000000..5b58836483
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/Informable.java
@@ -0,0 +1,18 @@
+package io.javaoperatorsdk.operator.api.config;
+
+
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
+
+public interface Informable {
+
+ default String getResourceTypeName() {
+ return getInformerConfig().getResourceTypeName();
+ }
+
+ InformerConfiguration getInformerConfig();
+
+ default Class getResourceClass() {
+ return getInformerConfig().getResourceClass();
+ }
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java
index 0ab72ff165..cfce453e14 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfiguration.java
@@ -20,6 +20,7 @@ public class LeaderElectionConfiguration {
private final Duration retryPeriod;
private final LeaderCallbacks leaderCallbacks;
+ private final boolean exitOnStopLeading;
public LeaderElectionConfiguration(String leaseName, String leaseNamespace, String identity) {
this(
@@ -27,7 +28,7 @@ public LeaderElectionConfiguration(String leaseName, String leaseNamespace, Stri
leaseNamespace,
LEASE_DURATION_DEFAULT_VALUE,
RENEW_DEADLINE_DEFAULT_VALUE,
- RETRY_PERIOD_DEFAULT_VALUE, identity, null);
+ RETRY_PERIOD_DEFAULT_VALUE, identity, null, true);
}
public LeaderElectionConfiguration(String leaseName, String leaseNamespace) {
@@ -36,7 +37,7 @@ public LeaderElectionConfiguration(String leaseName, String leaseNamespace) {
leaseNamespace,
LEASE_DURATION_DEFAULT_VALUE,
RENEW_DEADLINE_DEFAULT_VALUE,
- RETRY_PERIOD_DEFAULT_VALUE, null, null);
+ RETRY_PERIOD_DEFAULT_VALUE, null, null, true);
}
public LeaderElectionConfiguration(String leaseName) {
@@ -45,7 +46,7 @@ public LeaderElectionConfiguration(String leaseName) {
null,
LEASE_DURATION_DEFAULT_VALUE,
RENEW_DEADLINE_DEFAULT_VALUE,
- RETRY_PERIOD_DEFAULT_VALUE, null, null);
+ RETRY_PERIOD_DEFAULT_VALUE, null, null, true);
}
public LeaderElectionConfiguration(
@@ -54,7 +55,7 @@ public LeaderElectionConfiguration(
Duration leaseDuration,
Duration renewDeadline,
Duration retryPeriod) {
- this(leaseName, leaseNamespace, leaseDuration, renewDeadline, retryPeriod, null, null);
+ this(leaseName, leaseNamespace, leaseDuration, renewDeadline, retryPeriod, null, null, true);
}
public LeaderElectionConfiguration(
@@ -64,7 +65,8 @@ public LeaderElectionConfiguration(
Duration renewDeadline,
Duration retryPeriod,
String identity,
- LeaderCallbacks leaderCallbacks) {
+ LeaderCallbacks leaderCallbacks,
+ boolean exitOnStopLeading) {
this.leaseName = leaseName;
this.leaseNamespace = leaseNamespace;
this.leaseDuration = leaseDuration;
@@ -72,6 +74,7 @@ public LeaderElectionConfiguration(
this.retryPeriod = retryPeriod;
this.identity = identity;
this.leaderCallbacks = leaderCallbacks;
+ this.exitOnStopLeading = exitOnStopLeading;
}
public Optional getLeaseNamespace() {
@@ -101,4 +104,8 @@ public Optional getIdentity() {
public Optional getLeaderCallbacks() {
return Optional.ofNullable(leaderCallbacks);
}
+
+ public boolean isExitOnStopLeading() {
+ return exitOnStopLeading;
+ }
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java
index 4b21dd9d2d..eda262f9eb 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/LeaderElectionConfigurationBuilder.java
@@ -6,15 +6,17 @@
import static io.javaoperatorsdk.operator.api.config.LeaderElectionConfiguration.*;
+@SuppressWarnings("unused")
public final class LeaderElectionConfigurationBuilder {
- private String leaseName;
+ private final String leaseName;
private String leaseNamespace;
private String identity;
private Duration leaseDuration = LEASE_DURATION_DEFAULT_VALUE;
private Duration renewDeadline = RENEW_DEADLINE_DEFAULT_VALUE;
private Duration retryPeriod = RETRY_PERIOD_DEFAULT_VALUE;
private LeaderCallbacks leaderCallbacks;
+ private boolean exitOnStopLeading = true;
private LeaderElectionConfigurationBuilder(String leaseName) {
this.leaseName = leaseName;
@@ -54,8 +56,13 @@ public LeaderElectionConfigurationBuilder withLeaderCallbacks(LeaderCallbacks le
return this;
}
+ public LeaderElectionConfigurationBuilder withExitOnStopLeading(boolean exitOnStopLeading) {
+ this.exitOnStopLeading = exitOnStopLeading;
+ return this;
+ }
+
public LeaderElectionConfiguration build() {
return new LeaderElectionConfiguration(leaseName, leaseNamespace, leaseDuration, renewDeadline,
- retryPeriod, identity, leaderCallbacks);
+ retryPeriod, identity, leaderCallbacks, exitOnStopLeading);
}
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
index 307e75080f..7e8415f584 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResolvedControllerConfiguration.java
@@ -1,27 +1,24 @@
package io.javaoperatorsdk.operator.api.config;
import java.time.Duration;
-import java.util.*;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.client.informers.cache.ItemStore;
-import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
+import io.javaoperatorsdk.operator.api.config.informer.InformerConfiguration;
+import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
-import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
import io.javaoperatorsdk.operator.processing.retry.Retry;
@SuppressWarnings("rawtypes")
public class ResolvedControllerConfiguration
- extends DefaultResourceConfiguration
- implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration
,
- DependentResourceConfigurationProvider {
+ implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration
{
+ private final InformerConfiguration
informerConfig;
private final String name;
private final boolean generationAware;
private final String associatedReconcilerClassName;
@@ -30,71 +27,43 @@ public class ResolvedControllerConfiguration
private final Duration maxReconciliationInterval;
private final String finalizer;
private final Map configurations;
- private final ItemStore itemStore;
private final ConfigurationService configurationService;
private final String fieldManager;
+ private WorkflowSpec workflowSpec;
- private ResourceEventFilter
eventFilter;
- private List dependentResources;
-
- public ResolvedControllerConfiguration(Class resourceClass, ControllerConfiguration
other) {
- this(resourceClass, other.getName(), other.isGenerationAware(),
+ public ResolvedControllerConfiguration(ControllerConfiguration
other) {
+ this(other.getName(), other.isGenerationAware(),
other.getAssociatedReconcilerClassName(), other.getRetry(), other.getRateLimiter(),
other.maxReconciliationInterval().orElse(null),
- other.onAddFilter().orElse(null), other.onUpdateFilter().orElse(null),
- other.genericFilter().orElse(null),
- other.getDependentResources(), other.getNamespaces(),
- other.getFinalizerName(), other.getLabelSelector(), Collections.emptyMap(),
- other.getItemStore().orElse(null), other.fieldManager(),
+ other.getFinalizerName(), Collections.emptyMap(),
+ other.fieldManager(),
other.getConfigurationService(),
- other.getInformerListLimit().orElse(null));
+ other.getInformerConfig(),
+ other.getWorkflowSpec().orElse(null));
}
- public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
- return interval > 0 ? Duration.of(interval, timeUnit.toChronoUnit()) : null;
- }
-
- public static String getAssociatedReconcilerClassName(
- Class extends Reconciler> reconcilerClass) {
- return reconcilerClass.getCanonicalName();
- }
-
- protected Retry ensureRetry(Retry given) {
- return given == null ? ControllerConfiguration.super.getRetry() : given;
- }
-
- protected RateLimiter ensureRateLimiter(RateLimiter given) {
- return given == null ? ControllerConfiguration.super.getRateLimiter() : given;
- }
-
- public ResolvedControllerConfiguration(Class
resourceClass, String name,
+ public ResolvedControllerConfiguration(String name,
boolean generationAware, String associatedReconcilerClassName, Retry retry,
RateLimiter rateLimiter, Duration maxReconciliationInterval,
- OnAddFilter super P> onAddFilter, OnUpdateFilter super P> onUpdateFilter,
- GenericFilter super P> genericFilter,
- List dependentResources,
- Set namespaces, String finalizer, String labelSelector,
- Map configurations, ItemStore itemStore,
+ String finalizer,
+ Map configurations,
String fieldManager,
- ConfigurationService configurationService, Long informerListLimit) {
- this(resourceClass, name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
- maxReconciliationInterval, onAddFilter, onUpdateFilter, genericFilter,
- namespaces, finalizer, labelSelector, configurations, itemStore, fieldManager,
- configurationService, informerListLimit);
- setDependentResources(dependentResources);
+ ConfigurationService configurationService,
+ InformerConfiguration informerConfig,
+ WorkflowSpec workflowSpec) {
+ this(name, generationAware, associatedReconcilerClassName, retry, rateLimiter,
+ maxReconciliationInterval, finalizer, configurations, fieldManager,
+ configurationService, informerConfig);
+ setWorkflowSpec(workflowSpec);
}
- protected ResolvedControllerConfiguration(Class
resourceClass, String name,
+ protected ResolvedControllerConfiguration(String name,
boolean generationAware, String associatedReconcilerClassName, Retry retry,
- RateLimiter rateLimiter, Duration maxReconciliationInterval,
- OnAddFilter super P> onAddFilter, OnUpdateFilter super P> onUpdateFilter,
- GenericFilter super P> genericFilter,
- Set namespaces, String finalizer, String labelSelector,
- Map configurations, ItemStore itemStore,
+ RateLimiter rateLimiter, Duration maxReconciliationInterval, String finalizer,
+ Map configurations,
String fieldManager,
- ConfigurationService configurationService, Long informerListLimit) {
- super(resourceClass, namespaces, labelSelector, onAddFilter, onUpdateFilter, genericFilter,
- itemStore, informerListLimit);
+ ConfigurationService configurationService, InformerConfiguration informerConfig) {
+ this.informerConfig = informerConfig;
this.configurationService = configurationService;
this.name = ControllerConfiguration.ensureValidName(name, associatedReconcilerClassName);
this.generationAware = generationAware;
@@ -103,7 +72,6 @@ protected ResolvedControllerConfiguration(Class
resourceClass, String name,
this.rateLimiter = ensureRateLimiter(rateLimiter);
this.maxReconciliationInterval = maxReconciliationInterval;
this.configurations = configurations != null ? configurations : Collections.emptyMap();
- this.itemStore = itemStore;
this.finalizer =
ControllerConfiguration.ensureValidFinalizerName(finalizer, getResourceTypeName());
this.fieldManager = fieldManager;
@@ -111,9 +79,31 @@ protected ResolvedControllerConfiguration(Class
resourceClass, String name,
protected ResolvedControllerConfiguration(Class
resourceClass, String name,
Class extends Reconciler> reconcilerClas, ConfigurationService configurationService) {
- this(resourceClass, name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
- null, null, null, null, null,
- null, null, null, null, null, configurationService, null);
+ this(name, false, getAssociatedReconcilerClassName(reconcilerClas), null, null,
+ null, null, null, null, configurationService,
+ InformerConfiguration.builder(resourceClass).buildForController());
+ }
+
+ @Override
+ public InformerConfiguration
getInformerConfig() {
+ return informerConfig;
+ }
+
+ public static Duration getMaxReconciliationInterval(long interval, TimeUnit timeUnit) {
+ return interval > 0 ? Duration.of(interval, timeUnit.toChronoUnit()) : null;
+ }
+
+ public static String getAssociatedReconcilerClassName(
+ Class extends Reconciler> reconcilerClass) {
+ return reconcilerClass.getCanonicalName();
+ }
+
+ protected Retry ensureRetry(Retry given) {
+ return given == null ? ControllerConfiguration.super.getRetry() : given;
+ }
+
+ protected RateLimiter ensureRateLimiter(RateLimiter given) {
+ return given == null ? ControllerConfiguration.super.getRateLimiter() : given;
}
@Override
@@ -146,14 +136,14 @@ public RateLimiter getRateLimiter() {
return rateLimiter;
}
+
@Override
- public List getDependentResources() {
- return dependentResources;
+ public Optional getWorkflowSpec() {
+ return Optional.ofNullable(workflowSpec);
}
- protected void setDependentResources(List dependentResources) {
- this.dependentResources = dependentResources == null ? Collections.emptyList()
- : Collections.unmodifiableList(dependentResources);
+ public void setWorkflowSpec(WorkflowSpec workflowSpec) {
+ this.workflowSpec = workflowSpec;
}
@Override
@@ -167,28 +157,15 @@ public ConfigurationService getConfigurationService() {
}
@Override
- public ResourceEventFilter getEventFilter() {
- return eventFilter;
- }
-
- /**
- * @deprecated Use {@link OnAddFilter}, {@link OnUpdateFilter} and {@link GenericFilter} instead
- *
- * @param eventFilter generic event filter
- */
- @Deprecated(forRemoval = true)
- protected void setEventFilter(ResourceEventFilter
eventFilter) {
- this.eventFilter = eventFilter;
- }
-
- @Override
- public Object getConfigurationFor(DependentResourceSpec spec) {
- return configurations.get(spec);
- }
-
- @Override
- public Optional> getItemStore() {
- return Optional.ofNullable(itemStore);
+ @SuppressWarnings("unchecked")
+ public C getConfigurationFor(DependentResourceSpec, P, C> spec) {
+ // first check if there's an overridden configuration at the controller level
+ var config = configurations.get(spec);
+ if (config == null) {
+ // if not, check the spec for configuration
+ config = spec.getConfiguration().orElse(null);
+ }
+ return (C) config;
}
@Override
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceClassResolver.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceClassResolver.java
index e15d53016a..001eb3e0de 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceClassResolver.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceClassResolver.java
@@ -5,6 +5,7 @@
public interface ResourceClassResolver {
- Class getResourceClass(Class extends Reconciler> reconcilerClass);
+ Class
getPrimaryResourceClass(
+ Class extends Reconciler
> reconcilerClass);
}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java
deleted file mode 100644
index d94504f50f..0000000000
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ResourceConfiguration.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package io.javaoperatorsdk.operator.api.config;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-
-import io.fabric8.kubernetes.api.model.HasMetadata;
-import io.fabric8.kubernetes.client.informers.cache.ItemStore;
-import io.javaoperatorsdk.operator.OperatorException;
-import io.javaoperatorsdk.operator.ReconcilerUtils;
-import io.javaoperatorsdk.operator.api.reconciler.Constants;
-import io.javaoperatorsdk.operator.processing.event.source.cache.BoundedItemStore;
-import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
-import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
-
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.DEFAULT_NAMESPACES_SET;
-import static io.javaoperatorsdk.operator.api.reconciler.Constants.WATCH_CURRENT_NAMESPACE_SET;
-
-public interface ResourceConfiguration {
-
- default String getResourceTypeName() {
- return ReconcilerUtils.getResourceTypeName(getResourceClass());
- }
-
- default Optional> onAddFilter() {
- return Optional.empty();
- }
-
- default Optional> onUpdateFilter() {
- return Optional.empty();
- }
-
- default Optional> genericFilter() {
- return Optional.empty();
- }
-
- /**
- * Retrieves the label selector that is used to filter which resources are actually watched by the
- * associated event source. See the official documentation on the
- * topic
- * for more details on syntax.
- *
- * @return the label selector filtering watched resources
- */
- default String getLabelSelector() {
- return null;
- }
-
- static String ensureValidLabelSelector(String labelSelector) {
- // might want to implement validation here?
- return labelSelector;
- }
-
- @SuppressWarnings("unchecked")
- default Class getResourceClass() {
- return (Class