33use std:: str:: FromStr ;
44
55use snafu:: { ResultExt , Snafu } ;
6- use stackable_operator:: v2:: types:: operator:: { ProductVersion , RoleGroupName } ;
6+ use stackable_operator:: v2:: types:: operator:: RoleGroupName ;
77
88use crate :: {
99 controller:: {
@@ -12,6 +12,7 @@ use crate::{
1212 config_map:: build_rolegroup_config_map,
1313 listener:: { build_group_listener, group_listener_name} ,
1414 pdb:: build_pdb,
15+ rbac:: { build_role_binding, build_service_account} ,
1516 service:: { build_rolegroup_headless_service, build_rolegroup_metrics_service} ,
1617 statefulset:: build_rolegroup_statefulset,
1718 } ,
@@ -28,10 +29,6 @@ stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleG
2829// (which is not tied to a single role group).
2930stackable_operator:: constant!( pub ( crate ) PLACEHOLDER_LISTENER_ROLE_GROUP : RoleGroupName = "none" ) ;
3031
31- // Placeholder product version used for labels on PVC templates, which cannot be modified once
32- // deployed. A constant value keeps the labels stable across version upgrades.
33- stackable_operator:: constant!( pub ( crate ) UNVERSIONED_PRODUCT_VERSION : ProductVersion = "none" ) ;
34-
3532pub mod authentication;
3633pub mod graceful_shutdown;
3734pub mod jvm;
@@ -66,10 +63,7 @@ pub enum Error {
6663/// The Router group `Listener` and the discovery `ConfigMap`s are not built here: the discovery
6764/// `ConfigMap` derives from the *applied* Router listener's ingress address, so both are built and
6865/// applied in the reconcile step instead.
69- pub fn build (
70- cluster : & ValidatedCluster ,
71- service_account_name : & str ,
72- ) -> Result < KubernetesResources , Error > {
66+ pub fn build ( cluster : & ValidatedCluster ) -> Result < KubernetesResources , Error > {
7367 let mut stateful_sets = vec ! [ ] ;
7468 let mut services = vec ! [ ] ;
7569 let mut listeners = vec ! [ ] ;
@@ -116,16 +110,11 @@ pub fn build(
116110 ) ?,
117111 ) ;
118112 stateful_sets. push (
119- build_rolegroup_statefulset (
120- cluster,
121- druid_role,
122- role_group_name,
123- rg,
124- service_account_name,
125- )
126- . context ( StatefulSetSnafu {
127- role_group : role_group_name. clone ( ) ,
128- } ) ?,
113+ build_rolegroup_statefulset ( cluster, druid_role, role_group_name, rg) . context (
114+ StatefulSetSnafu {
115+ role_group : role_group_name. clone ( ) ,
116+ } ,
117+ ) ?,
129118 ) ;
130119 }
131120 }
@@ -136,11 +125,15 @@ pub fn build(
136125 listeners,
137126 config_maps,
138127 pod_disruption_budgets,
128+ service_accounts : vec ! [ build_service_account( cluster) ] ,
129+ role_bindings : vec ! [ build_role_binding( cluster) ] ,
139130 } )
140131}
141132
142133#[ cfg( test) ]
143134mod tests {
135+ use std:: collections:: BTreeMap ;
136+
144137 use stackable_operator:: kube:: Resource ;
145138
146139 use super :: build;
@@ -161,7 +154,7 @@ mod tests {
161154 fn build_produces_expected_resource_names ( ) {
162155 let druid = druid_from_yaml ( MINIMAL_DRUID_YAML ) ;
163156 let cluster = validated_cluster ( & druid) ;
164- let resources = build ( & cluster, "simple-druid-serviceaccount" ) . expect ( "build succeeds" ) ;
157+ let resources = build ( & cluster) . expect ( "build succeeds" ) ;
165158
166159 // One StatefulSet and one ConfigMap per role group (one role group per role).
167160 let expected_role_group_names = [
@@ -188,4 +181,54 @@ mod tests {
188181 [ "simple-druid-broker" , "simple-druid-coordinator" ]
189182 ) ;
190183 }
184+
185+ /// Locks the RBAC resource names, the roleRef, and the recommended label set against
186+ /// accidental drift. The fixture's cluster name deliberately differs from the product name so
187+ /// that swapped `name`/`instance` label values cannot pass unnoticed.
188+ #[ test]
189+ fn build_produces_rbac ( ) {
190+ let druid = druid_from_yaml ( MINIMAL_DRUID_YAML ) ;
191+ let cluster = validated_cluster ( & druid) ;
192+ let resources = build ( & cluster) . expect ( "build succeeds" ) ;
193+
194+ assert_eq ! (
195+ sorted_names( & resources. service_accounts) ,
196+ [ "simple-druid-serviceaccount" ]
197+ ) ;
198+ assert_eq ! (
199+ sorted_names( & resources. role_bindings) ,
200+ [ "simple-druid-rolebinding" ]
201+ ) ;
202+
203+ let expected_labels = BTreeMap :: from (
204+ [
205+ ( "app.kubernetes.io/component" , "none" ) ,
206+ ( "app.kubernetes.io/instance" , "simple-druid" ) ,
207+ (
208+ "app.kubernetes.io/managed-by" ,
209+ "druid.stackable.tech_druidcluster" ,
210+ ) ,
211+ ( "app.kubernetes.io/name" , "druid" ) ,
212+ ( "app.kubernetes.io/role-group" , "none" ) ,
213+ ( "app.kubernetes.io/version" , "30.0.0-stackable0.0.0-dev" ) ,
214+ ( "stackable.tech/vendor" , "Stackable" ) ,
215+ ]
216+ . map ( |( key, value) | ( key. to_string ( ) , value. to_string ( ) ) ) ,
217+ ) ;
218+ let service_account = resources
219+ . service_accounts
220+ . first ( )
221+ . expect ( "a ServiceAccount is built" ) ;
222+ assert_eq ! (
223+ service_account. metadata. labels,
224+ Some ( expected_labels. clone( ) )
225+ ) ;
226+
227+ let role_binding = resources
228+ . role_bindings
229+ . first ( )
230+ . expect ( "a RoleBinding is built" ) ;
231+ assert_eq ! ( role_binding. metadata. labels, Some ( expected_labels) ) ;
232+ assert_eq ! ( role_binding. role_ref. name, "druid-clusterrole" ) ;
233+ }
191234}
0 commit comments