-
-
Notifications
You must be signed in to change notification settings - Fork 320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement cel validation derive(Validated)
macro for generated CRDs
#1649
base: main
Are you sure you want to change the base?
Implement cel validation derive(Validated)
macro for generated CRDs
#1649
Conversation
8a8cef3
to
f286169
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1649 +/- ##
=======================================
- Coverage 75.6% 75.2% -0.3%
=======================================
Files 82 83 +1
Lines 7405 7450 +45
=======================================
+ Hits 5591 5600 +9
- Misses 1814 1850 +36
|
047b626
to
06ea3e9
Compare
- Extend with supported values from docs - https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules - Implement as Validated derive macro - Use the raw Rule for the validated attribute Signed-off-by: Danil-Grigorev <[email protected]>
06ea3e9
to
ee96ec4
Compare
derive(Validated)
macro for generated CRDs
struct CELValidation { | ||
#[darling(default)] | ||
crates: Crates, | ||
data: ast::Data<util::Ignored, Rule>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: may also be useful to provide naming template for common suffix/prefix per each attribute, in case when method
is not specified. By default generated method inherits the name of the field for simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some comments and questions. i think this is a pretty cool approach.
kube-core/src/validation.rs
Outdated
|
||
/// Reason is a machine-readable value providing more detail about why a field failed the validation. | ||
/// | ||
/// More in [docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-reason) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interestingly, I see these ones in the generated docs under https://github.com/kube-rs/k8s-pb/blob/ce4261fb52266f05cd7a06dbb8f4c0fcaa41c06a/k8s-pb/src/apiextensions_apiserver/pkg/apis/apiextensions/v1/mod.rs#L736 but because of bad go enum usage it's just a doc comment :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would not pretend I saw it being generated, but yeah, missing enum is better to have :). Maybe worth adding From/Into
conversion for ensuring compatibility.
kube-core/src/validation.rs
Outdated
@@ -0,0 +1,118 @@ | |||
//! CEL validation for CRDs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should probably call the module cel.rs
kube-core/src/validation.rs
Outdated
/// Example: | ||
/// "must be a URL with the host matching spec.host" | ||
Message(String), | ||
/// Expression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these doc strings taken from anywhere?
kube/src/lib.rs
Outdated
@@ -168,6 +168,10 @@ pub use kube_derive::CustomResource; | |||
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] | |||
pub use kube_derive::Resource; | |||
|
|||
#[cfg(feature = "derive")] | |||
#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] | |||
pub use kube_derive::Validated; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
naming wise, possibly this introduces an confusion possiblities with garde::Validate
- which we do advocate for
kube-derive/src/lib.rs
Outdated
/// #[kube(group = "kube.rs", version = "v1", kind = "Struct")] | ||
/// struct MyStruct { | ||
/// #[serde(default = "default")] | ||
/// #[validated(rule = Rule{rule: "self != ''".into(), message: Some("failure message".into()), ..Default::default()})] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pretty cool that you can do a full struct here like this. does it support shorthands as well if we were to create builders on Rule
? could lead to:
#[validated(rule = Rule::rule("self != ''").message("failure message")]
possibly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is possible, including something simple such as #[validated(rule = “self != ‘’”.into())]
.
kube-core/src/validation.rs
Outdated
/// Rule is a CEL validation rule for the CRD field | ||
#[derive(Default, Serialize, Deserialize, Clone)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Rule { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note to self; this may perhaps be churned through a cel crate against an object to perform validation client side. should investigate this later.
Signed-off-by: Danil-Grigorev <[email protected]>
Motivation
Related to #1367
CRDs allow to declare server-side validation rules using CEL. This functionality is supported via
#[schemars(schema_with = "<schemagen-wrapper>")]
, but requires defining a method with handling logic, which may be error-prone.Since
kube
owns CRD generation code, the idea is to simplify this process for added validation rules and achieve more declarative approach, similar to thekubebuilder
library. This approach will be compatible withkopium
generation based on existing CRD structures, already using CEL expressions.Solution
Allow for a more native handling of CEL validation rules on the CRDs via a field macro.
Current approach only generate methods for the set of provided rules. User still have to specify
schemars(schema_with
attribute manually. Serde default also works as usual.A simple version involving
cel_validate
proc macro is also available:This PR is a followup on #1621 which addresses some of the concerns.
kube::core
and invoking fromkube::derive
.Other things tried (TLDR)
Visitor trait:
It is possible to generate a new
Visitor
implementation per each validation rule. But the problem with this approach is that the generation happens forValidator
derive on the structure, while theCustomResource
derive is responsible for populating additional visitors forcrd()
. There is no one specific method which can collect all visitors under one chain, invoked fromschemars
. This likely requires every individual field in each struct to implement theValidated
trait, involving creation of ashemars/serde
type of logic.Then the
schemars Schema
has no indicators for the source structure in the schema within Visitor, so there is no way (without generatingschemars(title = “FooSpec”)
as a metadata) to match the added visitor on the processed object param to make modifications. It is possible to addpreserve_order
feature to schemars and “search” for the property of the structure, as long as the source struct name is mapped to theSchema
content.Generating schemars attributes
While it is a viable option, such thing is not possible with
derive
macro, and has to useproc_macro
instead, This approach is additionally hiding the updates of derive attributes under the hood, which feels unintuitive, as it performs updates to the macro markers, meant to generate code. Explored in #1621Manually generating a
JsonSchema
implementation.This approach is the most powerful, but ends up being really hacky, involving copying the structure in a separate
mod
and using derivedJsonSchema
as a “starting template”.