diff --git a/CHANGELOG.md b/CHANGELOG.md index 18686a45..ac3fb154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,12 +28,14 @@ All notable changes to this project will be documented in this file. - Bump csi-node-driver-registrar to `v2.15.0` ([#337]). - Bump csi-provisioner to `v5.3.0` ([#338]). - We now default to the `ephemeral-nodes` helm preset. Read on the [issue](https://github.com/stackabletech/issues/issues/770) for details ([#340]). +- olm-deployer: update to align with new operator configuration ([#344]). [#334]: https://github.com/stackabletech/listener-operator/pull/334 [#337]: https://github.com/stackabletech/listener-operator/pull/337 [#338]: https://github.com/stackabletech/listener-operator/pull/338 [#339]: https://github.com/stackabletech/listener-operator/pull/339 [#340]: https://github.com/stackabletech/listener-operator/pull/340 +[#344]: https://github.com/stackabletech/listener-operator/pull/344 ## [25.7.0] - 2025-07-23 diff --git a/rust/olm-deployer/README.md b/rust/olm-deployer/README.md new file mode 100644 index 00000000..6fbc25ac --- /dev/null +++ b/rust/olm-deployer/README.md @@ -0,0 +1,20 @@ +# 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 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. diff --git a/rust/olm-deployer/src/data.rs b/rust/olm-deployer/src/data.rs index 36345942..4568184a 100644 --- a/rust/olm-deployer/src/data.rs +++ b/rust/olm-deployer/src/data.rs @@ -1,33 +1,11 @@ use anyhow::{Context, anyhow}; 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> { +pub fn containers(target: &mut DynamicObject) -> anyhow::Result<&mut Vec> { let tname = target.name_any(); let path = "template/spec/containers".split("/"); - match get_or_create( - target - .data - .pointer_mut("/spec") - .context(anyhow!("object [{tname}] has no .spec property"))?, - 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"); - } + 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}"), } } diff --git a/rust/olm-deployer/src/env/mod.rs b/rust/olm-deployer/src/env/mod.rs index 305b1cbb..e3d86342 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 "listener-operator-deployer" container in `source` -/// to the container "listener-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, "listener-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()), } } } @@ -151,7 +156,9 @@ spec: }, ]); assert_eq!( - container(&mut daemonset, "listener-operator")? + containers(&mut daemonset)? + .first() + .expect("daemonset has no containers") .get("env") .unwrap(), &expected diff --git a/rust/olm-deployer/src/main.rs b/rust/olm-deployer/src/main.rs index fe997d8a..9b3ccf5d 100644 --- a/rust/olm-deployer/src/main.rs +++ b/rust/olm-deployer/src/main.rs @@ -22,6 +22,7 @@ use clap::Parser; use stackable_operator::{ cli::Command, client, + commons::networking::DomainName, k8s_openapi::api::{apps::v1::Deployment, rbac::v1::ClusterRole}, kube::{ self, @@ -50,7 +51,6 @@ struct Opts { struct OlmDeployerRun { #[arg( long, - short, default_value = "false", help = "Keep running after manifests have been successfully applied." )] @@ -58,25 +58,21 @@ struct OlmDeployerRun { #[arg( long, - short, help = "Name of ClusterServiceVersion object that owns this Deployment." )] csv: String, - #[arg(long, short, help = "Name of deployment object that owns this Pod.")] + #[arg(long, help = "Name of deployment object that owns this Pod.")] deployer: String, - #[arg(long, short, help = "Namespace of the ClusterServiceVersion object.")] + #[arg(long, help = "Namespace of the ClusterServiceVersion object.")] namespace: String, - #[arg(long, short, help = "Directory with manifests to patch and apply.")] + #[arg(long, help = "Directory with manifests to patch and apply.")] dir: std::path::PathBuf, #[command(flatten)] pub telemetry: TelemetryOptions, - - #[command(flatten)] - pub cluster_info: KubernetesClusterInfoOptions, } #[tokio::main] @@ -89,7 +85,6 @@ async fn main() -> Result<()> { namespace, dir, telemetry, - cluster_info, }) = opts.cmd { // NOTE (@NickLarsenNZ): Before stackable-telemetry was used: @@ -108,7 +103,16 @@ async fn main() -> Result<()> { description = built_info::PKG_DESCRIPTION ); - let client = client::initialize_operator(Some(APP_NAME.to_string()), &cluster_info).await?; + // Not used by the olm deployer but still want to use client::initialize_operator() + // Without this dummy value, the KUBERNETES_NODE_NAME env/cli argument would be required + // but not used. + let dummy_cluster_info = KubernetesClusterInfoOptions { + kubernetes_cluster_domain: Some(DomainName::try_from("cluster.local")?), + kubernetes_node_name: "".to_string(), + }; + + let client = + client::initialize_operator(Some(APP_NAME.to_string()), &dummy_cluster_info).await?; let deployment = get_deployment(&csv, &deployer, &namespace, &client).await?; let cluster_role = get_cluster_role(&csv, &client).await?; diff --git a/rust/olm-deployer/src/resources/mod.rs b/rust/olm-deployer/src/resources/mod.rs index b72d443d..c53c53b8 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 "listener-operator-deployer" from `source` -/// to the container "listener-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, "listener-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()), } } } @@ -152,7 +157,9 @@ spec: ..ResourceRequirements::default() }); assert_eq!( - container(&mut daemonset, "listener-operator")? + containers(&mut daemonset)? + .first() + .expect("daemonset has no containers") .get("resources") .unwrap(), &expected