From 3fceb1a5fb4a71799ba4057e764211f2e32ccb9b Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 15:57:54 +0200 Subject: [PATCH 1/8] refactor: decouple statefulset builders from applied ServiceAccount --- rust/operator-binary/src/controller.rs | 9 ++++++--- .../src/controller/build/resource/statefulset.rs | 15 +++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 395c1766..2bbc8a4a 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -20,7 +20,7 @@ use stackable_operator::{ commons::{networking::DomainName, product_image_selection::ResolvedProductImage}, crd::listener, kube::{ - Resource, + Resource, ResourceExt, api::{DynamicObject, ObjectMeta}, core::{DeserializeGuard, error_boundary}, runtime::{controller::Action, reflector::ObjectRef}, @@ -557,6 +557,9 @@ pub async fn reconcile_kafka( .add(client, rbac_sa.clone()) .await .context(ApplyServiceAccountSnafu)?; + // The ServiceAccount name is deterministic, so the statefulset builders only need the name, + // not the applied object. + let service_account_name = rbac_sa.name_any(); cluster_resources .add(client, rbac_rolebinding) .await @@ -608,7 +611,7 @@ pub async fn reconcile_kafka( rolegroup_name, &validated_cluster, validated_rg, - &rbac_sa, + &service_account_name, ) .context(BuildStatefulsetSnafu)?, KafkaRole::Controller => build_controller_rolegroup_statefulset( @@ -616,7 +619,7 @@ pub async fn reconcile_kafka( rolegroup_name, &validated_cluster, validated_rg, - &rbac_sa, + &service_account_name, ) .context(BuildStatefulsetSnafu)?, }; diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 8a9abea1..70997d08 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -20,12 +20,11 @@ use stackable_operator::{ apps::v1::{StatefulSet, StatefulSetSpec, StatefulSetUpdateStrategy}, core::v1::{ ConfigMapVolumeSource, ContainerPort, EnvVar, EnvVarSource, ExecAction, - ObjectFieldSelector, PodSpec, Probe, ServiceAccount, TCPSocketAction, Volume, + ObjectFieldSelector, PodSpec, Probe, TCPSocketAction, Volume, }, }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, - kube::ResourceExt, product_logging, v2::{ builder::{ @@ -189,7 +188,7 @@ pub fn build_broker_rolegroup_statefulset( role_group_name: &RoleGroupName, validated_cluster: &ValidatedCluster, validated_rg: &ValidatedRoleGroupConfig, - service_account: &ServiceAccount, + service_account_name: &str, ) -> Result { let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; @@ -395,7 +394,7 @@ pub fn build_broker_rolegroup_statefulset( .add_container(cb_kcat_prober.build()) .affinity(&merged_config.affinity); - add_common_pod_config(&mut pod_builder, &resource_names, service_account)?; + add_common_pod_config(&mut pod_builder, &resource_names, service_account_name)?; add_vector_container( &mut pod_builder, @@ -453,7 +452,7 @@ pub fn build_controller_rolegroup_statefulset( role_group_name: &RoleGroupName, validated_cluster: &ValidatedCluster, validated_rg: &ValidatedRoleGroupConfig, - service_account: &ServiceAccount, + service_account_name: &str, ) -> Result { let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; @@ -578,7 +577,7 @@ pub fn build_controller_rolegroup_statefulset( .add_container(kafka_container) .affinity(&merged_config.affinity); - add_common_pod_config(&mut pod_builder, &resource_names, service_account)?; + add_common_pod_config(&mut pod_builder, &resource_names, service_account_name)?; add_vector_container( &mut pod_builder, @@ -729,7 +728,7 @@ fn add_log_config_volume( fn add_common_pod_config( pod_builder: &mut PodBuilder, resource_names: &ResourceNames, - service_account: &ServiceAccount, + service_account_name: &str, ) -> Result<(), Error> { pod_builder .add_volume(Volume { @@ -748,7 +747,7 @@ fn add_common_pod_config( )), ) .context(AddVolumeSnafu)? - .service_account_name(service_account.name_any()) + .service_account_name(service_account_name) .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); Ok(()) } From 3d321ba08a98f209367f73c6b974eb66aaf5df7f Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 16:03:38 +0200 Subject: [PATCH 2/8] refactor: read cluster_domain from ValidatedCluster in listener config --- rust/operator-binary/src/controller.rs | 1 - .../controller/build/properties/listener.rs | 27 +++++++------------ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 2bbc8a4a..46f3aadc 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -593,7 +593,6 @@ pub async fn reconcile_kafka( &validated_cluster.cluster_config.kafka_security, kafka_role, rolegroup_name, - &client.kubernetes_cluster_info, ); let rg_configmap = build::resource::config_map::build_rolegroup_config_map( diff --git a/rust/operator-binary/src/controller/build/properties/listener.rs b/rust/operator-binary/src/controller/build/properties/listener.rs index 9b5953fe..d58cba74 100644 --- a/rust/operator-binary/src/controller/build/properties/listener.rs +++ b/rust/operator-binary/src/controller/build/properties/listener.rs @@ -6,9 +6,7 @@ use std::collections::BTreeMap; -use stackable_operator::{ - utils::cluster_info::KubernetesClusterInfo, v2::types::kubernetes::NamespaceName, -}; +use stackable_operator::{commons::networking::DomainName, v2::types::kubernetes::NamespaceName}; use crate::{ controller::{RoleGroupName, ValidatedCluster, security::ValidatedKafkaSecurity}, @@ -27,7 +25,6 @@ pub fn get_kafka_listener_config( kafka_security: &ValidatedKafkaSecurity, role: &KafkaRole, role_group_name: &RoleGroupName, - cluster_info: &KubernetesClusterInfo, ) -> KafkaListenerConfig { let headless_service_name = validated_cluster .resource_names(role, role_group_name) @@ -35,7 +32,7 @@ pub fn get_kafka_listener_config( let pod_fqdn = pod_fqdn( &validated_cluster.namespace, headless_service_name.as_ref(), - cluster_info, + &validated_cluster.cluster_domain, ); let mut listeners = vec![]; let mut advertised_listeners = vec![]; @@ -143,12 +140,9 @@ pub fn get_kafka_listener_config( pub(crate) fn pod_fqdn( namespace: &NamespaceName, sts_service_name: &str, - cluster_info: &KubernetesClusterInfo, + cluster_domain: &DomainName, ) -> String { - format!( - "${{env:POD_NAME}}.{sts_service_name}.{namespace}.svc.{cluster_domain}", - cluster_domain = cluster_info.cluster_domain - ) + format!("${{env:POD_NAME}}.{sts_service_name}.{namespace}.svc.{cluster_domain}") } #[cfg(test)] @@ -157,6 +151,7 @@ mod tests { builder::meta::ObjectMetaBuilder, commons::networking::DomainName, crd::authentication::{core, kerberos, tls}, + utils::cluster_info::KubernetesClusterInfo, }; use super::*; @@ -217,7 +212,6 @@ mod tests { &kafka_security, &KafkaRole::Broker, &role_group_name, - &cluster_info, ); assert_eq!( @@ -250,7 +244,7 @@ mod tests { .resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), - &cluster_info + &cluster_info.cluster_domain ), internal_port = kafka_security.internal_port(), ) @@ -280,7 +274,6 @@ mod tests { &kafka_security, &KafkaRole::Broker, &role_group_name, - &cluster_info, ); assert_eq!( @@ -313,7 +306,7 @@ mod tests { .resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), - &cluster_info + &cluster_info.cluster_domain ), internal_port = kafka_security.internal_port(), ) @@ -344,7 +337,6 @@ mod tests { &kafka_security, &KafkaRole::Broker, &role_group_name, - &cluster_info, ); assert_eq!( @@ -377,7 +369,7 @@ mod tests { .resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), - &cluster_info + &cluster_info.cluster_domain ), internal_port = kafka_security.internal_port(), ) @@ -442,7 +434,6 @@ mod tests { &kafka_security, &KafkaRole::Broker, &role_group_name, - &cluster_info, ); assert_eq!( @@ -478,7 +469,7 @@ mod tests { .resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), - &cluster_info + &cluster_info.cluster_domain ), internal_port = kafka_security.internal_port(), bootstrap_name = KafkaListenerName::Bootstrap, From 6c06c70fd15df1046573d4cf5ea5ed83c9ef6c1d Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 16:12:23 +0200 Subject: [PATCH 3/8] refactor: introduce build() aggregator for Kubernetes resources --- rust/operator-binary/src/controller.rs | 230 ++++++------------ .../src/controller/build/mod.rs | 150 ++++++++++++ 2 files changed, 220 insertions(+), 160 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 46f3aadc..0371fb63 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -19,6 +19,11 @@ use stackable_operator::{ cluster_resources::ClusterResourceApplyStrategy, commons::{networking::DomainName, product_image_selection::ResolvedProductImage}, crd::listener, + k8s_openapi::api::{ + apps::v1::StatefulSet, + core::v1::{ConfigMap, Service}, + policy::v1::PodDisruptionBudget, + }, kube::{ Resource, ResourceExt, api::{DynamicObject, ObjectMeta}, @@ -55,18 +60,7 @@ pub use stackable_operator::v2::types::operator::{RoleGroupName, RoleName}; use crate::{ controller::{ - build::{ - properties::listener::get_kafka_listener_config, - resource::{ - listener::build_broker_rolegroup_bootstrap_listener, - pdb::build_pdb, - rbac::{build_rbac_role_binding, build_rbac_service_account}, - service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, - statefulset::{ - build_broker_rolegroup_statefulset, build_controller_rolegroup_statefulset, - }, - }, - }, + build::resource::rbac::{build_rbac_role_binding, build_rbac_service_account}, node_id_hasher::node_id_hash32_offset, security::ValidatedKafkaSecurity, }, @@ -94,6 +88,19 @@ pub enum PodDescriptorsError { }, } +/// Every Kubernetes resource produced by the [`build`] step. +/// +/// The discovery `ConfigMap` is not part of this: it depends on the applied bootstrap +/// [`Listener`](listener)s' status and is therefore built in [`reconcile_kafka`] after they are +/// applied. +pub struct KubernetesResources { + pub stateful_sets: Vec, + pub services: Vec, + pub listeners: Vec, + pub config_maps: Vec, + pub pod_disruption_budgets: Vec, +} + /// The validated cluster. Carries everything the build steps need, resolved once /// here so downstream code never re-derives it or touches the raw spec. /// @@ -393,27 +400,12 @@ pub enum Error { #[snafu(display("failed to validate cluster"))] ValidateCluster { source: validate::Error }, - #[snafu(display("failed to apply bootstrap Listener"))] - ApplyBootstrapListener { - source: stackable_operator::cluster_resources::Error, - }, + #[snafu(display("failed to build the Kubernetes resources"))] + BuildResources { source: build::Error }, - #[snafu(display("failed to apply Service for role group {role_group}"))] - ApplyRoleGroupService { + #[snafu(display("failed to apply Kubernetes resource"))] + ApplyResource { source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to apply ConfigMap for role group {role_group}"))] - ApplyRoleGroupConfig { - source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to apply StatefulSet for role group {role_group}"))] - ApplyRoleGroupStatefulSet { - source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, }, #[snafu(display("failed to build discovery ConfigMap"))] @@ -446,11 +438,6 @@ pub enum Error { source: stackable_operator::client::Error, }, - #[snafu(display("failed to apply PodDisruptionBudget"))] - ApplyPdb { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to get required Labels"))] GetRequiredLabels { source: @@ -461,16 +448,6 @@ pub enum Error { InvalidKafkaCluster { source: error_boundary::InvalidObject, }, - - #[snafu(display("failed to build statefulset"))] - BuildStatefulset { - source: crate::controller::build::resource::statefulset::Error, - }, - - #[snafu(display("failed to build configmap"))] - BuildConfigMap { - source: crate::controller::build::resource::config_map::Error, - }, } type Result = std::result::Result; @@ -483,21 +460,16 @@ impl ReconcilerError for Error { match self { Error::Dereference { .. } => None, Error::ValidateCluster { .. } => None, - Error::ApplyBootstrapListener { .. } => None, - Error::ApplyRoleGroupService { .. } => None, - Error::ApplyRoleGroupConfig { .. } => None, - Error::ApplyRoleGroupStatefulSet { .. } => None, + Error::BuildResources { .. } => None, + Error::ApplyResource { .. } => None, Error::BuildDiscoveryConfig { .. } => None, Error::ApplyDiscoveryConfig { .. } => None, Error::DeleteOrphans { .. } => None, Error::ApplyServiceAccount { .. } => None, Error::ApplyRoleBinding { .. } => None, Error::ApplyStatus { .. } => None, - Error::ApplyPdb { .. } => None, Error::GetRequiredLabels { .. } => None, Error::InvalidKafkaCluster { .. } => None, - Error::BuildStatefulset { .. } => None, - Error::BuildConfigMap { .. } => None, } } } @@ -565,121 +537,59 @@ pub async fn reconcile_kafka( .await .context(ApplyRoleBindingSnafu)?; - let mut bootstrap_listeners = Vec::::new(); - - for (kafka_role, rg_map) in &validated_cluster.role_group_configs { - for (rolegroup_name, validated_rg) in rg_map { - // The Vector agent config is the static `vector.yaml`, added to the rolegroup - // ConfigMap only when the Vector agent is enabled (resolved during validation). - let vector_config = validated_rg - .config - .logging - .vector_container - .is_some() - .then(build::properties::product_logging::vector_config_file_content); - - let rg_headless_service = build_rolegroup_headless_service( - &validated_cluster, - kafka_role, - rolegroup_name, - &validated_cluster.cluster_config.kafka_security, - ); - - let rg_metrics_service = - build_rolegroup_metrics_service(&validated_cluster, kafka_role, rolegroup_name); - - let kafka_listeners = get_kafka_listener_config( - &validated_cluster, - &validated_cluster.cluster_config.kafka_security, - kafka_role, - rolegroup_name, - ); - - let rg_configmap = build::resource::config_map::build_rolegroup_config_map( - &validated_cluster, - rolegroup_name, - validated_rg, - &kafka_listeners, - vector_config, - ) - .context(BuildConfigMapSnafu)?; - - let rg_statefulset = match kafka_role { - KafkaRole::Broker => build_broker_rolegroup_statefulset( - kafka_role, - rolegroup_name, - &validated_cluster, - validated_rg, - &service_account_name, - ) - .context(BuildStatefulsetSnafu)?, - KafkaRole::Controller => build_controller_rolegroup_statefulset( - kafka_role, - rolegroup_name, - &validated_cluster, - validated_rg, - &service_account_name, - ) - .context(BuildStatefulsetSnafu)?, - }; - - if let AnyConfig::Broker(broker_config) = &validated_rg.config.config { - let rg_bootstrap_listener = build_broker_rolegroup_bootstrap_listener( - &validated_cluster, - kafka_role, - rolegroup_name, - broker_config, - ); - bootstrap_listeners.push( - cluster_resources - .add(client, rg_bootstrap_listener) - .await - .context(ApplyBootstrapListenerSnafu)?, - ); - } + // Build every Kubernetes resource up front (client-free). The discovery ConfigMap is not part + // of this, as it depends on the applied bootstrap Listeners' status (see below). + let resources = + build::build(&validated_cluster, &service_account_name).context(BuildResourcesSnafu)?; + + // Apply order: Services, then Listeners (collecting the applied bootstrap Listeners for the + // discovery ConfigMap), then ConfigMaps, then PodDisruptionBudgets, and finally the + // StatefulSets. The StatefulSets must be applied after all ConfigMaps and Secrets they mount to + // prevent unnecessary Pod restarts. + // See https://github.com/stackabletech/commons-operator/issues/111 for details. + for service in resources.services { + cluster_resources + .add(client, service) + .await + .context(ApplyResourceSnafu)?; + } + let mut bootstrap_listeners = Vec::::new(); + for rg_listener in resources.listeners { + bootstrap_listeners.push( cluster_resources - .add(client, rg_headless_service) - .await - .with_context(|_| ApplyRoleGroupServiceSnafu { - role_group: rolegroup_name.clone(), - })?; - cluster_resources - .add(client, rg_metrics_service) - .await - .with_context(|_| ApplyRoleGroupServiceSnafu { - role_group: rolegroup_name.clone(), - })?; - cluster_resources - .add(client, rg_configmap) + .add(client, rg_listener) .await - .with_context(|_| ApplyRoleGroupConfigSnafu { - role_group: rolegroup_name.clone(), - })?; - - // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts - // to prevent unnecessary Pod restarts. - // See https://github.com/stackabletech/commons-operator/issues/111 for details. - ss_cond_builder.add( - cluster_resources - .add(client, rg_statefulset) - .await - .with_context(|_| ApplyRoleGroupStatefulSetSnafu { - role_group: rolegroup_name.clone(), - })?, - ); - } + .context(ApplyResourceSnafu)?, + ); + } + + for config_map in resources.config_maps { + cluster_resources + .add(client, config_map) + .await + .context(ApplyResourceSnafu)?; + } + + for pdb in resources.pod_disruption_budgets { + cluster_resources + .add(client, pdb) + .await + .context(ApplyResourceSnafu)?; + } - if let Some(role_config) = validated_cluster.role_configs.get(kafka_role) - && let Some(pdb) = build_pdb(&role_config.pdb, &validated_cluster, kafka_role) - { + for stateful_set in resources.stateful_sets { + ss_cond_builder.add( cluster_resources - .add(client, pdb) + .add(client, stateful_set) .await - .context(ApplyPdbSnafu)?; - } + .context(ApplyResourceSnafu)?, + ); } + // The discovery ConfigMap reports the bootstrap Listeners' ingress addresses, which are only + // populated on the applied Listener objects (by the Listener operator), so it is built here + // rather than in the client-free build() step. let discovery_cm = build::resource::discovery::build_discovery_configmap( &validated_cluster, &bootstrap_listeners, diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index fc57c343..05cfa4b3 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -1,5 +1,28 @@ //! Builders that assemble Kubernetes resources for kafka rolegroups. +use snafu::{ResultExt, Snafu}; + +use crate::{ + controller::{ + KubernetesResources, RoleGroupName, ValidatedCluster, + build::{ + properties::{ + listener::get_kafka_listener_config, product_logging::vector_config_file_content, + }, + resource::{ + config_map::build_rolegroup_config_map, + listener::build_broker_rolegroup_bootstrap_listener, + pdb::build_pdb, + service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, + statefulset::{ + build_broker_rolegroup_statefulset, build_controller_rolegroup_statefulset, + }, + }, + }, + }, + crd::role::{AnyConfig, KafkaRole}, +}; + pub mod command; pub mod graceful_shutdown; pub mod jvm; @@ -8,3 +31,130 @@ pub mod labels; pub mod properties; pub mod resource; pub mod security; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build ConfigMap for role group {role_group}"))] + ConfigMap { + source: resource::config_map::Error, + role_group: RoleGroupName, + }, + + #[snafu(display("failed to build StatefulSet for role group {role_group}"))] + StatefulSet { + source: resource::statefulset::Error, + role_group: RoleGroupName, + }, +} + +/// Builds every Kubernetes resource for the given validated cluster. +/// +/// Does not need a Kubernetes client: every external reference is already dereferenced and +/// validated by this point, so the only errors are resource-assembly failures. +/// +/// The discovery `ConfigMap` is intentionally excluded: it reports the applied bootstrap +/// `Listener`s' ingress addresses (populated by the Listener operator only after apply), so it is +/// built in the reconcile step once those `Listener`s exist. +/// +/// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under. +/// The RBAC resources are built and applied separately, in the reconcile step; the name is +/// deterministic, so the build step does not depend on the applied `ServiceAccount`. +pub fn build( + cluster: &ValidatedCluster, + service_account_name: &str, +) -> Result { + let mut stateful_sets = vec![]; + let mut services = vec![]; + let mut listeners = vec![]; + let mut config_maps = vec![]; + let mut pod_disruption_budgets = vec![]; + + for (role, role_group_configs) in &cluster.role_group_configs { + // Kafka's `GenericRoleConfig` only carries the PodDisruptionBudget. + if let Some(role_config) = cluster.role_configs.get(role) { + pod_disruption_budgets.extend(build_pdb(&role_config.pdb, cluster, role)); + } + + for (role_group_name, validated_rg) in role_group_configs { + // The Vector agent config is the static `vector.yaml`, added to the rolegroup + // ConfigMap only when the Vector agent is enabled (resolved during validation). + let vector_config = validated_rg + .config + .logging + .vector_container + .is_some() + .then(vector_config_file_content); + + services.push(build_rolegroup_headless_service( + cluster, + role, + role_group_name, + &cluster.cluster_config.kafka_security, + )); + services.push(build_rolegroup_metrics_service( + cluster, + role, + role_group_name, + )); + + let kafka_listeners = get_kafka_listener_config( + cluster, + &cluster.cluster_config.kafka_security, + role, + role_group_name, + ); + + config_maps.push( + build_rolegroup_config_map( + cluster, + role_group_name, + validated_rg, + &kafka_listeners, + vector_config, + ) + .context(ConfigMapSnafu { + role_group: role_group_name.clone(), + })?, + ); + + let stateful_set = match role { + KafkaRole::Broker => build_broker_rolegroup_statefulset( + role, + role_group_name, + cluster, + validated_rg, + service_account_name, + ), + KafkaRole::Controller => build_controller_rolegroup_statefulset( + role, + role_group_name, + cluster, + validated_rg, + service_account_name, + ), + } + .context(StatefulSetSnafu { + role_group: role_group_name.clone(), + })?; + stateful_sets.push(stateful_set); + + // Only broker role groups get a bootstrap Listener. + if let AnyConfig::Broker(broker_config) = &validated_rg.config.config { + listeners.push(build_broker_rolegroup_bootstrap_listener( + cluster, + role, + role_group_name, + broker_config, + )); + } + } + } + + Ok(KubernetesResources { + stateful_sets, + services, + listeners, + config_maps, + pod_disruption_budgets, + }) +} From e7124097cca43faf4f276282aaa9b8130a86d917 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 16:17:48 +0200 Subject: [PATCH 4/8] test: cover the build() aggregator's resource output --- .../src/controller/build/mod.rs | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 05cfa4b3..f33b2f30 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -158,3 +158,149 @@ pub fn build( pod_disruption_budgets, }) } + +#[cfg(test)] +mod tests { + use stackable_operator::kube::Resource; + + use super::build; + use crate::controller::{ + ValidatedCluster, + test_support::{minimal_kafka, validated_cluster}, + }; + + /// Sorted `metadata.name`s of the given resources, for order-independent assertions. + fn sorted_names(resources: &[impl Resource]) -> Vec<&str> { + let mut names: Vec<&str> = resources + .iter() + .filter_map(|resource| resource.meta().name.as_deref()) + .collect(); + names.sort(); + names + } + + /// A KRaft cluster with one `broker` and one `controller` role group, resolved through the real + /// validate step (mirroring the other build fixtures), since [`ValidatedCluster`] carries + /// several resolved types that are impractical to construct by hand. + fn kraft_cluster() -> ValidatedCluster { + let kafka = minimal_kafka( + r#" + apiVersion: kafka.stackable.tech/v1alpha1 + kind: KafkaCluster + metadata: + name: simple-kafka + namespace: default + uid: 12345678-1234-1234-1234-123456789012 + spec: + image: + productVersion: 3.9.2 + clusterConfig: + metadataManager: kraft + controllers: + roleGroups: + default: + replicas: 3 + brokers: + roleGroups: + default: + replicas: 3 + "#, + ); + validated_cluster(&kafka) + } + + /// A ZooKeeper-mode cluster with a single `broker` role group (no controllers). + fn zookeeper_cluster() -> ValidatedCluster { + let kafka = minimal_kafka( + r#" + apiVersion: kafka.stackable.tech/v1alpha1 + kind: KafkaCluster + metadata: + name: simple-kafka + namespace: default + uid: 12345678-1234-1234-1234-123456789012 + spec: + image: + productVersion: 3.9.2 + clusterConfig: + zookeeperConfigMapName: xyz + brokers: + roleGroups: + default: + replicas: 1 + "#, + ); + validated_cluster(&kafka) + } + + #[test] + fn build_produces_expected_resource_names() { + let cluster = kraft_cluster(); + let resources = build(&cluster, "simple-kafka-serviceaccount").expect("build succeeds"); + + // One StatefulSet per role group. + assert_eq!( + sorted_names(&resources.stateful_sets), + [ + "simple-kafka-broker-default", + "simple-kafka-controller-default" + ] + ); + // One rolegroup ConfigMap per role group. + assert_eq!( + sorted_names(&resources.config_maps), + [ + "simple-kafka-broker-default", + "simple-kafka-controller-default" + ] + ); + // One headless and one metrics Service per role group. + assert_eq!( + sorted_names(&resources.services), + [ + "simple-kafka-broker-default-headless", + "simple-kafka-broker-default-metrics", + "simple-kafka-controller-default-headless", + "simple-kafka-controller-default-metrics", + ] + ); + // Only broker role groups get a bootstrap Listener. + assert_eq!( + sorted_names(&resources.listeners), + ["simple-kafka-broker-default-bootstrap"] + ); + // A default PodDisruptionBudget per role. + assert_eq!( + sorted_names(&resources.pod_disruption_budgets), + ["simple-kafka-broker", "simple-kafka-controller"] + ); + } + + /// ZooKeeper mode has no `controller` role, so `build()` emits no controller resources while + /// still producing the broker's bootstrap Listener. + #[test] + fn build_zookeeper_mode_has_no_controller_resources() { + let cluster = zookeeper_cluster(); + let resources = build(&cluster, "simple-kafka-serviceaccount").expect("build succeeds"); + + assert_eq!( + sorted_names(&resources.stateful_sets), + ["simple-kafka-broker-default"] + ); + assert_eq!( + sorted_names(&resources.services), + [ + "simple-kafka-broker-default-headless", + "simple-kafka-broker-default-metrics", + ] + ); + assert_eq!( + sorted_names(&resources.listeners), + ["simple-kafka-broker-default-bootstrap"] + ); + assert_eq!( + sorted_names(&resources.pod_disruption_budgets), + ["simple-kafka-broker"] + ); + } +} From c52326333ddf729e2dc96e9a7325ce94a3a36bde Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 20 Jul 2026 12:54:24 +0200 Subject: [PATCH 5/8] factor: add infallible rbac functions --- Cargo.lock | 11 +- Cargo.toml | 2 +- rust/operator-binary/src/controller.rs | 135 ++++++++++++------ .../src/controller/build/labels.rs | 71 --------- .../src/controller/build/mod.rs | 75 ++++++++-- .../controller/build/resource/config_map.rs | 7 +- .../controller/build/resource/discovery.rs | 15 +- .../src/controller/build/resource/listener.rs | 10 +- .../src/controller/build/resource/rbac.rs | 101 ++++--------- .../src/controller/build/resource/service.rs | 24 ++-- .../controller/build/resource/statefulset.rs | 35 +++-- 11 files changed, 225 insertions(+), 261 deletions(-) delete mode 100644 rust/operator-binary/src/controller/build/labels.rs diff --git a/Cargo.lock b/Cargo.lock index 0a4f94c6..92193f9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1599,7 +1599,6 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "darling", "regex", @@ -3007,7 +3006,6 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "const-oid", "ecdsa", @@ -3051,8 +3049,7 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.113.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" +version = "0.113.4" dependencies = [ "base64", "clap", @@ -3097,7 +3094,6 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "darling", "proc-macro2", @@ -3108,7 +3104,6 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.2" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "jiff", "k8s-openapi", @@ -3125,7 +3120,6 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.5" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "axum", "clap", @@ -3149,7 +3143,6 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.11.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "kube", "schemars", @@ -3163,7 +3156,6 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.11.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "convert_case", "convert_case_extras", @@ -3181,7 +3173,6 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.2" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#013bbf43f7006a4ddfc08a147f68441ed88b462b" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index d467c87c..7483e808 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,5 @@ tokio = { version = "1.52", features = ["full"] } tracing = "0.1" [patch."https://github.com/stackabletech/operator-rs.git"] -# stackable-operator = { path = "../operator-rs/crates/stackable-operator" } +stackable-operator = { path = "../operator-rs/crates/stackable-operator" } # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 0371fb63..b753c79a 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -21,15 +21,17 @@ use stackable_operator::{ crd::listener, k8s_openapi::api::{ apps::v1::StatefulSet, - core::v1::{ConfigMap, Service}, + core::v1::{ConfigMap, Service, ServiceAccount}, policy::v1::PodDisruptionBudget, + rbac::v1::RoleBinding, }, kube::{ - Resource, ResourceExt, + Resource, api::{DynamicObject, ObjectMeta}, core::{DeserializeGuard, error_boundary}, runtime::{controller::Action, reflector::ObjectRef}, }, + kvp::Labels, logging::controller::ReconcilerError, shared::time::Duration, status::condition::{ @@ -39,7 +41,9 @@ use stackable_operator::{ v2::{ HasName, HasUid, NameIsValidLabelValue, cluster_resources::cluster_resources_new, + kvp::label::{recommended_labels, role_group_selector}, role_group_utils::ResourceNames, + role_utils, types::{ kubernetes::{ConfigMapName, ListenerName, NamespaceName, Uid}, operator::{ClusterName, ControllerName, OperatorName, ProductName, ProductVersion}, @@ -59,11 +63,7 @@ pub(crate) mod validate; pub use stackable_operator::v2::types::operator::{RoleGroupName, RoleName}; use crate::{ - controller::{ - build::resource::rbac::{build_rbac_role_binding, build_rbac_service_account}, - node_id_hasher::node_id_hash32_offset, - security::ValidatedKafkaSecurity, - }, + controller::{node_id_hasher::node_id_hash32_offset, security::ValidatedKafkaSecurity}, crd::{ APP_NAME, KafkaClusterStatus, KafkaPodDescriptor, MetadataManager, OPERATOR_NAME, authorization::KafkaAuthorizationConfig, @@ -75,6 +75,9 @@ use crate::{ pub const KAFKA_CONTROLLER_NAME: &str = "kafkacluster"; pub const KAFKA_FULL_CONTROLLER_NAME: &str = concatcp!(KAFKA_CONTROLLER_NAME, '.', OPERATOR_NAME); +// Placeholder version label value for resources whose labels must not change after deployment. +stackable_operator::constant!(UNVERSIONED_PRODUCT_VERSION: ProductVersion = "none"); + #[derive(Snafu, Debug)] pub enum PodDescriptorsError { #[snafu(display( @@ -99,6 +102,8 @@ pub struct KubernetesResources { pub listeners: Vec, pub config_maps: Vec, pub pod_disruption_budgets: Vec, + pub service_accounts: Vec, + pub role_bindings: Vec, } /// The validated cluster. Carries everything the build steps need, resolved once @@ -228,6 +233,15 @@ impl ValidatedCluster { RoleName::from_str(&role.to_string()).expect("a KafkaRole is a valid role name") } + /// Type-safe names for the per-cluster RBAC resources: the ServiceAccount shared by all + /// Pods, its (namespaced) RoleBinding, and the operator-deployed ClusterRole it binds. + pub fn rbac_resource_names(&self) -> role_utils::ResourceNames { + role_utils::ResourceNames { + cluster_name: self.name.clone(), + product_name: product_name(), + } + } + /// Type-safe names for the resources of a given role group. pub(crate) fn resource_names( &self, @@ -241,6 +255,61 @@ impl ValidatedCluster { } } + /// Recommended labels for a role-group resource. + pub fn recommended_labels(&self, role: &KafkaRole, role_group_name: &RoleGroupName) -> Labels { + self.recommended_labels_for(&Self::role_name(role), role_group_name) + } + + /// Recommended labels for a resource that is not tied to a concrete [`KafkaRole`], using a free-form role/role-group label value. + pub fn recommended_labels_for( + &self, + role_name: &RoleName, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_with(&self.product_version, role_name, role_group_name) + } + + /// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates + /// that cannot be modified after deployment (keeps the labels stable across version upgrades). + pub fn unversioned_recommended_labels( + &self, + role: &KafkaRole, + role_group_name: &RoleGroupName, + ) -> Labels { + self.recommended_labels_with( + &UNVERSIONED_PRODUCT_VERSION, + &Self::role_name(role), + role_group_name, + ) + } + + fn recommended_labels_with( + &self, + product_version: &ProductVersion, + role_name: &RoleName, + role_group_name: &RoleGroupName, + ) -> Labels { + recommended_labels( + self, + &product_name(), + product_version, + &operator_name(), + &controller_name(), + role_name, + role_group_name, + ) + } + + /// Selector labels matching the pods of a role group. + pub fn role_group_selector(&self, role: &KafkaRole, role_group_name: &RoleGroupName) -> Labels { + role_group_selector( + self, + &product_name(), + &Self::role_name(role), + role_group_name, + ) + } + /// The name of the broker rolegroup's bootstrap [`Listener`](stackable_operator::crd::listener), /// `---bootstrap`. pub fn bootstrap_listener_name( @@ -423,27 +492,11 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to patch service account"))] - ApplyServiceAccount { - source: stackable_operator::cluster_resources::Error, - }, - - #[snafu(display("failed to patch role binding"))] - ApplyRoleBinding { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to update status"))] ApplyStatus { source: stackable_operator::client::Error, }, - #[snafu(display("failed to get required Labels"))] - GetRequiredLabels { - source: - stackable_operator::kvp::KeyValuePairError, - }, - #[snafu(display("KafkaCluster object is invalid"))] InvalidKafkaCluster { source: error_boundary::InvalidObject, @@ -465,10 +518,7 @@ impl ReconcilerError for Error { Error::BuildDiscoveryConfig { .. } => None, Error::ApplyDiscoveryConfig { .. } => None, Error::DeleteOrphans { .. } => None, - Error::ApplyServiceAccount { .. } => None, - Error::ApplyRoleBinding { .. } => None, Error::ApplyStatus { .. } => None, - Error::GetRequiredLabels { .. } => None, Error::InvalidKafkaCluster { .. } => None, } } @@ -519,34 +569,27 @@ pub async fn reconcile_kafka( let mut ss_cond_builder = StatefulSetConditionBuilder::default(); - let required_labels = cluster_resources - .get_required_labels() - .context(GetRequiredLabelsSnafu)?; - let rbac_sa = build_rbac_service_account(&validated_cluster, required_labels.clone()); - let rbac_rolebinding = build_rbac_role_binding(&validated_cluster, required_labels); - - let rbac_sa = cluster_resources - .add(client, rbac_sa.clone()) - .await - .context(ApplyServiceAccountSnafu)?; - // The ServiceAccount name is deterministic, so the statefulset builders only need the name, - // not the applied object. - let service_account_name = rbac_sa.name_any(); - cluster_resources - .add(client, rbac_rolebinding) - .await - .context(ApplyRoleBindingSnafu)?; - // Build every Kubernetes resource up front (client-free). The discovery ConfigMap is not part // of this, as it depends on the applied bootstrap Listeners' status (see below). - let resources = - build::build(&validated_cluster, &service_account_name).context(BuildResourcesSnafu)?; + let resources = build::build(&validated_cluster).context(BuildResourcesSnafu)?; // Apply order: Services, then Listeners (collecting the applied bootstrap Listeners for the // discovery ConfigMap), then ConfigMaps, then PodDisruptionBudgets, and finally the // StatefulSets. The StatefulSets must be applied after all ConfigMaps and Secrets they mount to // prevent unnecessary Pod restarts. // See https://github.com/stackabletech/commons-operator/issues/111 for details. + for service_account in resources.service_accounts { + cluster_resources + .add(client, service_account) + .await + .context(ApplyResourceSnafu)?; + } + for role_binding in resources.role_bindings { + cluster_resources + .add(client, role_binding) + .await + .context(ApplyResourceSnafu)?; + } for service in resources.services { cluster_resources .add(client, service) diff --git a/rust/operator-binary/src/controller/build/labels.rs b/rust/operator-binary/src/controller/build/labels.rs deleted file mode 100644 index db86b3cf..00000000 --- a/rust/operator-binary/src/controller/build/labels.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! Recommended-label and selector construction for the KafkaCluster build step. -//! -//! These build Kubernetes labels/selectors from a [`ValidatedCluster`]. They live here rather -//! than as methods on [`ValidatedCluster`] so the validated model only exposes views on its -//! properties, not build-step helpers. - -use std::str::FromStr; - -use stackable_operator::{ - kvp::Labels, - v2::{kvp::label, types::operator::ProductVersion}, -}; - -use crate::{ - controller::{RoleGroupName, ValidatedCluster, controller_name, operator_name, product_name}, - crd::role::KafkaRole, -}; - -/// Recommended labels for a role-group resource, using the given product version. -fn recommended_labels_for( - cluster: &ValidatedCluster, - product_version: &ProductVersion, - role: &KafkaRole, - role_group_name: &RoleGroupName, -) -> Labels { - label::recommended_labels( - cluster, - &product_name(), - product_version, - &operator_name(), - &controller_name(), - &ValidatedCluster::role_name(role), - role_group_name, - ) -} - -/// Recommended labels for a role-group resource. -pub fn recommended_labels( - cluster: &ValidatedCluster, - role: &KafkaRole, - role_group_name: &RoleGroupName, -) -> Labels { - recommended_labels_for(cluster, &cluster.product_version, role, role_group_name) -} - -/// Recommended labels without a version, for PVC templates that cannot be modified once -/// deployed. -pub fn unversioned_recommended_labels( - cluster: &ValidatedCluster, - role: &KafkaRole, - role_group_name: &RoleGroupName, -) -> Labels { - // A version value is required, and we do want to use the "recommended" format for the - // other desired labels. - let none_version = ProductVersion::from_str("none").expect("'none' is a valid product version"); - recommended_labels_for(cluster, &none_version, role, role_group_name) -} - -/// Selector labels matching the pods of a role group. -pub fn role_group_selector( - cluster: &ValidatedCluster, - role: &KafkaRole, - role_group_name: &RoleGroupName, -) -> Labels { - label::role_group_selector( - cluster, - &product_name(), - &ValidatedCluster::role_name(role), - role_group_name, - ) -} diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index f33b2f30..47e0be99 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -13,6 +13,7 @@ use crate::{ config_map::build_rolegroup_config_map, listener::build_broker_rolegroup_bootstrap_listener, pdb::build_pdb, + rbac::{build_role_binding, build_service_account}, service::{build_rolegroup_headless_service, build_rolegroup_metrics_service}, statefulset::{ build_broker_rolegroup_statefulset, build_controller_rolegroup_statefulset, @@ -27,7 +28,6 @@ pub mod command; pub mod graceful_shutdown; pub mod jvm; pub mod kerberos; -pub mod labels; pub mod properties; pub mod resource; pub mod security; @@ -59,10 +59,7 @@ pub enum Error { /// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under. /// The RBAC resources are built and applied separately, in the reconcile step; the name is /// deterministic, so the build step does not depend on the applied `ServiceAccount`. -pub fn build( - cluster: &ValidatedCluster, - service_account_name: &str, -) -> Result { +pub fn build(cluster: &ValidatedCluster) -> Result { let mut stateful_sets = vec![]; let mut services = vec![]; let mut listeners = vec![]; @@ -118,19 +115,14 @@ pub fn build( ); let stateful_set = match role { - KafkaRole::Broker => build_broker_rolegroup_statefulset( - role, - role_group_name, - cluster, - validated_rg, - service_account_name, - ), + KafkaRole::Broker => { + build_broker_rolegroup_statefulset(role, role_group_name, cluster, validated_rg) + } KafkaRole::Controller => build_controller_rolegroup_statefulset( role, role_group_name, cluster, validated_rg, - service_account_name, ), } .context(StatefulSetSnafu { @@ -156,11 +148,15 @@ pub fn build( listeners, config_maps, pod_disruption_budgets, + service_accounts: vec![build_service_account(cluster)], + role_bindings: vec![build_role_binding(cluster)], }) } #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use stackable_operator::kube::Resource; use super::build; @@ -236,7 +232,7 @@ mod tests { #[test] fn build_produces_expected_resource_names() { let cluster = kraft_cluster(); - let resources = build(&cluster, "simple-kafka-serviceaccount").expect("build succeeds"); + let resources = build(&cluster).expect("build succeeds"); // One StatefulSet per role group. assert_eq!( @@ -281,7 +277,7 @@ mod tests { #[test] fn build_zookeeper_mode_has_no_controller_resources() { let cluster = zookeeper_cluster(); - let resources = build(&cluster, "simple-kafka-serviceaccount").expect("build succeeds"); + let resources = build(&cluster).expect("build succeeds"); assert_eq!( sorted_names(&resources.stateful_sets), @@ -303,4 +299,53 @@ mod tests { ["simple-kafka-broker"] ); } + + /// Locks the RBAC resource names, the roleRef, and the recommended label set against + /// accidental drift. The fixture's cluster name deliberately differs from the product name so + /// that swapped `name`/`instance` label values cannot pass unnoticed. + #[test] + fn build_produces_rbac() { + let cluster = kraft_cluster(); + let resources = build(&cluster).expect("build succeeds"); + + assert_eq!( + sorted_names(&resources.service_accounts), + ["simple-kafka-serviceaccount"] + ); + assert_eq!( + sorted_names(&resources.role_bindings), + ["simple-kafka-rolebinding"] + ); + + let expected_labels = BTreeMap::from( + [ + ("app.kubernetes.io/component", "none"), + ("app.kubernetes.io/instance", "simple-kafka"), + ( + "app.kubernetes.io/managed-by", + "kafka.stackable.tech_kafkacluster", + ), + ("app.kubernetes.io/name", "kafka"), + ("app.kubernetes.io/role-group", "none"), + ("app.kubernetes.io/version", "3.9.2-stackable0.0.0-dev"), + ("stackable.tech/vendor", "Stackable"), + ] + .map(|(key, value)| (key.to_string(), value.to_string())), + ); + let service_account = resources + .service_accounts + .first() + .expect("a ServiceAccount is built"); + assert_eq!( + service_account.metadata.labels, + Some(expected_labels.clone()) + ); + + let role_binding = resources + .role_bindings + .first() + .expect("a RoleBinding is built"); + assert_eq!(role_binding.metadata.labels, Some(expected_labels)); + assert_eq!(role_binding.role_ref.name, "kafka-clusterrole"); + } } diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 6df8f2db..392e0961 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -14,7 +14,6 @@ use crate::{ controller::{ RoleGroupName, ValidatedCluster, ValidatedRoleGroupConfig, build::{ - labels, properties::{ ConfigFileName, config_file_name, product_logging::role_group_config_map_data, }, @@ -134,11 +133,7 @@ pub fn build_rolegroup_config_map( None, Some(true), )) - .with_labels(labels::recommended_labels( - validated_cluster, - &role, - role_group_name, - )) + .with_labels(validated_cluster.recommended_labels(&role, role_group_name)) .build(), ) .add_data( diff --git a/rust/operator-binary/src/controller/build/resource/discovery.rs b/rust/operator-binary/src/controller/build/resource/discovery.rs index 6a9a9c07..de25fd3a 100644 --- a/rust/operator-binary/src/controller/build/resource/discovery.rs +++ b/rust/operator-binary/src/controller/build/resource/discovery.rs @@ -9,7 +9,7 @@ use stackable_operator::{ }; use crate::{ - controller::{RoleGroupName, ValidatedCluster, build::labels}, + controller::{RoleGroupName, ValidatedCluster}, crd::role::KafkaRole, }; @@ -57,12 +57,13 @@ pub fn build_discovery_configmap( None, Some(true), )) - .with_labels(labels::recommended_labels( - validated_cluster, - &KafkaRole::Broker, - &RoleGroupName::from_str("discovery") - .expect("'discovery' is a valid role group name"), - )) + .with_labels( + validated_cluster.recommended_labels( + &KafkaRole::Broker, + &RoleGroupName::from_str("discovery") + .expect("'discovery' is a valid role group name"), + ), + ) .build(), ) .add_data("KAFKA", bootstrap_servers) diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index c8497d31..6e5adc88 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -4,9 +4,7 @@ use stackable_operator::{ }; use crate::{ - controller::{ - RoleGroupName, ValidatedCluster, build::labels, security::ValidatedKafkaSecurity, - }, + controller::{RoleGroupName, ValidatedCluster, security::ValidatedKafkaSecurity}, crd::role::{KafkaRole, broker::BrokerConfig}, }; @@ -30,11 +28,7 @@ pub fn build_broker_rolegroup_bootstrap_listener( None, Some(true), )) - .with_labels(labels::recommended_labels( - validated_cluster, - role, - role_group_name, - )) + .with_labels(validated_cluster.recommended_labels(role, role_group_name)) .build(), spec: listener::v1alpha1::ListenerSpec { class_name: Some(merged_config.bootstrap_listener_class.to_string()), diff --git a/rust/operator-binary/src/controller/build/resource/rbac.rs b/rust/operator-binary/src/controller/build/resource/rbac.rs index dfe0e057..8d52854b 100644 --- a/rust/operator-binary/src/controller/build/resource/rbac.rs +++ b/rust/operator-binary/src/controller/build/resource/rbac.rs @@ -1,81 +1,42 @@ -//! Builds the cluster-wide RBAC resources (`ServiceAccount` and `RoleBinding`). -//! -//! The names come from [`ResourceNames`] -//! and are identical to the previously used `commons::rbac::build_rbac_resources` -//! (`-serviceaccount`, `-rolebinding`, `-clusterrole`), so switching to -//! this builder does not rename any RBAC objects. +//! Builds the RBAC resources (ServiceAccount + RoleBinding) shared by all role groups. + +use std::str::FromStr; use stackable_operator::{ - builder::meta::ObjectMetaBuilder, - k8s_openapi::api::{ - core::v1::ServiceAccount, - rbac::v1::{RoleBinding, RoleRef, Subject}, - }, + k8s_openapi::api::{core::v1::ServiceAccount, rbac::v1::RoleBinding}, kvp::Labels, - v2::{builder::meta::ownerreference_from_resource, role_utils::ResourceNames}, + v2::{ + rbac, + types::operator::{RoleGroupName, RoleName}, + }, }; -use crate::controller::{ValidatedCluster, product_name}; +use crate::controller::ValidatedCluster; -/// Type-safe RBAC resource names for this cluster. -fn rbac_resource_names(validated_cluster: &ValidatedCluster) -> ResourceNames { - ResourceNames { - cluster_name: validated_cluster.name.clone(), - product_name: product_name(), - } -} - -/// Builds the [`ServiceAccount`] shared by all role groups, named `-serviceaccount`. -pub fn build_rbac_service_account( - validated_cluster: &ValidatedCluster, - labels: Labels, -) -> ServiceAccount { - let resource_names = rbac_resource_names(validated_cluster); +stackable_operator::constant!(NONE_ROLE_NAME: RoleName = "none"); +stackable_operator::constant!(NONE_ROLE_GROUP_NAME: RoleGroupName = "none"); - ServiceAccount { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(validated_cluster) - .name(resource_names.service_account_name().to_string()) - .ownerreference(ownerreference_from_resource( - validated_cluster, - None, - Some(true), - )) - .with_labels(labels) - .build(), - ..ServiceAccount::default() - } +/// Builds the [`ServiceAccount`] that the role-group Pods run under. +pub fn build_service_account(cluster: &ValidatedCluster) -> ServiceAccount { + rbac::build_service_account( + cluster, + &cluster.rbac_resource_names(), + rbac_labels(cluster), + ) } -/// Builds the [`RoleBinding`] (named `-rolebinding`) that binds the -/// [`ServiceAccount`] to the `-clusterrole` `ClusterRole`. -pub fn build_rbac_role_binding( - validated_cluster: &ValidatedCluster, - labels: Labels, -) -> RoleBinding { - let resource_names = rbac_resource_names(validated_cluster); +/// Builds the [`RoleBinding`] that binds the [`ServiceAccount`] from [`build_service_account`] to +/// the operator-deployed ClusterRole. +pub fn build_role_binding(cluster: &ValidatedCluster) -> RoleBinding { + rbac::build_role_binding( + cluster, + &cluster.rbac_resource_names(), + rbac_labels(cluster), + ) +} - RoleBinding { - metadata: ObjectMetaBuilder::new() - .name_and_namespace(validated_cluster) - .name(resource_names.role_binding_name().to_string()) - .ownerreference(ownerreference_from_resource( - validated_cluster, - None, - Some(true), - )) - .with_labels(labels) - .build(), - role_ref: RoleRef { - api_group: Some("rbac.authorization.k8s.io".to_owned()), - kind: "ClusterRole".to_string(), - name: resource_names.cluster_role_name().to_string(), - }, - subjects: Some(vec![Subject { - kind: "ServiceAccount".to_string(), - name: resource_names.service_account_name().to_string(), - namespace: Some(validated_cluster.namespace.to_string()), - ..Subject::default() - }]), - } +/// Both resources are shared by the whole cluster rather than tied to a role or role group, so +/// the recommended labels carry `none` for both values. +fn rbac_labels(cluster: &ValidatedCluster) -> Labels { + cluster.recommended_labels_for(&NONE_ROLE_NAME, &NONE_ROLE_GROUP_NAME) } diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 07703051..0a229f95 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -8,9 +8,7 @@ use stackable_operator::{ }; use crate::{ - controller::{ - RoleGroupName, ValidatedCluster, build::labels, security::ValidatedKafkaSecurity, - }, + controller::{RoleGroupName, ValidatedCluster, security::ValidatedKafkaSecurity}, crd::{METRICS_PORT, METRICS_PORT_NAME, role::KafkaRole}, }; @@ -37,17 +35,15 @@ pub fn build_rolegroup_headless_service( None, Some(true), )) - .with_labels(labels::recommended_labels( - validated_cluster, - role, - role_group_name, - )) + .with_labels(validated_cluster.recommended_labels(role, role_group_name)) .build(), spec: Some(ServiceSpec { cluster_ip: Some("None".to_string()), ports: Some(headless_ports(kafka_security)), selector: Some( - labels::role_group_selector(validated_cluster, role, role_group_name).into(), + validated_cluster + .role_group_selector(role, role_group_name) + .into(), ), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() @@ -76,11 +72,7 @@ pub fn build_rolegroup_metrics_service( None, Some(true), )) - .with_labels(labels::recommended_labels( - validated_cluster, - role, - role_group_name, - )) + .with_labels(validated_cluster.recommended_labels(role, role_group_name)) .with_labels(prometheus_labels(&Scraping::Enabled)) .with_annotations(prometheus_annotations( &Scraping::Enabled, @@ -95,7 +87,9 @@ pub fn build_rolegroup_metrics_service( cluster_ip: Some("None".to_string()), ports: Some(metrics_ports()), selector: Some( - labels::role_group_selector(validated_cluster, role, role_group_name).into(), + validated_cluster + .role_group_selector(role, role_group_name) + .into(), ), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 70997d08..e6d6b43e 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -53,7 +53,6 @@ use crate::{ }, graceful_shutdown::add_graceful_shutdown_config, kerberos::add_kerberos_pod_config, - labels, properties::product_logging::MAX_KAFKA_LOG_FILES_SIZE, security::{ add_broker_volume_and_volume_mounts, add_controller_volume_and_volume_mounts, @@ -188,17 +187,15 @@ pub fn build_broker_rolegroup_statefulset( role_group_name: &RoleGroupName, validated_cluster: &ValidatedCluster, validated_rg: &ValidatedRoleGroupConfig, - service_account_name: &str, ) -> Result { let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; let merged_config = &validated_rg.config.config; let resource_names = validated_cluster.resource_names(kafka_role, role_group_name); - let recommended_labels = - labels::recommended_labels(validated_cluster, kafka_role, role_group_name); + let recommended_labels = validated_cluster.recommended_labels(kafka_role, role_group_name); // Used for PVC templates that cannot be modified once they are deployed let unversioned_recommended_labels = - labels::unversioned_recommended_labels(validated_cluster, kafka_role, role_group_name); + validated_cluster.unversioned_recommended_labels(kafka_role, role_group_name); let kcat_prober_container_name = BrokerContainer::KcatProber.to_string(); let mut cb_kcat_prober = @@ -394,7 +391,14 @@ pub fn build_broker_rolegroup_statefulset( .add_container(cb_kcat_prober.build()) .affinity(&merged_config.affinity); - add_common_pod_config(&mut pod_builder, &resource_names, service_account_name)?; + add_common_pod_config( + &mut pod_builder, + &resource_names, + validated_cluster + .rbac_resource_names() + .service_account_name() + .as_ref(), + )?; add_vector_container( &mut pod_builder, @@ -432,7 +436,8 @@ pub fn build_broker_rolegroup_statefulset( replicas: validated_rg.replicas.map(i32::from), selector: LabelSelector { match_labels: Some( - labels::role_group_selector(validated_cluster, kafka_role, role_group_name) + validated_cluster + .role_group_selector(kafka_role, role_group_name) .into(), ), ..LabelSelector::default() @@ -452,14 +457,12 @@ pub fn build_controller_rolegroup_statefulset( role_group_name: &RoleGroupName, validated_cluster: &ValidatedCluster, validated_rg: &ValidatedRoleGroupConfig, - service_account_name: &str, ) -> Result { let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; let merged_config = &validated_rg.config.config; let resource_names = validated_cluster.resource_names(kafka_role, role_group_name); - let recommended_labels = - labels::recommended_labels(validated_cluster, kafka_role, role_group_name); + let recommended_labels = validated_cluster.recommended_labels(kafka_role, role_group_name); let kafka_container_name = ControllerContainer::Kafka.to_string(); let mut cb_kafka = @@ -577,7 +580,14 @@ pub fn build_controller_rolegroup_statefulset( .add_container(kafka_container) .affinity(&merged_config.affinity); - add_common_pod_config(&mut pod_builder, &resource_names, service_account_name)?; + add_common_pod_config( + &mut pod_builder, + &resource_names, + validated_cluster + .rbac_resource_names() + .service_account_name() + .as_ref(), + )?; add_vector_container( &mut pod_builder, @@ -615,7 +625,8 @@ pub fn build_controller_rolegroup_statefulset( replicas: validated_rg.replicas.map(i32::from), selector: LabelSelector { match_labels: Some( - labels::role_group_selector(validated_cluster, kafka_role, role_group_name) + validated_cluster + .role_group_selector(kafka_role, role_group_name) .into(), ), ..LabelSelector::default() From 1826c222f62c51c0b895a8245fe925bacfb0ad7d Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 22 Jul 2026 18:13:53 +0200 Subject: [PATCH 6/8] fixed comment --- rust/operator-binary/src/controller.rs | 21 ++++++++++++++----- .../controller/build/properties/listener.rs | 10 ++++----- .../controller/build/resource/config_map.rs | 2 +- .../src/controller/build/resource/rbac.rs | 4 ++-- .../src/controller/build/resource/service.rs | 4 ++-- .../controller/build/resource/statefulset.rs | 8 +++---- 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index b753c79a..f5732b04 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -204,7 +204,7 @@ impl ValidatedCluster { seen_hashes.insert(node_id_hash_offset, (role.clone(), role_group_name.clone())); if requested_kafka_role.is_none() || Some(role) == requested_kafka_role { - let resource_names = self.resource_names(role, role_group_name); + let resource_names = self.role_group_resource_names(role, role_group_name); let role_group_statefulset_name = resource_names.stateful_set_name(); let role_group_service_name = resource_names.headless_service_name(); // Pods must be predicted from a concrete count (e.g. for KRaft quorum @@ -235,7 +235,7 @@ impl ValidatedCluster { /// Type-safe names for the per-cluster RBAC resources: the ServiceAccount shared by all /// Pods, its (namespaced) RoleBinding, and the operator-deployed ClusterRole it binds. - pub fn rbac_resource_names(&self) -> role_utils::ResourceNames { + pub fn cluster_resource_names(&self) -> role_utils::ResourceNames { role_utils::ResourceNames { cluster_name: self.name.clone(), product_name: product_name(), @@ -243,7 +243,7 @@ impl ValidatedCluster { } /// Type-safe names for the resources of a given role group. - pub(crate) fn resource_names( + pub(crate) fn role_group_resource_names( &self, role: &KafkaRole, role_group_name: &RoleGroupName, @@ -319,7 +319,7 @@ impl ValidatedCluster { ) -> ListenerName { ListenerName::from_str(&format!( "{}-bootstrap", - self.resource_names(role, role_group_name) + self.role_group_resource_names(role, role_group_name) .stateful_set_name() )) .expect("the bootstrap listener name is a valid Listener name") @@ -723,8 +723,10 @@ pub(crate) mod test_support { mod tests { use std::collections::BTreeSet; + use strum::IntoEnumIterator; + use super::{ - PodDescriptorsError, + PodDescriptorsError, ValidatedCluster, test_support::{minimal_kafka, validated_cluster}, }; use crate::crd::role::KafkaRole; @@ -806,4 +808,13 @@ mod tests { let node_ids: BTreeSet = descriptors.iter().map(|d| d.node_id).collect(); assert_eq!(node_ids.len(), 3, "node ids must be unique: {node_ids:?}"); } + + /// Locks the invariant behind the `expect` in [`ValidatedCluster::role_name`]: every + /// `KafkaRole` variant (present and future) must serialise to a valid `RoleName`. + #[test] + fn every_kafka_role_serialises_to_a_valid_role_name() { + for role in KafkaRole::iter() { + ValidatedCluster::role_name(&role); + } + } } diff --git a/rust/operator-binary/src/controller/build/properties/listener.rs b/rust/operator-binary/src/controller/build/properties/listener.rs index d58cba74..3ba733bf 100644 --- a/rust/operator-binary/src/controller/build/properties/listener.rs +++ b/rust/operator-binary/src/controller/build/properties/listener.rs @@ -27,7 +27,7 @@ pub fn get_kafka_listener_config( role_group_name: &RoleGroupName, ) -> KafkaListenerConfig { let headless_service_name = validated_cluster - .resource_names(role, role_group_name) + .role_group_resource_names(role, role_group_name) .headless_service_name(); let pod_fqdn = pod_fqdn( &validated_cluster.namespace, @@ -241,7 +241,7 @@ mod tests { internal_host = pod_fqdn( &validated.namespace, validated - .resource_names(&KafkaRole::Broker, &role_group_name) + .role_group_resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), &cluster_info.cluster_domain @@ -303,7 +303,7 @@ mod tests { internal_host = pod_fqdn( &validated.namespace, validated - .resource_names(&KafkaRole::Broker, &role_group_name) + .role_group_resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), &cluster_info.cluster_domain @@ -366,7 +366,7 @@ mod tests { internal_host = pod_fqdn( &validated.namespace, validated - .resource_names(&KafkaRole::Broker, &role_group_name) + .role_group_resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), &cluster_info.cluster_domain @@ -466,7 +466,7 @@ mod tests { internal_host = pod_fqdn( &validated.namespace, validated - .resource_names(&KafkaRole::Broker, &role_group_name) + .role_group_resource_names(&KafkaRole::Broker, &role_group_name) .headless_service_name() .as_ref(), &cluster_info.cluster_domain diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 392e0961..284a281b 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -124,7 +124,7 @@ pub fn build_rolegroup_config_map( .name_and_namespace(validated_cluster) .name( validated_cluster - .resource_names(&role, role_group_name) + .role_group_resource_names(&role, role_group_name) .role_group_config_map() .to_string(), ) diff --git a/rust/operator-binary/src/controller/build/resource/rbac.rs b/rust/operator-binary/src/controller/build/resource/rbac.rs index 8d52854b..e59a8818 100644 --- a/rust/operator-binary/src/controller/build/resource/rbac.rs +++ b/rust/operator-binary/src/controller/build/resource/rbac.rs @@ -20,7 +20,7 @@ stackable_operator::constant!(NONE_ROLE_GROUP_NAME: RoleGroupName = "none"); pub fn build_service_account(cluster: &ValidatedCluster) -> ServiceAccount { rbac::build_service_account( cluster, - &cluster.rbac_resource_names(), + &cluster.cluster_resource_names(), rbac_labels(cluster), ) } @@ -30,7 +30,7 @@ pub fn build_service_account(cluster: &ValidatedCluster) -> ServiceAccount { pub fn build_role_binding(cluster: &ValidatedCluster) -> RoleBinding { rbac::build_role_binding( cluster, - &cluster.rbac_resource_names(), + &cluster.cluster_resource_names(), rbac_labels(cluster), ) } diff --git a/rust/operator-binary/src/controller/build/resource/service.rs b/rust/operator-binary/src/controller/build/resource/service.rs index 0a229f95..5f574531 100644 --- a/rust/operator-binary/src/controller/build/resource/service.rs +++ b/rust/operator-binary/src/controller/build/resource/service.rs @@ -26,7 +26,7 @@ pub fn build_rolegroup_headless_service( .name_and_namespace(validated_cluster) .name( validated_cluster - .resource_names(role, role_group_name) + .role_group_resource_names(role, role_group_name) .headless_service_name() .to_string(), ) @@ -63,7 +63,7 @@ pub fn build_rolegroup_metrics_service( .name_and_namespace(validated_cluster) .name( validated_cluster - .resource_names(role, role_group_name) + .role_group_resource_names(role, role_group_name) .metrics_service_name() .to_string(), ) diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index e6d6b43e..57104c50 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -191,7 +191,7 @@ pub fn build_broker_rolegroup_statefulset( let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; let merged_config = &validated_rg.config.config; - let resource_names = validated_cluster.resource_names(kafka_role, role_group_name); + let resource_names = validated_cluster.role_group_resource_names(kafka_role, role_group_name); let recommended_labels = validated_cluster.recommended_labels(kafka_role, role_group_name); // Used for PVC templates that cannot be modified once they are deployed let unversioned_recommended_labels = @@ -395,7 +395,7 @@ pub fn build_broker_rolegroup_statefulset( &mut pod_builder, &resource_names, validated_cluster - .rbac_resource_names() + .cluster_resource_names() .service_account_name() .as_ref(), )?; @@ -461,7 +461,7 @@ pub fn build_controller_rolegroup_statefulset( let kafka_security = &validated_cluster.cluster_config.kafka_security; let resolved_product_image = &validated_cluster.image; let merged_config = &validated_rg.config.config; - let resource_names = validated_cluster.resource_names(kafka_role, role_group_name); + let resource_names = validated_cluster.role_group_resource_names(kafka_role, role_group_name); let recommended_labels = validated_cluster.recommended_labels(kafka_role, role_group_name); let kafka_container_name = ControllerContainer::Kafka.to_string(); @@ -584,7 +584,7 @@ pub fn build_controller_rolegroup_statefulset( &mut pod_builder, &resource_names, validated_cluster - .rbac_resource_names() + .cluster_resource_names() .service_account_name() .as_ref(), )?; From da3cf91c2c9094432602df343a7d87bae45a3161 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 22 Jul 2026 18:24:45 +0200 Subject: [PATCH 7/8] reference op-rs main branch, fix comment --- Cargo.lock | 75 +------ Cargo.nix | 263 +++---------------------- Cargo.toml | 4 +- crate-hashes.json | 19 +- rust/operator-binary/src/controller.rs | 3 +- 5 files changed, 51 insertions(+), 313 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92193f9b..14b715c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,21 +265,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bitflags" version = "1.3.2" @@ -835,17 +820,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fancy-regex" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" -dependencies = [ - "bit-set", - "regex-automata", - "regex-syntax", -] - [[package]] name = "fastrand" version = "2.4.1" @@ -1599,6 +1573,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "darling", "regex", @@ -2257,22 +2232,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "product-config" -version = "0.8.0" -source = "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#678fb7cf30af7d7b516c9a46698a1b661120d54a" -dependencies = [ - "fancy-regex", - "java-properties", - "schemars", - "semver", - "serde", - "serde_json", - "serde_yaml", - "snafu 0.8.9", - "xml", -] - [[package]] name = "prost" version = "0.14.4" @@ -2918,15 +2877,6 @@ dependencies = [ "snafu-derive 0.6.10", ] -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive 0.8.9", -] - [[package]] name = "snafu" version = "0.9.1" @@ -2947,18 +2897,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.118", -] - [[package]] name = "snafu-derive" version = "0.9.1" @@ -3006,6 +2944,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "stackable-certs" version = "0.4.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "const-oid", "ecdsa", @@ -3049,7 +2988,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.113.4" +version = "0.114.0" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "base64", "clap", @@ -3066,7 +3006,6 @@ dependencies = [ "json-patch", "k8s-openapi", "kube", - "product-config", "rand 0.9.4", "regex", "schemars", @@ -3094,6 +3033,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "darling", "proc-macro2", @@ -3104,6 +3044,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "jiff", "k8s-openapi", @@ -3120,6 +3061,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.5" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "axum", "clap", @@ -3143,6 +3085,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.11.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "kube", "schemars", @@ -3156,6 +3099,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.11.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "convert_case", "convert_case_extras", @@ -3173,6 +3117,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.2" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=main#ee66b8d66fe10c216acfed0f81070b362604b392" dependencies = [ "arc-swap", "async-trait", diff --git a/Cargo.nix b/Cargo.nix index 5e9a9f0e..bb4e1c0c 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -862,50 +862,6 @@ rec { }; resolvedDefaultFeatures = [ "alloc" ]; }; - "bit-set" = rec { - crateName = "bit-set"; - version = "0.8.0"; - edition = "2015"; - sha256 = "18riaa10s6n59n39vix0cr7l2dgwdhcpbcm97x1xbyfp1q47x008"; - libName = "bit_set"; - authors = [ - "Alexis Beingessner " - ]; - dependencies = [ - { - name = "bit-vec"; - packageId = "bit-vec"; - usesDefaultFeatures = false; - } - ]; - features = { - "default" = [ "std" ]; - "serde" = [ "dep:serde" "bit-vec/serde" ]; - "std" = [ "bit-vec/std" ]; - }; - resolvedDefaultFeatures = [ "std" ]; - }; - "bit-vec" = rec { - crateName = "bit-vec"; - version = "0.8.0"; - edition = "2015"; - sha256 = "1xxa1s2cj291r7k1whbxq840jxvmdsq9xgh7bvrxl46m80fllxjy"; - libName = "bit_vec"; - authors = [ - "Alexis Beingessner " - ]; - features = { - "borsh" = [ "dep:borsh" ]; - "borsh_std" = [ "borsh/std" ]; - "default" = [ "std" ]; - "miniserde" = [ "dep:miniserde" ]; - "nanoserde" = [ "dep:nanoserde" ]; - "serde" = [ "dep:serde" ]; - "serde_no_std" = [ "serde/alloc" ]; - "serde_std" = [ "std" "serde/std" ]; - }; - resolvedDefaultFeatures = [ "std" ]; - }; "bitflags 1.3.2" = rec { crateName = "bitflags"; version = "1.3.2"; @@ -2555,43 +2511,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "fancy-regex" = rec { - crateName = "fancy-regex"; - version = "0.16.2"; - edition = "2018"; - sha256 = "0vy4c012f82xcg3gs068mq110zhsrnajh58fmq1jxr7vaijhb2wr"; - libName = "fancy_regex"; - authors = [ - "Raph Levien " - "Robin Stocker " - "Keith Hall " - ]; - dependencies = [ - { - name = "bit-set"; - packageId = "bit-set"; - usesDefaultFeatures = false; - } - { - name = "regex-automata"; - packageId = "regex-automata"; - usesDefaultFeatures = false; - features = [ "alloc" "syntax" "meta" "nfa" "dfa" "hybrid" ]; - } - { - name = "regex-syntax"; - packageId = "regex-syntax"; - usesDefaultFeatures = false; - } - ]; - features = { - "default" = [ "unicode" "perf" "std" ]; - "perf" = [ "regex-automata/perf" ]; - "std" = [ "regex-automata/std" "regex-syntax/std" "bit-set/std" ]; - "unicode" = [ "regex-automata/unicode" "regex-syntax/unicode" ]; - }; - resolvedDefaultFeatures = [ "default" "perf" "std" "unicode" ]; - }; "fastrand" = rec { crateName = "fastrand"; version = "2.4.1"; @@ -5083,9 +5002,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "k8s_version"; authors = [ @@ -7394,61 +7313,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "proc-macro" ]; }; - "product-config" = rec { - crateName = "product-config"; - version = "0.8.0"; - edition = "2021"; - workspace_member = null; - src = pkgs.fetchgit { - url = "https://github.com/stackabletech/product-config.git"; - rev = "678fb7cf30af7d7b516c9a46698a1b661120d54a"; - sha256 = "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987"; - }; - libName = "product_config"; - authors = [ - "Malte Sander " - ]; - dependencies = [ - { - name = "fancy-regex"; - packageId = "fancy-regex"; - } - { - name = "java-properties"; - packageId = "java-properties"; - } - { - name = "schemars"; - packageId = "schemars"; - } - { - name = "semver"; - packageId = "semver"; - } - { - name = "serde"; - packageId = "serde"; - features = [ "derive" ]; - } - { - name = "serde_json"; - packageId = "serde_json"; - } - { - name = "serde_yaml"; - packageId = "serde_yaml"; - } - { - name = "snafu"; - packageId = "snafu 0.8.9"; - } - { - name = "xml"; - packageId = "xml"; - } - ]; - - }; "prost" = rec { crateName = "prost"; version = "0.14.4"; @@ -7949,7 +7813,7 @@ rec { "unicode-script" = [ "regex-syntax?/unicode-script" ]; "unicode-segment" = [ "regex-syntax?/unicode-segment" ]; }; - resolvedDefaultFeatures = [ "alloc" "dfa" "dfa-build" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ]; + resolvedDefaultFeatures = [ "alloc" "dfa-build" "dfa-onepass" "dfa-search" "hybrid" "meta" "nfa-backtrack" "nfa-pikevm" "nfa-thompson" "perf-inline" "perf-literal" "perf-literal-multisubstring" "perf-literal-substring" "std" "syntax" "unicode" "unicode-age" "unicode-bool" "unicode-case" "unicode-gencat" "unicode-perl" "unicode-script" "unicode-segment" "unicode-word-boundary" ]; }; "regex-syntax" = rec { crateName = "regex-syntax"; @@ -9612,37 +9476,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "guide" "std" ]; }; - "snafu 0.8.9" = rec { - crateName = "snafu"; - version = "0.8.9"; - edition = "2018"; - sha256 = "18p1y5qxwjn5j902wqsdr75n17b29lxpdipa0p7a3wybxbsb713f"; - authors = [ - "Jake Goulding " - ]; - dependencies = [ - { - name = "snafu-derive"; - packageId = "snafu-derive 0.8.9"; - } - ]; - features = { - "backtrace" = [ "dep:backtrace" ]; - "backtraces-impl-backtrace-crate" = [ "backtrace" ]; - "default" = [ "std" "rust_1_65" ]; - "futures" = [ "futures-core-crate" "pin-project" ]; - "futures-core-crate" = [ "dep:futures-core-crate" ]; - "futures-crate" = [ "dep:futures-crate" ]; - "internal-dev-dependencies" = [ "futures-crate" ]; - "pin-project" = [ "dep:pin-project" ]; - "rust_1_61" = [ "snafu-derive/rust_1_61" ]; - "rust_1_65" = [ "rust_1_61" ]; - "rust_1_81" = [ "rust_1_65" ]; - "std" = [ "alloc" ]; - "unstable-provider-api" = [ "snafu-derive/unstable-provider-api" ]; - }; - resolvedDefaultFeatures = [ "alloc" "default" "rust_1_61" "rust_1_65" "std" ]; - }; "snafu 0.9.1" = rec { crateName = "snafu"; version = "0.9.1"; @@ -9695,40 +9528,6 @@ rec { features = { }; }; - "snafu-derive 0.8.9" = rec { - crateName = "snafu-derive"; - version = "0.8.9"; - edition = "2018"; - sha256 = "0lg4s58jzx6w48ig4qp8jasrrs886pifqqd58k5b2jzlvd3pgjf1"; - procMacro = true; - libName = "snafu_derive"; - authors = [ - "Jake Goulding " - ]; - dependencies = [ - { - name = "heck"; - packageId = "heck"; - usesDefaultFeatures = false; - } - { - name = "proc-macro2"; - packageId = "proc-macro2"; - } - { - name = "quote"; - packageId = "quote"; - } - { - name = "syn"; - packageId = "syn 2.0.118"; - features = [ "full" ]; - } - ]; - features = { - }; - resolvedDefaultFeatures = [ "rust_1_61" ]; - }; "snafu-derive 0.9.1" = rec { crateName = "snafu-derive"; version = "0.9.1"; @@ -9867,9 +9666,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_certs"; authors = [ @@ -10054,13 +9853,13 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.113.3"; + version = "0.114.0"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_operator"; authors = [ @@ -10135,10 +9934,6 @@ rec { usesDefaultFeatures = false; features = [ "client" "jsonpatch" "runtime" "derive" "admission" "rustls-tls" "ring" ]; } - { - name = "product-config"; - packageId = "product-config"; - } { name = "rand"; packageId = "rand 0.9.4"; @@ -10257,9 +10052,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -10292,9 +10087,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_shared"; authors = [ @@ -10373,9 +10168,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_telemetry"; authors = [ @@ -10483,9 +10278,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_versioned"; authors = [ @@ -10533,9 +10328,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10601,9 +10396,9 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "013bbf43f7006a4ddfc08a147f68441ed88b462b"; - sha256 = "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "ee66b8d66fe10c216acfed0f81070b362604b392"; + sha256 = "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj"; }; libName = "stackable_webhook"; authors = [ diff --git a/Cargo.toml b/Cargo.toml index 7483e808..9d0377ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,5 +29,5 @@ tokio = { version = "1.52", features = ["full"] } tracing = "0.1" [patch."https://github.com/stackabletech/operator-rs.git"] -stackable-operator = { path = "../operator-rs/crates/stackable-operator" } -# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } +# stackable-operator = { path = "../operator-rs/crates/stackable-operator" } +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } diff --git a/crate-hashes.json b/crate-hashes.json index d20c0c9d..3ddbc0ba 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,11 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#k8s-version@0.1.3": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-certs@0.4.1": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-operator-derive@0.3.1": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-operator@0.113.3": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-shared@0.1.2": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-telemetry@0.6.5": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-versioned-macros@0.11.1": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-versioned@0.11.1": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.113.3#stackable-webhook@0.9.2": "1qq2z64gzx4sd1csh35cxcfshvhgrm0c9b13rq0sqv0046lmhh33", - "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" + "git+https://github.com/stackabletech//operator-rs.git?branch=main#k8s-version@0.1.3": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-certs@0.4.1": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-operator-derive@0.3.1": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-operator@0.114.0": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-shared@0.1.2": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-telemetry@0.6.5": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-versioned-macros@0.11.1": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-versioned@0.11.1": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj", + "git+https://github.com/stackabletech//operator-rs.git?branch=main#stackable-webhook@0.9.2": "1v6slybgc0xqsmh3bxyid6xjvmz8ps41nfmmc6csgyzqs2v0wzxj" } \ No newline at end of file diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index f5732b04..81aeb778 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -174,8 +174,7 @@ impl ValidatedCluster { /// node-id hash offsets must be unique across the whole cluster, so collisions are detected /// across all role groups regardless of `requested_kafka_role`. /// - /// Resource names reuse [`Self::resource_names`] (the canonical - /// `--` naming) so they stay in sync with the StatefulSet and + /// Resource names reuse resource names so they stay in sync with the StatefulSet and /// headless Service this descriptor refers to. pub fn pod_descriptors( &self, From 7c52f90ffd9739f4dd1f97a93767f7b6b92faad7 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 23 Jul 2026 12:36:20 +0200 Subject: [PATCH 8/8] changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4696be96..e50d7b50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,11 @@ All notable changes to this project will be documented in this file. - Internal operator refactoring: introduce a build() step in the reconciler that assembles all relevant Kubernetes resources before anything is applied ([#985]). - Bump stackable-operator to 0.114.0 ([#994]). +- The RBAC ServiceAccount and RoleBinding are now built with the operator-rs `v2::rbac` + functions and carry the full set of recommended labels ([#990]). [#985]: https://github.com/stackabletech/kafka-operator/pull/985 +[#990]: https://github.com/stackabletech/kafka-operator/pull/990 [#994]: https://github.com/stackabletech/kafka-operator/pull/994 ## [26.7.0] - 2026-07-21