Skip to content

Commit 5c47870

Browse files
authored
Merge branch 'main' into chore/external-traffic-policy-default
2 parents 40329fb + 5ff7d2e commit 5c47870

16 files changed

Lines changed: 696 additions & 299 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stackable-operator/CHANGELOG.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,29 @@ All notable changes to this project will be documented in this file.
1010

1111
[#1107]: https://github.com/stackabletech/operator-rs/pull/1107
1212

13+
## [0.100.0] - 2025-10-16
14+
15+
### Added
16+
17+
- Add `LabelExt` trait which enables adding validated labels to any Kubernetes resource ([#1106]).
18+
- Add new associated convenience functions to `Label` ([#1106]).
19+
- `Label::stackable_vendor`: stackable.tech/vendor=Stackable
20+
- `Label::instance`: app.kubernetes.io/instance
21+
- `Label::name`: app.kubernetes.io/name
22+
- Add a `Client::create_if_missing` associated function to create a resource if it doesn't
23+
exist ([#1099]).
24+
- BREAKING: Add new ListenerClass `.spec.pinnedNodePorts` field ([#1105]).
25+
26+
[#1099]: https://github.com/stackabletech/operator-rs/pull/1099
27+
[#1105]: https://github.com/stackabletech/operator-rs/pull/1105
28+
[#1106]: https://github.com/stackabletech/operator-rs/pull/1106
29+
1330
## [0.99.0] - 2025-10-06
1431

1532
### Added
1633

1734
- Add CLI argument and env var to disable the end-of-support checker: `EOS_DISABLED` (`--eos-disabled`) ([#1101]).
18-
- Add end-of-support checker ([#1096]).
35+
- Add end-of-support checker ([#1096], [#1103]).
1936
- The EoS checker can be constructed using `EndOfSupportChecker::new()`.
2037
- Add new `MaintenanceOptions` and `EndOfSupportOptions` structs.
2138
- Add new CLI arguments and env vars:

crates/stackable-operator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "stackable-operator"
33
description = "Stackable Operator Framework"
4-
version = "0.99.0"
4+
version = "0.100.0"
55
authors.workspace = true
66
license.workspace = true
77
edition.workspace = true

crates/stackable-operator/crds/ListenerClass.yaml

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/stackable-operator/src/client.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,25 @@ impl Client {
253253
})
254254
}
255255

256+
/// Optionally creates a resource if it does not exist yet.
257+
///
258+
/// The name used for lookup is extracted from the resource via [`ResourceExt::name_any()`].
259+
/// This function either returns the existing resource or the newly created one.
260+
pub async fn create_if_missing<T>(&self, resource: &T) -> Result<T>
261+
where
262+
T: Clone + Debug + DeserializeOwned + Resource + Serialize + GetApi,
263+
<T as Resource>::DynamicType: Default,
264+
{
265+
if let Some(r) = self
266+
.get_opt(&resource.name_any(), resource.get_namespace())
267+
.await?
268+
{
269+
return Ok(r);
270+
}
271+
272+
self.create(resource).await
273+
}
274+
256275
/// Patches a resource using the `MERGE` patch strategy described
257276
/// in [JSON Merge Patch](https://tools.ietf.org/html/rfc7386)
258277
/// This will fail for objects that do not exist yet.

crates/stackable-operator/src/crd/listener/class/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,19 @@ pub mod versioned {
7474
/// Defaults to `HostnameConservative`.
7575
#[serde(default = "ListenerClassSpec::default_preferred_address_type")]
7676
pub preferred_address_type: core_v1alpha1::PreferredAddressType,
77+
78+
/// Whether or not a Pod exposed using a NodePort should be pinned to a specific Kubernetes node.
79+
///
80+
/// By pinning the Pod to a specific (stable) Kubernetes node, stable addresses can be
81+
/// provided using NodePorts. The pinning is achieved by listener-operator setting the
82+
/// `volume.kubernetes.io/selected-node` annotation on the Listener PVC.
83+
///
84+
/// However, this only works on setups with long-living nodes. If your nodes are rotated on
85+
/// a regular basis, the Pods previously running on a removed node will be stuck in Pending
86+
/// until you delete the PVC with the pinning.
87+
///
88+
/// Because of this we don't enable pinning by default to support all environments.
89+
#[serde(default)]
90+
pub pinned_node_ports: bool,
7791
}
7892
}

crates/stackable-operator/src/kvp/label/mod.rs

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! This module provides various types and functions to construct valid
22
//! Kubernetes labels. Labels are key/value pairs, where the key must meet
3-
//! certain requirementens regarding length and character set. The value can
3+
//! certain requirements regarding length and character set. The value can
44
//! contain a limited set of ASCII characters.
55
//!
66
//! Additionally, the [`Label`] struct provides various helper functions to
@@ -42,6 +42,63 @@ pub type LabelsError = KeyValuePairsError;
4242
/// of labels fails.
4343
pub type LabelError = KeyValuePairError<LabelValueError>;
4444

45+
/// Add [`Label`]s to any Kubernetes resource.
46+
///
47+
/// It should be noted, that after the addition of labels to the resource, the validity of keys and
48+
/// values **can no longer be enforced** as they are both stored as plain [`String`]s. To update a
49+
/// label use [`LabelExt::add_label`] which will update the label in place if it is already present.
50+
pub trait LabelExt
51+
where
52+
Self: ResourceExt,
53+
{
54+
/// Adds a single label to `self`.
55+
fn add_label(&mut self, label: Label) -> &mut Self;
56+
57+
/// Adds multiple labels to `self`.
58+
fn add_labels(&mut self, label: Labels) -> &mut Self;
59+
}
60+
61+
impl<T> LabelExt for T
62+
where
63+
T: ResourceExt,
64+
{
65+
fn add_label(&mut self, label: Label) -> &mut Self {
66+
let meta = self.meta_mut();
67+
68+
match &mut meta.labels {
69+
Some(labels) => {
70+
// TODO (@Techassi): Add an API to consume key and value
71+
let KeyValuePair { key, value } = label.into_inner();
72+
labels.insert(key.to_string(), value.to_string());
73+
}
74+
None => {
75+
let mut labels = BTreeMap::new();
76+
77+
// TODO (@Techassi): Add an API to consume key and value
78+
let KeyValuePair { key, value } = label.into_inner();
79+
labels.insert(key.to_string(), value.to_string());
80+
81+
meta.labels = Some(labels);
82+
}
83+
}
84+
85+
self
86+
}
87+
88+
fn add_labels(&mut self, labels: Labels) -> &mut Self {
89+
let meta = self.meta_mut();
90+
91+
match &mut meta.labels {
92+
Some(existing_labels) => {
93+
existing_labels.extend::<BTreeMap<String, String>>(labels.into())
94+
}
95+
None => meta.labels = Some(labels.into()),
96+
}
97+
98+
self
99+
}
100+
}
101+
45102
/// A specialized implementation of a key/value pair representing Kubernetes
46103
/// labels.
47104
///
@@ -99,26 +156,28 @@ impl Label {
99156
self.0
100157
}
101158

102-
/// Creates the `app.kubernetes.io/component` label with `role` as the
103-
/// value. This function will return an error if `role` violates the required
104-
/// Kubernetes restrictions.
159+
/// Creates the `app.kubernetes.io/component` label with `role` as the value.
160+
///
161+
/// This function will return an error if `role` violates the required Kubernetes restrictions.
105162
pub fn component(component: &str) -> Result<Self, LabelError> {
106163
let kvp = KeyValuePair::try_from((K8S_APP_COMPONENT_KEY, component))?;
107164
Ok(Self(kvp))
108165
}
109166

110-
/// Creates the `app.kubernetes.io/role-group` label with `role_group` as
111-
/// the value. This function will return an error if `role_group` violates
112-
/// the required Kubernetes restrictions.
167+
/// Creates the `app.kubernetes.io/role-group` label with `role_group` as the value.
168+
///
169+
/// This function will return an error if `role_group` violates the required Kubernetes
170+
/// restrictions.
113171
pub fn role_group(role_group: &str) -> Result<Self, LabelError> {
114172
let kvp = KeyValuePair::try_from((K8S_APP_ROLE_GROUP_KEY, role_group))?;
115173
Ok(Self(kvp))
116174
}
117175

118-
/// Creates the `app.kubernetes.io/managed-by` label with the formated
119-
/// full controller name based on `operator_name` and `controller_name` as
120-
/// the value. This function will return an error if the formatted controller
121-
/// name violates the required Kubernetes restrictions.
176+
/// Creates the `app.kubernetes.io/managed-by` label with the formatted full controller name
177+
/// based on `operator_name` and `controller_name` as the value.
178+
///
179+
/// This function will return an error if the formatted controller name violates the required
180+
/// Kubernetes restrictions.
122181
pub fn managed_by(operator_name: &str, controller_name: &str) -> Result<Self, LabelError> {
123182
let kvp = KeyValuePair::try_from((
124183
K8S_APP_MANAGED_BY_KEY,
@@ -127,14 +186,40 @@ impl Label {
127186
Ok(Self(kvp))
128187
}
129188

130-
/// Creates the `app.kubernetes.io/version` label with `version` as the
131-
/// value. This function will return an error if `role_group` violates the
132-
/// required Kubernetes restrictions.
189+
/// Creates the `app.kubernetes.io/version` label with `version` as the value.
190+
///
191+
/// This function will return an error if `version` violates the required Kubernetes
192+
/// restrictions.
133193
pub fn version(version: &str) -> Result<Self, LabelError> {
134194
// NOTE (Techassi): Maybe use semver::Version
135195
let kvp = KeyValuePair::try_from((K8S_APP_VERSION_KEY, version))?;
136196
Ok(Self(kvp))
137197
}
198+
199+
/// Creates the `app.kubernetes.io/instance` label with `instance` as the value.
200+
///
201+
/// This function will return an error if `instance` violates the required Kubernetes
202+
/// restrictions.
203+
pub fn instance(instance: &str) -> Result<Self, LabelError> {
204+
let kvp = KeyValuePair::try_from((K8S_APP_INSTANCE_KEY, instance))?;
205+
Ok(Self(kvp))
206+
}
207+
208+
/// Creates the `app.kubernetes.io/name` label with `name` as the value.
209+
///
210+
/// This function will return an error if `name` violates the required Kubernetes restrictions.
211+
pub fn name(name: &str) -> Result<Self, LabelError> {
212+
let kvp = KeyValuePair::try_from((K8S_APP_NAME_KEY, name))?;
213+
Ok(Self(kvp))
214+
}
215+
216+
/// Creates the Stackable specific vendor label.
217+
///
218+
/// See [`STACKABLE_VENDOR_KEY`] and [`STACKABLE_VENDOR_VALUE`].
219+
pub fn stackable_vendor() -> Self {
220+
Self::try_from((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE))
221+
.expect("constant vendor label must be valid")
222+
}
138223
}
139224

140225
/// A validated set/list of Kubernetes labels.
@@ -331,7 +416,7 @@ impl Labels {
331416
labels.insert(version);
332417

333418
// Stackable-specific labels
334-
labels.parse_insert((STACKABLE_VENDOR_KEY, STACKABLE_VENDOR_VALUE))?;
419+
labels.insert(Label::stackable_vendor());
335420

336421
Ok(labels)
337422
}

crates/stackable-webhook/CHANGELOG.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7+
## [0.7.0] - 2025-10-16
8+
9+
### Added
10+
11+
- Add `CustomResourceDefinitionMaintainer` which applies and patches CRDs triggered by TLS
12+
certificate rotations of the `ConversionWebhookServer`. It additionally provides a `oneshot`
13+
channel which can for example be used to trigger creation/patching of any custom resources
14+
deployed by the operator ([#1099]).
15+
- Add `ConversionWebhookServer::with_maintainer` which creates a conversion webhook server and a CRD
16+
maintainer ([#1099]).
17+
18+
### Changed
19+
20+
- BREAKING: `ConversionWebhookServer::new` now returns a pair of values ([#1099]):
21+
- The conversion webhook server itself
22+
- A `mpsc::Receiver<Certificate>` to provide consumers the newly generated TLS certificate
23+
- BREAKING: Constants for ports, IP addresses and socket addresses are now associated constants on
24+
`(Conversion)WebhookServer` instead of free-standing ones ([#1099]).
25+
26+
### Removed
27+
28+
- BREAKING: The `maintain_crds` and `field_manager` fields in `ConversionWebhookOptions`
29+
are removed ([#1099]).
30+
31+
[#1099]: https://github.com/stackabletech/operator-rs/pull/1099
32+
733
## [0.6.0] - 2025-09-09
834

935
### Added

crates/stackable-webhook/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "stackable-webhook"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
authors.workspace = true
55
license.workspace = true
66
edition.workspace = true

crates/stackable-webhook/src/constants.rs

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)