@@ -19,7 +19,7 @@ use kube::{
1919} ;
2020use snafu:: { OptionExt , ResultExt , Snafu } ;
2121use stackable_operator:: cli:: OperatorEnvironmentOpts ;
22- use tokio:: sync:: mpsc;
22+ use tokio:: { sync:: mpsc, try_join } ;
2323use tracing:: instrument;
2424use x509_cert:: {
2525 Certificate ,
@@ -44,6 +44,12 @@ pub enum ConversionWebhookError {
4444 #[ snafu( display( "failed to convert CA certificate into PEM format" ) ) ]
4545 ConvertCaToPem { source : x509_cert:: der:: Error } ,
4646
47+ #[ snafu( display( "failed to reconcile CRDs" ) ) ]
48+ ReconcileCRDs {
49+ #[ snafu( source( from( ConversionWebhookError , Box :: new) ) ) ]
50+ source : Box < ConversionWebhookError > ,
51+ } ,
52+
4753 #[ snafu( display( "failed to update CRD {crd_name:?}" ) ) ]
4854 UpdateCRD {
4955 source : stackable_operator:: kube:: Error ,
6571/// See [`ConversionWebhookServer::new()`] for usage examples.
6672pub struct ConversionWebhookServer {
6773 server : WebhookServer ,
68- current_cert : Certificate ,
69-
74+ cert_rx : mpsc:: Receiver < Certificate > ,
7075 client : Client ,
7176 field_manager : String ,
7277 crds : HashMap < String , CustomResourceDefinition > ,
@@ -144,6 +149,7 @@ impl ConversionWebhookServer {
144149 H : WebhookHandler < ConversionReview , ConversionReview > + Clone + Send + Sync + ' static ,
145150 {
146151 tracing:: debug!( "create new conversion webhook server" ) ;
152+ let field_manager: String = field_manager. into ( ) ;
147153
148154 let mut router = Router :: new ( ) ;
149155 let mut crds = HashMap :: new ( ) ;
@@ -160,52 +166,111 @@ impl ConversionWebhookServer {
160166
161167 // This is how Kubernetes calls us, so it decides about the naming.
162168 // AFAIK we can not influence this, so this is the only SAN entry needed.
163- let webhook_domain_name = format ! (
169+ let sans = vec ! [ format!(
164170 "{service_name}.{operator_namespace}.svc" ,
165171 service_name = operator_environment. operator_service_name,
166172 operator_namespace = operator_environment. operator_namespace,
167- ) ;
173+ ) ] ;
168174
169- let ( cert_tx, mut cert_rx) = mpsc:: channel ( 1 ) ;
170- let server = WebhookServer :: new ( router, options, vec ! [ webhook_domain_name] , cert_tx)
175+ let ( server, mut cert_rx) = WebhookServer :: new ( router, options, sans)
171176 . await
172177 . context ( CreateWebhookServerSnafu ) ?;
178+
179+ // We block the ConversionWebhookServer creation until the certificates have been generated.
180+ // This way we
181+ // 1. Are able to apply the CRDs before we start the actual controllers relying on them
182+ // 2. Avoid updating them shortly after as cert have been generated. Doing so would cause
183+ // unnecessary "too old resource version" errors in the controllers as the CRD was updated.
173184 let current_cert = cert_rx
174185 . recv ( )
175186 . await
176187 . context ( ReceiverCertificateFromChannelSnafu ) ?;
188+ Self :: reconcile_crds (
189+ & client,
190+ & field_manager,
191+ & crds,
192+ & operator_environment,
193+ & current_cert,
194+ )
195+ . await
196+ . context ( ReconcileCRDsSnafu ) ?;
177197
178198 Ok ( Self {
179199 server,
180- current_cert ,
200+ cert_rx ,
181201 client,
182- field_manager : field_manager . into ( ) ,
202+ field_manager,
183203 crds,
184204 operator_environment,
185205 } )
186206 }
187207
188- /// Starts the conversion webhook server
189- ///
190- /// Use [`Self::reconcile_crds`] first to avoid "too old resource version" error
191208 pub async fn run ( self ) -> Result < ( ) , ConversionWebhookError > {
192209 tracing:: info!( "starting conversion webhook server" ) ;
193210
194- self . server . run ( ) . await . context ( RunWebhookServerSnafu ) ?;
211+ let Self {
212+ server,
213+ cert_rx,
214+ client,
215+ field_manager,
216+ crds,
217+ operator_environment,
218+ } = self ;
195219
220+ try_join ! (
221+ Self :: run_webhook_server( server) ,
222+ Self :: run_cert_update_loop(
223+ cert_rx,
224+ & client,
225+ & field_manager,
226+ & crds,
227+ & operator_environment
228+ ) ,
229+ ) ?;
230+
231+ Ok ( ( ) )
232+ }
233+
234+ async fn run_webhook_server ( server : WebhookServer ) -> Result < ( ) , ConversionWebhookError > {
235+ server. run ( ) . await . context ( RunWebhookServerSnafu )
236+ }
237+
238+ async fn run_cert_update_loop (
239+ mut cert_rx : mpsc:: Receiver < Certificate > ,
240+ client : & Client ,
241+ field_manager : & str ,
242+ crds : & HashMap < String , CustomResourceDefinition > ,
243+ operator_environment : & OperatorEnvironmentOpts ,
244+ ) -> Result < ( ) , ConversionWebhookError > {
245+ while let Some ( current_cert) = cert_rx. recv ( ) . await {
246+ Self :: reconcile_crds (
247+ client,
248+ field_manager,
249+ crds,
250+ operator_environment,
251+ & current_cert,
252+ )
253+ . await
254+ . context ( ReconcileCRDsSnafu ) ?;
255+ }
196256 Ok ( ( ) )
197257 }
198258
199259 #[ instrument( skip_all) ]
200- pub async fn reconcile_crds ( & self ) -> Result < ( ) , ConversionWebhookError > {
201- tracing:: info!( kinds = ?self . crds. keys( ) , "Reconciling CRDs" ) ;
202- let ca_bundle = self
203- . current_cert
260+ async fn reconcile_crds (
261+ client : & Client ,
262+ field_manager : & str ,
263+ crds : & HashMap < String , CustomResourceDefinition > ,
264+ operator_environment : & OperatorEnvironmentOpts ,
265+ current_cert : & Certificate ,
266+ ) -> Result < ( ) , ConversionWebhookError > {
267+ tracing:: info!( kinds = ?crds. keys( ) , "Reconciling CRDs" ) ;
268+ let ca_bundle = current_cert
204269 . to_pem ( LineEnding :: LF )
205270 . context ( ConvertCaToPemSnafu ) ?;
206271
207- let crd_api: Api < CustomResourceDefinition > = Api :: all ( self . client . clone ( ) ) ;
208- for ( kind, crd) in & self . crds {
272+ let crd_api: Api < CustomResourceDefinition > = Api :: all ( client. clone ( ) ) ;
273+ for ( kind, crd) in crds {
209274 let mut crd = crd. clone ( ) ;
210275
211276 crd. spec . conversion = Some ( CustomResourceConversion {
@@ -217,8 +282,8 @@ impl ConversionWebhookServer {
217282 conversion_review_versions : vec ! [ "v1" . to_string( ) ] ,
218283 client_config : Some ( WebhookClientConfig {
219284 service : Some ( ServiceReference {
220- name : self . operator_environment . operator_service_name . clone ( ) ,
221- namespace : self . operator_environment . operator_namespace . clone ( ) ,
285+ name : operator_environment. operator_service_name . clone ( ) ,
286+ namespace : operator_environment. operator_namespace . clone ( ) ,
222287 path : Some ( format ! ( "/convert/{kind}" ) ) ,
223288 port : Some (
224289 DEFAULT_HTTPS_PORT
@@ -235,14 +300,14 @@ impl ConversionWebhookServer {
235300 // TODO: Move this into function and do a more clever update mechanism
236301 let crd_name = crd. name_any ( ) ;
237302 let patch = Patch :: Apply ( & crd) ;
238- let patch_params = PatchParams :: apply ( & self . field_manager ) ;
303+ let patch_params = PatchParams :: apply ( field_manager) ;
239304 crd_api
240305 . patch ( & crd_name, & patch_params, & patch)
241306 . await
242307 . with_context ( |_| UpdateCRDSnafu {
243308 crd_name : crd_name. to_string ( ) ,
244309 } ) ?;
245- tracing:: info!( crd_name, "Reconciled CRDs " ) ;
310+ tracing:: info!( crd . name = crd_name, "Reconciled CRD " ) ;
246311 }
247312 Ok ( ( ) )
248313 }
0 commit comments