@@ -21,15 +21,17 @@ use stackable_operator::{
2121 crd:: listener,
2222 k8s_openapi:: api:: {
2323 apps:: v1:: StatefulSet ,
24- core:: v1:: { ConfigMap , Service } ,
24+ core:: v1:: { ConfigMap , Service , ServiceAccount } ,
2525 policy:: v1:: PodDisruptionBudget ,
26+ rbac:: v1:: RoleBinding ,
2627 } ,
2728 kube:: {
28- Resource , ResourceExt ,
29+ Resource ,
2930 api:: { DynamicObject , ObjectMeta } ,
3031 core:: { DeserializeGuard , error_boundary} ,
3132 runtime:: { controller:: Action , reflector:: ObjectRef } ,
3233 } ,
34+ kvp:: Labels ,
3335 logging:: controller:: ReconcilerError ,
3436 shared:: time:: Duration ,
3537 status:: condition:: {
@@ -39,7 +41,9 @@ use stackable_operator::{
3941 v2:: {
4042 HasName , HasUid , NameIsValidLabelValue ,
4143 cluster_resources:: cluster_resources_new,
44+ kvp:: label:: { recommended_labels, role_group_selector} ,
4245 role_group_utils:: ResourceNames ,
46+ role_utils,
4347 types:: {
4448 kubernetes:: { ConfigMapName , ListenerName , NamespaceName , Uid } ,
4549 operator:: { ClusterName , ControllerName , OperatorName , ProductName , ProductVersion } ,
@@ -59,11 +63,7 @@ pub(crate) mod validate;
5963pub use stackable_operator:: v2:: types:: operator:: { RoleGroupName , RoleName } ;
6064
6165use crate :: {
62- controller:: {
63- build:: resource:: rbac:: { build_rbac_role_binding, build_rbac_service_account} ,
64- node_id_hasher:: node_id_hash32_offset,
65- security:: ValidatedKafkaSecurity ,
66- } ,
66+ controller:: { node_id_hasher:: node_id_hash32_offset, security:: ValidatedKafkaSecurity } ,
6767 crd:: {
6868 APP_NAME , KafkaClusterStatus , KafkaPodDescriptor , MetadataManager , OPERATOR_NAME ,
6969 authorization:: KafkaAuthorizationConfig ,
@@ -75,6 +75,9 @@ use crate::{
7575pub const KAFKA_CONTROLLER_NAME : & str = "kafkacluster" ;
7676pub const KAFKA_FULL_CONTROLLER_NAME : & str = concatcp ! ( KAFKA_CONTROLLER_NAME , '.' , OPERATOR_NAME ) ;
7777
78+ // Placeholder version label value for resources whose labels must not change after deployment.
79+ stackable_operator:: constant!( UNVERSIONED_PRODUCT_VERSION : ProductVersion = "none" ) ;
80+
7881#[ derive( Snafu , Debug ) ]
7982pub enum PodDescriptorsError {
8083 #[ snafu( display(
@@ -99,6 +102,8 @@ pub struct KubernetesResources {
99102 pub listeners : Vec < listener:: v1alpha1:: Listener > ,
100103 pub config_maps : Vec < ConfigMap > ,
101104 pub pod_disruption_budgets : Vec < PodDisruptionBudget > ,
105+ pub service_accounts : Vec < ServiceAccount > ,
106+ pub role_bindings : Vec < RoleBinding > ,
102107}
103108
104109/// The validated cluster. Carries everything the build steps need, resolved once
@@ -228,6 +233,15 @@ impl ValidatedCluster {
228233 RoleName :: from_str ( & role. to_string ( ) ) . expect ( "a KafkaRole is a valid role name" )
229234 }
230235
236+ /// Type-safe names for the per-cluster RBAC resources: the ServiceAccount shared by all
237+ /// Pods, its (namespaced) RoleBinding, and the operator-deployed ClusterRole it binds.
238+ pub fn rbac_resource_names ( & self ) -> role_utils:: ResourceNames {
239+ role_utils:: ResourceNames {
240+ cluster_name : self . name . clone ( ) ,
241+ product_name : product_name ( ) ,
242+ }
243+ }
244+
231245 /// Type-safe names for the resources of a given role group.
232246 pub ( crate ) fn resource_names (
233247 & self ,
@@ -241,6 +255,61 @@ impl ValidatedCluster {
241255 }
242256 }
243257
258+ /// Recommended labels for a role-group resource.
259+ pub fn recommended_labels ( & self , role : & KafkaRole , role_group_name : & RoleGroupName ) -> Labels {
260+ self . recommended_labels_for ( & Self :: role_name ( role) , role_group_name)
261+ }
262+
263+ /// Recommended labels for a resource that is not tied to a concrete [`KafkaRole`], using a free-form role/role-group label value.
264+ pub fn recommended_labels_for (
265+ & self ,
266+ role_name : & RoleName ,
267+ role_group_name : & RoleGroupName ,
268+ ) -> Labels {
269+ self . recommended_labels_with ( & self . product_version , role_name, role_group_name)
270+ }
271+
272+ /// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates
273+ /// that cannot be modified after deployment (keeps the labels stable across version upgrades).
274+ pub fn unversioned_recommended_labels (
275+ & self ,
276+ role : & KafkaRole ,
277+ role_group_name : & RoleGroupName ,
278+ ) -> Labels {
279+ self . recommended_labels_with (
280+ & UNVERSIONED_PRODUCT_VERSION ,
281+ & Self :: role_name ( role) ,
282+ role_group_name,
283+ )
284+ }
285+
286+ fn recommended_labels_with (
287+ & self ,
288+ product_version : & ProductVersion ,
289+ role_name : & RoleName ,
290+ role_group_name : & RoleGroupName ,
291+ ) -> Labels {
292+ recommended_labels (
293+ self ,
294+ & product_name ( ) ,
295+ product_version,
296+ & operator_name ( ) ,
297+ & controller_name ( ) ,
298+ role_name,
299+ role_group_name,
300+ )
301+ }
302+
303+ /// Selector labels matching the pods of a role group.
304+ pub fn role_group_selector ( & self , role : & KafkaRole , role_group_name : & RoleGroupName ) -> Labels {
305+ role_group_selector (
306+ self ,
307+ & product_name ( ) ,
308+ & Self :: role_name ( role) ,
309+ role_group_name,
310+ )
311+ }
312+
244313 /// The name of the broker rolegroup's bootstrap [`Listener`](stackable_operator::crd::listener),
245314 /// `<cluster>-<role>-<role-group>-bootstrap`.
246315 pub fn bootstrap_listener_name (
@@ -423,27 +492,11 @@ pub enum Error {
423492 source : stackable_operator:: cluster_resources:: Error ,
424493 } ,
425494
426- #[ snafu( display( "failed to patch service account" ) ) ]
427- ApplyServiceAccount {
428- source : stackable_operator:: cluster_resources:: Error ,
429- } ,
430-
431- #[ snafu( display( "failed to patch role binding" ) ) ]
432- ApplyRoleBinding {
433- source : stackable_operator:: cluster_resources:: Error ,
434- } ,
435-
436495 #[ snafu( display( "failed to update status" ) ) ]
437496 ApplyStatus {
438497 source : stackable_operator:: client:: Error ,
439498 } ,
440499
441- #[ snafu( display( "failed to get required Labels" ) ) ]
442- GetRequiredLabels {
443- source :
444- stackable_operator:: kvp:: KeyValuePairError < stackable_operator:: kvp:: LabelValueError > ,
445- } ,
446-
447500 #[ snafu( display( "KafkaCluster object is invalid" ) ) ]
448501 InvalidKafkaCluster {
449502 source : error_boundary:: InvalidObject ,
@@ -465,10 +518,7 @@ impl ReconcilerError for Error {
465518 Error :: BuildDiscoveryConfig { .. } => None ,
466519 Error :: ApplyDiscoveryConfig { .. } => None ,
467520 Error :: DeleteOrphans { .. } => None ,
468- Error :: ApplyServiceAccount { .. } => None ,
469- Error :: ApplyRoleBinding { .. } => None ,
470521 Error :: ApplyStatus { .. } => None ,
471- Error :: GetRequiredLabels { .. } => None ,
472522 Error :: InvalidKafkaCluster { .. } => None ,
473523 }
474524 }
@@ -519,34 +569,27 @@ pub async fn reconcile_kafka(
519569
520570 let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
521571
522- let required_labels = cluster_resources
523- . get_required_labels ( )
524- . context ( GetRequiredLabelsSnafu ) ?;
525- let rbac_sa = build_rbac_service_account ( & validated_cluster, required_labels. clone ( ) ) ;
526- let rbac_rolebinding = build_rbac_role_binding ( & validated_cluster, required_labels) ;
527-
528- let rbac_sa = cluster_resources
529- . add ( client, rbac_sa. clone ( ) )
530- . await
531- . context ( ApplyServiceAccountSnafu ) ?;
532- // The ServiceAccount name is deterministic, so the statefulset builders only need the name,
533- // not the applied object.
534- let service_account_name = rbac_sa. name_any ( ) ;
535- cluster_resources
536- . add ( client, rbac_rolebinding)
537- . await
538- . context ( ApplyRoleBindingSnafu ) ?;
539-
540572 // Build every Kubernetes resource up front (client-free). The discovery ConfigMap is not part
541573 // of this, as it depends on the applied bootstrap Listeners' status (see below).
542- let resources =
543- build:: build ( & validated_cluster, & service_account_name) . context ( BuildResourcesSnafu ) ?;
574+ let resources = build:: build ( & validated_cluster) . context ( BuildResourcesSnafu ) ?;
544575
545576 // Apply order: Services, then Listeners (collecting the applied bootstrap Listeners for the
546577 // discovery ConfigMap), then ConfigMaps, then PodDisruptionBudgets, and finally the
547578 // StatefulSets. The StatefulSets must be applied after all ConfigMaps and Secrets they mount to
548579 // prevent unnecessary Pod restarts.
549580 // See https://github.com/stackabletech/commons-operator/issues/111 for details.
581+ for service_account in resources. service_accounts {
582+ cluster_resources
583+ . add ( client, service_account)
584+ . await
585+ . context ( ApplyResourceSnafu ) ?;
586+ }
587+ for role_binding in resources. role_bindings {
588+ cluster_resources
589+ . add ( client, role_binding)
590+ . await
591+ . context ( ApplyResourceSnafu ) ?;
592+ }
550593 for service in resources. services {
551594 cluster_resources
552595 . add ( client, service)
0 commit comments