Skip to content
6 changes: 6 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- BREAKING: Add a new CLI flag/env to disabling CRD maintenance: `--disable-crd-maintenance` ([#1085]).

[#1085]: https://github.com/stackabletech/operator-rs/pull/1085

## [0.96.0] - 2025-08-25

### Added
Expand Down
12 changes: 12 additions & 0 deletions crates/stackable-operator/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ pub enum Command<Run: Args = ProductOperatorRun> {
/// operator_namespace: "stackable-operators".to_string(),
/// operator_service_name: "foo-operator".to_string(),
/// },
/// disable_crd_maintenance: false,
/// },
/// }));
/// ```
Expand Down Expand Up @@ -245,6 +246,17 @@ pub struct ProductOperatorRun {
/// Provides a specific namespace to watch (instead of watching all namespaces)
#[arg(long, env, default_value = "")]
pub watch_namespace: WatchNamespace,

/// Don't maintain the CustomResourceDefinitions (CRDs) the operator is responsible for.
///
/// Maintenance includes creating the CRD initially, adding new versions and keeping the TLS
/// certificate of webhooks up to date. Turning this off can be desirable to reduce the RBAC
/// permissions of the operator.
///
/// WARNING: If you disable CRD maintenance you are responsible for maintaining it, including,
/// but not limited to, the points above.
#[arg(long, env)]
pub disable_crd_maintenance: bool,
}

/// All the CLI arguments that all (or at least most) Stackable applications use.
Expand Down
6 changes: 6 additions & 0 deletions crates/stackable-webhook/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Added

- BREAKING: Support disabling CRD maintenance using a new boolean flag in `ConversionWebhookOptions` ([#1085]).

[#1085]: https://github.com/stackabletech/operator-rs/pull/1085

## [0.5.0] - 2025-08-21

### Changed
Expand Down
64 changes: 41 additions & 23 deletions crates/stackable-webhook/src/servers/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ pub struct ConversionWebhookOptions {
/// The name of the Kubernetes service which points to the operator/webhook.
pub service_name: String,

/// If the CRDs should be maintained automatically. Use the (negated) value from
/// `stackable_operator::cli::ProductOperatorRun::disable_crd_maintenance`
/// for this.
// # Because of https://github.com/rust-lang/cargo/issues/3475 we can not use a real link here
pub maintain_crds: bool,

/// The field manager used to apply Kubernetes objects, typically the operator name, e.g.
/// `airflow-operator`.
pub field_manager: String,
Expand All @@ -97,11 +103,12 @@ impl ConversionWebhookServer {
/// Creates a new conversion webhook server, which expects POST requests being made to the
/// `/convert/{crd name}` endpoint.
///
/// You need to provide two things for every CRD passed in via the `crds_and_handlers` argument:
/// You need to provide a few things for every CRD passed in via the `crds_and_handlers` argument:
///
/// 1. The CRD
/// 2. A conversion function to convert between CRD versions. Typically you would use the
/// the auto-generated `try_convert` function on CRD spec definition structs for this.
/// the auto-generated `try_convert` function on CRD spec definition structs for this.
/// 3. A [`kube::Client`] used to create/update the CRDs.
///
/// The [`ConversionWebhookServer`] takes care of reconciling the CRDs into the Kubernetes
/// cluster and takes care of adding itself as conversion webhook. This includes TLS
Expand All @@ -119,14 +126,18 @@ impl ConversionWebhookServer {
/// use stackable_operator::{
/// kube::Client,
/// crd::s3::{S3Connection, S3ConnectionVersion},
/// cli::OperatorEnvironmentOptions,
/// cli::ProductOperatorRun,
/// };
///
/// # async fn test() {
/// // Things that should already be in you operator:
/// const OPERATOR_NAME: &str = "product-operator";
/// let client = Client::try_default().await.expect("failed to create Kubernetes client");
/// let operator_environment = OperatorEnvironmentOptions::parse();
/// let ProductOperatorRun {
/// operator_environment,
/// disable_crd_maintenance,
/// ..
/// } = ProductOperatorRun::parse();
///
/// let crds_and_handlers = [
/// (
Expand All @@ -140,9 +151,10 @@ impl ConversionWebhookServer {
/// socket_addr: format!("0.0.0.0:{CONVERSION_WEBHOOK_HTTPS_PORT}")
/// .parse()
/// .expect("static address is always valid"),
/// field_manager: OPERATOR_NAME.to_owned(),
/// namespace: operator_environment.operator_namespace,
/// service_name: operator_environment.operator_service_name,
/// maintain_crds: !disable_crd_maintenance,
/// field_manager: OPERATOR_NAME.to_owned(),
/// };
///
/// // Construct the conversion webhook server
Expand Down Expand Up @@ -205,9 +217,10 @@ impl ConversionWebhookServer {

let ConversionWebhookOptions {
socket_addr,
field_manager,
namespace: operator_namespace,
service_name: operator_service_name,
maintain_crds,
field_manager,
} = &options;

// This is how Kubernetes calls us, so it decides about the naming.
Expand All @@ -233,28 +246,33 @@ impl ConversionWebhookServer {
.recv()
.await
.context(ReceiveCertificateFromChannelSnafu)?;
Self::reconcile_crds(
&client,
field_manager,
&crds,
operator_namespace,
operator_service_name,
current_cert,
)
.await
.context(ReconcileCrdsSnafu)?;

try_join!(
Self::run_webhook_server(server),
Self::run_crd_reconciliation_loop(
cert_rx,

if *maintain_crds {
Self::reconcile_crds(
&client,
field_manager,
&crds,
operator_namespace,
operator_service_name,
),
)?;
current_cert,
)
.await
.context(ReconcileCrdsSnafu)?;

try_join!(
Self::run_webhook_server(server),
Self::run_crd_reconciliation_loop(
cert_rx,
&client,
field_manager,
&crds,
operator_namespace,
operator_service_name,
),
)?;
} else {
Self::run_webhook_server(server).await?;
};

Ok(())
}
Expand Down