-
-
Notifications
You must be signed in to change notification settings - Fork 322
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 derive(CELSchema)
macro for generating cel validation on CRDs
#1649
base: main
Are you sure you want to change the base?
Implement derive(CELSchema)
macro for generating cel validation on CRDs
#1649
Conversation
8a8cef3
to
f286169
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1649 +/- ##
=======================================
+ Coverage 75.8% 75.8% +0.1%
=======================================
Files 82 84 +2
Lines 7513 7613 +100
=======================================
+ Hits 5693 5770 +77
- Misses 1820 1843 +23
|
06ea3e9
to
ee96ec4
Compare
derive(Validated)
macro for generated 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.
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
/// 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.
derive(Validated)
macro for generated CRDsderive(ValidateSchema)
macro for generated CRDs
derive(ValidateSchema)
macro for generated CRDsderive(ValidateSchema)
macro for generating cel validation on 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.
I think this approach makes sense and is a nice way to opt into injecting validations. Have added some comments for code organisation (file is getting big) and for naming, but ultimately am happy with this!
- 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 <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
5026bdc
to
97a9131
Compare
Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
derive(ValidateSchema)
macro for generating cel validation on CRDsderive(CELSchema)
macro for generating cel validation on 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.
Thanks a lot for this! It's a lot more readable and understandable now as it's factored out. Very minor set of comments, and a few questions for my own sanity. Only want one doc field added.
/// group = "kube.rs", | ||
/// version = "v1", | ||
/// kind = "Struct", | ||
/// rule = Rule::new("self.matadata.name == 'singleton'"), |
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.
Minor lack: this is a new #[kube(
attribute and should have a documentation entry in lib.rs in kube-derive. Something like:
/// ## `#[kube(rule = Rule)]`
/// Inject a top level CEL validation rule for the top level generated struct.
///
/// This attribute is for resources deriving [`CELSchema`] instead of [`JsonSchema`].
ast.attrs = ast | ||
.attrs | ||
.iter() | ||
.filter(|attr| attribute_whitelist.iter().any(|i| attr.path().is_ident(i))) | ||
.cloned() | ||
.collect(); |
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.
These lines look identical to the ones in 104-109, could be its own fn with documentation as to why we are filtering out.
// Remove all unknown attributes | ||
// Has to happen on the original definition at all times, as we don't have #[derive] stanzes. | ||
let attribute_whitelist = ["serde", "schemars", "doc"]; |
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.
AFAIKT, there's no real drawbacks to this filtering because it only affects the generated, private Validated
struct that serve as a proxy for schema generation. Is my intuition right?
#[derive(#serde::Serialize, #schemars::JsonSchema)] | ||
#(#struct_attrs)* | ||
#[automatically_derived] | ||
#[allow(missing_docs)] | ||
struct Validated { | ||
#field | ||
} |
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.
So basically, this creates a validated wrapper struct for EVERY field that uses #[cel_validate]
, and these structs do not clash because they are inlined in a private scope for the purposes of using merge_properties
only?
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.
This PR is a followup on #1621 which addresses some of the concerns.
JsonSchema
, which allows further additions to the schema later,struct
level validation rules and is not affected byschemars
version.kube::core
and invoking fromkube::derive
.Other things tried (TLDR)
Visitor trait:
It is possible to generate a newVisitor
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 theschemars 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.With generating
JsonSchema
visitor extensions for more complex scenarios are possible to explore in the future.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 #1621