Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 208 additions & 2 deletions docs/content/en/docs/documentation/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,212 @@ For more information on how to use this feature, we recommend looking at how thi
`KubernetesDependentResource` in the core framework, `SchemaDependentResource` in the samples or `CustomAnnotationDep`
in the `BaseConfigurationServiceTest` test class.

## EventSource-level configuration
## Loading Configuration from External Sources

JOSDK ships a `ConfigLoader` that bridges any key-value configuration source to the operator and
controller configuration APIs. This lets you drive operator behaviour from environment variables,
system properties, YAML files, or any config library (MicroProfile Config, SmallRye Config,
Spring Environment, etc.) without writing glue code by hand.

### Architecture

The system is built around two thin abstractions:

- **[`ConfigProvider`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/ConfigProvider.java)**
— a single-method interface that resolves a typed value for a dot-separated key:

```java
public interface ConfigProvider {
<T> Optional<T> getValue(String key, Class<T> type);
}
```

- **[`ConfigLoader`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/loader/ConfigLoader.java)**
— reads all known JOSDK keys from a `ConfigProvider` and returns
`Consumer<ConfigurationServiceOverrider>` / `Consumer<ControllerConfigurationOverrider<R>>`
values that you pass directly to the `Operator` constructor or `operator.register()`.

The default `ConfigLoader` (no-arg constructor) stacks environment variables over system
properties: environment variables win, system properties are the fallback.

```java
// uses env vars + system properties out of the box
Operator operator = new Operator(ConfigLoader.getDefault().applyConfigs());
```

### Built-in Providers

| Provider | Source | Key mapping |
|---|---|---|
| `EnvVarConfigProvider` | `System.getenv()` | dots and hyphens → underscores, upper-cased (`josdk.check-crd` → `JOSDK_CHECK_CRD`) |
| `PropertiesConfigProvider` | `java.util.Properties` or `.properties` file | key used as-is; use `PropertiesConfigProvider.systemProperties()` to read Java system properties |
| `YamlConfigProvider` | YAML file | dot-separated key traverses nested mappings |
| `AgregatePriorityListConfigProvider` | ordered list of providers | first non-empty result wins |

All string-based providers convert values to the target type automatically.
Supported types: `String`, `Boolean`, `Integer`, `Long`, `Double`, `Duration` (ISO-8601, e.g. `PT30S`).

### Plugging in Any Config Library

