Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c351c02
feat(stackable-versioned): Generate downgrade From impls
Techassi May 14, 2025
711aabe
feat(stackable-versioned): Generate primitive Status struct
Techassi May 14, 2025
92ed46c
feat(stackable-versioned): Emit status struct
Techassi May 15, 2025
857710f
chore: Fix clippy lints
Techassi May 16, 2025
14b09f2
fix(stackable-versioned): Emit correct code for modules
Techassi May 16, 2025
93570c6
test(stackable-versioned): Update snapshot tests
Techassi May 16, 2025
760a7bc
test(stackable-versioned): Update doc tests
Techassi May 16, 2025
3fc0b82
chore(stackable-versioned): Update changelog
Techassi May 16, 2025
bdf70e8
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 16, 2025
1048bae
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
6579eb0
chore(stackable-versioned): Remove unused dev dependencies
Techassi May 21, 2025
4f3a8d8
chore(stackable-versioned): Adjust {upgrade,downgrade}_with validation
Techassi May 21, 2025
c448ed2
chore(stackable-versioned): Improve K8s code generation
Techassi May 21, 2025
122e000
chore(stackable-versioned): Change crd_values to changed_values
Techassi May 21, 2025
0f7468b
test(stackable-versioned): Update snapshot tests
Techassi May 21, 2025
db47269
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
634b977
chore(stackable-versioned): Clean up changelog
Techassi May 21, 2025
7fd4808
test(stackable-versioned): Update 'added' snapshot test
Techassi May 21, 2025
c613404
test(stackable-versioned): Fix and comment previously untested code
Techassi May 21, 2025
ee8a0a6
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 21, 2025
8b095da
docs(stackable-versioned): Update 'convert_with' doc comment
Techassi May 22, 2025
229dcef
test(stackable-versioned): Rename convert_with test to downgrade_with
Techassi May 22, 2025
fd4d1ad
chore(stackable-versioned): Add disclaimer comment
Techassi May 22, 2025
436e294
chore: Merge branch 'main' into feat/stackable-versioned-downgrade-fr…
Techassi May 22, 2025
abf5deb
refactor(stackable-versioned): Make conversion tracking opt in
Techassi May 22, 2025
1a0fcc3
refactor(stackable-versioned): Move status struct behind feature flag
Techassi May 22, 2025
44bb861
test(stackable-versioned): Add passing Kubernetes compile tests
Techassi May 22, 2025
69ee88c
fix(stackable-versioned): Use correct module name
Techassi May 22, 2025
e38161b
chore(stackable-versioned): Update status struct name to avoid collis…
Techassi May 22, 2025
1fdc2aa
chore(stackable-versioned): Update changelog
Techassi May 22, 2025
46da1d6
test(stackable-versioned): Update status struct name
Techassi May 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions crates/stackable-versioned-macros/src/attrs/k8s.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,30 @@ use syn::Path;
/// times.
/// - `skip`: Controls skipping parts of the generation.
#[derive(Clone, Debug, FromMeta)]
pub(crate) struct KubernetesArguments {
pub(crate) group: String,
pub(crate) kind: Option<String>,
pub(crate) singular: Option<String>,
pub(crate) plural: Option<String>,
pub(crate) namespaced: Flag,
pub struct KubernetesArguments {
pub group: String,
pub kind: Option<String>,
pub singular: Option<String>,
pub plural: Option<String>,
pub namespaced: Flag,
// root
pub(crate) crates: Option<KubernetesCrateArguments>,
pub(crate) status: Option<Path>,
pub crates: Option<KubernetesCrateArguments>,
pub status: Option<Path>,
// derive
// schema
// scale
// printcolumn
#[darling(multiple, rename = "shortname")]
pub(crate) shortnames: Vec<String>,
pub shortnames: Vec<String>,
// category
// selectable
// doc
// annotation
// label
pub(crate) skip: Option<KubernetesSkipArguments>,
pub skip: Option<KubernetesSkipArguments>,

#[darling(default)]
pub options: RawKubernetesOptions,
}

/// This struct contains supported kubernetes skip arguments.
Expand All @@ -52,15 +55,15 @@ pub(crate) struct KubernetesArguments {
/// - `merged_crd` flag, which skips generating the `crd()` and `merged_crd()` functions are
/// generated.
#[derive(Clone, Debug, FromMeta)]
pub(crate) struct KubernetesSkipArguments {
pub struct KubernetesSkipArguments {
/// Whether the `crd()` and `merged_crd()` generation should be skipped for
/// this container.
pub(crate) merged_crd: Flag,
pub merged_crd: Flag,
}

/// This struct contains crate overrides to be passed to `#[kube]`.
#[derive(Clone, Debug, FromMeta)]
pub(crate) struct KubernetesCrateArguments {
pub struct KubernetesCrateArguments {
pub kube_core: Option<Path>,
pub kube_client: Option<Path>,
pub k8s_openapi: Option<Path>,
Expand All @@ -69,3 +72,8 @@ pub(crate) struct KubernetesCrateArguments {
pub serde_json: Option<Path>,
pub versioned: Option<Path>,
}

#[derive(Clone, Default, Debug, FromMeta)]
pub struct RawKubernetesOptions {
pub experimental_conversion_tracking: Flag,
}
36 changes: 22 additions & 14 deletions crates/stackable-versioned-macros/src/codegen/container/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use syn::{Attribute, Ident, ItemEnum, ItemStruct, Path, Visibility, parse_quote}
use crate::{
attrs::{
container::StandaloneContainerAttributes,
k8s::{KubernetesArguments, KubernetesCrateArguments},
k8s::{KubernetesArguments, KubernetesCrateArguments, RawKubernetesOptions},
},
codegen::{
VersionDefinition,
Expand Down Expand Up @@ -44,13 +44,9 @@ pub enum Container {

impl Container {
/// Generates the container definition for the specified `version`.
pub(crate) fn generate_definition(
&self,
version: &VersionDefinition,
multiple_versions: bool,
) -> TokenStream {
pub(crate) fn generate_definition(&self, version: &VersionDefinition) -> TokenStream {
match self {
Container::Struct(s) => s.generate_definition(version, multiple_versions),
Container::Struct(s) => s.generate_definition(version),
Container::Enum(e) => e.generate_definition(version),
}
}
Expand Down Expand Up @@ -206,12 +202,9 @@ impl StandaloneContainer {
let mut kubernetes_enum_variant_strings = Vec::new();

let mut versions = self.versions.iter().peekable();
let multiple_versions = versions.len() > 1;

while let Some(version) = versions.next() {
let container_definition = self
.container
.generate_definition(version, multiple_versions);
let container_definition = self.container.generate_definition(version);

// NOTE (@Techassi): Using '.copied()' here does not copy or clone the data, but instead
// removes one level of indirection of the double reference '&&'.
Expand Down Expand Up @@ -266,9 +259,7 @@ impl StandaloneContainer {
false,
));

if multiple_versions {
tokens.extend(self.container.generate_kubernetes_status_struct());
}
tokens.extend(self.container.generate_kubernetes_status_struct());

tokens
}
Expand Down Expand Up @@ -314,6 +305,8 @@ pub struct ContainerOptions {
pub skip_from: bool,
}

// TODO (@Techassi): Get rid of this whole mess. There should be an elegant way of using the
// attributes directly (with all defaults set and validation done).
#[derive(Debug)]
pub struct KubernetesOptions {
pub group: String,
Expand All @@ -335,6 +328,7 @@ pub struct KubernetesOptions {
// annotation
// label
pub skip_merged_crd: bool,
pub config_options: KubernetesConfigOptions,
}

impl From<KubernetesArguments> for KubernetesOptions {
Expand All @@ -351,6 +345,7 @@ impl From<KubernetesArguments> for KubernetesOptions {
status: args.status,
shortnames: args.shortnames,
skip_merged_crd: args.skip.is_some_and(|s| s.merged_crd.is_present()),
config_options: args.options.into(),
}
}
}
Expand Down Expand Up @@ -473,3 +468,16 @@ impl<T> Deref for Override<T> {
}
}
}

#[derive(Debug)]
pub struct KubernetesConfigOptions {
experimental_conversion_tracking: bool,
}

impl From<RawKubernetesOptions> for KubernetesConfigOptions {
fn from(options: RawKubernetesOptions) -> Self {
Self {
experimental_conversion_tracking: options.experimental_conversion_tracking.is_present(),
}
}
}
108 changes: 49 additions & 59 deletions crates/stackable-versioned-macros/src/codegen/container/struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,7 @@ pub struct Struct {
// Common token generation
impl Struct {
/// Generates code for the struct definition.
pub fn generate_definition(
&self,
version: &VersionDefinition,
multiple_versions: bool,
) -> TokenStream {
pub fn generate_definition(&self, version: &VersionDefinition) -> TokenStream {
let where_clause = self.generics.where_clause.as_ref();
let type_generics = &self.generics;

Expand All @@ -152,7 +148,7 @@ impl Struct {
}

// This only returns Some, if K8s features are enabled
let kube_attribute = self.generate_kube_attribute(version, multiple_versions);
let kube_attribute = self.generate_kube_attribute(version);

quote! {
#(#[doc = #version_docs])*
Expand Down Expand Up @@ -323,11 +319,7 @@ impl Struct {
// makes keeping track of interconnected parts easier.
// Kubernetes-specific token generation
impl Struct {
pub fn generate_kube_attribute(
&self,
version: &VersionDefinition,
_multiple_versions: bool,
) -> Option<TokenStream> {
pub fn generate_kube_attribute(&self, version: &VersionDefinition) -> Option<TokenStream> {
let kubernetes_options = self.common.options.kubernetes_options.as_ref()?;

// Required arguments
Expand Down Expand Up @@ -356,24 +348,16 @@ impl Struct {
.then_some(quote! { , namespaced });
let crates = kubernetes_options.crates.to_token_stream();

// TODO (@Techassi): Comment back in, once we are happy with the status struct
// let status = if multiple_versions {
// let status_ident = format_ident!(
// "{struct_ident}Status",
// struct_ident = self.common.idents.kubernetes.as_ident()
// );
// Some(quote! { , status = #status_ident })
// } else {
// kubernetes_options
// .status
// .as_ref()
// .map(|s| quote! { , status = #s })
// };

let status = kubernetes_options
.status
.as_ref()
.map(|s| quote! { , status = #s });
.config_options
.experimental_conversion_tracking
.then(|| {
let status_ident = format_ident!(
"{struct_ident}Status",
struct_ident = self.common.idents.kubernetes.as_ident()
);
quote! { , status = #status_ident }
});

let shortnames: TokenStream = kubernetes_options
.shortnames
Expand Down Expand Up @@ -473,36 +457,42 @@ impl Struct {
pub fn generate_kubernetes_status_struct(&self) -> Option<TokenStream> {
let kubernetes_options = self.common.options.kubernetes_options.as_ref()?;

let status_ident = format_ident!(
"{struct_ident}Status",
struct_ident = self.common.idents.kubernetes.as_ident()
);

let versioned_crate = &*kubernetes_options.crates.versioned;
let schemars_crate = &*kubernetes_options.crates.schemars;
let serde_crate = &*kubernetes_options.crates.serde;

let status = kubernetes_options.status.as_ref().map(|status| {
quote! {
#[serde(flatten)]
pub status: #status,
}
});

Some(quote! {
#[derive(
::core::clone::Clone,
::core::fmt::Debug,
#serde_crate::Deserialize,
#serde_crate::Serialize,
#schemars_crate::JsonSchema
)]
#[serde(rename_all = "camelCase")]
pub struct #status_ident {
pub changed_values: #versioned_crate::ChangedValues,

#status
}
})
kubernetes_options
.config_options
.experimental_conversion_tracking
.then(|| {
let status_ident = format_ident!(
"{struct_ident}Status",
struct_ident = self.common.idents.kubernetes.as_ident()
);

let versioned_crate = &*kubernetes_options.crates.versioned;
let schemars_crate = &*kubernetes_options.crates.schemars;
let serde_crate = &*kubernetes_options.crates.serde;

// TODO (@Techassi): Validate that users don't specify the status we generate
let status = kubernetes_options.status.as_ref().map(|status| {
quote! {
#[serde(flatten)]
pub status: #status,
}
});

quote! {
#[derive(
::core::clone::Clone,
::core::fmt::Debug,
#serde_crate::Deserialize,
#serde_crate::Serialize,
#schemars_crate::JsonSchema
)]
#[serde(rename_all = "camelCase")]
pub struct #status_ident {
pub changed_values: #versioned_crate::ChangedValues,

#status
}
}
})
}
}
4 changes: 1 addition & 3 deletions crates/stackable-versioned-macros/src/codegen/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@ impl Module {

let mut kubernetes_container_items: HashMap<Ident, KubernetesItems> = HashMap::new();
let mut versions = self.versions.iter().peekable();
let multiple_versions = self.versions.len() > 1;

while let Some(version) = versions.next() {
let next_version = versions.peek().copied();
Expand All @@ -159,8 +158,7 @@ impl Module {
let version_ident = &version.ident;

for container in &self.containers {
container_definitions
.extend(container.generate_definition(version, multiple_versions));
container_definitions.extend(container.generate_definition(version));

if !self.skip_from {
from_impls.extend(container.generate_upgrade_from_impl(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use kube::CustomResource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use stackable_versioned::versioned;
// ---
#[versioned(
version(name = "v1alpha1"),
version(name = "v1beta1"),
version(name = "v1"),
k8s(
group = "stackable.tech",
status = MyStatus,
options(experimental_conversion_tracking),
)
)]
// ---
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, CustomResource)]
pub(crate) struct FooSpec {
#[versioned(added(since = "v1beta1"), changed(since = "v1", from_name = "bah"))]
bar: usize,
baz: bool,
}
// ---
fn main() {}

#[derive(Clone, Debug, JsonSchema, Deserialize, Serialize)]
pub struct MyStatus {
foo: String,
}

fn usize_to_u16(input: usize) -> u16 {
input.try_into().unwrap()
}
Loading