Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c8b29e6

Browse files
csvirimetacosm
authored andcommittedNov 20, 2024
docs: migrate v5 changes to docsy (#2397)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 89115de commit c8b29e6

File tree

4 files changed

+248
-271
lines changed

4 files changed

+248
-271
lines changed
 

‎docsy/content/en/docs/dependent-resources/_index.md

+84-167
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: Dependent Resources
33
weight: 60
44
---
55

6+
# Dependent Resources
7+
68
## Motivations and Goals
79

810
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:
9698

9799
```java
98100
public class MyDependentResource extends KubernetesDependentResource<MyDependent, MyPrimary>
99-
implements Matcher<MyDependent, MyPrimary> {
100-
// your implementation
101+
implements Matcher<MyDependent, MyPrimary> {
102+
// your implementation
101103

102-
public Result<MyDependent> match(MyDependent actualResource, MyPrimary primary,
103-
Context<MyPrimary> context) {
104-
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
105-
}
104+
public Result<MyDependent> match(MyDependent actualResource, MyPrimary primary,
105+
Context<MyPrimary> context) {
106+
return GenericKubernetesResourceMatcher.match(this, actualResource, primary, context, true);
107+
}
106108
}
107109
```
108110

@@ -136,24 +138,24 @@ Deleted (or set to be garbage collected). The following example shows how to cre
136138
@KubernetesDependent(labelSelector = WebPageManagedDependentsReconciler.SELECTOR)
137139
class DeploymentDependentResource extends CRUDKubernetesDependentResource<Deployment, WebPage> {
138140

139-
public DeploymentDependentResource() {
140-
super(Deployment.class);
141-
}
142-
143-
@Override
144-
protected Deployment desired(WebPage webPage, Context<WebPage> context) {
145-
var deploymentName = deploymentName(webPage);
146-
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
147-
deployment.getMetadata().setName(deploymentName);
148-
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
149-
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
150-
151-
deployment.getSpec().getTemplate().getMetadata().getLabels()
152-
.put("app", deploymentName);
153-
deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
154-
.setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
155-
return deployment;
156-
}
141+
public DeploymentDependentResource() {
142+
super(Deployment.class);
143+
}
144+
145+
@Override
146+
protected Deployment desired(WebPage webPage, Context<WebPage> context) {
147+
var deploymentName = deploymentName(webPage);
148+
Deployment deployment = loadYaml(Deployment.class, getClass(), "deployment.yaml");
149+
deployment.getMetadata().setName(deploymentName);
150+
deployment.getMetadata().setNamespace(webPage.getMetadata().getNamespace());
151+
deployment.getSpec().getSelector().getMatchLabels().put("app", deploymentName);
152+
153+
deployment.getSpec().getTemplate().getMetadata().getLabels()
154+
.put("app", deploymentName);
155+
deployment.getSpec().getTemplate().getSpec().getVolumes().get(0)
156+
.setConfigMap(new ConfigMapVolumeSourceBuilder().withName(configMapName(webPage)).build());
157+
return deployment;
158+
}
157159
}
158160
```
159161

@@ -189,25 +191,25 @@ instances are managed by JOSDK, an example of which can be seen below:
189191
```java
190192

191193
@ControllerConfiguration(
192-
labelSelector = SELECTOR,
193-
dependents = {
194-
@Dependent(type = ConfigMapDependentResource.class),
195-
@Dependent(type = DeploymentDependentResource.class),
196-
@Dependent(type = ServiceDependentResource.class)
197-
})
194+
labelSelector = SELECTOR,
195+
dependents = {
196+
@Dependent(type = ConfigMapDependentResource.class),
197+
@Dependent(type = DeploymentDependentResource.class),
198+
@Dependent(type = ServiceDependentResource.class)
199+
})
198200
public class WebPageManagedDependentsReconciler
199-
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {
201+
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {
200202

201-
// omitted code
203+
// omitted code
202204

203-
@Override
204-
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
205+
@Override
206+
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
205207

206-
final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
207-
.getMetadata().getName();
208-
webPage.setStatus(createStatus(name));
209-
return UpdateControl.patchStatus(webPage);
210-
}
208+
final var name = context.getSecondaryResource(ConfigMap.class).orElseThrow()
209+
.getMetadata().getName();
210+
webPage.setStatus(createStatus(name));
211+
return UpdateControl.patchStatus(webPage);
212+
}
211213

