@@ -199,25 +199,25 @@ instances are managed by JOSDK, an example of which can be seen below:
199199``` java
200200
201201@ControllerConfiguration (
202- labelSelector = SELECTOR ,
203- dependents = {
204- @Dependent (type = ConfigMapDependentResource . class),
205- @Dependent (type = DeploymentDependentResource . class),
206- @Dependent (type = ServiceDependentResource . class)
207- })
202+ labelSelector = SELECTOR ,
203+ dependents = {
204+ @Dependent (type = ConfigMapDependentResource . class),
205+ @Dependent (type = DeploymentDependentResource . class),
206+ @Dependent (type = ServiceDependentResource . class)
207+ })
208208public class WebPageManagedDependentsReconciler
209- implements Reconciler<WebPage > , ErrorStatusHandler<WebPage > {
209+ implements Reconciler<WebPage > , ErrorStatusHandler<WebPage > {
210210
211- // omitted code
211+ // omitted code
212212
213- @Override
214- public UpdateControl<WebPage > reconcile (WebPage webPage , Context<WebPage > context ) {
213+ @Override
214+ public UpdateControl<WebPage > reconcile (WebPage webPage , Context<WebPage > context ) {
215215
216- final var name = context. getSecondaryResource(ConfigMap . class). orElseThrow()
217- .getMetadata(). getName();
218- webPage. setStatus(createStatus(name));
219- return UpdateControl . patchStatus(webPage);
220- }
216+ final var name = context. getSecondaryResource(ConfigMap . class). orElseThrow()
217+ .getMetadata(). getName();
218+ webPage. setStatus(createStatus(name));
219+ return UpdateControl . patchStatus(webPage);
220+ }
221221
222222}
223223```
@@ -244,68 +244,68 @@ conditionally creating an `Ingress`:
244244
245245@ControllerConfiguration
246246public class WebPageStandaloneDependentsReconciler
247- implements Reconciler<WebPage > , ErrorStatusHandler<WebPage > ,
248- EventSourceInitializer<WebPage > {
249-
250- private KubernetesDependentResource<ConfigMap , WebPage > configMapDR;
251- private KubernetesDependentResource<Deployment , WebPage > deploymentDR;
252- private KubernetesDependentResource<Service , WebPage > serviceDR;
253- private KubernetesDependentResource<Service , WebPage > ingressDR;
254-
255- public WebPageStandaloneDependentsReconciler (KubernetesClient kubernetesClient ) {
256- // 1.
257- createDependentResources(kubernetesClient);
258- }
259-
260- @Override
261- public List<EventSource > prepareEventSources (EventSourceContext<WebPage > context ) {
262- // 2.
263- return List . of(
264- configMapDR. initEventSource(context),
265- deploymentDR. initEventSource(context),
266- serviceDR. initEventSource(context));
267- }
268-
269- @Override
270- public UpdateControl<WebPage > reconcile (WebPage webPage , Context<WebPage > context ) {
271-
272- // 3.
273- if (! isValidHtml(webPage. getHtml())) {
274- return UpdateControl . patchStatus(setInvalidHtmlErrorMessage(webPage));
275- }
276-
277- // 4.
278- configMapDR. reconcile(webPage, context);
279- deploymentDR. reconcile(webPage, context);
280- serviceDR. reconcile(webPage, context);
281-
282- // 5.
283- if (Boolean . TRUE. equals(webPage. getSpec(). getExposed())) {
284- ingressDR. reconcile(webPage, context);
285- } else {
286- ingressDR. delete(webPage, context);
287- }
288-
289- // 6.
290- webPage. setStatus(
291- createStatus(configMapDR. getResource(webPage). orElseThrow(). getMetadata(). getName()));
292- return UpdateControl . patchStatus(webPage);
293- }
294-
295- private void createDependentResources (KubernetesClient client ) {
296- this . configMapDR = new ConfigMapDependentResource ();
297- this . deploymentDR = new DeploymentDependentResource ();
298- this . serviceDR = new ServiceDependentResource ();
299- this . ingressDR = new IngressDependentResource ();
300-
301- Arrays . asList(configMapDR, deploymentDR, serviceDR, ingressDR). forEach(dr - > {
302- dr. setKubernetesClient(client);
303- dr. configureWith(new KubernetesDependentResourceConfig ()
304- .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR ));
305- });
306- }
307-
308- // omitted code
247+ implements Reconciler<WebPage > , ErrorStatusHandler<WebPage > ,
248+ EventSourceInitializer<WebPage > {
249+
250+ private KubernetesDependentResource<ConfigMap , WebPage > configMapDR;
251+ private KubernetesDependentResource<Deployment , WebPage > deploymentDR;
252+ private KubernetesDependentResource<Service , WebPage > serviceDR;
253+ private KubernetesDependentResource<Service , WebPage > ingressDR;
254+
255+ public WebPageStandaloneDependentsReconciler (KubernetesClient kubernetesClient ) {
256+ // 1.
257+ createDependentResources(kubernetesClient);
258+ }
259+
260+ @Override
261+ public List<EventSource > prepareEventSources (EventSourceContext<WebPage > context ) {
262+ // 2.
263+ return List . of(
264+ configMapDR. initEventSource(context),
265+ deploymentDR. initEventSource(context),
266+ serviceDR. initEventSource(context));
267+ }
268+
269+ @Override
270+ public UpdateControl<WebPage > reconcile (WebPage webPage , Context<WebPage > context ) {
271+
272+ // 3.
273+ if (! isValidHtml(webPage. getHtml())) {
274+ return UpdateControl . patchStatus(setInvalidHtmlErrorMessage(webPage));
275+ }
276+
277+ // 4.
278+ configMapDR. reconcile(webPage, context);
279+ deploymentDR. reconcile(webPage, context);
280+ serviceDR. reconcile(webPage, context);
281+
282+ // 5.
283+ if (Boolean . TRUE. equals(webPage. getSpec(). getExposed())) {
284+ ingressDR. reconcile(webPage, context);
285+ } else {
286+ ingressDR. delete(webPage, context);
287+ }
288+
289+ // 6.
290+ webPage. setStatus(
291+ createStatus(configMapDR. getResource(webPage). orElseThrow(). getMetadata(). getName()));
292+ return UpdateControl . patchStatus(webPage);
293+ }
294+
295+ private void createDependentResources (KubernetesClient client ) {
296+ this . configMapDR = new ConfigMapDependentResource ();
297+ this . deploymentDR = new DeploymentDependentResource ();
298+ this . serviceDR = new ServiceDependentResource ();
299+ this . ingressDR = new IngressDependentResource ();
300+
301+ Arrays . asList(configMapDR, deploymentDR, serviceDR, ingressDR). forEach(dr - > {
302+ dr. setKubernetesClient(client);
303+ dr. configureWith(new KubernetesDependentResourceConfig ()
304+ .setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR ));
305+ });
306+ }
307+
308+ // omitted code
309309}
310310```
311311
@@ -331,6 +331,50 @@ sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/
331331Note also the Workflows feature makes it possible to also support this conditional creation use
332332case in managed dependent resources.
333333
334+ ## Creating/Updating Kubernetes Resources
335+
336+ From version 4.4 of the framework the resources are created and updated
337+ using [ Server Side Apply] ( https://kubernetes.io/docs/reference/using-api/server-side-apply/ )
338+ , thus the desired state is simply sent using this approach to update the actual resource.
339+
340+ ## Comparing desired and actual state (matching)
341+
342+ During the reconciliation of a dependent resource, the desired state is matched with the actual
343+ state from the caches. The dependent resource only gets updated on the server if the actual,
344+ observed state differs from the desired one. Comparing these two states is a complex problem
345+ when dealing with Kubernetes resources because a strict equality check is usually not what is
346+ wanted due to the fact that multiple fields might be automatically updated or added by
347+ the platform (
348+ by [ dynamic admission controllers] ( https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/ )
349+ or validation webhooks, for example). Solving this problem in a generic way is therefore a tricky
350+ proposition.
351+
352+ JOSDK provides such a generic matching implementation which is used by default:
353+ [ SSABasedGenericKubernetesResourceMatcher] ( https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/SSABasedGenericKubernetesResourceMatcher.java )
354+ This implementation relies on the managed fields used by the Server Side Apply feature to
355+ compare only the values of the fields that the controller manages. This ensures that only
356+ semantically relevant fields are compared. See javadoc for further details.
357+
358+ JOSDK versions prior to 4.4 were using a different matching algorithm as implemented in
359+ [ GenericKubernetesResourceMatcher] ( https://github.com/java-operator-sdk/java-operator-sdk/blob/e16559fd41bbb8bef6ce9d1f47bffa212a941b09/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesResourceMatcher.java ) .
360+
361+ Since SSA is a complex feature, JOSDK implements a feature flag allowing users to switch between
362+ these implementations. See
363+ 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 ) .
364+
365+ It is, however, important to note that these implementations are default, generic
366+ implementations that the framework can provide expected behavior out of the box. In many
367+ situations, these will work just fine but it is also possible to provide matching algorithms
368+ optimized for specific use cases. This is easily done by simply overriding
369+ 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 ) .
370+
371+ It is also possible to bypass the matching logic altogether to simply rely on the server-side
372+ apply mechanism if always sending potentially unchanged resources to the cluster is not an issue.
373+ JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API
374+ server. To bypass the matching feature completely, simply override the ` match ` method to always
375+ return ` false ` , thus telling JOSDK that the actual state never matches the desired one, making
376+ it always update the resources using SSA.
377+
334378## Telling JOSDK how to find which secondary resources are associated with a given primary resource
335379
336380[ ` KubernetesDependentResource ` ] ( https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java )
@@ -467,29 +511,31 @@ as a sample.
467511 there should be a shared event source between them, or a label selector on the event sources
468512 to select only the relevant events, see
469513 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 )
470- .
514+ .
471515
472516## "Read-only" Dependent Resources vs. Event Source
473517
474- 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 ) .
518+ See Integration test for a read-only
519+ dependent [ here] ( https://github.com/java-operator-sdk/java-operator-sdk/blob/249b41f3c68c4d0e9c77c41eca647a69a24347b0/operator-framework/src/test/java/io/javaoperatorsdk/operator/PrimaryToSecondaryDependentIT.java ) .
475520
476521Some secondary resources only exist as input for the reconciliation process and are never
477522updated * by a controller* (they might, and actually usually do, get updated by users interacting
478- with the resources directly, however). This might be the case, for example, of a ` ConfigMap ` that is
523+ with the resources directly, however). This might be the case, for example, of a ` ConfigMap ` that is
479524used to configure common characteristics of multiple resources in one convenient place.
480525
481- In such situations, one might wonder whether it makes sense to create a dependent resource in
482- this case or simply use an ` EventSource ` so that the primary resource gets reconciled whenever a
483- user changes the resource. Typical dependent resources provide a desired state that the
484- reconciliation process attempts to match. In the case of so-called read-only dependents, though,
485- there is no such desired state because the operator / controller will never update the resource
486- itself, just react to external changes to it. An ` EventSource ` would achieve the same result.
526+ In such situations, one might wonder whether it makes sense to create a dependent resource in
527+ this case or simply use an ` EventSource ` so that the primary resource gets reconciled whenever a
528+ user changes the resource. Typical dependent resources provide a desired state that the
529+ reconciliation process attempts to match. In the case of so-called read-only dependents, though,
530+ there is no such desired state because the operator / controller will never update the resource
531+ itself, just react to external changes to it. An ` EventSource ` would achieve the same result.
487532
488- Using a dependent resource for that purpose instead of a simple ` EventSource ` , however, provides
533+ Using a dependent resource for that purpose instead of a simple ` EventSource ` , however, provides
489534several benefits:
535+
490536- dependents can be created declaratively, while an event source would need to be manually created
491- - if dependents are already used in a controller, it makes sense to unify the handling of all
537+ - if dependents are already used in a controller, it makes sense to unify the handling of all
492538 secondary resources as dependents from a code organization perspective
493- - dependent resources can also interact with the workflow feature, thus allowing the read-only
494- resource to participate in conditions, in particular to decide whether or not the primary
539+ - dependent resources can also interact with the workflow feature, thus allowing the read-only
540+ resource to participate in conditions, in particular to decide whether or not the primary
495541 resource needs/can be reconciled using reconcile pre-conditions
0 commit comments