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 @@ -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
Expand All @@ -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

Expand Down
23 changes: 22 additions & 1 deletion rust/olm-deployer/README.md
Original file line number Diff line number Diff line change
@@ -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:

Expand Down
31 changes: 3 additions & 28 deletions rust/olm-deployer/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,12 @@
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>(
pub fn containers<'a>(
target: &'a mut DynamicObject,
container_name: &str,
) -> anyhow::Result<&'a mut serde_json::Value> {
) -> anyhow::Result<&'a 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").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");
}
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 "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::<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 @@ -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
Expand Down
15 changes: 8 additions & 7 deletions rust/olm-deployer/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
///
mod data;
mod env;
mod namespace;
mod owner;
mod resources;
mod tolerations;
Expand All @@ -23,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 @@ -71,9 +71,6 @@ struct OlmDeployerRun {

#[command(flatten)]
pub telemetry: TelemetryOptions,

#[command(flatten)]
pub cluster_info: KubernetesClusterInfoOptions,
}

#[tokio::main]
Expand All @@ -85,7 +82,6 @@ async fn main() -> Result<()> {
namespace,
dir,
telemetry,
cluster_info,
}) = opts.cmd
{
// NOTE (@NickLarsenNZ): Before stackable-telemetry was used:
Expand All @@ -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: 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, &namespace, &client).await?;
let cluster_role = get_cluster_role(&csv, &client).await?;
Expand Down Expand Up @@ -143,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
Expand Down
76 changes: 0 additions & 76 deletions rust/olm-deployer/src/namespace/mod.rs

This file was deleted.

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 "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()),
}
}
}
Expand Down Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions rust/olm-deployer/src/tolerations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
Loading