212214
}
213215
```
@@ -222,104 +224,11 @@ It is also possible to wire dependent resources programmatically. In practice th
222224
developer is responsible for initializing and managing the dependent resources as well as calling
223225
their `reconcile` method. However, this makes it possible for developers to fully customize the
224226
reconciliation process. Standalone dependent resources should be used in cases when the managed use
225-
case does not fit.
226-
227-
Note that [Workflows](https://javaoperatorsdk.io/docs/workflows) also can be invoked from standalone
228-
resources.
229-
230-
The following sample is similar to the one above, simply performing additional checks, and
231-
conditionally creating an `Ingress`:
232-
233-
```java
234-
235-
@ControllerConfiguration
236-
public class WebPageStandaloneDependentsReconciler
237-
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage>,
238-
EventSourceInitializer<WebPage> {
239-
240-
private KubernetesDependentResource<ConfigMap, WebPage> configMapDR;
241-
private KubernetesDependentResource<Deployment, WebPage> deploymentDR;
242-
private KubernetesDependentResource<Service, WebPage> serviceDR;
243-
private KubernetesDependentResource<Service, WebPage> ingressDR;
244-
245-
public WebPageStandaloneDependentsReconciler(KubernetesClient kubernetesClient) {
246-
// 1.
247-
createDependentResources(kubernetesClient);
248-
}
249-
250-
@Override
251-
public List<EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
252-
// 2.
253-
return List.of(
254-
configMapDR.initEventSource(context),
255-
deploymentDR.initEventSource(context),
256-
serviceDR.initEventSource(context));
257-
}
258-
259-
@Override
260-
public UpdateControl<WebPage> reconcile(WebPage webPage, Context<WebPage> context) {
261-
262-
// 3.
263-
if (!isValidHtml(webPage.getHtml())) {
264-
return UpdateControl.patchStatus(setInvalidHtmlErrorMessage(webPage));
265-
}
266-
267-
// 4.
268-
configMapDR.reconcile(webPage, context);
269-
deploymentDR.reconcile(webPage, context);
270-
serviceDR.reconcile(webPage, context);
271-
272-
// 5.
273-
if (Boolean.TRUE.equals(webPage.getSpec().getExposed())) {
274-
ingressDR.reconcile(webPage, context);
275-
} else {
276-
ingressDR.delete(webPage, context);
277-
}
227+
case does not fit. You can, of course, also use [Workflows](https://javaoperatorsdk.io/docs/workflows) when managing
228+
resources programmatically.
278229

279-
// 6.
280-
webPage.setStatus(
281-
createStatus(configMapDR.getResource(webPage).orElseThrow().getMetadata().getName()));
282-
return UpdateControl.patchStatus(webPage);
283-
}
284-
285-
private void createDependentResources(KubernetesClient client) {
286-
this.configMapDR = new ConfigMapDependentResource();
287-
this.deploymentDR = new DeploymentDependentResource();
288-
this.serviceDR = new ServiceDependentResource();
289-
this.ingressDR = new IngressDependentResource();
290-
291-
Arrays.asList(configMapDR, deploymentDR, serviceDR, ingressDR).forEach(dr -> {
292-
dr.setKubernetesClient(client);
293-
dr.configureWith(new KubernetesDependentResourceConfig()
294-
.setLabelSelector(DEPENDENT_RESOURCE_LABEL_SELECTOR));
295-
});
296-
}
297-
298-
// omitted code
299-
}
300-
```
301-
302-
There are multiple things happening here:
303-
304-
1. Dependent resources are explicitly created and can be access later by reference.
305-
2. Event sources are produced by the dependent resources, but needs to be explicitly registered in
306-
this case by implementing
307-
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)
308-
interface.
309-
3. The input html is validated, and error message is set in case it is invalid.
310-
4. Reconciliation of dependent resources is called explicitly, but here the workflow
311-
customization is fully in the hand of the developer.
312-
5. An `Ingress` is created but only in case `exposed` flag set to true on custom resource. Tries to
313-
delete it if not.
314-
6. Status is set in a different way, this is just an alternative way to show, that the actual state
315-
can be read using the reference. This could be written in a same way as in the managed example.
316-
317-
See the full source code of
318-
sample [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java)
319-
.
320-
321-
Note also the Workflows feature makes it possible to also support this conditional creation use
322-
case in managed dependent resources.
230+
You can see a commented example of how to do
231+
so [here](https://github.com/operator-framework/java-operator-sdk/blob/main/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStandaloneDependentsReconciler.java).
323232

324233
## Creating/Updating Kubernetes Resources
325234

@@ -352,17 +261,17 @@ Since SSA is a complex feature, JOSDK implements a feature flag allowing users t
352261
these implementations. See
353262
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).
354263

355-
It is, however, important to note that these implementations are default, generic
356-
implementations that the framework can provide expected behavior out of the box. In many
357-
situations, these will work just fine but it is also possible to provide matching algorithms
264+
It is, however, important to note that these implementations are default, generic
265+
implementations that the framework can provide expected behavior out of the box. In many
266+
situations, these will work just fine but it is also possible to provide matching algorithms
358267
optimized for specific use cases. This is easily done by simply overriding
359-
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).
268+
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).
360269

361-
It is also possible to bypass the matching logic altogether to simply rely on the server-side
270+
It is also possible to bypass the matching logic altogether to simply rely on the server-side
362271
apply mechanism if always sending potentially unchanged resources to the cluster is not an issue.
363272
JOSDK's matching mechanism allows to spare some potentially useless calls to the Kubernetes API
364-
server. To bypass the matching feature completely, simply override the `match` method to always
365-
return `false`, thus telling JOSDK that the actual state never matches the desired one, making
273+
server. To bypass the matching feature completely, simply override the `match` method to always
274+
return `false`, thus telling JOSDK that the actual state never matches the desired one, making
366275
it always update the resources using SSA.
367276

368277
WARNING: Older versions of Kubernetes before 1.25 would create an additional resource version for every SSA update
@@ -389,20 +298,25 @@ tests [here](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/op
389298

390299
When dealing with multiple dependent resources of same type, the dependent resource implementation
391300
needs to know which specific resource should be targeted when reconciling a given dependent
392-
resource, since there will be multiple instances of that type which could possibly be used, each
393-
associated with the same primary resource. In order to do this, JOSDK relies on the
394-
[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)
395-
concept. Resource discriminators uniquely identify the target resource of a dependent resource.
396-
In the managed Kubernetes dependent resources case, the discriminator can be declaratively set
397-
using the `@KubernetesDependent` annotation:
398-
399-
```java
400-
401-
@KubernetesDependent(resourceDiscriminator = ConfigMap1Discriminator.class)
402-
public class MultipleManagedDependentResourceConfigMap1 {
403-
//...
404-
}
405-
```
301+
resource, since there could be multiple instances of that type which could possibly be used, each
302+
associated with the same primary resource. In this situation, JOSDK automatically selects the appropriate secondary
303+
resource matching the desired state associated with the primary resource. This makes sense because the desired
304+
state computation already needs to be able to discriminate among multiple related secondary resources to tell JOSDK how
305+
they should be reconciled.
306+
307+
There might be casees, 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:
308+
309+
- Implement your own `getSecondaryResource` method on your `DependentResource` implementation from scratch.
310+
- Override the `selectManagedSecondaryResource` method, if your `DependentResource` extends `AbstractDependentResource`.
311+
This should be relatively simple to override this method to optimize the matching to your needs. You can see an
312+
example of such an implementation in
313+
the [`ExternalWithStateDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/6cd0f884a7c9b60c81bd2d52da54adbd64d6e118/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/externalstate/ExternalWithStateDependentResource.java#L43-L49)
314+
class.
315+
- Override the `managedSecondaryResourceID` method, if your `DependentResource` extends `KubernetesDependentResource`,
316+
where it's very often possible to easily determine the `ResourceID` of the secondary resource. This would probably be
317+
the easiest solution if you're working with Kubernetes resources.
318+
319+
### Sharing an Event Source Between Dependent Resources
406320

