Skip to content

Commit e9ec0d8

Browse files
committed
feat: configuration adapters (#3177)
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent ebbbe16 commit e9ec0d8

19 files changed

+2271
-2
lines changed

docs/content/en/docs/documentation/configuration.md

Lines changed: 208 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,212 @@ For more information on how to use this feature, we recommend looking at how thi
149149
`KubernetesDependentResource` in the core framework, `SchemaDependentResource` in the samples or `CustomAnnotationDep`
150150
in the `BaseConfigurationServiceTest` test class.
151151

152-
## EventSource-level configuration
152+
## Loading Configuration from External Sources
153+
154+
JOSDK ships a `ConfigLoader` that bridges any key-value configuration source to the operator and
155+
controller configuration APIs. This lets you drive operator behaviour from environment variables,
156+
system properties, YAML files, or any config library (MicroProfile Config, SmallRye Config,
157+
Spring Environment, etc.) without writing glue code by hand.
158+
159+
### Architecture
160+
161+
The system is built around two thin abstractions:
162+
163+
- **[`ConfigProvider`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/ConfigProvider.java)**
164+
— a single-method interface that resolves a typed value for a dot-separated key:
165+
166+
```java
167+
public interface ConfigProvider {
168+
<T> Optional<T> getValue(String key, Class<T> type);
169+
}
170+
```
171+
172+
- **[`ConfigLoader`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/ConfigLoader.java)**
173+
— reads all known JOSDK keys from a `ConfigProvider` and returns
174+
`Consumer<ConfigurationServiceOverrider>` / `Consumer<ControllerConfigurationOverrider<R>>`
175+
values that you pass directly to the `Operator` constructor or `operator.register()`.
176+
177+
The default `ConfigLoader` (no-arg constructor) stacks environment variables over system
178+
properties: environment variables win, system properties are the fallback.
179+
180+
```java
181+
// uses env vars + system properties out of the box
182+
Operator operator = new Operator(ConfigLoader.getDefault().applyConfigs());
183+
```
184+
185+
### Built-in Providers
186+
187+
| Provider | Source | Key mapping |
188+
|---|---|---|
189+
| `EnvVarConfigProvider` | `System.getenv()` | dots and hyphens → underscores, upper-cased (`josdk.check-crd``JOSDK_CHECK_CRD`) |
190+
| `PropertiesConfigProvider` | `java.util.Properties` or `.properties` file | key used as-is; use `PropertiesConfigProvider.systemProperties()` to read Java system properties |
191+
| `YamlConfigProvider` | YAML file | dot-separated key traverses nested mappings |
192+
| `AgregatePriorityListConfigProvider` | ordered list of providers | first non-empty result wins |
193+
194+
All string-based providers convert values to the target type automatically.
195+
Supported types: `String`, `Boolean`, `Integer`, `Long`, `Double`, `Duration` (ISO-8601, e.g. `PT30S`).
196+
197+
### Plugging in Any Config Library
198+
199+
`ConfigProvider` is a single-method interface, so adapting any config library takes only a few
200+
lines. As an example, here is an adapter for
201+
[SmallRye Config](https://smallrye.io/smallrye-config/):
202+
203+
```java
204+
public class SmallRyeConfigProvider implements ConfigProvider {
205+
206+
private final SmallRyeConfig config;
207+
208+
public SmallRyeConfigProvider(SmallRyeConfig config) {
209+
this.config = config;
210+
}
211+
212+
@Override
213+
public <T> Optional<T> getValue(String key, Class<T> type) {
214+
return config.getOptionalValue(key, type);
215+
}
216+
}
217+
```
218+
219+
The same pattern applies to MicroProfile Config, Spring `Environment`, Apache Commons
220+
Configuration, or any other library that can look up typed values by string key.
221+
222+
### Wiring Everything Together
223+
224+
Pass the `ConfigLoader` results when constructing the operator and registering reconcilers:
225+
226+
```java
227+
// Load operator-wide config from a YAML file via SmallRye Config
228+
URL configUrl = MyOperator.class.getResource("/application.yaml");
229+
var configLoader = new ConfigLoader(
230+
new SmallRyeConfigProvider(
231+
new SmallRyeConfigBuilder()
232+
.withSources(new YamlConfigSource(configUrl))
233+
.build()));
234+
235+
// applyConfigs() → Consumer<ConfigurationServiceOverrider>
236+
Operator operator = new Operator(configLoader.applyConfigs());
237+
238+
// applyControllerConfigs(name) → Consumer<ControllerConfigurationOverrider<R>>
239+
operator.register(new MyReconciler(),
240+
configLoader.applyControllerConfigs(MyReconciler.NAME));
241+
```
242+
243+
Only keys that are actually present in the source are applied; everything else retains its
244+
programmatic or annotation-based default.
245+
246+
You can also compose multiple sources with explicit priority using
247+
`AgregatePriorityListConfigProvider`:
248+
249+
```java
250+
var configLoader = new ConfigLoader(
251+
new AgregatePriorityListConfigProvider(List.of(
252+
new EnvVarConfigProvider(), // highest priority
253+
PropertiesConfigProvider.systemProperties(),
254+
new YamlConfigProvider(Path.of("config/operator.yaml")) // lowest priority
255+
)));
256+
```
257+
258+
### Operator-Level Configuration Keys
259+
260+
All operator-level keys are prefixed with `josdk.`.
261+
262+
#### General
263+
264+
| Key | Type | Description |
265+
|---|---|---|
266+
| `josdk.check-crd` | `Boolean` | Validate CRDs against local model on startup |
267+
| `josdk.close-client-on-stop` | `Boolean` | Close the Kubernetes client when the operator stops |
268+
| `josdk.use-ssa-to-patch-primary-resource` | `Boolean` | Use Server-Side Apply to patch the primary resource |
269+
| `josdk.clone-secondary-resources-when-getting-from-cache` | `Boolean` | Clone secondary resources on cache reads |
270+
271+
#### Reconciliation
272+
273+
| Key | Type | Description |
274+
|---|---|---|
275+
| `josdk.reconciliation.concurrent-threads` | `Integer` | Thread pool size for reconciliation |
276+
| `josdk.reconciliation.termination-timeout` | `Duration` | How long to wait for in-flight reconciliations to finish on shutdown |
277+
278+
#### Workflow
279+
280+
| Key | Type | Description |
281+
|---|---|---|
282+
| `josdk.workflow.executor-threads` | `Integer` | Thread pool size for workflow execution |
283+
284+
#### Informer
285+
286+
| Key | Type | Description |
287+
|---|---|---|
288+
| `josdk.informer.cache-sync-timeout` | `Duration` | Timeout for the initial informer cache sync |
289+
| `josdk.informer.stop-on-error-during-startup` | `Boolean` | Stop the operator if an informer fails to start |
290+
291+
#### Dependent Resources
292+
293+
| Key | Type | Description |
294+
|---|---|---|
295+
| `josdk.dependent-resources.ssa-based-create-update-match` | `Boolean` | Use SSA-based matching for dependent resource create/update |
296+
297+
#### Leader Election
298+
299+
Leader election is activated when at least one `josdk.leader-election.*` key is present.
300+
`josdk.leader-election.lease-name` is required when any other leader-election key is set.
301+
Setting `josdk.leader-election.enabled=false` suppresses leader election even if other keys are
302+
present.
303+
304+
| Key | Type | Description |
305+
|---|---|---|
306+
| `josdk.leader-election.enabled` | `Boolean` | Explicitly enable (`true`) or disable (`false`) leader election |
307+
| `josdk.leader-election.lease-name` | `String` | **Required.** Name of the Kubernetes Lease object used for leader election |
308+
| `josdk.leader-election.lease-namespace` | `String` | Namespace for the Lease object (defaults to the operator's namespace) |
309+
| `josdk.leader-election.identity` | `String` | Unique identity for this instance; defaults to the pod name |
310+
| `josdk.leader-election.lease-duration` | `Duration` | How long a lease is valid (default `PT15S`) |
311+
| `josdk.leader-election.renew-deadline` | `Duration` | How long the leader tries to renew before giving up (default `PT10S`) |
312+
| `josdk.leader-election.retry-period` | `Duration` | How often a candidate polls while waiting to become leader (default `PT2S`) |
313+
314+
### Controller-Level Configuration Keys
315+
316+
All controller-level keys are prefixed with `josdk.controller.<controller-name>.`, where
317+
`<controller-name>` is the value returned by the reconciler's name (typically set via
318+
`@ControllerConfiguration(name = "...")`).
319+
320+
#### General
321+
322+
| Key | Type | Description |
323+
|---|---|---|
324+
| `josdk.controller.<name>.finalizer` | `String` | Finalizer string added to managed resources |
325+
| `josdk.controller.<name>.generation-aware` | `Boolean` | Skip reconciliation when the resource generation has not changed |
326+
| `josdk.controller.<name>.label-selector` | `String` | Label selector to filter watched resources |
327+
| `josdk.controller.<name>.max-reconciliation-interval` | `Duration` | Maximum interval between reconciliations even without events |
328+
| `josdk.controller.<name>.field-manager` | `String` | Field manager name used for SSA operations |
329+
| `josdk.controller.<name>.trigger-reconciler-on-all-events` | `Boolean` | Trigger reconciliation on every event, not only meaningful changes |
330+
331+
#### Informer
332+
333+
| Key | Type | Description |
334+
|---|---|---|
335+
| `josdk.controller.<name>.informer.label-selector` | `String` | Label selector for the primary resource informer (alias for `label-selector`) |
336+
| `josdk.controller.<name>.informer.list-limit` | `Long` | Page size for paginated informer list requests; omit for no pagination |
337+
338+
#### Retry
339+
340+
If any `retry.*` key is present, a `GenericRetry` is configured starting from the
341+
[default limited exponential retry](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/retry/GenericRetry.java).
342+
Only explicitly set keys override the defaults.
343+
344+
| Key | Type | Description |
345+
|---|---|---|
346+
| `josdk.controller.<name>.retry.max-attempts` | `Integer` | Maximum number of retry attempts |
347+
| `josdk.controller.<name>.retry.initial-interval` | `Long` (ms) | Initial backoff interval in milliseconds |
348+
| `josdk.controller.<name>.retry.interval-multiplier` | `Double` | Exponential backoff multiplier |
349+
| `josdk.controller.<name>.retry.max-interval` | `Long` (ms) | Maximum backoff interval in milliseconds |
350+
351+
#### Rate Limiter
352+
353+
The rate limiter is only activated when `rate-limiter.limit-for-period` is present and has a
354+
positive value. `rate-limiter.refresh-period` is optional and falls back to the default of 10 s.
355+
356+
| Key | Type | Description |
357+
|---|---|---|
358+
| `josdk.controller.<name>.rate-limiter.limit-for-period` | `Integer` | Maximum number of reconciliations allowed per refresh period. Must be positive to activate the limiter |
359+
| `josdk.controller.<name>.rate-limiter.refresh-period` | `Duration` | Window over which the limit is counted (default `PT10S`) |
153360

154-
TODO
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.config.loader;
17+
18+
import java.util.function.BiConsumer;
19+
20+
/**
21+
* Associates a configuration key and its expected type with the setter that should be called on an
22+
* overrider when the {@link ConfigProvider} returns a value for that key.
23+
*
24+
* @param <O> the overrider type (e.g. {@code ConfigurationServiceOverrider})
25+
* @param <T> the value type expected for this key
26+
*/
27+
public class ConfigBinding<O, T> {
28+
29+
private final String key;
30+
private final Class<T> type;
31+
private final BiConsumer<O, T> setter;
32+
33+
public ConfigBinding(String key, Class<T> type, BiConsumer<O, T> setter) {
34+
this.key = key;
35+
this.type = type;
36+
this.setter = setter;
37+
}
38+
39+
public String key() {
40+
return key;
41+
}
42+
43+
public Class<T> type() {
44+
return type;
45+
}
46+
47+
public BiConsumer<O, T> setter() {
48+
return setter;
49+
}
50+
}

0 commit comments

Comments
 (0)