Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
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