Skip to content
Merged
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions rust/olm-deployer/README.md
Original file line number Diff line number Diff line change
@@ -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.
28 changes: 3 additions & 25 deletions rust/olm-deployer/src/data.rs
Original file line number Diff line number Diff line change
@@ -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<serde_json::Value>> {
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}"),
}
}
Expand Down
55 changes: 31 additions & 24 deletions rust/olm-deployer/src/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<serde_json::Value>>();

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::<Vec<serde_json::Value>>();

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()),
}
}
}
Expand Down Expand Up @@ -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
Expand Down
24 changes: 14 additions & 10 deletions rust/olm-deployer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -50,33 +51,28 @@ struct Opts {
struct OlmDeployerRun {
#[arg(
long,
short,
default_value = "false",
help = "Keep running after manifests have been successfully applied."
)]
keep_alive: bool,

#[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]
Expand All @@ -89,7 +85,6 @@ async fn main() -> Result<()> {
namespace,
dir,
telemetry,
cluster_info,
}) = opts.cmd
{
// NOTE (@NickLarsenNZ): Before stackable-telemetry was used:
Expand All @@ -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?;
Expand Down
25 changes: 16 additions & 9 deletions rust/olm-deployer/src/resources/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
}
}
}
Expand Down Expand Up @@ -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
Expand Down