`ConfigProvider` is a single-method interface, so adapting any config library takes only a few
lines. As an example, here is an adapter for
[SmallRye Config](https://smallrye.io/smallrye-config/):

```java
public class SmallRyeConfigProvider implements ConfigProvider {

private final SmallRyeConfig config;

public SmallRyeConfigProvider(SmallRyeConfig config) {
this.config = config;
}

@Override
public <T> Optional<T> getValue(String key, Class<T> type) {
return config.getOptionalValue(key, type);
}
}
```

The same pattern applies to MicroProfile Config, Spring `Environment`, Apache Commons
Configuration, or any other library that can look up typed values by string key.

### Wiring Everything Together

Pass the `ConfigLoader` results when constructing the operator and registering reconcilers:

```java
// Load operator-wide config from a YAML file via SmallRye Config
URL configUrl = MyOperator.class.getResource("/application.yaml");
var configLoader = new ConfigLoader(
new SmallRyeConfigProvider(
new SmallRyeConfigBuilder()
.withSources(new YamlConfigSource(configUrl))
.build()));

// applyConfigs() → Consumer<ConfigurationServiceOverrider>
Operator operator = new Operator(configLoader.applyConfigs());

// applyControllerConfigs(name) → Consumer<ControllerConfigurationOverrider<R>>
operator.register(new MyReconciler(),
configLoader.applyControllerConfigs(MyReconciler.NAME));
```

Only keys that are actually present in the source are applied; everything else retains its
programmatic or annotation-based default.

You can also compose multiple sources with explicit priority using
`AgregatePriorityListConfigProvider`:

```java
var configLoader = new ConfigLoader(
new AgregatePriorityListConfigProvider(List.of(
new EnvVarConfigProvider(), // highest priority
PropertiesConfigProvider.systemProperties(),
new YamlConfigProvider(Path.of("config/operator.yaml")) // lowest priority
)));
```

### Operator-Level Configuration Keys

All operator-level keys are prefixed with `josdk.`.

#### General

| Key | Type | Description |
|---|---|---|
| `josdk.check-crd` | `Boolean` | Validate CRDs against local model on startup |
| `josdk.close-client-on-stop` | `Boolean` | Close the Kubernetes client when the operator stops |
| `josdk.use-ssa-to-patch-primary-resource` | `Boolean` | Use Server-Side Apply to patch the primary resource |
| `josdk.clone-secondary-resources-when-getting-from-cache` | `Boolean` | Clone secondary resources on cache reads |

#### Reconciliation

| Key | Type | Description |
|---|---|---|
| `josdk.reconciliation.concurrent-threads` | `Integer` | Thread pool size for reconciliation |
| `josdk.reconciliation.termination-timeout` | `Duration` | How long to wait for in-flight reconciliations to finish on shutdown |

#### Workflow

| Key | Type | Description |
|---|---|---|
| `josdk.workflow.executor-threads` | `Integer` | Thread pool size for workflow execution |

#### Informer

| Key | Type | Description |
|---|---|---|
| `josdk.informer.cache-sync-timeout` | `Duration` | Timeout for the initial informer cache sync |
| `josdk.informer.stop-on-error-during-startup` | `Boolean` | Stop the operator if an informer fails to start |

#### Dependent Resources

| Key | Type | Description |
|---|---|---|
| `josdk.dependent-resources.ssa-based-create-update-match` | `Boolean` | Use SSA-based matching for dependent resource create/update |

#### Leader Election

Leader election is activated when at least one `josdk.leader-election.*` key is present.
`josdk.leader-election.lease-name` is required when any other leader-election key is set.
Setting `josdk.leader-election.enabled=false` suppresses leader election even if other keys are
present.

| Key | Type | Description |
|---|---|---|
| `josdk.leader-election.enabled` | `Boolean` | Explicitly enable (`true`) or disable (`false`) leader election |
| `josdk.leader-election.lease-name` | `String` | **Required.** Name of the Kubernetes Lease object used for leader election |
| `josdk.leader-election.lease-namespace` | `String` | Namespace for the Lease object (defaults to the operator's namespace) |
| `josdk.leader-election.identity` | `String` | Unique identity for this instance; defaults to the pod name |
| `josdk.leader-election.lease-duration` | `Duration` | How long a lease is valid (default `PT15S`) |
| `josdk.leader-election.renew-deadline` | `Duration` | How long the leader tries to renew before giving up (default `PT10S`) |
| `josdk.leader-election.retry-period` | `Duration` | How often a candidate polls while waiting to become leader (default `PT2S`) |

### Controller-Level Configuration Keys

All controller-level keys are prefixed with `josdk.controller.<controller-name>.`, where
`<controller-name>` is the value returned by the reconciler's name (typically set via
`@ControllerConfiguration(name = "...")`).

#### General

| Key | Type | Description |
|---|---|---|
| `josdk.controller.<name>.finalizer` | `String` | Finalizer string added to managed resources |
| `josdk.controller.<name>.generation-aware` | `Boolean` | Skip reconciliation when the resource generation has not changed |
| `josdk.controller.<name>.label-selector` | `String` | Label selector to filter watched resources |
| `josdk.controller.<name>.max-reconciliation-interval` | `Duration` | Maximum interval between reconciliations even without events |
| `josdk.controller.<name>.field-manager` | `String` | Field manager name used for SSA operations |
| `josdk.controller.<name>.trigger-reconciler-on-all-events` | `Boolean` | Trigger reconciliation on every event, not only meaningful changes |

#### Informer

| Key | Type | Description |
|---|---|---|
| `josdk.controller.<name>.informer.label-selector` | `String` | Label selector for the primary resource informer (alias for `label-selector`) |
| `josdk.controller.<name>.informer.list-limit` | `Long` | Page size for paginated informer list requests; omit for no pagination |

#### Retry

If any `retry.*` key is present, a `GenericRetry` is configured starting from the
[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).
Only explicitly set keys override the defaults.

| Key | Type | Description |
|---|---|---|
| `josdk.controller.<name>.retry.max-attempts` | `Integer` | Maximum number of retry attempts |
| `josdk.controller.<name>.retry.initial-interval` | `Long` (ms) | Initial backoff interval in milliseconds |
| `josdk.controller.<name>.retry.interval-multiplier` | `Double` | Exponential backoff multiplier |
| `josdk.controller.<name>.retry.max-interval` | `Long` (ms) | Maximum backoff interval in milliseconds |

#### Rate Limiter

The rate limiter is only activated when `rate-limiter.limit-for-period` is present and has a
positive value. `rate-limiter.refresh-period` is optional and falls back to the default of 10 s.

| Key | Type | Description |
|---|---|---|
| `josdk.controller.<name>.rate-limiter.limit-for-period` | `Integer` | Maximum number of reconciliations allowed per refresh period. Must be positive to activate the limiter |
| `josdk.controller.<name>.rate-limiter.refresh-period` | `Duration` | Window over which the limit is counted (default `PT10S`) |

TODO
18 changes: 18 additions & 0 deletions helm/operator/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: v2
name: josdk-operator
description: >
Generic Helm chart template for deploying a Java Operator SDK based operator.
Copy and customise this chart for your own operator: adjust the values, extend
the ClusterRole rules with your CRD API groups, and optionally add your own
templates.
type: application
version: 0.1.0
# Set to the version of your operator image.
appVersion: "latest"
keywords:
- operator
- kubernetes
- java-operator-sdk
home: https://javaoperatorsdk.io
sources:
- https://github.com/operator-framework/java-operator-sdk
45 changes: 45 additions & 0 deletions helm/operator/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Thank you for installing {{ .Chart.Name }} ({{ .Chart.AppVersion }}).

Release: {{ .Release.Name }}
Namespace: {{ include "josdk-operator.namespace" . }}

Operator deployment: {{ include "josdk-operator.fullname" . }}

{{- if .Values.josdkConfig.enabled }}

ConfigLoader properties are mounted from ConfigMap
{{ include "josdk-operator.configMapName" . }}
at {{ .Values.josdkConfig.mountPath }}/josdk.properties.

Wire it up in your operator main class:

ConfigLoader loader = new ConfigLoader(
PropertiesConfigProvider.fromFile(
Path.of("{{ .Values.josdkConfig.mountPath }}/josdk.properties")));
{{- end }}

{{- if .Values.log4j2.enabled }}

Log4j2 configuration is mounted from ConfigMap
{{ include "josdk-operator.log4j2ConfigMapName" . }}
at {{ .Values.log4j2.mountPath }}/log4j2.xml.

Root log level: {{ .Values.log4j2.rootLevel }}
{{- if .Values.log4j2.loggers }}
Per-logger overrides:
{{- range $logger, $level := .Values.log4j2.loggers }}
{{ $logger }} -> {{ $level }}
{{- end }}
{{- end }}

The JVM flag -Dlog4j2.configurationFile={{ .Values.log4j2.mountPath }}/log4j2.xml
has been added to JAVA_TOOL_OPTIONS automatically.
{{- end }}

To change the log level at runtime without redeploying, update the ConfigMap:

kubectl edit configmap {{ include "josdk-operator.log4j2ConfigMapName" . }} \
-n {{ include "josdk-operator.namespace" . }}

Log4j2 will pick up the change within 30 seconds (monitorInterval="30" in the
default configuration).
93 changes: 93 additions & 0 deletions helm/operator/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "josdk-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "josdk-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Chart label.
*/}}
{{- define "josdk-operator.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels applied to every resource.
*/}}
{{- define "josdk-operator.labels" -}}
helm.sh/chart: {{ include "josdk-operator.chart" . }}
{{ include "josdk-operator.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels used in Deployment and Service selectors.
*/}}
{{- define "josdk-operator.selectorLabels" -}}
app.kubernetes.io/name: {{ include "josdk-operator.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
ServiceAccount name.
*/}}
{{- define "josdk-operator.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "josdk-operator.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Deployment namespace.
*/}}
{{- define "josdk-operator.namespace" -}}
{{- default .Release.Namespace .Values.namespace }}
{{- end }}

{{/*
Name of the JOSDK config ConfigMap.
*/}}
{{- define "josdk-operator.configMapName" -}}
{{- default (printf "%s-config" (include "josdk-operator.fullname" .)) .Values.josdkConfig.configMapName }}
{{- end }}

{{/*
Name of the log4j2 ConfigMap.
*/}}
{{- define "josdk-operator.log4j2ConfigMapName" -}}
{{- default (printf "%s-log4j2" (include "josdk-operator.fullname" .)) .Values.log4j2.configMapName }}
{{- end }}

{{/*
JAVA_TOOL_OPTIONS / JVM args value.
Appends the log4j2 config file system property automatically when log4j2 is enabled.
*/}}
{{- define "josdk-operator.jvmArgs" -}}
{{- $args := .Values.jvmArgs | default "" }}
{{- if .Values.log4j2.enabled }}
{{- $args = printf "%s -Dlog4j2.configurationFile=%s/log4j2.xml" $args .Values.log4j2.mountPath | trim }}
{{- end }}
{{- $args }}
{{- end }}
Loading
Loading