Skip to content

Commit b7e9c56

Browse files
adwk67claude
andcommitted
feat: add framework builder, kvp, and utility modules
Port remaining framework modules from the opensearch-operator reference: builder (meta, pdb, pod, statefulset), kvp labels, product logging, cluster resources, controller utils, role/role-group utilities. These provide infallible wrappers around stackable-operator builder APIs and type-safe resource name derivation with compile-time length assertions. All code is currently unused and will be wired into the controller in the next commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5cba7dd commit b7e9c56

16 files changed

Lines changed: 1932 additions & 0 deletions

File tree

rust/operator-binary/src/framework.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,21 @@
2121
2222
use types::kubernetes::Uid;
2323

24+
#[allow(dead_code)]
25+
pub mod builder;
26+
#[allow(dead_code)]
27+
pub mod cluster_resources;
28+
#[allow(dead_code)]
29+
pub mod controller_utils;
30+
#[allow(dead_code)]
31+
pub mod kvp;
2432
pub mod macros;
33+
#[allow(dead_code)]
34+
pub mod product_logging;
35+
#[allow(dead_code)]
36+
pub mod role_group_utils;
37+
#[allow(dead_code)]
38+
pub mod role_utils;
2539
pub mod types;
2640

2741
/// Has a non-empty name
@@ -34,11 +48,13 @@ pub trait HasName {
3448
}
3549

3650
/// Has a Kubernetes UID
51+
#[allow(dead_code)]
3752
pub trait HasUid {
3853
fn to_uid(&self) -> Uid;
3954
}
4055

