-
Notifications
You must be signed in to change notification settings - Fork 0
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
tracking: CRD versioning macro #507
Comments
related: this ticket (#504) tracks a bunch of breaking changes that we want to do when we have CRD versioning in place |
Love the idea expressed in the description!!! Just one question, will there be a way to express transformation logic? v1 has a field |
I have some ideas for that in mind indeed. I will add a few more details on that! But I agree, this will probably be added at a later point in time. Also see the second point in the Questions / Ideas section.
Ideally we want to use Duration which supports human-readable duration strings. When using this type, fields are just named |
I updated the ticket and added "Idea 2" which dives into the customization of the conversion process. |
Disclaimer: Just to write it down somewhere, no need to respond, we can discuss next week. How do we want to handle actual code that is currently in the crd module, haven't gone and checked, but I think there is a not insignificant amount of fn's in the crd modules that is needed for the operator to do its work. Will the operators internally always work with the latest version and all actual code is moved to that module any time a new version is added? |
This is a very thorough overview - great work! Two things occurred to me:
will we plan to move new optional fields out into being non-optional in a subsequent update, so that
|
Unfortunately yes, i think we have to traitify the structs and each version of the struct will implement the trait in its own module or something.
I think the operators will work with the latest version, just keeping the old stuff for crd generation i guess.
Yeah everything that appears in the CRD must be versioned somehow. Not a big deal for standalone CRDs like AuthClass with their own version etc. but e.g. a change in the Role struct will result in a CRD version bump of the implementing operators. |
If the operator always gets the latest version, couldn't we.in theory |
Actually yeah, we will only need the "implementation" for the version (whatever it will be) the operator is requesting. |
Hi, I read a lot of details, HOW CRD Versioning can be implemented, but nod description WHAT value the feature delivers. |
Just regarding the modules being the "version", and containing the originally named structs inside it, eg: pub mod v1alpha1 {
pub struct MyCrd {
bar: usize,
baz: bool,
gau: usize,
}
} I think that should be inverted, like: pub mod my_crd {
pub struct V1Alpha1 {
bar: usize,
baz: bool,
gau: usize,
}
} I can't think of good wording, but here it is raw:
Related comment: stackabletech/operator-rs#793 (comment) |
Results of a first (and quick) test round:
Fixes for these issues are tracked in the tasklists above. |
Closing this tracking issue as the initial implementation is finished. Tracking of CRD versioning moved to #642. |
This tracks the implementation of a first working version of the
#[versioned]
macro. Further improvements will be done as part of our normal development workflow as well as alongside #642.General
Currently, all our CRDs are defined by Rust structs. This works really well for multiple reasons:
This strict type safety ensures we build code which will never crash during runtime due to invalid types. The future introduction of CRD versions warrants the same strict typing for different version of the CRD. Additionally, we want to be able to upgrade (and downgrade) between versions in a controlled and automated manner. Rust already provides traits for conversion of types:
From<T>
,TryFrom<T>
and friends. A goal of this implementation should be support for this native mechanism.Requirements
Vision
Initial Sketch
Adding the Derive Macro
The derive macro uses a simple and fitting name. A few examples are:
Versioned
,Version
andUpdate
. The macro additionally uses an attribute to customize the code generation. The name of that attribute will be the same as the derive macro name, just all lowercase letters.The macro provides a mechanism to provide traits which should be derived on the generated structs/enums. Attributes like
#[serde]
or#[schemars]
will be passed through as well.Adding Versions
Each distinct version of a struct/enum needs to be declared via the accommodating attribute. The
version
field can be used as often as one would like. Each definition creates a new versioned version of the base struct/enum. Each version name must be unique, enforced by the macro and surfaced with easy to understand error message.(Optional) Because we plan to publish this crate publicly for the Rust community, we don't mandate the use of Kubernetes versions as version names. Instead, the validation should be as generic as possible to allow usages outside of the context of Kubernetes. Initially we might want to only support SemVer and Kubernetes version formats. Each version must provide an implementation for
PartialEq
andPartialOrd
.(Optional) Users can customize the visibility of the generated structs.
Each added version will generate a new struct or enum specific for that version. A basic example without any fields/variants (and semantics around them) could look like this:
The naming scheme uses Rust modules. Different version of the struct would then be separated by modules, like
v1alpha1::MyCrd
andv1beta1::MyCrd
. Additionally, the macro provides stable modules, which for example always point to the latest version of the struct/enum.Deprecation of Versions
CRDs can mark particular versions as deprecated. This can be achieved by providing the
deprecated
flag. Versioned structs will additionally include Rust's#[deprecated]
attribute.The resulting generated code would look similar to this:
Field Attributes
Each field can be attached with additional (but optional) attributes. These attributes indicate when the fields were added, renamed, deprecated and removed. Any field which doesn't have a attribute attached is presumed to exist since the earliest version (version
v1alpha1
in the example below). This feature is one reason why versions need to implementPartialEq
andPartialOrd
. Any attribute field cannot be combined with any other field if the same version is used.Add Fields
This attribute marks fields as added in a specific version. This field will not appear in generated structs of earlier versions.
The resulting generated code would look similar to this:
Adding required fields in CRDs is against the Kubernetes guidelines. Instead, CRD developers should use optional fields with a sane default value. There will be multiple pieces of Rust code to make this work:
Default
.#[versioned(added(default = default_fn))]
.This value will be used when using the
From<T>
trait for conversion between versions.The
From<Old> for New
implementation will look similar to this:Without Custom Default Value
With Custom Default Value
Rename Fields
This attribute marks fields as renamed in a specific version. This field will use the original name up to the version it was renamed in. The
From<T>
trait implementation will move the value from the old field to the new field.The resulting generated code would look similar to this:
The
From<Old> for New
implementation will look similar to this:Deprecate Fields
This attribute marks fields as deprecated in a specific version. Each deprecated field will be included in later versions but renamed to
deprecated_<FIELD_NAME>
. They will additionally include Rust's#[deprecated]
attribute to indicate the deprecation in the Rust code. Conversions from earlier versions will move the value of the field to the deprecated field automatically.The resulting generated code would look similar to this:
The
From<Old> for New
implementation will look similar to this:Full Example
Kubernetes API Version Crate
Initial Sketch
We want to parse raw Kubernetes (API) version strings into Rust structs. This enables us to more easily work with these versions. It enables sorting, equality checking and ordering. It additionally enables us to provide better error messages in case of invalid versioning.
A PoC implementation for this already exists.
This is implemented. See https://github.com/stackabletech/operator-rs/tree/main/crates/k8s-version
Open Questions / Ideas
pub(crate)
.This can be done by implementing the
From<Old> for New
manually instead of using the automatically derived one.Users can opt out of the auto-generation by providing the
#[versioned(version(name = "v1beta1", skip("from")))]
References
Selection of Crates
convert_case
proc-macro2
darling
quote
syn
Research
Tasks
Implementation
Tasks
stackable-versioned
andk8s-version
crates for CRD versioning operator-rs#764Test Rounds
2024-09-06
Fixes
2024-09-18
Fixes
Acceptance criteria:
The text was updated successfully, but these errors were encountered: