11pub mod resource;
22
33use snafu:: { ResultExt , Snafu } ;
4- use stackable_operator:: k8s_openapi:: api:: { core:: v1:: ServiceAccount , rbac:: v1:: RoleBinding } ;
54
65use crate :: {
76 crd:: constants:: HISTORY_ROLE_NAME ,
@@ -11,6 +10,7 @@ use crate::{
1110 config_map:: { self , build_config_map} ,
1211 listener:: build_group_listener,
1312 pdb:: build_pdb,
13+ rbac:: { build_role_binding, build_service_account} ,
1414 service:: build_rolegroup_metrics_service,
1515 statefulset:: { self , build_stateful_set} ,
1616 } ,
@@ -30,11 +30,7 @@ pub enum Error {
3030type Result < T , E = Error > = std:: result:: Result < T , E > ;
3131
3232/// Builds every Kubernetes resource for the given validated SparkHistoryServer.
33- pub fn build (
34- validated : & ValidatedSparkHistoryServer ,
35- service_account : ServiceAccount ,
36- role_binding : RoleBinding ,
37- ) -> Result < SparkHistoryResources > {
33+ pub fn build ( validated : & ValidatedSparkHistoryServer ) -> Result < SparkHistoryResources > {
3834 let log_dir = & validated. cluster_config . log_dir ;
3935
4036 let mut config_maps = vec ! [ ] ;
@@ -46,7 +42,7 @@ pub fn build(
4642 . push ( build_config_map ( validated, role_group_name, rg) . context ( BuildConfigMapSnafu ) ?) ;
4743 metrics_services. push ( build_rolegroup_metrics_service ( validated, role_group_name) ) ;
4844 stateful_sets. push (
49- build_stateful_set ( validated, role_group_name, rg, log_dir, & service_account )
45+ build_stateful_set ( validated, role_group_name, rg, log_dir)
5046 . context ( BuildStatefulSetSnafu ) ?,
5147 ) ;
5248 }
@@ -60,12 +56,112 @@ pub fn build(
6056 let pod_disruption_budget = build_pdb ( & validated. role_config . pdb , validated) ;
6157
6258 Ok ( SparkHistoryResources {
63- service_account,
64- role_binding,
59+ service_account : build_service_account ( validated ) ,
60+ role_binding : build_role_binding ( validated ) ,
6561 config_maps,
6662 metrics_services,
6763 stateful_sets,
6864 listener,
6965 pod_disruption_budget,
7066 } )
7167}
68+
69+ #[ cfg( test) ]
70+ mod tests {
71+ use std:: collections:: BTreeMap ;
72+
73+ use indoc:: indoc;
74+ use stackable_operator:: { cli:: OperatorEnvironmentOptions , utils:: yaml_from_str_singleton_map} ;
75+
76+ use super :: build;
77+ use crate :: {
78+ crd:: { history:: v1alpha1, logdir:: ResolvedLogDir } ,
79+ history:: controller:: {
80+ dereference:: DereferencedSparkHistoryServer ,
81+ validate:: { ValidatedSparkHistoryServer , validate} ,
82+ } ,
83+ } ;
84+
85+ /// Minimal custom-log-dir `SparkHistoryServer` fixture. The custom log directory keeps the
86+ /// dereference step client-free; the `uid` allows owner references to be derived from it.
87+ const HISTORY_YAML : & str = indoc ! { r#"
88+ apiVersion: spark.stackable.tech/v1alpha1
89+ kind: SparkHistoryServer
90+ metadata:
91+ name: my-history
92+ namespace: default
93+ uid: 12345678-1234-1234-1234-123456789012
94+ spec:
95+ image:
96+ productVersion: 3.5.8
97+ logFileDirectory:
98+ customLogDirectory: file:///stackable/spark/logs
99+ nodes:
100+ roleGroups:
101+ default:
102+ replicas: 1
103+ "# } ;
104+
105+ /// Runs the real validate step against the minimal fixture.
106+ fn minimal_validated_cluster ( ) -> ValidatedSparkHistoryServer {
107+ let shs: v1alpha1:: SparkHistoryServer = yaml_from_str_singleton_map ( HISTORY_YAML )
108+ . expect ( "invalid test SparkHistoryServer YAML" ) ;
109+ validate (
110+ & shs,
111+ DereferencedSparkHistoryServer {
112+ log_dir : ResolvedLogDir :: Custom ( "file:///stackable/spark/logs" . to_string ( ) ) ,
113+ } ,
114+ & OperatorEnvironmentOptions {
115+ operator_namespace : "stackable-operators" . to_string ( ) ,
116+ operator_service_name : "spark-k8s-operator" . to_string ( ) ,
117+ image_repository : "oci.example.org/sdp" . to_string ( ) ,
118+ } ,
119+ )
120+ . expect ( "validate should succeed for the test fixture" )
121+ }
122+
123+ /// Locks the RBAC resource names, the roleRef, and the recommended label set against
124+ /// accidental drift. The fixture's cluster name deliberately differs from the product name so
125+ /// that swapped `name`/`instance` label values cannot pass unnoticed.
126+ #[ test]
127+ fn build_produces_rbac ( ) {
128+ let resources = build ( & minimal_validated_cluster ( ) ) . expect ( "build succeeds" ) ;
129+
130+ assert_eq ! (
131+ resources. service_account. metadata. name. as_deref( ) ,
132+ Some ( "my-history-serviceaccount" )
133+ ) ;
134+ assert_eq ! (
135+ resources. role_binding. metadata. name. as_deref( ) ,
136+ Some ( "my-history-rolebinding" )
137+ ) ;
138+
139+ let expected_labels = BTreeMap :: from (
140+ [
141+ ( "app.kubernetes.io/component" , "none" ) ,
142+ ( "app.kubernetes.io/instance" , "my-history" ) ,
143+ (
144+ "app.kubernetes.io/managed-by" ,
145+ "spark.stackable.tech_history" ,
146+ ) ,
147+ ( "app.kubernetes.io/name" , "spark-history" ) ,
148+ ( "app.kubernetes.io/role-group" , "none" ) ,
149+ ( "app.kubernetes.io/version" , "3.5.8-stackable0.0.0-dev" ) ,
150+ ( "stackable.tech/vendor" , "Stackable" ) ,
151+ ]
152+ . map ( |( key, value) | ( key. to_string ( ) , value. to_string ( ) ) ) ,
153+ ) ;
154+ assert_eq ! (
155+ resources. service_account. metadata. labels,
156+ Some ( expected_labels. clone( ) )
157+ ) ;
158+ assert_eq ! (
159+ resources. role_binding. metadata. labels,
160+ Some ( expected_labels)
161+ ) ;
162+ assert_eq ! (
163+ resources. role_binding. role_ref. name,
164+ "spark-history-clusterrole"
165+ ) ;
166+ }
167+ }
0 commit comments