407321
Dependent resources usually also provide event sources. When dealing with multiple dependents of
408322
the same type, one needs to decide whether these dependent resources should track the same
@@ -418,10 +332,10 @@ would look as follows:
418332
useEventSourceWithName = "configMapSource")
419333
```
420334

421-
A sample is provided as an integration test both
422-
for [managed](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentSameTypeIT.java)
423-
and
424-
for [standalone](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
335+
A sample is provided as an integration test both:
336+
for [managed](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleManagedDependentNoDiscriminatorIT.java)
337+
338+
For [standalone](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/MultipleDependentResourceIT.java)
425339
cases.
426340

427341
## Bulk Dependent Resources
@@ -484,15 +398,18 @@ also be created, one per dependent resource.
484398
See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java)
485399
as a sample.
486400

487-
488401
## GenericKubernetesResource based Dependent Resources
489402

490-
In rare circumstances resource handling where there is no class representation or just typeless handling might be needed.
491-
Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
492-
to support that.
403+
In rare circumstances resource handling where there is no class representation or just typeless handling might be
404+
needed.
405+
Fabric8 Client
406+
provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
407+
to support that.
493408

494-
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)
495-
. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
409+
For dependent resource this is supported
410+
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)
411+
. See
412+
samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).
496413

497414
## Other Dependent Resource Features
498415

‎docsy/content/en/docs/features/_index.md

+63-94
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: Features
33
weight: 50
44
---
55

6+
# Features
7+
68
The Java Operator SDK (JOSDK) is a high level framework and related tooling aimed at
79
facilitating the implementation of Kubernetes operators. The features are by default following
810
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:
6264
@Override
6365
public UpdateControl<MyCustomResource> reconcile(
6466
EventSourceTestCustomResource resource, Context context) {
65-
...
67+
// omitted code
68+
6669
return UpdateControl.patchStatus(resource).rescheduleAfter(10, TimeUnit.SECONDS);
6770
}
6871
```
@@ -73,7 +76,8 @@ without an update:
7376
@Override
7477
public UpdateControl<MyCustomResource> reconcile(
7578
EventSourceTestCustomResource resource, Context context) {
76-
...
79+
// omitted code
80+
7781
return UpdateControl.<MyCustomResource>noUpdate().rescheduleAfter(10, TimeUnit.SECONDS);
7882
}
7983
```
@@ -85,17 +89,31 @@ Those are the typical use cases of resource updates, however in some cases there
8589
the controller wants to update the resource itself (for example to add annotations) or not perform
8690
any updates, which is also supported.
8791

88-
It is also possible to update both the status and the resource with the
89-
`updateResourceAndStatus` method. In this case, the resource is updated first followed by the
90-
status, using two separate requests to the Kubernetes API.
91-
92-
You should always state your intent using `UpdateControl` and let the SDK deal with the actual
93-
updates instead of performing these updates yourself using the actual Kubernetes client so that
94-
the SDK can update its internal state accordingly.
95-
96-
Resource updates are protected using optimistic version control, to make sure that other updates
97-
that might have occurred in the mean time on the server are not overwritten. This is ensured by
98-
setting the `resourceVersion` field on the processed resources.
92+
It is also possible to update both the status and the resource with the `patchResourceAndStatus` method. In this case,
93+
the resource is updated first followed by the status, using two separate requests to the Kubernetes API.
94+
95+
From v5 `UpdateControl` only supports patching the resources, by default
96+
using [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/).
97+
It is important to understand how SSA works in Kubernetes. Mainly, resources applied using SSA
98+
should contain only the fields identifying the resource and those the user is interested in (a 'fully specified intent'
99+
in Kubernetes parlance), thus usually using a resource created from scratch, see
100+
[sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourcewithssa/PatchResourceWithSSAReconciler.java#L18-L22).
101+
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/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java#L16-L16).
102+
103+
Non-SSA based patch is still supported.
104+
You can control whether or not to use SSA
105+
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)
106+
and the related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource` method.
107+
Related integration test can be
108+
found [here](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/patchresourceandstatusnossa/PatchResourceAndStatusNoSSAReconciler.java).
109+
110+
Handling resources directly using the client, instead of delegating these updates operations to JOSDK by returning
111+
an `UpdateControl` at the end of your reconciliation, should work appropriately. However, we do recommend to
112+
use `UpdateControl` instead since JOSDK makes sure that the operations are handled properly, since there are subtleties
113+
to be aware of. For example, if you are using a finalizer, JOSDK makes sure to include it in your fully specified intent
114+
so that it is not unintentionally removed from the resource (which would happen if you omit it, since your controller is
115+
the designated manager for that field and Kubernetes interprets the finalizer being gone from the specified intent as a
116+
request for removal).
99117

