From 8210a9fba07a736cbab8c4ae376518fb0ba741e5 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Wed, 18 Mar 2026 19:31:55 +0100 Subject: [PATCH 1/8] feat: config overrides --- .../opa/pages/usage-guide/OpenTelemetry.adoc | 31 +--- .../configuration-environment-overrides.adoc | 32 +++- extra/crds.yaml | 172 ++++++++++++++++-- rust/operator-binary/src/controller.rs | 63 ++++++- rust/operator-binary/src/crd/mod.rs | 26 ++- .../config-overrides/00-limit-range.yaml | 11 ++ .../config-overrides/00-patch-ns.yaml.j2 | 9 + .../kuttl/config-overrides/01-assert.yaml.j2 | 10 + ...tor-aggregator-discovery-configmap.yaml.j2 | 9 + .../kuttl/config-overrides/05-assert.yaml | 7 + .../config-overrides/05-install-test-pod.yaml | 29 +++ .../kuttl/config-overrides/10-assert.yaml.j2 | 63 +++++++ .../config-overrides/10-install-opa.yaml.j2 | 65 +++++++ tests/test-definition.yaml | 4 + 14 files changed, 479 insertions(+), 52 deletions(-) create mode 100644 tests/templates/kuttl/config-overrides/00-limit-range.yaml create mode 100644 tests/templates/kuttl/config-overrides/00-patch-ns.yaml.j2 create mode 100644 tests/templates/kuttl/config-overrides/01-assert.yaml.j2 create mode 100644 tests/templates/kuttl/config-overrides/01-install-vector-aggregator-discovery-configmap.yaml.j2 create mode 100644 tests/templates/kuttl/config-overrides/05-assert.yaml create mode 100644 tests/templates/kuttl/config-overrides/05-install-test-pod.yaml create mode 100644 tests/templates/kuttl/config-overrides/10-assert.yaml.j2 create mode 100644 tests/templates/kuttl/config-overrides/10-install-opa.yaml.j2 diff --git a/docs/modules/opa/pages/usage-guide/OpenTelemetry.adoc b/docs/modules/opa/pages/usage-guide/OpenTelemetry.adoc index f592b089..4272f8e8 100644 --- a/docs/modules/opa/pages/usage-guide/OpenTelemetry.adoc +++ b/docs/modules/opa/pages/usage-guide/OpenTelemetry.adoc @@ -1,30 +1,19 @@ = OpenTelemetry :description: Ship OPA traces and logs to OpenTelemetry -:opa-docs: https://v1-4-2--opa-docs.netlify.app/configuration/#distributed-tracing +:opa-docs: https://www.openpolicyagent.org/docs/configuration/#distributed-tracing -Opa supports sending OpenTelemetry traces as stated in {opa-docs}[the documentation]. +OPA supports sending OpenTelemetry traces as stated in {opa-docs}[the documentation]. -As of SDP 25.7, `configOverrides` are (still) not supported, we are tracking the progress in https://github.com/stackabletech/opa-operator/issues/756[this GitHub issue]. -To enable traces you need to modify the config and thus xref:opa:usage-guide/operations/cluster-operations.adoc[pause the reconciliation] of your OpaCluster, so that changes to the ConfigMap aren't immediately overridden by the opa-operator. - -WARNING: It's not encouraged to pause the reconciliation more than just temporarily. We recommend disabling it while you debug e.g. performance problems and re-enabling it afterwards. This problem will be solved once we support configOverrides for OPA. - -Afterwards you can edit the `-server-default` ConfigMap and append a `distributed_tracing` section as follows. +To enable traces, add a `distributed_tracing` section to the OPA configuration using `configOverrides`. Please check the {opa-docs}[OPA documentation] to see what other settings you can configure. [source,yaml] ---- -apiVersion: v1 -kind: ConfigMap -metadata: - name: opa-server-default -data: - config.json: |- - { - <<< existing JSON >>> - "distributed_tracing": { - "address": "jaeger-collector.default.svc.cluster.local:4317", - "type": "grpc" - } - } +servers: + configOverrides: + config.json: + jsonMergePatch: + distributed_tracing: + address: jaeger-collector.default.svc.cluster.local:4317 + type: grpc ---- diff --git a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc index a5abbd44..e61df279 100644 --- a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc +++ b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc @@ -8,7 +8,37 @@ This will lead to faulty installations. == Configuration properties -Currently, not supported for `config.json`. +OPA's `config.json` can be overridden using `configOverrides`, the supported override strategies are `jsonMergePatch` (RFC 7396) and `jsonPatches` (RFC 6902). + +For example per role group: + +[source,yaml] +---- +servers: + roleGroups: + default: + configOverrides: + config.json: + jsonMergePatch: + distributed_tracing: + address: jaeger-collector.default.svc.cluster.local:4317 + type: grpc +---- + +or per role: + +[source,yaml] +---- +servers: + configOverrides: + config.json: + jsonPatches: + - '{"op": "replace", "path": "/bundles/stackable/polling/min_delay_seconds", "value": 3}' + - '{"op": "add", "path": "/default_decision", "value": "allow"}' + roleGroups: + default: + config: {} +---- == Environment variables diff --git a/extra/crds.yaml b/extra/crds.yaml index 208b8153..f770cd3a 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -1121,17 +1121,50 @@ spec: type: object type: object configOverrides: - additionalProperties: - additionalProperties: - type: string - type: object - default: {} description: |- The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + properties: + config.json: + description: Overrides for the OPA `config.json` file. + nullable: true + oneOf: + - required: + - jsonMergePatch + - required: + - jsonPatches + - required: + - userProvided + properties: + jsonMergePatch: + description: |- + Can be set to arbitrary YAML content, which is converted to JSON and used as + [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) JSON merge patch. + x-kubernetes-preserve-unknown-fields: true + jsonPatches: + description: |- + List of [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) JSON patches. + + Can be used when more flexibility is needed, e.g. to only modify elements + in a list based on a condition. + + A patch looks something like + + `{"op": "test", "path": "/0/name", "value": "Andrew"}` + + or + + `{"op": "add", "path": "/0/happy", "value": true}` + items: + type: string + type: array + userProvided: + description: Override the entire config file with the specified String. + type: string + type: object type: object envOverrides: additionalProperties: @@ -1680,17 +1713,50 @@ spec: type: object type: object configOverrides: - additionalProperties: - additionalProperties: - type: string - type: object - default: {} description: |- The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + properties: + config.json: + description: Overrides for the OPA `config.json` file. + nullable: true + oneOf: + - required: + - jsonMergePatch + - required: + - jsonPatches + - required: + - userProvided + properties: + jsonMergePatch: + description: |- + Can be set to arbitrary YAML content, which is converted to JSON and used as + [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) JSON merge patch. + x-kubernetes-preserve-unknown-fields: true + jsonPatches: + description: |- + List of [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) JSON patches. + + Can be used when more flexibility is needed, e.g. to only modify elements + in a list based on a condition. + + A patch looks something like + + `{"op": "test", "path": "/0/name", "value": "Andrew"}` + + or + + `{"op": "add", "path": "/0/happy", "value": true}` + items: + type: string + type: array + userProvided: + description: Override the entire config file with the specified String. + type: string + type: object type: object envOverrides: additionalProperties: @@ -2887,17 +2953,50 @@ spec: type: object type: object configOverrides: - additionalProperties: - additionalProperties: - type: string - type: object - default: {} description: |- The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + properties: + config.json: + description: Overrides for the OPA `config.json` file. + nullable: true + oneOf: + - required: + - jsonMergePatch + - required: + - jsonPatches + - required: + - userProvided + properties: + jsonMergePatch: + description: |- + Can be set to arbitrary YAML content, which is converted to JSON and used as + [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) JSON merge patch. + x-kubernetes-preserve-unknown-fields: true + jsonPatches: + description: |- + List of [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) JSON patches. + + Can be used when more flexibility is needed, e.g. to only modify elements + in a list based on a condition. + + A patch looks something like + + `{"op": "test", "path": "/0/name", "value": "Andrew"}` + + or + + `{"op": "add", "path": "/0/happy", "value": true}` + items: + type: string + type: array + userProvided: + description: Override the entire config file with the specified String. + type: string + type: object type: object envOverrides: additionalProperties: @@ -3446,17 +3545,50 @@ spec: type: object type: object configOverrides: - additionalProperties: - additionalProperties: - type: string - type: object - default: {} description: |- The `configOverrides` can be used to configure properties in product config files that are not exposed in the CRD. Read the [config overrides documentation](https://docs.stackable.tech/home/nightly/concepts/overrides#config-overrides) and consult the operator specific usage guide documentation for details on the available config files and settings for the specific product. + properties: + config.json: + description: Overrides for the OPA `config.json` file. + nullable: true + oneOf: + - required: + - jsonMergePatch + - required: + - jsonPatches + - required: + - userProvided + properties: + jsonMergePatch: + description: |- + Can be set to arbitrary YAML content, which is converted to JSON and used as + [RFC 7396](https://datatracker.ietf.org/doc/html/rfc7396) JSON merge patch. + x-kubernetes-preserve-unknown-fields: true + jsonPatches: + description: |- + List of [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902) JSON patches. + + Can be used when more flexibility is needed, e.g. to only modify elements + in a list based on a condition. + + A patch looks something like + + `{"op": "test", "path": "/0/name", "value": "Andrew"}` + + or + + `{"op": "add", "path": "/0/happy", "value": true}` + items: + type: string + type: array + userProvided: + description: Override the entire config file with the specified String. + type: string + type: object type: object envOverrides: additionalProperties: diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index f3d1567c..1969fc2c 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -30,6 +30,7 @@ use stackable_operator::{ secret_class::{SecretClassVolume, SecretClassVolumeScope}, tls_verification::{TlsClientDetails, TlsClientDetailsError}, }, + config_overrides, crd::authentication::ldap, k8s_openapi::{ DeepMerge, @@ -339,6 +340,19 @@ pub enum Error { TlsVolumeBuild { source: builder::pod::volume::SecretOperatorVolumeSourceBuilderError, }, + + #[snafu(display("failed to apply config overrides to {file}"))] + ApplyConfigOverrides { + source: config_overrides::Error, + file: String, + }, + + #[snafu(display("failed to serialize config file {file}"))] + SerializeConfigFile { + source: serde_json::Error, + file: String, + }, + } type Result = std::result::Result; @@ -630,6 +644,17 @@ fn build_server_rolegroup_config_map( ) -> Result { let mut cm_builder = ConfigMapBuilder::new(); + // Collect config overrides from role and rolegroup levels. + // Both are applied in order: role-level first, then rolegroup-level on top, + // so rolegroup overrides take precedence. + let role_config_override = opa.spec.servers.config.config_overrides.config_json.as_ref(); + let rolegroup_config_override = opa + .spec + .servers + .role_groups + .get(&rolegroup.role_group) + .and_then(|rg| rg.config.config_overrides.config_json.as_ref()); + let metadata = ObjectMetaBuilder::new() .name_and_namespace(opa) .name(rolegroup.object_name()) @@ -644,9 +669,14 @@ fn build_server_rolegroup_config_map( .context(ObjectMetaSnafu)? .build(); - cm_builder - .metadata(metadata) - .add_data(CONFIG_FILE, build_config_file(merged_config)); + cm_builder.metadata(metadata).add_data( + CONFIG_FILE, + build_config_file( + merged_config, + role_config_override, + rolegroup_config_override, + )?, + ); if let Some(user_info) = &opa.spec.cluster_config.user_info { cm_builder.add_data( @@ -1176,7 +1206,11 @@ pub fn error_policy( } } -fn build_config_file(merged_config: &OpaConfig) -> String { +fn build_config_file( + merged_config: &OpaConfig, + role_config_override: Option<&config_overrides::JsonConfigOverrides>, + rolegroup_config_override: Option<&config_overrides::JsonConfigOverrides>, +) -> Result { let mut decision_logging_enabled = DEFAULT_DECISION_LOGGING_ENABLED; if let Some(ContainerLogConfig { @@ -1196,9 +1230,24 @@ fn build_config_file(merged_config: &OpaConfig) -> String { let config = OpaClusterConfigFile::new(decision_logging); - // The unwrap() shouldn't panic under any circumstances because Rusts type checker takes care of the OpaClusterConfigFile - // and serde + serde_json therefore serialize/deserialize a valid struct - serde_json::to_string_pretty(&json!(config)).unwrap() + let mut config_value = serde_json::to_value(&config).context(SerializeConfigFileSnafu { + file: CONFIG_FILE.to_string(), + })?; + + // Apply role-level overrides first, then rolegroup-level on top. + // This way rolegroup settings take precedence over role settings. + for overrides in [role_config_override, rolegroup_config_override] + .into_iter() + .flatten() + { + config_value = overrides.apply(&config_value).context(ApplyConfigOverridesSnafu { + file: CONFIG_FILE.to_string(), + })?; + } + + serde_json::to_string_pretty(&config_value).context(SerializeConfigFileSnafu { + file: CONFIG_FILE.to_string(), + }) } fn build_opa_start_command( diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 6a0745ce..07ac5d24 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -16,6 +16,7 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, + config_overrides::{JsonConfigOverrides, KeyValueOverridesProvider}, deep_merger::ObjectOverrides, k8s_openapi::apimachinery::pkg::api::resource::Quantity, kube::{CustomResource, ResourceExt}, @@ -95,7 +96,7 @@ pub mod versioned { /// OPA server configuration. // #[versioned(hint(role))] - pub servers: Role, + pub servers: Role, /// The OPA image to use pub image: ProductImage, @@ -164,6 +165,25 @@ pub mod versioned { } } +/// Typed config override strategies for OPA config files. +/// +/// OPA only has one config file (`config.json`), which is JSON-formatted. +/// Users can override it using JSON merge patch (RFC 7396), JSON patch (RFC 6902), +/// or by providing the full file content. +#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OpaConfigOverrides { + /// Overrides for the OPA `config.json` file. + #[serde(default, rename = "config.json", skip_serializing_if = "Option::is_none")] + pub config_json: Option, +} + +// OPA has no key-value config files, all overrides go through JsonConfigOverrides. +// This impl is still required because the shared product config pipeline +// (`transform_all_roles_to_config`) requires the `KeyValueOverridesProvider` bound +// at compile time. The default implementation returns an empty map. +impl KeyValueOverridesProvider for OpaConfigOverrides {} + #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, Debug, Default, Fragment, JsonSchema, PartialEq)] #[fragment_attrs( @@ -339,7 +359,7 @@ impl Configuration for OpaConfigFragment { impl v1alpha2::OpaCluster { /// Returns a reference to the role. - pub fn role(&self, role_variant: &OpaRole) -> &Role { + pub fn role(&self, role_variant: &OpaRole) -> &Role { match role_variant { OpaRole::Server => &self.spec.servers, } @@ -349,7 +369,7 @@ impl v1alpha2::OpaCluster { pub fn rolegroup( &self, rolegroup_ref: &RoleGroupRef, - ) -> Result<&RoleGroup, Error> { + ) -> Result<&RoleGroup, Error> { let role_variant = OpaRole::from_str(&rolegroup_ref.role).with_context(|_| UnknownOpaRoleSnafu { role: rolegroup_ref.role.to_owned(), diff --git a/tests/templates/kuttl/config-overrides/00-limit-range.yaml b/tests/templates/kuttl/config-overrides/00-limit-range.yaml new file mode 100644 index 00000000..7b6cb30e --- /dev/null +++ b/tests/templates/kuttl/config-overrides/00-limit-range.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: LimitRange +metadata: + name: limit-request-ratio +spec: + limits: + - type: "Container" + maxLimitRequestRatio: + cpu: 5 + memory: 1 diff --git a/tests/templates/kuttl/config-overrides/00-patch-ns.yaml.j2 b/tests/templates/kuttl/config-overrides/00-patch-ns.yaml.j2 new file mode 100644 index 00000000..67185acf --- /dev/null +++ b/tests/templates/kuttl/config-overrides/00-patch-ns.yaml.j2 @@ -0,0 +1,9 @@ +{% if test_scenario['values']['openshift'] == 'true' %} +# see https://github.com/stackabletech/issues/issues/566 +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: kubectl patch namespace $NAMESPACE -p '{"metadata":{"labels":{"pod-security.kubernetes.io/enforce":"privileged"}}}' + timeout: 120 +{% endif %} diff --git a/tests/templates/kuttl/config-overrides/01-assert.yaml.j2 b/tests/templates/kuttl/config-overrides/01-assert.yaml.j2 new file mode 100644 index 00000000..50b1d4c3 --- /dev/null +++ b/tests/templates/kuttl/config-overrides/01-assert.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +{% endif %} diff --git a/tests/templates/kuttl/config-overrides/01-install-vector-aggregator-discovery-configmap.yaml.j2 b/tests/templates/kuttl/config-overrides/01-install-vector-aggregator-discovery-configmap.yaml.j2 new file mode 100644 index 00000000..2d6a0df5 --- /dev/null +++ b/tests/templates/kuttl/config-overrides/01-install-vector-aggregator-discovery-configmap.yaml.j2 @@ -0,0 +1,9 @@ +{% if lookup('env', 'VECTOR_AGGREGATOR') %} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vector-aggregator-discovery +data: + ADDRESS: {{ lookup('env', 'VECTOR_AGGREGATOR') }} +{% endif %} diff --git a/tests/templates/kuttl/config-overrides/05-assert.yaml b/tests/templates/kuttl/config-overrides/05-assert.yaml new file mode 100644 index 00000000..54fb9ef7 --- /dev/null +++ b/tests/templates/kuttl/config-overrides/05-assert.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-pod +status: + readyReplicas: 1 diff --git a/tests/templates/kuttl/config-overrides/05-install-test-pod.yaml b/tests/templates/kuttl/config-overrides/05-install-test-pod.yaml new file mode 100644 index 00000000..bbf9e046 --- /dev/null +++ b/tests/templates/kuttl/config-overrides/05-install-test-pod.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: test-pod + labels: + app: test-pod +spec: + replicas: 1 + selector: + matchLabels: + app: test-pod + template: + metadata: + labels: + app: test-pod + spec: + containers: + - name: test-pod + image: oci.stackable.tech/sdp/testing-tools:0.3.0-stackable0.0.0-dev + stdin: true + tty: true + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "128Mi" + cpu: "500m" diff --git a/tests/templates/kuttl/config-overrides/10-assert.yaml.j2 b/tests/templates/kuttl/config-overrides/10-assert.yaml.j2 new file mode 100644 index 00000000..4d9ec79d --- /dev/null +++ b/tests/templates/kuttl/config-overrides/10-assert.yaml.j2 @@ -0,0 +1,63 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +timeout: 300 +commands: + # Wait for OPA cluster to be available + - script: kubectl -n $NAMESPACE wait --for=condition=available opaclusters.opa.stackable.tech/test-opa --timeout 301s + + # Wait for test pod to be ready + - script: kubectl -n $NAMESPACE wait --for=condition=ready pod/test-pod-0 --timeout 60s + + # Verify configOverrides are applied correctly via OPA Config API + - script: | + # Fetch the actual running config from OPA's /v1/config endpoint + CONFIG_JSON=$(kubectl exec -n $NAMESPACE test-pod-0 -- curl -s http://test-opa-server:8081/v1/config | jq -r '.result') + + # Check min_delay_seconds is 3 (from role level) + MIN_DELAY=$(echo "$CONFIG_JSON" | jq -r '.bundles.stackable.polling.min_delay_seconds') + if [ "$MIN_DELAY" != "3" ]; then + echo "ERROR: Expected min_delay_seconds=3 (from role configOverrides), got $MIN_DELAY" + exit 1 + fi + echo "OK: min_delay_seconds=3 (role level override)" + + # Check max_delay_seconds is 5 (rolegroup level overrides role level) + MAX_DELAY=$(echo "$CONFIG_JSON" | jq -r '.bundles.stackable.polling.max_delay_seconds') + if [ "$MAX_DELAY" != "5" ]; then + echo "ERROR: Expected max_delay_seconds=5 (rolegroup override), got $MAX_DELAY" + exit 1 + fi + echo "OK: max_delay_seconds=5 (rolegroup level override takes precedence)" + + # Check default_decision is set (from role level) + DEFAULT_DECISION=$(echo "$CONFIG_JSON" | jq -r '.default_decision') + if [ "$DEFAULT_DECISION" != "test/hello" ]; then + echo "ERROR: Expected default_decision='test/hello', got $DEFAULT_DECISION" + exit 1 + fi + echo "OK: default_decision=test/hello (role level override)" + + # Check labels.rolegroup is set (from rolegroup level) + ROLEGROUP_LABEL=$(echo "$CONFIG_JSON" | jq -r '.labels.rolegroup') + if [ "$ROLEGROUP_LABEL" != "default" ]; then + echo "ERROR: Expected labels.rolegroup='default', got $ROLEGROUP_LABEL" + exit 1 + fi + echo "OK: labels.rolegroup=default (rolegroup level override)" + + # Verify original values are still present + SERVICE_URL=$(echo "$CONFIG_JSON" | jq -r '.services[0].url') + if [ "$SERVICE_URL" != "http://localhost:3030/opa/v1" ]; then + echo "ERROR: Original service URL was overwritten, got $SERVICE_URL" + exit 1 + fi + echo "OK: Original service URL preserved" + + echo "All configOverrides tests passed!" +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: test-opa-server-default +status: + numberReady: 1 diff --git a/tests/templates/kuttl/config-overrides/10-install-opa.yaml.j2 b/tests/templates/kuttl/config-overrides/10-install-opa.yaml.j2 new file mode 100644 index 00000000..ba0b24ec --- /dev/null +++ b/tests/templates/kuttl/config-overrides/10-install-opa.yaml.j2 @@ -0,0 +1,65 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - < 0 %} + custom: "{{ test_scenario['values']['opa-latest'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['opa-latest'].split(',')[0] }}" +{% else %} + productVersion: "{{ test_scenario['values']['opa-latest'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: +{% if lookup('env', 'VECTOR_AGGREGATOR') %} + vectorAggregatorConfigMapName: vector-aggregator-discovery +{% endif %} + servers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + # Role-level configOverrides using JSON merge patch (RFC 7396) + configOverrides: + config.json: + jsonMergePatch: + bundles: + stackable: + polling: + min_delay_seconds: 3 + max_delay_seconds: 7 + default_decision: test/hello + roleGroups: + default: + # Rolegroup-level configOverrides (takes precedence over role level) + configOverrides: + config.json: + jsonMergePatch: + bundles: + stackable: + polling: + max_delay_seconds: 5 + labels: + rolegroup: default + EOF diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index f28d0ca3..3b0a8da2 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -39,6 +39,10 @@ tests: dimensions: - opa-latest - openshift + - name: config-overrides + dimensions: + - opa-latest + - openshift - name: keycloak-user-info dimensions: - opa-latest From 797698e0ba9d71d5f20148a72987448030f9f5ea Mon Sep 17 00:00:00 2001 From: dervoeti Date: Wed, 18 Mar 2026 22:07:16 +0100 Subject: [PATCH 2/8] chore: changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ce5ea5..001f9132 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Support `configOverrides` for `config.json` (#818). + +[#818]: https://github.com/stackabletech/opa-operator/pull/818 + ## [26.3.0] - 2026-03-16 ## [26.3.0-rc1] - 2026-03-16 From 33c446215444bf105cfacc8bea05ec1947af48b0 Mon Sep 17 00:00:00 2001 From: Lukas Krug Date: Tue, 7 Apr 2026 16:27:16 +0200 Subject: [PATCH 3/8] Update rust/operator-binary/src/controller.rs Co-authored-by: Sebastian Bernauer --- rust/operator-binary/src/controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 1969fc2c..f89193ed 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -341,7 +341,7 @@ pub enum Error { source: builder::pod::volume::SecretOperatorVolumeSourceBuilderError, }, - #[snafu(display("failed to apply config overrides to {file}"))] + #[snafu(display("failed to apply config overrides to the file {file:?}"))] ApplyConfigOverrides { source: config_overrides::Error, file: String, From 1dc457d2265b9a901ef7659b4219e33485a015ab Mon Sep 17 00:00:00 2001 From: Lukas Krug Date: Tue, 7 Apr 2026 16:27:48 +0200 Subject: [PATCH 4/8] Update rust/operator-binary/src/controller.rs Co-authored-by: Sebastian Bernauer --- rust/operator-binary/src/controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index f89193ed..215031c4 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -347,7 +347,7 @@ pub enum Error { file: String, }, - #[snafu(display("failed to serialize config file {file}"))] + #[snafu(display("failed to serialize config file {file:?}"))] SerializeConfigFile { source: serde_json::Error, file: String, From a75fa14e3ac46c9f5bb8793ddeebe38344750a23 Mon Sep 17 00:00:00 2001 From: dervoeti Date: Wed, 8 Apr 2026 07:09:21 +0000 Subject: [PATCH 5/8] fix: address review feedback --- .../configuration-environment-overrides.adoc | 3 ++- rust/operator-binary/src/controller.rs | 18 +++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc index e61df279..c586a917 100644 --- a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc +++ b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc @@ -8,7 +8,8 @@ This will lead to faulty installations. == Configuration properties -OPA's `config.json` can be overridden using `configOverrides`, the supported override strategies are `jsonMergePatch` (RFC 7396) and `jsonPatches` (RFC 6902). +OPA's `config.json` can be overridden using `configOverrides`. +For details on the supported override strategies, see the xref:concepts:overrides.adoc#config-overrides[config overrides documentation]. For example per role group: diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 215031c4..6a757311 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -1230,9 +1230,10 @@ fn build_config_file( let config = OpaClusterConfigFile::new(decision_logging); - let mut config_value = serde_json::to_value(&config).context(SerializeConfigFileSnafu { - file: CONFIG_FILE.to_string(), - })?; + let mut config_value = + serde_json::to_value(&config).with_context(|_| SerializeConfigFileSnafu { + file: CONFIG_FILE.to_string(), + })?; // Apply role-level overrides first, then rolegroup-level on top. // This way rolegroup settings take precedence over role settings. @@ -1240,12 +1241,15 @@ fn build_config_file( .into_iter() .flatten() { - config_value = overrides.apply(&config_value).context(ApplyConfigOverridesSnafu { - file: CONFIG_FILE.to_string(), - })?; + config_value = + overrides + .apply(&config_value) + .with_context(|_| ApplyConfigOverridesSnafu { + file: CONFIG_FILE.to_string(), + })?; } - serde_json::to_string_pretty(&config_value).context(SerializeConfigFileSnafu { + serde_json::to_string_pretty(&config_value).with_context(|_| SerializeConfigFileSnafu { file: CONFIG_FILE.to_string(), }) } From 5feaa82299a6f977a3cf151105a2fd00d54f730f Mon Sep 17 00:00:00 2001 From: Lukas Krug Date: Tue, 24 Mar 2026 13:53:01 +0100 Subject: [PATCH 6/8] feat: daemonset maxsurge to prevent unavailability on config changes (#819) * feat: add maxSurge to DaemonSet rolling update strategy * chore: add TODO to use PreferSameNode once k8s 1.35 is minimum * test: assert DaemonSet rolling update strategy in smoke test * chore: update changelog * chore: lint fixes --- CHANGELOG.md | 6 ++++++ rust/operator-binary/src/controller.rs | 9 ++++++++- rust/operator-binary/src/service.rs | 6 ++++++ tests/templates/kuttl/smoke/10-assert.yaml.j2 | 5 +++++ 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 001f9132..3fd3fe6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,13 @@ All notable changes to this project will be documented in this file. - Support `configOverrides` for `config.json` (#818). +### Changed + +- Set `maxSurge=1` and `maxUnavailable=0` on the OPA DaemonSet rolling update strategy to eliminate + availability gaps during rolling updates ([#819]). + [#818]: https://github.com/stackabletech/opa-operator/pull/818 +[#819]: https://github.com/stackabletech/opa-operator/pull/819 ## [26.3.0] - 2026-03-16 diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 6a757311..b4750770 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -35,7 +35,7 @@ use stackable_operator::{ k8s_openapi::{ DeepMerge, api::{ - apps::v1::{DaemonSet, DaemonSetSpec}, + apps::v1::{DaemonSet, DaemonSetSpec, DaemonSetUpdateStrategy, RollingUpdateDaemonSet}, core::v1::{ ConfigMap, EmptyDirVolumeSource, EnvVar, EnvVarSource, HTTPGetAction, ObjectFieldSelector, Probe, SecretVolumeSource, ServiceAccount, @@ -1183,6 +1183,13 @@ fn build_server_rolegroup_daemonset( ..LabelSelector::default() }, template: pod_template, + update_strategy: Some(DaemonSetUpdateStrategy { + type_: Some("RollingUpdate".to_string()), + rolling_update: Some(RollingUpdateDaemonSet { + max_surge: Some(IntOrString::Int(1)), + max_unavailable: Some(IntOrString::Int(0)), + }), + }), ..DaemonSetSpec::default() }; diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index 5cfbbd4f..2b23ed99 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -63,6 +63,12 @@ pub(crate) fn build_server_role_service( type_: Some(opa.spec.cluster_config.listener_class.k8s_service_type()), ports: Some(data_service_ports(opa.spec.cluster_config.tls_enabled())), selector: Some(service_selector_labels.into()), + // This ensures that products (e.g. Trino) on a node always talk to the OPA pod on the + // same node, avoiding cross-node latency. The downside is that if the local OPA pod is + // unavailable, requests fail instead of falling back to another node. + // TODO: Once our minimum supported Kubernetes version is 1.35, use + // `trafficDistribution: PreferSameNode` instead, which prefers the local node but + // gracefully falls back to other nodes if the local pod is unavailable. internal_traffic_policy: Some("Local".to_string()), ..ServiceSpec::default() }; diff --git a/tests/templates/kuttl/smoke/10-assert.yaml.j2 b/tests/templates/kuttl/smoke/10-assert.yaml.j2 index 6b86f13f..76703fb2 100644 --- a/tests/templates/kuttl/smoke/10-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-assert.yaml.j2 @@ -9,6 +9,11 @@ kind: DaemonSet metadata: name: test-opa-server-default spec: + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 template: spec: containers: From 899baa2e8e672c00c23823c0ce411fa227d8a9bc Mon Sep 17 00:00:00 2001 From: Stacky McStackface <95074132+stackable-bot@users.noreply.github.com> Date: Mon, 30 Mar 2026 11:50:40 +0200 Subject: [PATCH 7/8] chore: Generated commit to update templated files since the last template run up to stackabletech/operator-templating@2428552dc3a822ace7e57f2eae68433be7a5fa5e (#821) Reference-to: stackabletech/operator-templating@2428552 (Rollout CI fixes for external contributions on forks) --- .github/workflows/build.yaml | 36 ++++++++++++++++++-------- .github/workflows/integration-test.yml | 4 +-- .github/workflows/pr_pre-commit.yaml | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ccb485d2..3c0e36af 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -48,7 +48,7 @@ jobs: - name: Check for changed files id: check - uses: stackabletech/actions/detect-changes@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/detect-changes@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: patterns: | - '.github/workflows/build.yaml' @@ -166,7 +166,7 @@ jobs: - name: Build Container Image id: build - uses: stackabletech/actions/build-container-image@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/build-container-image@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: image-name: ${{ env.OPERATOR_NAME }} image-index-manifest-tag: ${{ steps.version.outputs.OPERATOR_VERSION }} @@ -174,7 +174,8 @@ jobs: container-file: docker/Dockerfile - name: Publish Container Image - uses: stackabletech/actions/publish-image@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + if: ${{ !github.event.pull_request.head.repo.fork }} + uses: stackabletech/actions/publish-image@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: image-registry-uri: oci.stackable.tech image-registry-username: robot$sdp+github-action-build @@ -185,7 +186,10 @@ jobs: publish-index-manifest: name: Publish/Sign ${{ needs.build-container-image.outputs.operator-version }} Index - if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + if: | + (github.event_name != 'merge_group') + && needs.detect-changes.outputs.detected == 'true' + && !github.event.pull_request.head.repo.fork needs: - detect-changes - build-container-image @@ -199,7 +203,7 @@ jobs: persist-credentials: false - name: Publish and Sign Image Index - uses: stackabletech/actions/publish-image-index-manifest@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/publish-image-index-manifest@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: image-registry-uri: oci.stackable.tech image-registry-username: robot$sdp+github-action-build @@ -209,7 +213,9 @@ jobs: publish-helm-chart: name: Package/Publish ${{ needs.build-container-image.outputs.operator-version }} Helm Chart - if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + if: | + (github.event_name != 'merge_group') + && needs.detect-changes.outputs.detected == 'true' needs: - detect-changes - build-container-image @@ -224,7 +230,7 @@ jobs: submodules: recursive - name: Package, Publish, and Sign Helm Chart - uses: stackabletech/actions/publish-helm-chart@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/publish-helm-chart@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: chart-registry-uri: oci.stackable.tech chart-registry-username: robot$sdp-charts+github-action-build @@ -233,10 +239,14 @@ jobs: chart-directory: deploy/helm/${{ env.OPERATOR_NAME }} chart-version: ${{ needs.build-container-image.outputs.operator-version }} app-version: ${{ needs.build-container-image.outputs.operator-version }} + publish-and-sign: ${{ !github.event.pull_request.head.repo.fork }} openshift-preflight-check: name: Run OpenShift Preflight Check for ${{ needs.build-container-image.outputs.operator-version }}-${{ matrix.arch }} - if: (github.event_name != 'merge_group') && needs.detect-changes.outputs.detected == 'true' + if: | + (github.event_name != 'merge_group') + && needs.detect-changes.outputs.detected == 'true' + && !github.event.pull_request.head.repo.fork needs: - detect-changes - build-container-image @@ -250,7 +260,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Run OpenShift Preflight Check - uses: stackabletech/actions/run-openshift-preflight@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/run-openshift-preflight@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: image-index-uri: oci.stackable.tech/sdp/${{ env.OPERATOR_NAME }}:${{ needs.build-container-image.outputs.operator-version }} image-architecture: ${{ matrix.arch }} @@ -272,7 +282,11 @@ jobs: notify: name: Failure Notification - if: (failure() || github.run_attempt > 1) && github.event_name != 'merge_group' && needs.detect-changes.outputs.detected == 'true' + if: | + (failure() || github.run_attempt > 1) + && github.event_name != 'merge_group' + && needs.detect-changes.outputs.detected == 'true' + && !github.event.pull_request.head.repo.fork needs: - detect-changes - build-container-image @@ -286,7 +300,7 @@ jobs: persist-credentials: false - name: Send Notification - uses: stackabletech/actions/send-slack-notification@babe44d7b1db87f8e7731c011151d22a8a374191 # v0.12.0 + uses: stackabletech/actions/send-slack-notification@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: publish-helm-chart-result: ${{ needs.publish-helm-chart.result }} publish-manifests-result: ${{ needs.publish-index-manifest.result }} diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 0f9f268e..6ef8c57d 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -41,7 +41,7 @@ jobs: # TODO: Enable the scheduled runs which hard-code what profile to use - name: Run Integration Test id: test - uses: stackabletech/actions/run-integration-test@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/run-integration-test@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: replicated-api-token: ${{ secrets.REPLICATED_API_TOKEN }} test-mode-input: ${{ inputs.test-mode-input }} @@ -51,7 +51,7 @@ jobs: - name: Send Notification if: ${{ failure() || github.run_attempt > 1 }} - uses: stackabletech/actions/send-slack-notification@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + uses: stackabletech/actions/send-slack-notification@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: slack-token: ${{ secrets.SLACK_INTEGRATION_TEST_TOKEN }} failed-tests: ${{ steps.test.outputs.failed-tests }} diff --git a/.github/workflows/pr_pre-commit.yaml b/.github/workflows/pr_pre-commit.yaml index b45e2daf..b096292f 100644 --- a/.github/workflows/pr_pre-commit.yaml +++ b/.github/workflows/pr_pre-commit.yaml @@ -27,7 +27,7 @@ jobs: persist-credentials: false submodules: recursive fetch-depth: 0 - - uses: stackabletech/actions/run-pre-commit@29bea1b451c0c2e994bd495969286f95bf49ed6a # v0.11.0 + - uses: stackabletech/actions/run-pre-commit@9848c5593dff4793aacba240116a648c02f20fa4 # v0.13.1 with: python-version: ${{ env.PYTHON_VERSION }} rust: ${{ env.RUST_TOOLCHAIN_VERSION }} From ef8ba503c3e974dc349ecccbfd3b6d35785a3b4e Mon Sep 17 00:00:00 2001 From: dervoeti Date: Wed, 8 Apr 2026 13:07:59 +0200 Subject: [PATCH 8/8] refactor: mention role overrides first --- .../configuration-environment-overrides.adoc | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc index c586a917..f00501fd 100644 --- a/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc +++ b/docs/modules/opa/pages/usage-guide/configuration-environment-overrides.adoc @@ -11,62 +11,62 @@ This will lead to faulty installations. OPA's `config.json` can be overridden using `configOverrides`. For details on the supported override strategies, see the xref:concepts:overrides.adoc#config-overrides[config overrides documentation]. -For example per role group: +For example per role: [source,yaml] ---- servers: + configOverrides: + config.json: + jsonPatches: + - '{"op": "replace", "path": "/bundles/stackable/polling/min_delay_seconds", "value": 3}' + - '{"op": "add", "path": "/default_decision", "value": "allow"}' roleGroups: default: - configOverrides: - config.json: - jsonMergePatch: - distributed_tracing: - address: jaeger-collector.default.svc.cluster.local:4317 - type: grpc + config: {} ---- -or per role: +or per role group: [source,yaml] ---- servers: - configOverrides: - config.json: - jsonPatches: - - '{"op": "replace", "path": "/bundles/stackable/polling/min_delay_seconds", "value": 3}' - - '{"op": "add", "path": "/default_decision", "value": "allow"}' roleGroups: default: - config: {} + configOverrides: + config.json: + jsonMergePatch: + distributed_tracing: + address: jaeger-collector.default.svc.cluster.local:4317 + type: grpc ---- == Environment variables Environment variables can be (over)written by adding the `envOverrides` property. -For example per role group: +For example per role: [source,yaml] ---- servers: + envOverrides: + MY_ENV_VAR: "MY_VALUE" roleGroups: default: config: {} - envOverrides: - MY_ENV_VAR: "MY_VALUE" ---- -or per role: +or per role group: [source,yaml] ---- servers: - envOverrides: - MY_ENV_VAR: "MY_VALUE" roleGroups: default: config: {} + envOverrides: + MY_ENV_VAR: "MY_VALUE" ---- == CLI overrides @@ -76,28 +76,28 @@ This allows you to customize OPA's behavior by passing additional or overriding CLI overrides can be specified at both the role and rolegroup level, with rolegroup overrides taking precedence over role overrides. -For example, per rolegroup: +For example, per role: [source,yaml] ---- servers: + cliOverrides: + --log-format: json + --diagnostic-addr: "0.0.0.0:8282" roleGroups: - default: - cliOverrides: - --log-format: json-pretty - --diagnostic-addr: "0.0.0.0:8282" + default: {} ---- -or per role: +or per rolegroup: [source,yaml] ---- servers: - cliOverrides: - --log-format: json - --diagnostic-addr: "0.0.0.0:8282" roleGroups: - default: {} + default: + cliOverrides: + --log-format: json-pretty + --diagnostic-addr: "0.0.0.0:8282" ---- For a complete list of available flags, refer to the https://www.openpolicyagent.org/docs/latest/cli/#run[OPA documentation].