From 42d018a19f5d9c08f728c251cc8131f9e1a3440a Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:08:03 +0200 Subject: [PATCH 1/8] fix: remove cluster info cli arguments from olm deployer --- rust/olm-deployer/src/env/mod.rs | 2 +- rust/olm-deployer/src/main.rs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rust/olm-deployer/src/env/mod.rs b/rust/olm-deployer/src/env/mod.rs index d9c421dc..5a793c2d 100644 --- a/rust/olm-deployer/src/env/mod.rs +++ b/rust/olm-deployer/src/env/mod.rs @@ -10,7 +10,7 @@ use crate::data::container; /// Copy the environment from the "secret-operator-deployer" container in `source` /// to the container "secret-operator" in `target`. -/// The `target` must be a DaemonSet object otherwise this is a no-op. +/// The `target` must be a DaemonSet object, otherwise this is a no-op. pub(super) fn maybe_copy_env( source: &Deployment, target: &mut DynamicObject, diff --git a/rust/olm-deployer/src/main.rs b/rust/olm-deployer/src/main.rs index 81aa506c..59a51730 100644 --- a/rust/olm-deployer/src/main.rs +++ b/rust/olm-deployer/src/main.rs @@ -71,9 +71,6 @@ struct OlmDeployerRun { #[command(flatten)] pub telemetry: TelemetryOptions, - - #[command(flatten)] - pub cluster_info: KubernetesClusterInfoOptions, } #[tokio::main] @@ -85,7 +82,6 @@ async fn main() -> Result<()> { namespace, dir, telemetry, - cluster_info, }) = opts.cmd { // NOTE (@NickLarsenNZ): Before stackable-telemetry was used: @@ -104,7 +100,13 @@ async fn main() -> Result<()> { description = built_info::PKG_DESCRIPTION ); - let client = client::initialize_operator(Some(APP_NAME.to_string()), &cluster_info).await?; + let dummy_cluster_info = KubernetesClusterInfoOptions { + kubernetes_cluster_domain: None, + kubernetes_node_name: "".to_string(), + }; + + let client = + client::initialize_operator(Some(APP_NAME.to_string()), &dummy_cluster_info).await?; let deployment = get_deployment(&csv, &namespace, &client).await?; let cluster_role = get_cluster_role(&csv, &client).await?; From d9e3f61de74447da1c53f2c2b410d11e4d12d6c2 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:50:50 +0200 Subject: [PATCH 2/8] need to pass a domain name --- rust/olm-deployer/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/olm-deployer/src/main.rs b/rust/olm-deployer/src/main.rs index 59a51730..0247b5db 100644 --- a/rust/olm-deployer/src/main.rs +++ b/rust/olm-deployer/src/main.rs @@ -23,6 +23,7 @@ use clap::Parser; use stackable_operator::{ cli::Command, client, + commons::networking::DomainName, k8s_openapi::api::{apps::v1::Deployment, rbac::v1::ClusterRole}, kube::{ self, @@ -101,7 +102,7 @@ async fn main() -> Result<()> { ); let dummy_cluster_info = KubernetesClusterInfoOptions { - kubernetes_cluster_domain: None, + kubernetes_cluster_domain: Some(DomainName::try_from("cluster.local")?), kubernetes_node_name: "".to_string(), }; From 1fd80aed7896a8bb00c4924f6320a64cf8cf8201 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:24:37 +0200 Subject: [PATCH 3/8] copy subscription env to op deployment too --- rust/olm-deployer/src/data.rs | 11 +++++++ rust/olm-deployer/src/env/mod.rs | 55 ++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/rust/olm-deployer/src/data.rs b/rust/olm-deployer/src/data.rs index 3642cb91..2fe41792 100644 --- a/rust/olm-deployer/src/data.rs +++ b/rust/olm-deployer/src/data.rs @@ -36,6 +36,17 @@ pub fn container<'a>( } } +pub fn containers<'a>( + target: &'a mut DynamicObject, +) -> anyhow::Result<&'a mut Vec> { + let tname = target.name_any(); + let path = "template/spec/containers".split("/"); + match get_or_create(target.data.pointer_mut("/spec").unwrap(), path)? { + serde_json::Value::Array(containers) => Ok(containers), + _ => anyhow::bail!("no containers found in object {tname}"), + } +} + /// Returns the object nested in `root` by traversing the `path` of nested keys. /// Creates any missing objects in path. /// In case of success, the returned value is either the existing object or diff --git a/rust/olm-deployer/src/env/mod.rs b/rust/olm-deployer/src/env/mod.rs index 5a793c2d..d05da7d2 100644 --- a/rust/olm-deployer/src/env/mod.rs +++ b/rust/olm-deployer/src/env/mod.rs @@ -6,41 +6,46 @@ use stackable_operator::{ }, }; -use crate::data::container; +use crate::data::containers; /// Copy the environment from the "secret-operator-deployer" container in `source` -/// to the container "secret-operator" in `target`. -/// The `target` must be a DaemonSet object, otherwise this is a no-op. +/// to *all* containers in target. +/// The target must be a DaemonSet or Deployment, otherwise this function is a no-op. +/// This function allows OLM Subscription objects to configure the environment +/// of operator containers. pub(super) fn maybe_copy_env( source: &Deployment, target: &mut DynamicObject, target_gvk: &GroupVersionKind, ) -> anyhow::Result<()> { - if target_gvk.kind == "DaemonSet" { + let target_kind_set = ["DaemonSet", "Deployment"]; + if target_kind_set.contains(&target_gvk.kind.as_str()) { if let Some(env) = deployer_env_var(source) { - match container(target, "secret-operator")? { - serde_json::Value::Object(c) => { - let json_env = env - .iter() - .map(|e| serde_json::json!(e)) - .collect::>(); - - match c.get_mut("env") { - Some(env) => match env { - v @ serde_json::Value::Null => { - *v = serde_json::json!(json_env); + for container in containers(target)? { + match container { + serde_json::Value::Object(c) => { + let json_env = env + .iter() + .map(|e| serde_json::json!(e)) + .collect::>(); + + match c.get_mut("env") { + Some(env) => match env { + v @ serde_json::Value::Null => { + *v = serde_json::json!(json_env); + } + serde_json::Value::Array(container_env) => { + container_env.extend_from_slice(&json_env) + } + _ => anyhow::bail!("env is not null or an array"), + }, + None => { + c.insert("env".to_string(), serde_json::json!(json_env)); } - serde_json::Value::Array(container_env) => { - container_env.extend_from_slice(&json_env) - } - _ => anyhow::bail!("env is not null or an array"), - }, - None => { - c.insert("env".to_string(), serde_json::json!(json_env)); } } + _ => anyhow::bail!("no containers found in object {}", target.name_any()), } - _ => anyhow::bail!("no containers found in object {}", target.name_any()), } } } @@ -149,7 +154,9 @@ spec: }, ]); assert_eq!( - container(&mut daemonset, "secret-operator")? + containers(&mut daemonset)? + .first() + .expect("daemonset has no containers") .get("env") .unwrap(), &expected From 7900b9984bc1becd7971e0647b3739a24bcd9cce Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 12:37:53 +0200 Subject: [PATCH 4/8] cannot patch the ns of the tls secret class anymore --- rust/olm-deployer/src/data.rs | 11 ---- rust/olm-deployer/src/main.rs | 2 - rust/olm-deployer/src/namespace/mod.rs | 76 -------------------------- 3 files changed, 89 deletions(-) delete mode 100644 rust/olm-deployer/src/namespace/mod.rs diff --git a/rust/olm-deployer/src/data.rs b/rust/olm-deployer/src/data.rs index 2fe41792..2f0422e9 100644 --- a/rust/olm-deployer/src/data.rs +++ b/rust/olm-deployer/src/data.rs @@ -1,16 +1,5 @@ -use anyhow::{Result, bail}; use stackable_operator::kube::{ResourceExt, api::DynamicObject}; -pub fn data_field_as_mut<'a>( - value: &'a mut serde_json::Value, - pointer: &str, -) -> Result<&'a mut serde_json::Value> { - match value.pointer_mut(pointer) { - Some(field) => Ok(field), - x => bail!("invalid pointer {pointer} for object {x:?}"), - } -} - pub fn container<'a>( target: &'a mut DynamicObject, container_name: &str, diff --git a/rust/olm-deployer/src/main.rs b/rust/olm-deployer/src/main.rs index 0247b5db..f3afecee 100644 --- a/rust/olm-deployer/src/main.rs +++ b/rust/olm-deployer/src/main.rs @@ -13,7 +13,6 @@ /// mod data; mod env; -mod namespace; mod owner; mod resources; mod tolerations; @@ -146,7 +145,6 @@ async fn main() -> Result<()> { &deployment, &cluster_role, )?; - namespace::maybe_patch_namespace(&namespace, &mut obj, &gvk)?; env::maybe_copy_env(&deployment, &mut obj, &gvk)?; resources::maybe_copy_resources(&deployment, &mut obj, &gvk)?; // ---------- apply diff --git a/rust/olm-deployer/src/namespace/mod.rs b/rust/olm-deployer/src/namespace/mod.rs deleted file mode 100644 index 8324b5b5..00000000 --- a/rust/olm-deployer/src/namespace/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -use anyhow::Result; -use serde_json::{Value, json}; -use stackable_operator::kube::api::{DynamicObject, GroupVersionKind}; - -use crate::data; - -/// Path the namespace of the autoTls secret class. -/// Otherwise do nothing. -pub(super) fn maybe_patch_namespace( - ns: &str, - res: &mut DynamicObject, - gvk: &GroupVersionKind, -) -> Result<()> { - if gvk.kind == "SecretClass" { - *auto_tls_namespace(&mut res.data)? = json!(ns.to_string()); - } - Ok(()) -} - -fn auto_tls_namespace(value: &mut serde_json::Value) -> Result<&mut Value> { - data::data_field_as_mut(value, "/spec/backend/autoTls/ca/secret/namespace") -} - -#[cfg(test)] -mod test { - use std::sync::LazyLock; - - use anyhow::Result; - use serde::Deserialize; - - use super::*; - - static TLS_SECRET_CLASS: LazyLock = LazyLock::new(|| { - const STR_TLS_SECRET_CLASS: &str = r#" ---- -apiVersion: secrets.stackable.tech/v1alpha1 -kind: SecretClass -metadata: - name: tls - labels: - app.kubernetes.io/name: secret-operator - app.kubernetes.io/instance: secret-operator - stackable.tech/vendor: Stackable - app.kubernetes.io/version: "24.11.0" -spec: - backend: - autoTls: - ca: - secret: - name: secret-provisioner-tls-ca - namespace: "${NAMESPACE}" # TODO patch with olm-deployer - autoGenerate: true -"#; - - let data = serde_yaml::Value::deserialize(serde_yaml::Deserializer::from_str( - STR_TLS_SECRET_CLASS, - )) - .unwrap(); - serde_yaml::from_value(data).unwrap() - }); - - #[test] - fn test_patch_namespace() -> Result<()> { - let gvk: GroupVersionKind = GroupVersionKind { - kind: "SecretClass".to_string(), - version: "v1alpha1".to_string(), - group: "secrets.stackable.tech".to_string(), - }; - let mut tls = TLS_SECRET_CLASS.clone(); - maybe_patch_namespace("prod", &mut tls, &gvk)?; - - let expected = json!("prod"); - assert_eq!(auto_tls_namespace(&mut tls.data)?, &expected); - Ok(()) - } -} From 9056ae42facd2dad51446ab8c8f7bdf214637f95 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 13:41:39 +0200 Subject: [PATCH 5/8] set deployment tolerations too --- rust/olm-deployer/src/tolerations/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/olm-deployer/src/tolerations/mod.rs b/rust/olm-deployer/src/tolerations/mod.rs index c8588bdd..9f73562c 100644 --- a/rust/olm-deployer/src/tolerations/mod.rs +++ b/rust/olm-deployer/src/tolerations/mod.rs @@ -7,13 +7,16 @@ use crate::data::get_or_create; /// Copies the pod tolerations from the `source` to the `target`. /// Does nothing if there are no tolerations or if the `target` is not -/// a DaemonSet. +/// a DaemonSet or a Deployment. +/// Admins can configure Subscription tolerations when installing the operator +/// and these need to be copied over to the objects created by the deployer. pub(super) fn maybe_copy_tolerations( source: &Deployment, target: &mut DynamicObject, target_gvk: &GroupVersionKind, ) -> anyhow::Result<()> { - if target_gvk.kind == "DaemonSet" { + let target_kind_set = ["DaemonSet", "Deployment"]; + if target_kind_set.contains(&target_gvk.kind.as_str()) { if let Some(tolerations) = deployment_tolerations(source) { let path = "template/spec/tolerations".split("/"); *get_or_create(target.data.pointer_mut("/spec").unwrap(), path)? = serde_json::json!( From c9c2e29f6f605d5cb4bdc3bde9affd5571f9208c Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:45:11 +0200 Subject: [PATCH 6/8] patch deployment resources too --- rust/olm-deployer/src/data.rs | 25 ------------------------- rust/olm-deployer/src/resources/mod.rs | 25 ++++++++++++++++--------- 2 files changed, 16 insertions(+), 34 deletions(-) diff --git a/rust/olm-deployer/src/data.rs b/rust/olm-deployer/src/data.rs index 2f0422e9..0206762b 100644 --- a/rust/olm-deployer/src/data.rs +++ b/rust/olm-deployer/src/data.rs @@ -1,30 +1,5 @@ use stackable_operator::kube::{ResourceExt, api::DynamicObject}; -pub fn container<'a>( - target: &'a mut DynamicObject, - container_name: &str, -) -> anyhow::Result<&'a mut serde_json::Value> { - let tname = target.name_any(); - let path = "template/spec/containers".split("/"); - match get_or_create(target.data.pointer_mut("/spec").unwrap(), path)? { - serde_json::Value::Array(containers) => { - for c in containers { - if c.is_object() { - if let Some(serde_json::Value::String(name)) = c.get("name") { - if container_name == name { - return Ok(c); - } - } - } else { - anyhow::bail!("container is not a object: {:?}", c); - } - } - anyhow::bail!("container named {container_name} not found"); - } - _ => anyhow::bail!("no containers found in object {tname}"), - } -} - pub fn containers<'a>( target: &'a mut DynamicObject, ) -> anyhow::Result<&'a mut Vec> { diff --git a/rust/olm-deployer/src/resources/mod.rs b/rust/olm-deployer/src/resources/mod.rs index 47182f01..2daf5fcb 100644 --- a/rust/olm-deployer/src/resources/mod.rs +++ b/rust/olm-deployer/src/resources/mod.rs @@ -6,23 +6,28 @@ use stackable_operator::{ }, }; -use crate::data::container; +use crate::data::containers; /// Copies the resources of the container named "secret-operator-deployer" from `source` -/// to the container "secret-operator" in `target`. -/// Does nothing if there are no resources or if the `target` is not a DaemonSet. +/// to *all* containers in `target`. +/// Does nothing if there are no resources or if the `target` is not a DaemonSet or a Deployment. +/// This function allows OLM Subscription objects to configure the resources +/// of operator containers. pub(super) fn maybe_copy_resources( source: &Deployment, target: &mut DynamicObject, target_gvk: &GroupVersionKind, ) -> anyhow::Result<()> { - if target_gvk.kind == "DaemonSet" { + let target_kind_set = ["DaemonSet", "Deployment"]; + if target_kind_set.contains(&target_gvk.kind.as_str()) { if let Some(res) = deployment_resources(source) { - match container(target, "secret-operator")? { - serde_json::Value::Object(c) => { - c.insert("resources".to_string(), serde_json::json!(res)); + for container in containers(target)? { + match container { + serde_json::Value::Object(c) => { + c.insert("resources".to_string(), serde_json::json!(res)); + } + _ => anyhow::bail!("no containers found in object {}", target.name_any()), } - _ => anyhow::bail!("no containers found in object {}", target.name_any()), } } } @@ -150,7 +155,9 @@ spec: ..ResourceRequirements::default() }); assert_eq!( - container(&mut daemonset, "secret-operator")? + containers(&mut daemonset)? + .first() + .expect("daemonset has no containers") .get("resources") .unwrap(), &expected From 1554d50b7a9e7e97e75e6bb01b58c74531944bf1 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 15:29:23 +0200 Subject: [PATCH 7/8] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4c48d21..bbf5ce5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ All notable changes to this project will be documented in this file. - `kubeletDir` has been move to `csiNodeDriver.kubeletDir`. - Bump csi-node-driver-registrar to `v2.15.0` ([#642]). - Bump csi-provisioner to `v5.3.0` ([#643]). +- OLM deployer: patch the new Deployment object too and other changes to align with the new operator structure ([#648]). [#597]: https://github.com/stackabletech/secret-operator/pull/597 [#636]: https://github.com/stackabletech/secret-operator/pull/636 @@ -37,6 +38,7 @@ All notable changes to this project will be documented in this file. [#643]: https://github.com/stackabletech/secret-operator/pull/643 [#644]: https://github.com/stackabletech/secret-operator/pull/644 [#645]: https://github.com/stackabletech/secret-operator/pull/645 +[#648]: https://github.com/stackabletech/secret-operator/pull/648 ## [25.7.0] - 2025-07-23 From 7cd5f2e6ee59dbd367d83cc0f4331d57fee007d9 Mon Sep 17 00:00:00 2001 From: Razvan-Daniel Mihai <84674+razvan@users.noreply.github.com> Date: Thu, 23 Oct 2025 17:17:02 +0200 Subject: [PATCH 8/8] updated olm deployer readme --- rust/olm-deployer/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/rust/olm-deployer/README.md b/rust/olm-deployer/README.md index 95a27a51..9dfe1262 100644 --- a/rust/olm-deployer/README.md +++ b/rust/olm-deployer/README.md @@ -1,4 +1,25 @@ -# How to test +# Description + +This is an deployment helper for the Operator Lifecycle Manager which is usually present on OpenShift environments. + +It is needed to work around various OLM restrictions. + +What it does: + +- creates Security Context Constraints just for this operator (maybe remove in the future) +- installs the Deployment and DaemonSet objects +- installs the operator webhook service +- installs the CSI driver and storage classes +- assigns it's own deployment as owner of all the namespaced objects to ensure proper cleanup +- patches the environment of all workload containers with any custom values provided in the Subscription object +- patches the resources of all workload containers with any custom values provided in the Subscription object +- patches the tolerations of all workload pods with any custom values provided in the Subscription object + +## Usage + +Users do not need to interact with the OLM deployer directly. + +## How to Test Requirements: