Skip to content

Commit 57011ab

Browse files
committed
chore(stackable-versioned): Move code, add error handling
1 parent de8db62 commit 57011ab

File tree

4 files changed

+201
-265
lines changed

4 files changed

+201
-265
lines changed

crates/stackable-versioned-macros/src/codegen/container/struct/k8s.rs

Lines changed: 176 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,7 @@ impl Struct {
304304
) -> Option<TokenStream> {
305305
let kubernetes_arguments = self.common.options.kubernetes_arguments.as_ref()?;
306306

307-
let variant_data_ident = &self.common.idents.kubernetes_parameter;
308307
let struct_ident = &self.common.idents.kubernetes;
309-
let spec_ident = &self.common.idents.original;
310308

311309
let kube_client_path = &*kubernetes_arguments.crates.kube_client;
312310
let serde_json_path = &*kubernetes_arguments.crates.serde_json;
@@ -316,93 +314,17 @@ impl Struct {
316314
let convert_object_error = quote! { #versioned_path::ConvertObjectError };
317315

318316
// Generate conversion paths and the match arms for these paths
319-
let conversion_chain = conversion_path(versions);
320-
let match_arms: Vec<_> = conversion_chain
321-
.iter()
322-
.map(|(start, path)| {
323-
let current_object_version_ident = &start.idents.variant;
324-
let current_object_version_string = &start.inner.to_string();
325-
326-
let desired_object_version = path.last().expect("the path always contains at least one element");
327-
let desired_object_version_string = desired_object_version.inner.to_string();
328-
let desired_object_variant_ident = &desired_object_version.idents.variant;
329-
let desired_object_module_ident = &desired_object_version.idents.module;
330-
331-
let conversions = path.iter().enumerate().map(|(i, v)| {
332-
let module_ident = &v.idents.module;
333-
334-
if i == 0 {
335-
quote! {
336-
let converted: #module_ident::#spec_ident = #variant_data_ident.spec.into();
337-
}
338-
} else {
339-
quote! {
340-
let converted: #module_ident::#spec_ident = converted.into();
341-
}
342-
}
343-
});
344-
345-
let kind = self.common.idents.kubernetes.to_string();
346-
let steps = path.len();
347-
348-
let convert_object_trace = kubernetes_arguments.options.enable_tracing.is_present().then(|| quote! {
349-
::tracing::trace!(
350-
k8s.crd.conversion.api_version = #current_object_version_string,
351-
k8s.crd.conversion.desired_api_version = #desired_object_version_string,
352-
k8s.crd.conversion.steps = #steps,
353-
k8s.crd.kind = #kind,
354-
"Successfully converted object"
355-
);
356-
});
357-
358-
quote! {
359-
(Self::#current_object_version_ident(#variant_data_ident), #desired_object_version_string) => {
360-
#(#conversions)*
361-
362-
let desired_object = Self::#desired_object_variant_ident(#desired_object_module_ident::#struct_ident {
363-
metadata: #variant_data_ident.metadata,
364-
spec: converted,
365-
});
366-
367-
let desired_object = desired_object.into_json_value()
368-
.map_err(|source| #convert_object_error::Serialize { source })?;
369-
370-
#convert_object_trace
371-
372-
converted_objects.push(desired_object);
373-
}
374-
}
375-
})
376-
.collect();
377-
378-
// Generate tracing attribute of tracing is enabled
379-
let (try_convert_instrumentation, convert_objects_instrumentation) = kubernetes_arguments
380-
.options
381-
.enable_tracing
382-
.is_present()
383-
.then(|| {
384-
// TODO (@Techassi): Make tracing path configurable. Currently not possible, needs
385-
// upstream change
386-
let try_convert_instrumentation = quote! {
387-
#[::tracing::instrument(
388-
skip_all,
389-
fields(
390-
k8s.crd.conversion.kind = review.types.kind,
391-
k8s.crd.conversion.api_version = review.types.api_version,
392-
)
393-
)]
394-
};
395-
396-
let convert_objects_instrumentation = quote! {
397-
#[::tracing::instrument(
398-
skip_all,
399-
err
400-
)]
401-
};
402-
403-
(try_convert_instrumentation, convert_objects_instrumentation)
404-
})
405-
.unzip();
317+
let match_arms =
318+
self.generate_kubernetes_conversion_match_arms(versions, kubernetes_arguments);
319+
320+
// TODO (@Techassi): Make this a feature, drop the option from the macro arguments
321+
// Generate tracing attributes and events if tracing is enabled
322+
let TracingTokens {
323+
successful_conversion_response_event,
324+
convert_objects_instrumentation,
325+
invalid_conversion_review_event,
326+
try_convert_instrumentation,
327+
} = self.generate_kubernetes_conversion_tracing(kubernetes_arguments);
406328

407329
// Generate doc comments
408330
let conversion_review_reference =
@@ -426,15 +348,31 @@ impl Struct {
426348
-> #kube_core_path::conversion::ConversionReview
427349
{
428350
// First, turn the review into a conversion request
429-
// TODO (@Techassi): Handle this error and return status Invalid
430-
let request = #kube_core_path::conversion::ConversionRequest::from_review(review).unwrap();
351+
let request = match #kube_core_path::conversion::ConversionRequest::from_review(review) {
352+
::std::result::Result::Ok(request) => request,
353+
::std::result::Result::Err(err) => {
354+
#invalid_conversion_review_event
355+
356+
return #kube_core_path::conversion::ConversionResponse::invalid(
357+
#kube_client_path::Status {
358+
status: Some(#kube_core_path::response::StatusSummary::Failure),
359+
message: err.to_string(),
360+
reason: err.to_string(),
361+
details: None,
362+
code: 400,
363+
}
364+
).into_review()
365+
}
366+
};
431367

432368
// Extract the desired api version
433369
let desired_api_version = request.desired_api_version.as_str();
434370

435371
// Convert all objects into the desired version
436372
let response = match Self::convert_objects(request.objects, desired_api_version) {
437373
::std::result::Result::Ok(converted_objects) => {
374+
#successful_conversion_response_event
375+
438376
// We construct the response from the ground up as the helper functions
439377
// don't provide any benefit over manually doing it. Constructing a
440378
// ConversionResponse via for_request is not possible due to a partial move
@@ -449,7 +387,23 @@ impl Struct {
449387
converted_objects,
450388
}
451389
},
452-
::std::result::Result::Err(_) => todo!(),
390+
::std::result::Result::Err(err) => {
391+
let code = err.http_status_code();
392+
let message = err.join_errors();
393+
394+
#kube_core_path::conversion::ConversionResponse {
395+
result: #kube_client_path::Status {
396+
status: Some(#kube_core_path::response::StatusSummary::Failure),
397+
message: message.clone(),
398+
reason: message,
399+
details: None,
400+
code,
401+
},
402+
types: request.types,
403+
uid: request.uid,
404+
converted_objects: vec![],
405+
}
406+
},
453407
};
454408

455409
response.into_review()
@@ -478,14 +432,142 @@ impl Struct {
478432
// apiserver should never send such a conversion review.
479433
_ => converted_objects.push(object),
480434
}
481-
482-
483435
}
484436

485437
::std::result::Result::Ok(converted_objects)
486438
}
487439
})
488440
}
441+
442+
fn generate_kubernetes_conversion_match_arms(
443+
&self,
444+
versions: &[VersionDefinition],
445+
kubernetes_arguments: &KubernetesArguments,
446+
) -> Vec<TokenStream> {
447+
let variant_data_ident = &self.common.idents.kubernetes_parameter;
448+
let struct_ident = &self.common.idents.kubernetes;
449+
let spec_ident = &self.common.idents.original;
450+
451+
let versioned_path = &*kubernetes_arguments.crates.versioned;
452+
let convert_object_error = quote! { #versioned_path::ConvertObjectError };
453+
454+
let conversion_chain = conversion_path(versions);
455+
456+
conversion_chain
457+
.iter()
458+
.map(|(start, path)| {
459+
let current_object_version_ident = &start.idents.variant;
460+
let current_object_version_string = &start.inner.to_string();
461+
462+
let desired_object_version = path.last().expect("the path always contains at least one element");
463+
let desired_object_version_string = desired_object_version.inner.to_string();
464+
let desired_object_variant_ident = &desired_object_version.idents.variant;
465+
let desired_object_module_ident = &desired_object_version.idents.module;
466+
467+
let conversions = path.iter().enumerate().map(|(i, v)| {
468+
let module_ident = &v.idents.module;
469+
470+
if i == 0 {
471+
quote! {
472+
let converted: #module_ident::#spec_ident = #variant_data_ident.spec.into();
473+
}
474+
} else {
475+
quote! {
476+
let converted: #module_ident::#spec_ident = converted.into();
477+
}
478+
}
479+
});
480+
481+
let kind = self.common.idents.kubernetes.to_string();
482+
let steps = path.len();
483+
484+
let convert_object_trace = kubernetes_arguments.options.enable_tracing.is_present().then(|| quote! {
485+
::tracing::trace!(
486+
k8s.crd.conversion.desired_api_version = #desired_object_version_string,
487+
k8s.crd.conversion.api_version = #current_object_version_string,
488+
k8s.crd.conversion.steps = #steps,
489+
k8s.crd.kind = #kind,
490+
"Successfully converted object"
491+
);
492+
});
493+
494+
quote! {
495+
(Self::#current_object_version_ident(#variant_data_ident), #desired_object_version_string) => {
496+
#(#conversions)*
497+
498+
let desired_object = Self::#desired_object_variant_ident(#desired_object_module_ident::#struct_ident {
499+
metadata: #variant_data_ident.metadata,
500+
spec: converted,
501+
});
502+
503+
let desired_object = desired_object.into_json_value()
504+
.map_err(|source| #convert_object_error::Serialize { source })?;
505+
506+
#convert_object_trace
507+
508+
converted_objects.push(desired_object);
509+
}
510+
}
511+
})
512+
.collect()
513+
}
514+
515+
fn generate_kubernetes_conversion_tracing(
516+
&self,
517+
kubernetes_arguments: &KubernetesArguments,
518+
) -> TracingTokens {
519+
if kubernetes_arguments.options.enable_tracing.is_present() {
520+
// TODO (@Techassi): Make tracing path configurable. Currently not possible, needs
521+
// upstream change
522+
let kind = self.common.idents.kubernetes.to_string();
523+
524+
let successful_conversion_response_event = Some(quote! {
525+
::tracing::debug!(
526+
k8s.crd.conversion.converted_object_count = converted_objects.len(),
527+
k8s.crd.kind = #kind,
528+
"Successfully converted objects"
529+
);
530+
});
531+
532+
let convert_objects_instrumentation = Some(quote! {
533+
#[::tracing::instrument(
534+
skip_all,
535+
err
536+
)]
537+
});
538+
539+
let invalid_conversion_review_event = Some(quote! {
540+
::tracing::warn!(?err, "received invalid conversion review");
541+
});
542+
543+
let try_convert_instrumentation = Some(quote! {
544+
#[::tracing::instrument(
545+
skip_all,
546+
fields(
547+
k8s.crd.conversion.api_version = review.types.api_version,
548+
k8s.crd.kind = review.types.kind,
549+
)
550+
)]
551+
});
552+
553+
TracingTokens {
554+
successful_conversion_response_event,
555+
convert_objects_instrumentation,
556+
invalid_conversion_review_event,
557+
try_convert_instrumentation,
558+
}
559+
} else {
560+
TracingTokens::default()
561+
}
562+
}
563+
}
564+
565+
#[derive(Debug, Default)]
566+
struct TracingTokens {
567+
successful_conversion_response_event: Option<TokenStream>,
568+
convert_objects_instrumentation: Option<TokenStream>,
569+
invalid_conversion_review_event: Option<TokenStream>,
570+
try_convert_instrumentation: Option<TokenStream>,
489571
}
490572

491573
fn conversion_path<'a, T>(elements: &'a [T]) -> Vec<(&'a T, Cow<'a, [T]>)>

0 commit comments

Comments
 (0)