100118
[`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)
101119
typically instructs the framework to remove the finalizer after the dependent
@@ -104,7 +122,8 @@ resource are cleaned up in `cleanup` implementation.
104122
```java
105123

106124
public DeleteControl cleanup(MyCustomResource customResource,Context context){
107-
...
125+
// omitted code
126+
108127
return DeleteControl.defaultDelete();
109128
}
110129

@@ -163,56 +182,7 @@ You can specify the name of the finalizer to use for your `Reconciler` using the
163182
[`@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)
164183
annotation. If you do not specify a finalizer name, one will be automatically generated for you.
165184

166-
## Automatic Observed Generation Handling
167-
168-
Having an `.observedGeneration` value on your resources' status is a best practice to
169-
indicate the last generation of the resource which was successfully reconciled by the controller.
170-
This helps users / administrators diagnose potential issues.
171-
172-
In order to have this feature working:
173-
174-
- the **status class** (not the resource itself) must implement the
175-
[`ObservedGenerationAware`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java)
176-
interface. See also
177-
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)
178-
convenience implementation that you can extend in your own status class implementations.
179-
- The other condition is that the `CustomResource.getStatus()` method should not return `null`.
180-
So the status should be instantiated when the object is returned using the `UpdateControl`.
181-
182-
If these conditions are fulfilled and generation awareness is activated, the observed generation
183-
is automatically set by the framework after the `reconcile` method is called. Note that the
184-
observed generation is also updated even when `UpdateControl.noUpdate()` is returned from the
185-
reconciler. See this feature at work in
186-
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)
187-
.
188-
189-
```java
190-
public class WebPageStatus extends ObservedGenerationAwareStatus {
191-
192-
private String htmlConfigMap;
193-
194-
...
195-
}
196-
```
197-
198-
Initializing status automatically on custom resource could be done by overriding the `initStatus` method
199-
of `CustomResource`. However, this is NOT advised, since breaks the status patching if you use:
200-
`UpdateControl.patchStatus`. See
201-
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)
202-
.
203-
204-
```java
205-
@Group("sample.javaoperatorsdk")
206-
@Version("v1")
207-
public class WebPage extends CustomResource<WebPageSpec, WebPageStatus>
208-
implements Namespaced {
209-
210-
@Override
211-
protected WebPageStatus initStatus() {
212-
return new WebPageStatus();
213-
}
214-
}
215-
```
185+
From v5 by default finalizer is added using Served Side Apply. See also UpdateControl in docs.
216186

217187
## Generation Awareness and Event Filtering
218188

@@ -231,9 +201,7 @@ To turn off this feature, set `generationAwareEventProcessing` to `false` for th
231201
## Support for Well Known (non-custom) Kubernetes Resources
232202

233203
A Controller can be registered for a non-custom resource, so well known Kubernetes resources like (
234-
`Ingress`, `Deployment`,...). Note that automatic observed generation handling is not supported
235-
for these resources, though, in this case, the handling of the observed generation is probably
236-
handled by the primary controller.
204+
`Ingress`, `Deployment`,...).
237205

238206
See
239207
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)
@@ -243,11 +211,12 @@ for reconciling deployments.
243211
public class DeploymentReconciler
244212
implements Reconciler<Deployment>, TestExecutionInfoProvider {
245213

246-
@Override
247-
public UpdateControl<Deployment> reconcile(
248-
Deployment resource, Context context) {
249-
...
250-
}
214+
@Override
215+
public UpdateControl<Deployment> reconcile(
216+
Deployment resource, Context context) {
217+
// omitted code
218+
}
219+
}
251220
```
252221

253222
## Max Interval Between Reconciliations
@@ -265,6 +234,7 @@ standard annotation:
265234
@ControllerConfiguration(maxReconciliationInterval = @MaxReconciliationInterval(
266235
interval = 50,
267236
timeUnit = TimeUnit.MILLISECONDS))
237+
public class MyReconciler implements Reconciler<HasMetadata> {}
268238
```
269239

270240
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
487457

488458
### Registering Event Sources
489459

490-
To register event sources, your `Reconciler` has to implement the
491-
[`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)
492-
interface and initialize a list of event sources to register. One way to see this in action is
460+
To register event sources, your `Reconciler` has to override the `prepareEventSources` and return
461+
list of event sources to register. One way to see this in action is
493462
to look at the
494463
[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)
495464
(irrelevant details omitted):
@@ -499,8 +468,9 @@ to look at the
499468
@ControllerConfiguration
500469
public class WebappReconciler
501470
implements Reconciler<Webapp>, Cleaner<Webapp>, EventSourceInitializer<Webapp> {
502-
503-
@Override
471+
// ommitted code
472+
473+
@Override
504474
public Map<String, EventSource> prepareEventSources(EventSourceContext<Webapp> context) {
505475
InformerConfiguration<Tomcat> configuration =
506476
InformerConfiguration.from(Tomcat.class, context)
@@ -512,7 +482,7 @@ public class WebappReconciler
512482
return EventSourceInitializer
513483
.nameEventSources(new InformerEventSource<>(configuration, context));
514484
}
515-
...
485+
516486
}
517487
```
518488

@@ -656,15 +626,15 @@ registering an associated `Informer` and then calling the `changeNamespaces` met
656626

657627
```java
658628

659-
public static void main(String[]args)throws IOException{
660-
KubernetesClient client=new DefaultKubernetesClient();
661-
Operator operator=new Operator(client);
662-
RegisteredController registeredController=operator.register(new WebPageReconciler(client));
663-
operator.installShutdownHook();
664-
operator.start();
629+
public static void main(String[] args) {
630+
KubernetesClient client = new DefaultKubernetesClient();
631+
Operator operator = new Operator(client);
632+
RegisteredController registeredController = operator.register(new WebPageReconciler(client));
633+
operator.installShutdownHook();
634+
operator.start();
665635

666-
// call registeredController further while operator is running
667-
}
636+
// call registeredController further while operator is running
637+
}
668638

669639
```
670640

@@ -676,8 +646,7 @@ configured appropriately so that the `followControllerNamespaceChanges` method r
676646
```java
677647

678648
@ControllerConfiguration
679-
public class MyReconciler
680-
implements Reconciler<TestCustomResource>, EventSourceInitializer<TestCustomResource> {
649+
public class MyReconciler implements Reconciler<TestCustomResource> {
681650

682651
@Override
683652
public Map<String, EventSource> prepareEventSources(
@@ -688,7 +657,7 @@ public class MyReconciler
688657
.withNamespacesInheritedFromController(context)
689658
.build(), context);
690659

691-
return EventSourceInitializer.nameEventSources(configMapES);
660+
return EventSourceUtils.nameEventSources(configMapES);
692661
}
693662

694663
}
@@ -767,8 +736,8 @@ You can use a different implementation by overriding the default one provided by
767736
follows:
768737

769738
```java
770-
Metrics metrics= …;
771-
Operator operator = new Operator(client, o -> o.withMetrics());
739+
Metrics metrics; // initialize your metrics implementation
740+
Operator operator = new Operator(client, o -> o.withMetrics(metrics));
772741
```
773742

774743
### Micrometer implementation
@@ -784,8 +753,8 @@ To create a `MicrometerMetrics` implementation that behaves how it has historica
784753
instance via:
785754

786755
```java
787-
MeterRegistry registry= …;
788-
Metrics metrics=new MicrometerMetrics(registry)
756+
MeterRegistry registry; // initialize your registry implementation
757+
Metrics metrics = new MicrometerMetrics(registry);
789758
```
790759

791760
Note, however, that this constructor is deprecated and we encourage you to use the factory methods instead, which either
@@ -801,7 +770,7 @@ basis, deleting the associated meters after 5 seconds when a resource is deleted
801770
MicrometerMetrics.newPerResourceCollectingMicrometerMetricsBuilder(registry)
802771
.withCleanUpDelayInSeconds(5)
803772
.withCleaningThreadNumber(2)
804-
.build()
773+
.build();
805774
```
806775

807776
### Operator SDK metrics
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: Migrating from v4.7 to v5.0
3+
description: Migrating from v4.7 to v5.0
4+
---
5+
6+
# Migrating from v4.7 to v5.0
7+
8+
## API Tweaks
9+
10+
1. [Result of managed dependent resources](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/dependent/managed/ManagedDependentResourceContext.java#L55-L57)
11+
is not `Optional` anymore. In case you use this result, simply use the result
12+
objects directly.
13+
2. `EventSourceInitializer` is not a separate interface anymore. It is part of the `Reconciler` interface with a
14+
default implementation. You can simply remove this interface from your reconciler. The
15+
[`EventSourceUtils`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/EventSourceUtils.java#L11-L11)
16+
now contains all the utility methods used for event sources naming that were previously defined in
17+
the `EventSourceInitializer` interface.
18+
3. Similarly, the `EventSourceProvider` interface has been remove, replaced by explicit initialization of the associated
19+
event source on `DependentResource` via the `
20+
Optional<? extends EventSource<R, P>> eventSource(EventSourceContext<P> eventSourceContext)` method.
21+
4. Event sources are now explicitly named (via the `name` method of the `EventSource` interface). Built-in event sources
22+
implementation have been updated to allow you to specify a name when instantiating them. If you don't provide a name
23+
for your `EventSource` implementation (for example, by using its default, no-arg constructor), one will be
24+
automatically generated. This simplifies the API to define event source to
25+
`List<EventSource> prepareEventSources(EventSourceContext<P> context)`.
26+
!!! IMPORTANT !!!
27+
If you use dynamic registration of event sources, be sure to name your event sources explicitly as letting JOSDK name
28+
them automatically might result in duplicated event sources being registered as JOSDK relies on the name to identify
29+
event sources and concurrent, dynamic registration might lead to identical event sources having different generated
30+
names, thus leading JOSDK to consider them as different and hence, register them multiple times.
31+
5. Updates through `UpdateControl` now
32+
use [Server Side Apply (SSA)](https://kubernetes.io/docs/reference/using-api/server-side-apply/) by default to add
33+
the finalizer and for all
34+
the patch operations in `UpdateControl`. The update operations were removed. If you do not wish to use SSA, you can
35+
deactivate the feature using `ConfigurationService.useSSAToPatchPrimaryResource` and
36+
related `ConfigurationServiceOverrider.withUseSSAToPatchPrimaryResource`.
37+
38+
!!! IMPORTANT !!!
39+
40+
See known issues with migration from non-SSA to SSA based status updates here:
41+
[integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L71-L82)
42+
where it is demonstrated. Also, the related part of
43+
a [workaround](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/StatusPatchSSAMigrationIT.java#L110-L116).
44+
45+
6. `ManagedDependentResourceContext` has been renamed to `ManagedWorkflowAndDependentResourceContext` and is accessed
46+
via the accordingly renamed `managedWorkflowAndDependentResourceContext` method.
47+
7. `ResourceDiscriminator` was removed. In most of the cases you can just delete the discriminator, everything should
48+
work without it by default. To optimize and handle special cases see the relevant section
49+
in [Dependent Resource documentation](/docs/dependent-resources#multiple-dependent-resources-of-same-type).
50+
8. `ConfigurationService.getTerminationTimeoutSeconds` and associated overriding mechanism have been removed,
51+
use `Operator.stop(Duration)` instead.
52+
9. `Operator.installShutdownHook()` has been removed, use `Operator.installShutdownHook(Duration)` instead
53+
10. Automated observed generation handling feature was removed (`ObservedGenerationAware` interface
54+
and `ObservedGenerationAwareStatus` class were deleted). Manually handling observed generation is fairly easy to do
55+
in your reconciler, however, it cannot be done automatically when using SSA. We therefore removed the feature since
56+
it would have been confusing to have a different behavior for SSA and non-SSA cases. For an example of how to do
57+
observed generation handling manually in your reconciler, see
58+
[this sample](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manualobservedgeneration/ManualObservedGenerationReconciler.java).
59+
11. `BulkDependentResource` now supports [read-only mode](https://github.com/operator-framework/java-operator-sdk/issues/2233).
60+
This also means, that `BulkDependentResource` now does not automatically implement `Creator` and `Deleter` as before.
61+
Make sure to implement those interfaces in your bulk dependent resources. You can use also the new helper interface, the
62+
[`CRUDBulkDependentResource`](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/CRUDBulkDependentResource.java)
63+
what also implement `BulkUpdater` interface.

‎docsy/content/en/docs/workflows/_index.md

+38-10
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,10 @@ reconciliation process.
4141
condition holds or not. This is a very useful feature when your operator needs to handle different flavors of the
4242
platform (e.g. OpenShift vs plain Kubernetes) and/or change its behavior based on the availability of optional
4343
resources / features (e.g. CertManager, a specific Ingress controller, etc.).
44-
45-
Activation condition is semi-experimental at the moment, and it has its limitations.
46-
For example event sources cannot be shared between multiple managed dependent resources which use activation condition.
47-
The intention is to further improve and explore the possibilities with this approach.
44+
45+
Activation condition is semi-experimental at the moment, and it has its limitations.
46+
For example event sources cannot be shared between multiple managed dependent resources which use activation condition.
47+
The intention is to further improve and explore the possibilities with this approach.
4848

4949
## Defining Workflows
5050

@@ -120,7 +120,7 @@ page sample):
120120
@ControllerConfiguration(
121121
labelSelector = WebPageDependentsWorkflowReconciler.DEPENDENT_RESOURCE_LABEL_SELECTOR)
122122
public class WebPageDependentsWorkflowReconciler
123-
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage>, EventSourceInitializer<WebPage> {
123+
implements Reconciler<WebPage>, ErrorStatusHandler<WebPage> {
124124

125125
public static final String DEPENDENT_RESOURCE_LABEL_SELECTOR = "!low-level";
126126
private static final Logger log =
@@ -131,7 +131,7 @@ public class WebPageDependentsWorkflowReconciler
131131
private KubernetesDependentResource<Service, WebPage> serviceDR;
132132
private KubernetesDependentResource<Ingress, WebPage> ingressDR;
133133

134-
private Workflow<WebPage> workflow;
134+
private final Workflow<WebPage> workflow;
135135

136136
public WebPageDependentsWorkflowReconciler(KubernetesClient kubernetesClient) {
137137
initDependentResources(kubernetesClient);
@@ -145,7 +145,7 @@ public class WebPageDependentsWorkflowReconciler
145145

146146
@Override
147147
public Map<String, EventSource> prepareEventSources(EventSourceContext<WebPage> context) {
148-
return EventSourceInitializer.nameEventSources(
148+
return EventSourceUtils.nameEventSources(
149149
configMapDR.initEventSource(context),
150150
deploymentDR.initEventSource(context),
151151
serviceDR.initEventSource(context),
@@ -198,7 +198,7 @@ demonstrated using examples:
198198
2. Root nodes, i.e. nodes in the graph that do not depend on other nodes are reconciled first,
199199
in a parallel manner.
200200
3. A DR is reconciled if it does not depend on any other DRs, or *ALL* the DRs it depends on are
201-
reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition,
201+
reconciled and ready. If a DR defines a reconcile pre-condition and/or an activation condition,
202202
then these condition must become `true` before the DR is reconciled.
203203
4. A DR is considered *ready* if it got successfully reconciled and any ready post-condition it
204204
might define is `true`.
@@ -328,10 +328,38 @@ provides such a delete post-condition implementation in the form of
328328

329329
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).
330330

331-
In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource`
331+
In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource`
332332
and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage Collector would delete the resources.
333333
In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement
334-
`GargageCollected` interface, otherwise the deletion order won't be guaranteed.
334+
`GargageCollected` interface, otherwise the deletion order won't be guaranteed.
335+
336+
337+
## Explicit Managed Workflow Invocation
338+
339+
Managed workflows, i.e. ones that are declared via annotations and therefore completely managed by JOSDK, are reconciled
340+
before the primary resource. Each dependent resource that can be reconciled (according to the workflow configuration)
341+
will therefore be reconciled before the primary reconciler is called to reconcile the primary resource. There are,
342+
however, situations where it would be be useful to perform additional steps before the workflow is reconciled, for
343+
example to validate the current state, execute arbitrary logic or even skip reconciliation altogether. Explicit
344+
invocation of managed workflow was therefore introduced to solve these issues.
345+
346+
To use this feature, you need to set the `explicitInvocation` field to `true` on the `@Workflow` annotation and then
347+
call the `reconcileManagedWorkflow` method from the `
348+
ManagedWorkflowAndDependentResourceContext` retrieved from the reconciliation `Context` provided as part of your primary
349+
resource reconciler `reconcile` method arguments.
350+
351+
See
352+
related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitInvocationIT.java)
353+
for more details.
354+
355+
For `cleanup`, if the `Cleaner` interface is implemented, the `cleanupManageWorkflow()` needs to be called explicitly.
356+
However, if `Cleaner` interface is not implemented, it will be called implicitly.
357+
See
358+
related [integration test](https://github.com/operator-framework/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/WorkflowExplicitCleanupIT.java).
359+
360+
While nothing prevents calling the workflow multiple times in a reconciler, it isn't typical or even recommended to do
361+
so. Conversely, if explicit invocation is requested but `reconcileManagedWorkflow` is not called in the primary resource
362+
reconciler, the workflow won't be reconciled at all.
335363

336364
## Notes and Caveats
337365

0 commit comments

Comments
 (0)
Please sign in to comment.