1
1
//! Reconciles state for ZooKeeper znodes between Kubernetes [`ZookeeperZnode`] objects and the ZooKeeper cluster
2
2
//!
3
3
//! See [`ZookeeperZnode`] for more details.
4
- use std:: { convert:: Infallible , sync:: Arc } ;
4
+ use std:: { borrow :: Cow , convert:: Infallible , sync:: Arc } ;
5
5
6
6
use snafu:: { OptionExt , ResultExt , Snafu } ;
7
7
use stackable_operator:: {
@@ -19,9 +19,11 @@ use stackable_operator::{
19
19
time:: Duration ,
20
20
} ;
21
21
use stackable_zookeeper_crd:: {
22
- security:: ZookeeperSecurity , ZookeeperCluster , ZookeeperZnode , DOCKER_IMAGE_BASE_NAME ,
22
+ security:: ZookeeperSecurity , ZookeeperCluster , ZookeeperZnode , ZookeeperZnodeStatus ,
23
+ DOCKER_IMAGE_BASE_NAME ,
23
24
} ;
24
25
use strum:: { EnumDiscriminants , IntoStaticStr } ;
26
+ use tracing:: { debug, info} ;
25
27
26
28
use crate :: {
27
29
discovery:: { self , build_discovery_configmaps} ,
@@ -92,6 +94,11 @@ pub enum Error {
92
94
cm : ObjectRef < ConfigMap > ,
93
95
} ,
94
96
97
+ #[ snafu( display( "failed to update status" ) ) ]
98
+ ApplyStatus {
99
+ source : stackable_operator:: client:: Error ,
100
+ } ,
101
+
95
102
#[ snafu( display( "error managing finalizer" ) ) ]
96
103
Finalizer {
97
104
source : finalizer:: Error < Infallible > ,
@@ -148,6 +155,7 @@ impl ReconcilerError for Error {
148
155
Error :: EnsureZnodeMissing { zk, .. } => Some ( zk. clone ( ) . erase ( ) ) ,
149
156
Error :: BuildDiscoveryConfigMap { source : _ } => None ,
150
157
Error :: ApplyDiscoveryConfigMap { cm, .. } => Some ( cm. clone ( ) . erase ( ) ) ,
158
+ Error :: ApplyStatus { .. } => None ,
151
159
Error :: Finalizer { source : _ } => None ,
152
160
Error :: DeleteOrphans { source : _ } => None ,
153
161
Error :: ObjectHasNoNamespace => None ,
@@ -174,14 +182,40 @@ pub async fn reconcile_znode(
174
182
let client = & ctx. client ;
175
183
176
184
let zk = find_zk_of_znode ( client, & znode) . await ;
177
- // Use the uid (managed by k8s itself) rather than the object name, to ensure that malicious users can't trick the controller
178
- // into letting them take over a znode owned by someone else
179
- let znode_path = format ! ( "/znode-{}" , uid) ;
185
+ let mut default_status_updates: Option < ZookeeperZnodeStatus > = None ;
186
+ // Store the znode path in the status rather than the object itself, to ensure that only K8s administrators can override it
187
+ let znode_path = match znode. status . as_ref ( ) . and_then ( |s| s. znode_path . as_deref ( ) ) {
188
+ Some ( znode_path) => {
189
+ debug ! ( znode. path = znode_path, "Using configured znode path" ) ;
190
+ Cow :: Borrowed ( znode_path)
191
+ }
192
+ None => {
193
+ // Default to the uid (managed by k8s itself) rather than the object name, to ensure that malicious users can't trick the controller
194
+ // into letting them take over a znode owned by someone else
195
+ let znode_path = format ! ( "/znode-{}" , uid) ;
196
+ info ! (
197
+ znode. path = znode_path,
198
+ "No znode path set, setting to default"
199
+ ) ;
200
+ default_status_updates
201
+ . get_or_insert_with ( Default :: default)
202
+ . znode_path = Some ( znode_path. clone ( ) ) ;
203
+ Cow :: Owned ( znode_path)
204
+ }
205
+ } ;
206
+
207
+ if let Some ( status) = default_status_updates {
208
+ info ! ( "Writing default configuration to status" ) ;
209
+ ctx. client
210
+ . merge_patch_status ( & * znode, & status)
211
+ . await
212
+ . context ( ApplyStatusSnafu ) ?;
213
+ }
180
214
181
215
finalizer (
182
216
& client. get_api :: < ZookeeperZnode > ( & ns) ,
183
217
& format ! ( "{OPERATOR_NAME}/znode" ) ,
184
- znode,
218
+ znode. clone ( ) ,
185
219
|ev| async {
186
220
match ev {
187
221
finalizer:: Event :: Apply ( znode) => {
0 commit comments