diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index e252901c9..c2304f3e1 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -6,20 +6,23 @@ All notable changes to this project will be documented in this file. ### Added +- Add `LabelExt` trait which enables adding validated labels to any Kubernetes resource ([#1106]). +- Add new associated convenience functions to `Label` ([#1106]). + - `Label::stackable_vendor`: stackable.tech/vendor=Stackable + - `Label::instance`: app.kubernetes.io/instance + - `Label::name`: app.kubernetes.io/name +- Add a `Client::create_if_missing` associated function to create a resource if it doesn't + exist ([#1099]). - BREAKING: Add new ListenerClass `.spec.pinnedNodePorts` field ([#1105]). +[#1099]: https://github.com/stackabletech/operator-rs/pull/1099 [#1105]: https://github.com/stackabletech/operator-rs/pull/1105 +[#1106]: https://github.com/stackabletech/operator-rs/pull/1106 ## [0.99.0] - 2025-10-06 ### Added -- Add `CustomResourceDefinitionMaintainer` which applies and patches CRDs triggered by TLS - certificate rotations of the `ConversionWebhookServer`. It additionally provides a `oneshot` - channel which can for example be used to trigger creation/patching of any custom resources deployed by - the operator ([#1099]). -- Add a `Client::create_if_missing` associated function to create a resource if it doesn't - exist ([#1099]). - Add CLI argument and env var to disable the end-of-support checker: `EOS_DISABLED` (`--eos-disabled`) ([#1101]). - Add end-of-support checker ([#1096], [#1103]). - The EoS checker can be constructed using `EndOfSupportChecker::new()`. @@ -40,7 +43,6 @@ All notable changes to this project will be documented in this file. [#1096]: https://github.com/stackabletech/operator-rs/pull/1096 [#1098]: https://github.com/stackabletech/operator-rs/pull/1098 -[#1099]: https://github.com/stackabletech/operator-rs/pull/1099 [#1101]: https://github.com/stackabletech/operator-rs/pull/1101 [#1103]: https://github.com/stackabletech/operator-rs/pull/1103 diff --git a/crates/stackable-operator/src/kvp/label/mod.rs b/crates/stackable-operator/src/kvp/label/mod.rs index 15411a198..2a83211fe 100644 --- a/crates/stackable-operator/src/kvp/label/mod.rs +++ b/crates/stackable-operator/src/kvp/label/mod.rs @@ -1,6 +1,6 @@ //! This module provides various types and functions to construct valid //! Kubernetes labels. Labels are key/value pairs, where the key must meet -//! certain requirementens regarding length and character set. The value can +//! certain requirements regarding length and character set. The value can //! contain a limited set of ASCII characters. //! //! Additionally, the [`Label`] struct provides various helper functions to @@ -42,6 +42,63 @@ pub type LabelsError = KeyValuePairsError; /// of labels fails. pub type LabelError = KeyValuePairError; +/// Add [`Label`]s to any Kubernetes resource. +/// +/// It should be noted, that after the addition of labels to the resource, the validity of keys and +/// values **can no longer be enforced** as they are both stored as plain [`String`]s. To update a +/// label use [`LabelExt::add_label`] which will update the label in place if it is already present. +pub trait LabelExt +where + Self: ResourceExt, +{ + /// Adds a single label to `self`. + fn add_label(&mut self, label: Label) -> &mut Self; + + /// Adds multiple labels to `self`. + fn add_labels(&mut self, label: Labels) -> &mut Self; +} + +impl LabelExt for T +where + T: ResourceExt, +{ + fn add_label(&mut self, label: Label) -> &mut Self { + let meta = self.meta_mut(); + + match &mut meta.labels { + Some(labels) => { + // TODO (@Techassi): Add an API to consume key and value + let KeyValuePair { key, value } = label.into_inner(); + labels.insert(key.to_string(), value.to_string()); + } + None => { + let mut labels = BTreeMap::new(); + + // TODO (@Techassi): Add an API to consume key and value + let KeyValuePair { key, value } = label.into_inner(); + labels.insert(key.to_string(), value.to_string()); + + meta.labels = Some(labels); + } + } + + self + } + + fn add_labels(&mut self, labels: Labels) -> &mut Self { + let meta = self.meta_mut(); + + match &mut meta.labels { + Some(existing_labels) => { + existing_labels.extend::>(labels.into()) + } + None => meta.labels = Some(labels.into()), + } + + self + } +} + /// A specialized implementation of a key/value pair representing Kubernetes /// labels. /// @@ -99,26 +156,28 @@ impl Label { self.0 } - /// Creates the `app.kubernetes.io/component` label with `role` as the - /// value. This function will return an error if `role` violates the required - /// Kubernetes restrictions. + /// Creates the `app.kubernetes.io/component` label with `role` as the value. + /// + /// This function will return an error if `role` violates the required Kubernetes restrictions. pub fn component(component: &str) -> Result { let kvp = KeyValuePair::try_from((K8S_APP_COMPONENT_KEY, component))?; Ok(Self(kvp)) } - /// Creates the `app.kubernetes.io/role-group` label with `role_group` as - /// the value. This function will return an error if `role_group` violates - /// the required Kubernetes restrictions. + /// Creates the `app.kubernetes.io/role-group` label with `role_group` as the value. + /// + /// This function will return an error if `role_group` violates the required Kubernetes + /// restrictions. pub fn role_group(role_group: &str) -> Result { let kvp = KeyValuePair::try_from((K8S_APP_ROLE_GROUP_KEY, role_group))?; Ok(Self(kvp)) } - /// Creates the `app.kubernetes.io/managed-by` label with the formated - /// full controller name based on `operator_name` and `controller_name` as - /// the value. This function will return an error if the formatted controller - /// name violates the required Kubernetes restrictions. + /// Creates the `app.kubernetes.io/managed-by` label with the formatted full controller name + /// based on `operator_name` and `controller_name` as the value. + /// + /// This function will return an error if the formatted controller name violates the required + /// Kubernetes restrictions. pub fn managed_by(operator_name: &str, controller_name: &str) -> Result { let kvp = KeyValuePair::try_from(( K8S_APP_MANAGED_BY_KEY, @@ -127,14 +186,40 @@ impl Label { Ok(Self(kvp)) } - /// Creates the `app.kubernetes.io/version` label with `version` as the - /// value. This function will return an error if `role_group` violates the - /// required Kubernetes restrictions. + /// Creates the `app.kubernetes.io/version` label with `version` as the value. + /// + /// This function will return an error if `version` violates the required Kubernetes + /// restrictions. pub fn version(version: &str) -> Result { // NOTE (Techassi): Maybe use semver::Version let kvp = KeyValuePair::try_from((K8S_APP_VERSION_KEY, version))?; Ok(Self(kvp)) } + + /// Creates the `app.kubernetes.io/instance` label with `instance` as the value. + /// + /// This function will return an error if `instance` violates the required Kubernetes + /// restrictions. + pub fn instance(instance: &str) -> Result { + let kvp = KeyValuePair::try_from((K8S_APP_INSTANCE_KEY, instance))?; + Ok(Self(kvp)) + } + + /// Creates the `app.kubernetes.io/name` label with `name` as the value. + /// + /// This function will return an error if `name` violates the required Kubernetes restrictions. + pub fn name(name: &str) -> Result { + let kvp = KeyValuePair::try_from((K8S_APP_NAME_KEY, name))?; + Ok(Self(kvp)) + } + + /// Creates the Stackable specific vendor label. + /// + /// See [`STACKABLE_VENDOR_KEY`] and [`STACKABLE_VENDOR_VALUE`]. + pub fn stackable_vendor() -> Self { + Self::try_from((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE)) + .expect("constant vendor label must be valid") + } } /// A validated set/list of Kubernetes labels. @@ -331,7 +416,7 @@ impl Labels { labels.insert(version); // Stackable-specific labels - labels.parse_insert((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE))?; + labels.insert(Label::stackable_vendor()); Ok(labels) } diff --git a/crates/stackable-webhook/CHANGELOG.md b/crates/stackable-webhook/CHANGELOG.md index 69f5261d4..a09718782 100644 --- a/crates/stackable-webhook/CHANGELOG.md +++ b/crates/stackable-webhook/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Added + +- Add `CustomResourceDefinitionMaintainer` which applies and patches CRDs triggered by TLS + certificate rotations of the `ConversionWebhookServer`. It additionally provides a `oneshot` + channel which can for example be used to trigger creation/patching of any custom resources + deployed by the operator ([#1099]). +- Add `ConversionWebhookServer::with_maintainer` which creates a conversion webhook server and a CRD + maintainer ([#1099]). + ### Changed - BREAKING: `ConversionWebhookServer::new` now returns a pair of values ([#1099]):