4156
/// The name is a valid label value
57+
#[allow(dead_code)]
4258
pub trait NameIsValidLabelValue {
4359
fn to_label_value(&self) -> String;
4460
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#[allow(dead_code)]
2+
pub mod meta;
3+
#[allow(dead_code)]
4+
pub mod pdb;
5+
#[allow(dead_code)]
6+
pub mod pod;
7+
#[allow(dead_code)]
8+
pub mod statefulset;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use stackable_operator::{
2+
builder::meta::OwnerReferenceBuilder,
3+
k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference, kube::Resource,
4+
};
5+
6+
use crate::framework::{HasName, HasUid};
7+
8+
/// Infallible variant of
9+
/// [`stackable_operator::builder::meta::ObjectMetaBuilder::ownerreference_from_resource`]
10+
pub fn ownerreference_from_resource(
11+
resource: &(impl Resource<DynamicType = ()> + HasName + HasUid),
12+
block_owner_deletion: Option<bool>,
13+
controller: Option<bool>,
14+
) -> OwnerReference {
15+
OwnerReferenceBuilder::new()
16+
// Set api_version, kind, name and additionally the UID if it exists.
17+
.initialize_from_resource(resource)
18+
// Ensure that the name is set.
19+
.name(resource.to_name())
20+
// Ensure that the UID is set.
21+
.uid(resource.to_uid().to_string())
22+
.block_owner_deletion_opt(block_owner_deletion)
23+
.controller_opt(controller)
24+
.build()
25+
.expect(
26+
"OwnerReference should be created because the resource has an api_version, kind, name \
27+
and uid.",
28+
)
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use std::borrow::Cow;
34+
35+
use stackable_operator::{
36+
k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, kube::Resource,
37+
};
38+
39+
use crate::framework::{
40+
HasName, HasUid, builder::meta::ownerreference_from_resource, types::kubernetes::Uid,
41+
};
42+
43+
struct TestCluster {
44+
object_meta: ObjectMeta,
45+
}
46+
47+
impl TestCluster {
48+
fn new() -> Self {
49+
TestCluster {
50+
object_meta: ObjectMeta {
51+
name: Some("test-cluster".to_owned()),
52+
uid: Some("a6b89911-d48e-4328-88d6-b9251226583d".to_owned()),
53+
..ObjectMeta::default()
54+
},
55+
}
56+
}
57+
}
58+
59+
impl Resource for TestCluster {
60+
type DynamicType = ();
61+
type Scope = ();
62+
63+
fn kind(_dt: &Self::DynamicType) -> Cow<'_, str> {
64+
Cow::from("HiveCluster")
65+
}
66+
67+
fn group(_dt: &Self::DynamicType) -> Cow<'_, str> {
68+
Cow::from("hive.stackable.tech")
69+
}
70+
71+
fn version(_dt: &Self::DynamicType) -> Cow<'_, str> {
72+
Cow::from("v1alpha1")
73+
}
74+
75+
fn plural(_dt: &Self::DynamicType) -> Cow<'_, str> {
76+
Cow::from("hiveclusters")
77+
}
78+
79+
fn meta(&self) -> &ObjectMeta {
80+
&self.object_meta
81+
}
82+
83+
fn meta_mut(&mut self) -> &mut ObjectMeta {
84+
&mut self.object_meta
85+
}
86+
}
87+
88+
impl HasName for TestCluster {
89+
fn to_name(&self) -> String {
90+
self.object_meta.name.clone().expect("set in new()")
91+
}
92+
}
93+
94+
impl HasUid for TestCluster {
95+
fn to_uid(&self) -> Uid {
96+
Uid::from_str_unsafe(&self.object_meta.uid.clone().expect("set in new()"))
97+
}
98+
}
99+
100+
#[test]
101+
fn test_ownerreference_from_resource() {
102+
let owner_ref = ownerreference_from_resource(&TestCluster::new(), Some(true), Some(true));
103+
assert_eq!(owner_ref.name, "test-cluster");
104+
assert_eq!(owner_ref.uid, "a6b89911-d48e-4328-88d6-b9251226583d");
105+
assert_eq!(owner_ref.controller, Some(true));
106+
assert_eq!(owner_ref.block_owner_deletion, Some(true));
107+
}
108+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
use stackable_operator::{
2+
builder::pdb::PodDisruptionBudgetBuilder,
3+
k8s_openapi::apimachinery::pkg::apis::meta::v1::LabelSelector,
4+
kube::{Resource, api::ObjectMeta},
5+
};
6+
7+
use crate::framework::{
8+
HasName, HasUid, NameIsValidLabelValue,
9+
types::operator::{ControllerName, OperatorName, ProductName, RoleName},
10+
};
11+
12+
/// Infallible variant of
13+
/// [`stackable_operator::builder::pdb::PodDisruptionBudgetBuilder::new_with_role`]
14+
pub fn pod_disruption_budget_builder_with_role(
15+
owner: &(impl Resource<DynamicType = ()> + HasName + NameIsValidLabelValue + HasUid),
16+
product_name: &ProductName,
17+
role_name: &RoleName,
18+
operator_name: &OperatorName,
19+
controller_name: &ControllerName,
20+
) -> PodDisruptionBudgetBuilder<ObjectMeta, LabelSelector, ()> {
21+
PodDisruptionBudgetBuilder::new_with_role(
22+
owner,
23+
&product_name.to_label_value(),
24+
&role_name.to_label_value(),
25+
&operator_name.to_label_value(),
26+
&controller_name.to_label_value(),
27+
)
28+
.expect(
29+
"PodDisruptionBudgetBuilder should be created because the owner has an object name and UID \
30+
and all given parameters produce valid label values.",
31+
)
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use std::borrow::Cow;
37+
38+
use stackable_operator::{
39+
k8s_openapi::{
40+
api::policy::v1::{PodDisruptionBudget, PodDisruptionBudgetSpec},
41+
apimachinery::pkg::{
42+
apis::meta::v1::{LabelSelector, ObjectMeta, OwnerReference},
43+
util::intstr::IntOrString,
44+
},
45+
},
46+
kube::Resource,
47+
};
48+
49+
use crate::framework::{
50+
HasName, HasUid, NameIsValidLabelValue,
51+
builder::pdb::pod_disruption_budget_builder_with_role,
52+
types::{
53+
kubernetes::Uid,
54+
operator::{ControllerName, OperatorName, ProductName, RoleName},
55+
},
56+
};
57+
58+
struct Cluster {
59+
object_meta: ObjectMeta,
60+
}
61+
62+
impl Cluster {
63+
fn new() -> Self {
64+
Cluster {
65+
object_meta: ObjectMeta {
66+
name: Some("cluster-name".to_owned()),
67+
uid: Some("a6b89911-d48e-4328-88d6-b9251226583d".to_owned()),
68+
..ObjectMeta::default()
69+
},
70+
}
71+
}
72+
}
73+
74+
impl Resource for Cluster {
75+
type DynamicType = ();
76+
type Scope = ();
77+
78+
fn kind(_dt: &Self::DynamicType) -> Cow<'_, str> { Cow::from("HiveCluster") }
79+
fn group(_dt: &Self::DynamicType) -> Cow<'_, str> { Cow::from("hive.stackable.tech") }
80+
fn version(_dt: &Self::DynamicType) -> Cow<'_, str> { Cow::from("v1alpha1") }
81+
fn plural(_dt: &Self::DynamicType) -> Cow<'_, str> { Cow::from("hiveclusters") }
82+
83+
fn meta(&self) -> &ObjectMeta { &self.object_meta }
84+
fn meta_mut(&mut self) -> &mut ObjectMeta { &mut self.object_meta }
85+
}
86+
87+
impl HasName for Cluster {
88+
fn to_name(&self) -> String {
89+
self.object_meta.name.clone().expect("set in new()")
90+
}
91+
}
92+
93+
impl HasUid for Cluster {
94+
fn to_uid(&self) -> Uid {
95+
Uid::from_str_unsafe(&self.object_meta.uid.clone().expect("set in new()"))
96+
}
97+
}
98+
99+
impl NameIsValidLabelValue for Cluster {
100+
fn to_label_value(&self) -> String {
101+
self.object_meta.name.clone().expect("set in new()")
102+
}
103+
}
104+
105+
#[test]
106+
fn test_pod_disruption_budget_builder_with_role() {
107+
let actual_pdb = pod_disruption_budget_builder_with_role(
108+
&Cluster::new(),
109+
&ProductName::from_str_unsafe("my-product"),
110+
&RoleName::from_str_unsafe("my-role"),
111+
&OperatorName::from_str_unsafe("my-operator"),
112+
&ControllerName::from_str_unsafe("my-controller"),
113+
)
114+
.with_max_unavailable(2)
115+
.build();
116+
117+
let expected_pdb = PodDisruptionBudget {
118+
metadata: ObjectMeta {
119+
labels: Some(
120+
[
121+
("app.kubernetes.io/component", "my-role"),
122+
("app.kubernetes.io/instance", "cluster-name"),
123+
("app.kubernetes.io/managed-by", "my-operator_my-controller"),
124+
("app.kubernetes.io/name", "my-product"),
125+
]
126+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
127+
.into(),
128+
),
129+
name: Some("cluster-name-my-role".to_owned()),
130+
owner_references: Some(vec![OwnerReference {
131+
api_version: "hive.stackable.tech/v1alpha1".to_owned(),
132+
controller: Some(true),
133+
kind: "HiveCluster".to_owned(),
134+
name: "cluster-name".to_owned(),
135+
uid: "a6b89911-d48e-4328-88d6-b9251226583d".to_owned(),
136+
..OwnerReference::default()
137+
}]),
138+
..ObjectMeta::default()
139+
},
140+
spec: Some(PodDisruptionBudgetSpec {
141+
max_unavailable: Some(IntOrString::Int(2)),
142+
selector: Some(LabelSelector {
143+
match_labels: Some(
144+
[
145+
("app.kubernetes.io/component", "my-role"),
146+
("app.kubernetes.io/instance", "cluster-name"),
147+
("app.kubernetes.io/name", "my-product"),
148+
]
149+
.map(|(k, v)| (k.to_owned(), v.to_owned()))
150+
.into(),
151+
),
152+
..LabelSelector::default()
153+
}),
154+
..PodDisruptionBudgetSpec::default()
155+
}),
156+
..PodDisruptionBudget::default()
157+
};
158+
159+
assert_eq!(expected_pdb, actual_pdb);
160+
}
161+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod container;
2+
pub mod volume;

0 commit comments

Comments
 (0)