@@ -16,18 +16,18 @@ use stackable_operator::{
1616 commons:: {
1717 affinity:: StackableAffinity ,
1818 product_image_selection:: ResolvedProductImage ,
19- rbac:: build_rbac_resources,
2019 resources:: { NoRuntimeLimits , Resources } ,
2120 } ,
2221 crd:: { listener:: v1alpha1:: Listener , s3} ,
2322 database_connections:: drivers:: jdbc:: JdbcDatabaseConnectionDetails ,
2423 k8s_openapi:: api:: {
2524 apps:: v1:: StatefulSet ,
26- core:: v1:: { ConfigMap , Service } ,
25+ core:: v1:: { ConfigMap , Service , ServiceAccount } ,
2726 policy:: v1:: PodDisruptionBudget ,
27+ rbac:: v1:: RoleBinding ,
2828 } ,
2929 kube:: {
30- Resource , ResourceExt ,
30+ Resource ,
3131 api:: ObjectMeta ,
3232 core:: { DeserializeGuard , error_boundary} ,
3333 runtime:: controller:: Action ,
@@ -44,6 +44,7 @@ use stackable_operator::{
4444 cluster_resources:: cluster_resources_new,
4545 kvp:: label:: { recommended_labels, role_group_selector} ,
4646 role_group_utils:: ResourceNames ,
47+ role_utils,
4748 types:: {
4849 kubernetes:: { ListenerClassName , ListenerName , SecretClassName } ,
4950 operator:: { ControllerName , OperatorName , ProductName , ProductVersion , RoleName } ,
@@ -54,7 +55,7 @@ use strum::EnumDiscriminants;
5455
5556use crate :: {
5657 OPERATOR_NAME ,
57- controller:: build:: resource:: discovery,
58+ controller:: build:: { UNVERSIONED_PRODUCT_VERSION , resource:: discovery} ,
5859 crd:: { APP_NAME , HdfsConnection , HiveClusterStatus , HiveRole , MetaStoreConfig , v1alpha1} ,
5960} ;
6061
@@ -95,27 +96,6 @@ pub enum Error {
9596 source : stackable_operator:: cluster_resources:: Error ,
9697 } ,
9798
98- #[ snafu( display( "failed to patch service account" ) ) ]
99- ApplyServiceAccount {
100- source : stackable_operator:: cluster_resources:: Error ,
101- } ,
102-
103- #[ snafu( display( "failed to patch role binding" ) ) ]
104- ApplyRoleBinding {
105- source : stackable_operator:: cluster_resources:: Error ,
106- } ,
107-
108- #[ snafu( display( "failed to build RBAC resources" ) ) ]
109- BuildRbacResources {
110- source : stackable_operator:: commons:: rbac:: Error ,
111- } ,
112-
113- #[ snafu( display( "failed to get required Labels" ) ) ]
114- GetRequiredLabels {
115- source :
116- stackable_operator:: kvp:: KeyValuePairError < stackable_operator:: kvp:: LabelValueError > ,
117- } ,
118-
11999 #[ snafu( display( "HiveCluster object is invalid" ) ) ]
120100 InvalidHiveCluster {
121101 source : error_boundary:: InvalidObject ,
@@ -253,6 +233,15 @@ impl ValidatedCluster {
253233 . expect ( "the metastore role name is a valid role name" )
254234 }
255235
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+
256245 /// Type-safe names for the resources of a given role group.
257246 pub ( crate ) fn resource_names ( & self , role_group_name : & RoleGroupName ) -> ResourceNames {
258247 ResourceNames {
@@ -262,10 +251,31 @@ impl ValidatedCluster {
262251 }
263252 }
264253
254+ /// Recommended labels for a resource that is not tied to a concrete,
255+ /// using a free-form role/role-group label value.
256+ pub fn recommended_labels_for (
257+ & self ,
258+ role_name : & RoleName ,
259+ role_group_name : & RoleGroupName ,
260+ ) -> Labels {
261+ self . recommended_labels_with ( & self . product_version , role_name, role_group_name)
262+ }
263+
264+ /// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates
265+ /// that cannot be modified after deployment (keeps the labels stable across version upgrades).
266+ pub fn unversioned_recommended_labels ( & self , role_group_name : & RoleGroupName ) -> Labels {
267+ self . recommended_labels_with (
268+ & UNVERSIONED_PRODUCT_VERSION ,
269+ & Self :: role_name ( ) ,
270+ role_group_name,
271+ )
272+ }
273+
265274 /// Recommended labels for a role-group resource, using the given product version.
266- fn recommended_labels_for (
275+ fn recommended_labels_with (
267276 & self ,
268277 product_version : & ProductVersion ,
278+ role_name : & RoleName ,
269279 role_group_name : & RoleGroupName ,
270280 ) -> Labels {
271281 recommended_labels (
@@ -274,14 +284,14 @@ impl ValidatedCluster {
274284 product_version,
275285 & operator_name ( ) ,
276286 & controller_name ( ) ,
277- & Self :: role_name ( ) ,
287+ role_name,
278288 role_group_name,
279289 )
280290 }
281291
282292 /// Recommended labels for a role-group resource.
283293 pub fn recommended_labels ( & self , role_group_name : & RoleGroupName ) -> Labels {
284- self . recommended_labels_for ( & self . product_version , role_group_name)
294+ self . recommended_labels_for ( & Self :: role_name ( ) , role_group_name)
285295 }
286296
287297 /// Selector labels matching the pods of a role group.
@@ -433,13 +443,15 @@ pub struct ValidatedRoleConfig {
433443///
434444/// The role-level discovery `ConfigMap` is deliberately absent: it is built from the *applied*
435445/// role [`Listener`]'s ingress addresses, so it is assembled in the reconcile step after the
436- /// Listener has been applied, not in the build step.
446+ /// Listener has been applied, not in the build step.
437447pub struct KubernetesResources {
438448 pub stateful_sets : Vec < StatefulSet > ,
439449 pub services : Vec < Service > ,
440450 pub listeners : Vec < Listener > ,
441451 pub config_maps : Vec < ConfigMap > ,
442452 pub pod_disruption_budgets : Vec < PodDisruptionBudget > ,
453+ pub service_accounts : Vec < ServiceAccount > ,
454+ pub role_bindings : Vec < RoleBinding > ,
443455}
444456
445457pub async fn reconcile_hive (
@@ -476,41 +488,26 @@ pub async fn reconcile_hive(
476488 & hive. spec . object_overrides ,
477489 ) ;
478490
479- let ( rbac_sa, rbac_rolebinding) = build_rbac_resources (
480- hive,
481- APP_NAME ,
482- cluster_resources
483- . get_required_labels ( )
484- . context ( GetRequiredLabelsSnafu ) ?,
485- )
486- . context ( BuildRbacResourcesSnafu ) ?;
487-
488- let rbac_sa = cluster_resources
489- . add ( client, rbac_sa)
490- . await
491- . context ( ApplyServiceAccountSnafu ) ?;
492-
493- cluster_resources
494- . add ( client, rbac_rolebinding)
495- . await
496- . context ( ApplyRoleBindingSnafu ) ?;
497-
498- // The ServiceAccount name is deterministic on the built object, so the client-free build step
499- // does not depend on the applied ServiceAccount.
500- let service_account_name = rbac_sa. name_any ( ) ;
501-
502- let resources = build:: build (
503- & validated_cluster,
504- & client. kubernetes_cluster_info ,
505- & service_account_name,
506- )
507- . context ( BuildResourcesSnafu ) ?;
491+ let resources = build:: build ( & validated_cluster, & client. kubernetes_cluster_info )
492+ . context ( BuildResourcesSnafu ) ?;
508493
509494 let mut ss_cond_builder = StatefulSetConditionBuilder :: default ( ) ;
510495
511496 // Apply order: everything before StatefulSets, StatefulSets last. A StatefulSet must only be
512497 // applied after all ConfigMaps and Secrets it mounts, to prevent unnecessary Pod restarts.
513498 // See https://github.com/stackabletech/commons-operator/issues/111 for details.
499+ for service_account in resources. service_accounts {
500+ cluster_resources
501+ . add ( client, service_account)
502+ . await
503+ . context ( ApplyResourceSnafu ) ?;
504+ }
505+ for role_binding in resources. role_bindings {
506+ cluster_resources
507+ . add ( client, role_binding)
508+ . await
509+ . context ( ApplyResourceSnafu ) ?;
510+ }
514511 for service in resources. services {
515512 cluster_resources
516513 . add ( client, service)
0 commit comments