Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
6 changes: 6 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ 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
- BREAKING: Add new ListenerClass `.spec.pinnedNodePorts` field ([#1105]).

[#1105]: https://github.com/stackabletech/operator-rs/pull/1105
[#1106]: https://github.com/stackabletech/operator-rs/pull/1106

## [0.99.0] - 2025-10-06

Expand Down
115 changes: 100 additions & 15 deletions crates/stackable-operator/src/kvp/label/mod.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -42,6 +42,63 @@ pub type LabelsError = KeyValuePairsError;
/// of labels fails.
pub type LabelError = KeyValuePairError<LabelValueError>;

/// 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<T> 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::<BTreeMap<String, String>>(labels.into())
}
None => meta.labels = Some(labels.into()),
}

self
}
}

/// A specialized implementation of a key/value pair representing Kubernetes
/// labels.
///
Expand Down Expand Up @@ -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<Self, LabelError> {
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<Self, LabelError> {
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<Self, LabelError> {
let kvp = KeyValuePair::try_from((
K8S_APP_MANAGED_BY_KEY,
Expand All @@ -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<Self, LabelError> {
// 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<Self, LabelError> {
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<Self, LabelError> {
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.
Expand Down Expand Up @@ -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)
}
Expand Down