Skip to content

Commit 00fd1b7

Browse files
committed
feat: Support objectOverrides
1 parent d74c0ce commit 00fd1b7

4 files changed

Lines changed: 620 additions & 6 deletions

File tree

crates/stackable-operator/src/cluster_resources.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
#[cfg(doc)]
99
use k8s_openapi::api::core::v1::{NodeSelector, Pod};
1010
use k8s_openapi::{
11-
NamespaceResourceScope,
11+
DeepMerge, NamespaceResourceScope,
1212
api::{
1313
apps::v1::{
1414
DaemonSet, DaemonSetSpec, Deployment, DeploymentSpec, StatefulSet, StatefulSetSpec,
@@ -22,9 +22,10 @@ use k8s_openapi::{
2222
},
2323
apimachinery::pkg::apis::meta::v1::{LabelSelector, LabelSelectorRequirement},
2424
};
25-
use kube::{Resource, ResourceExt, core::ErrorResponse};
25+
use kube::{Resource, ResourceExt, api::DynamicObject, core::ErrorResponse};
2626
use serde::{Serialize, de::DeserializeOwned};
2727
use snafu::{OptionExt, ResultExt, Snafu};
28+
use stackable_shared::patchinator::{self, apply_patches, parse_patches};
2829
use strum::Display;
2930
use tracing::{debug, info, warn};
3031

@@ -87,6 +88,12 @@ pub enum Error {
8788
#[snafu(source(from(crate::client::Error, Box::new)))]
8889
source: Box<crate::client::Error>,
8990
},
91+
92+
#[snafu(display("failed to parse user-provided object overrides"))]
93+
ParseObjectOverrides { source: patchinator::Error },
94+
95+
#[snafu(display("failed to apply user-provided object overrides"))]
96+
ApplyObjectOverrides { source: patchinator::Error },
9097
}
9198

9299
/// A cluster resource handled by [`ClusterResources`].
@@ -97,6 +104,7 @@ pub enum Error {
97104
/// it must be added to [`ClusterResources::delete_orphaned_resources`] as well.
98105
pub trait ClusterResource:
99106
Clone
107+
+ DeepMerge
100108
+ Debug
101109
+ DeserializeOwned
102110
+ Resource<DynamicType = (), Scope = NamespaceResourceScope>
@@ -413,7 +421,7 @@ impl ClusterResource for Deployment {
413421
/// Ok(Action::await_change())
414422
/// }
415423
/// ```
416-
#[derive(Debug, Eq, PartialEq)]
424+
#[derive(Debug)]
417425
pub struct ClusterResources {
418426
/// The namespace of the cluster
419427
namespace: String,
@@ -442,6 +450,9 @@ pub struct ClusterResources {
442450
/// Strategy to manage how cluster resources are applied. Resources could be patched, merged
443451
/// or not applied at all depending on the strategy.
444452
apply_strategy: ClusterResourceApplyStrategy,
453+
454+
/// Arbitrary Kubernetes object overrides specified by the user via the CRD.
455+
object_overrides: Vec<DynamicObject>,
445456
}
446457

447458
impl ClusterResources {
@@ -470,6 +481,7 @@ impl ClusterResources {
470481
controller_name: &str,
471482
cluster: &ObjectReference,
472483
apply_strategy: ClusterResourceApplyStrategy,
484+
object_overrides: Option<impl AsRef<str>>,
473485
) -> Result<Self> {
474486
let namespace = cluster
475487
.namespace
@@ -483,6 +495,12 @@ impl ClusterResources {
483495
.uid
484496
.clone()
485497
.context(MissingObjectKeySnafu { key: "uid" })?;
498+
let object_overrides = match object_overrides {
499+
Some(object_overrides) => {
500+
parse_patches(object_overrides).context(ParseObjectOverridesSnafu)?
501+
}
502+
None => vec![],
503+
};
486504

487505
Ok(ClusterResources {
488506
namespace,
@@ -494,6 +512,7 @@ impl ClusterResources {
494512
manager: format_full_controller_name(operator_name, controller_name),
495513
resource_ids: Default::default(),
496514
apply_strategy,
515+
object_overrides,
497516
})
498517
}
499518

@@ -563,7 +582,11 @@ impl ClusterResources {
563582
.unwrap_or_else(|err| warn!("{}", err));
564583
}
565584

566-
let mutated = resource.maybe_mutate(&self.apply_strategy);
585+
let mut mutated = resource.maybe_mutate(&self.apply_strategy);
586+
587+
// We apply the object overrides of the user at the very last to offer maximum flexibility.
588+
apply_patches(&mut mutated, self.object_overrides.iter())
589+
.context(ApplyObjectOverridesSnafu)?;
567590

568591
let patched_resource = self
569592
.apply_strategy
Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,94 @@
1-
use crate::crd::listener::listeners::v1alpha1::ListenerSpec;
1+
use crate::crd::listener::listeners::v1alpha1::{
2+
Listener, ListenerIngress, ListenerPort, ListenerSpec, ListenerStatus,
3+
};
24

35
impl ListenerSpec {
46
pub(super) const fn default_publish_not_ready_addresses() -> Option<bool> {
57
Some(true)
68
}
79
}
10+
11+
impl k8s_openapi::DeepMerge for Listener {
12+
fn merge_from(&mut self, other: Self) {
13+
k8s_openapi::DeepMerge::merge_from(&mut self.metadata, other.metadata);
14+
k8s_openapi::DeepMerge::merge_from(&mut self.spec, other.spec);
15+
k8s_openapi::DeepMerge::merge_from(&mut self.status, other.status);
16+
}
17+
}
18+
19+
impl k8s_openapi::DeepMerge for ListenerSpec {
20+
fn merge_from(&mut self, other: Self) {
21+
k8s_openapi::DeepMerge::merge_from(&mut self.class_name, other.class_name);
22+
k8s_openapi::merge_strategies::map::granular(
23+
&mut self.extra_pod_selector_labels,
24+
other.extra_pod_selector_labels,
25+
|current_item, other_item| {
26+
k8s_openapi::DeepMerge::merge_from(current_item, other_item);
27+
},
28+
);
29+
k8s_openapi::merge_strategies::list::map(
30+
&mut self.ports,
31+
other.ports,
32+
&[|lhs, rhs| lhs.name == rhs.name],
33+
|current_item, other_item| {
34+
k8s_openapi::DeepMerge::merge_from(current_item, other_item);
35+
},
36+
);
37+
k8s_openapi::DeepMerge::merge_from(
38+
&mut self.publish_not_ready_addresses,
39+
other.publish_not_ready_addresses,
40+
);
41+
todo!()
42+
}
43+
}
44+
45+
impl k8s_openapi::DeepMerge for ListenerStatus {
46+
fn merge_from(&mut self, other: Self) {
47+
k8s_openapi::DeepMerge::merge_from(&mut self.service_name, other.service_name);
48+
k8s_openapi::merge_strategies::list::map(
49+
&mut self.ingress_addresses,
50+
other.ingress_addresses,
51+
&[|lhs, rhs| lhs.address == rhs.address],
52+
|current_item, other_item| {
53+
k8s_openapi::DeepMerge::merge_from(current_item, other_item);
54+
},
55+
);
56+
k8s_openapi::merge_strategies::map::granular(
57+
&mut self.node_ports,
58+
other.node_ports,
59+
|current_item, other_item| {
60+
k8s_openapi::DeepMerge::merge_from(current_item, other_item);
61+
},
62+
);
63+
}
64+
}
65+
66+
impl k8s_openapi::DeepMerge for ListenerIngress {
67+
fn merge_from(&mut self, other: Self) {
68+
k8s_openapi::DeepMerge::merge_from(&mut self.address, other.address);
69+
self.address_type = other.address_type;
70+
k8s_openapi::merge_strategies::map::granular(
71+
&mut self.ports,
72+
other.ports,
73+
|current_item, other_item| {
74+
k8s_openapi::DeepMerge::merge_from(current_item, other_item);
75+
},
76+
);
77+
}
78+
}
79+
80+
impl k8s_openapi::DeepMerge for ListenerPort {
81+
fn merge_from(&mut self, other: Self) {
82+
k8s_openapi::DeepMerge::merge_from(&mut self.name, other.name);
83+
k8s_openapi::DeepMerge::merge_from(&mut self.port, other.port);
84+
k8s_openapi::DeepMerge::merge_from(&mut self.protocol, other.protocol);
85+
}
86+
}
87+
88+
#[cfg(test)]
89+
mod tests {
90+
#[test]
91+
fn deep_merge_listener() {
92+
todo!("Add some basic tests for merging");
93+
}
94+
}

crates/stackable-shared/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//! workspace.
33
44
pub mod crd;
5+
pub mod patchinator;
56
pub mod secret;
6-
77
pub mod time;
88
pub mod yaml;

0 commit comments

Comments
 (0)