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 } ,
@@ -26,11 +27,7 @@ stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleG
2627
2728// Placeholder role-group name used for the recommended labels of the role-level `Listener`
2829// (which is not tied to a single role group).
29- stackable_operator:: constant!( pub ( crate ) PLACEHOLDER_LISTENER_ROLE_GROUP : RoleGroupName = "none" ) ;
30-
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" ) ;
30+ stackable_operator:: constant!( pub ( crate ) NONE_ROLE_GROUP_NAME : RoleGroupName = "none" ) ;
3431
3532pub mod authentication;
3633pub mod graceful_shutdown;
@@ -57,19 +54,13 @@ pub enum Error {
5754/// Builds the Kubernetes resources for the given validated cluster.
5855///
5956/// Does not need a Kubernetes client: every reference to another Kubernetes resource is already
60- /// dereferenced and validated by this point. The remaining errors are resource-assembly failures
61- /// only.
62- ///
63- /// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under
64- /// (RBAC resources are built and applied separately, in the reconcile step).
57+ /// dereferenced and validated by this point.
58+ /// The remaining errors are resource-assembly failures only.
6559///
6660/// The Router group `Listener` and the discovery `ConfigMap`s are not built here: the discovery
6761/// `ConfigMap` derives from the *applied* Router listener's ingress address, so both are built and
6862/// applied in the reconcile step instead.
69- pub fn build (
70- cluster : & ValidatedCluster ,
71- service_account_name : & str ,
72- ) -> Result < KubernetesResources , Error > {
63+ pub fn build ( cluster : & ValidatedCluster ) -> Result < KubernetesResources , Error > {
7364 let mut stateful_sets = vec ! [ ] ;
7465 let mut services = vec ! [ ] ;
7566 let mut listeners = vec ! [ ] ;
@@ -116,16 +107,11 @@ pub fn build(
116107 ) ?,
117108 ) ;
118109 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- } ) ?,
110+ build_rolegroup_statefulset ( cluster, druid_role, role_group_name, rg) . context (
111+ StatefulSetSnafu {
112+ role_group : role_group_name. clone ( ) ,
113+ } ,
114+ ) ?,
129115 ) ;
130116 }
131117 }
@@ -136,11 +122,15 @@ pub fn build(
136122 listeners,
137123 config_maps,
138124 pod_disruption_budgets,
125+ service_accounts : vec ! [ build_service_account( cluster) ] ,
126+ role_bindings : vec ! [ build_role_binding( cluster) ] ,
139127 } )
140128}
141129
142130#[ cfg( test) ]
143131mod tests {
132+ use std:: collections:: BTreeMap ;
133+
144134 use stackable_operator:: kube:: Resource ;
145135
146136 use super :: build;
@@ -161,7 +151,7 @@ mod tests {
161151 fn build_produces_expected_resource_names ( ) {
162152 let druid = druid_from_yaml ( MINIMAL_DRUID_YAML ) ;
163153 let cluster = validated_cluster ( & druid) ;
164- let resources = build ( & cluster, "simple-druid-serviceaccount" ) . expect ( "build succeeds" ) ;
154+ let resources = build ( & cluster) . expect ( "build succeeds" ) ;
165155
166156 // One StatefulSet and one ConfigMap per role group (one role group per role).
167157 let expected_role_group_names = [
@@ -188,4 +178,54 @@ mod tests {
188178 [ "simple-druid-broker" , "simple-druid-coordinator" ]
189179 ) ;
190180 }
181+
182+ /// Locks the RBAC resource names, the roleRef, and the recommended label set against
183+ /// accidental drift. The fixture's cluster name deliberately differs from the product name so
184+ /// that swapped `name`/`instance` label values cannot pass unnoticed.
185+ #[ test]
186+ fn build_produces_rbac ( ) {
187+ let druid = druid_from_yaml ( MINIMAL_DRUID_YAML ) ;
188+ let cluster = validated_cluster ( & druid) ;
189+ let resources = build ( & cluster) . expect ( "build succeeds" ) ;
190+
191+ assert_eq ! (
192+ sorted_names( & resources. service_accounts) ,
193+ [ "simple-druid-serviceaccount" ]
194+ ) ;
195+ assert_eq ! (
196+ sorted_names( & resources. role_bindings) ,
197+ [ "simple-druid-rolebinding" ]
198+ ) ;
199+
200+ let expected_labels = BTreeMap :: from (
201+ [
202+ ( "app.kubernetes.io/component" , "none" ) ,
203+ ( "app.kubernetes.io/instance" , "simple-druid" ) ,
204+ (
205+ "app.kubernetes.io/managed-by" ,
206+ "druid.stackable.tech_druidcluster" ,
207+ ) ,
208+ ( "app.kubernetes.io/name" , "druid" ) ,
209+ ( "app.kubernetes.io/role-group" , "none" ) ,
210+ ( "app.kubernetes.io/version" , "30.0.0-stackable0.0.0-dev" ) ,
211+ ( "stackable.tech/vendor" , "Stackable" ) ,
212+ ]
213+ . map ( |( key, value) | ( key. to_string ( ) , value. to_string ( ) ) ) ,
214+ ) ;
215+ let service_account = resources
216+ . service_accounts
217+ . first ( )
218+ . expect ( "a ServiceAccount is built" ) ;
219+ assert_eq ! (
220+ service_account. metadata. labels,
221+ Some ( expected_labels. clone( ) )
222+ ) ;
223+
224+ let role_binding = resources
225+ . role_bindings
226+ . first ( )
227+ . expect ( "a RoleBinding is built" ) ;
228+ assert_eq ! ( role_binding. metadata. labels, Some ( expected_labels) ) ;
229+ assert_eq ! ( role_binding. role_ref. name, "druid-clusterrole" ) ;
230+ }
191231}
0 commit comments