From dfd4e21c82177795ae6dc985ca6864b42a1e1747 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 9 Oct 2025 09:35:43 +0200 Subject: [PATCH 1/4] feat(operator): Add LabelExt trait This trait enables adding one or multiple labels to any Kubernetes resource (through kube's ResourceExt trait). --- .../stackable-operator/src/kvp/label/mod.rs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/crates/stackable-operator/src/kvp/label/mod.rs b/crates/stackable-operator/src/kvp/label/mod.rs index 15411a198..3948be637 100644 --- a/crates/stackable-operator/src/kvp/label/mod.rs +++ b/crates/stackable-operator/src/kvp/label/mod.rs @@ -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. /// From c99705da1ba28356f7ab5b72df744fbf9da8bcd8 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 9 Oct 2025 09:42:15 +0200 Subject: [PATCH 2/4] feat(operator): Add new convenience functions to Label This adds the following new associated functions to construct labels: - `Label::instance`: app.kubernetes.io/instance - `Label::name`: app.kubernetes.io/name - `Label::stackable_vendor`: stackable.tech/vendor=Stackable --- .../stackable-operator/src/kvp/label/mod.rs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/stackable-operator/src/kvp/label/mod.rs b/crates/stackable-operator/src/kvp/label/mod.rs index 3948be637..6610db091 100644 --- a/crates/stackable-operator/src/kvp/label/mod.rs +++ b/crates/stackable-operator/src/kvp/label/mod.rs @@ -192,6 +192,31 @@ impl Label { 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. @@ -388,7 +413,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) } From 7971ce5fec076d4f8a2a23dfecb3d8bad1498caf Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 9 Oct 2025 09:42:42 +0200 Subject: [PATCH 3/4] chore(operator): Fix and adjust doc comments --- .../stackable-operator/src/kvp/label/mod.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/crates/stackable-operator/src/kvp/label/mod.rs b/crates/stackable-operator/src/kvp/label/mod.rs index 6610db091..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 @@ -156,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, @@ -184,9 +186,10 @@ 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))?; From eea1420c10b0164bfbec9fb6158bb2f2f8b11d48 Mon Sep 17 00:00:00 2001 From: Techassi Date: Thu, 9 Oct 2025 09:49:00 +0200 Subject: [PATCH 4/4] chore(operator): Add changelog entry --- crates/stackable-operator/CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/stackable-operator/CHANGELOG.md b/crates/stackable-operator/CHANGELOG.md index 05b18d722..3f5a2a03f 100644 --- a/crates/stackable-operator/CHANGELOG.md +++ b/crates/stackable-operator/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### 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 + +[#1106]: https://github.com/stackabletech/operator-rs/pull/1106 + ## [0.99.0] - 2025-10-06 ### Added