Skip to content

Commit cfd3619

Browse files
Perform ResourceDiff compare before patch (#317)
* Fmt fixes Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com> * Implement custom resource comparison logic Introduces a `ResourceDiff` trait and implements it for various Fleet API resources. This allows controllers to perform more granular comparisons between the desired state and the current state of resources, avoiding unnecessary updates and reconciliations. Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com> * Improve readability of the wait conditions individually Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com> --------- Signed-off-by: Danil-Grigorev <danil.grigorev@suse.com>
1 parent bb18849 commit cfd3619

24 files changed

+265
-78
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ fleet-api-rs = "0.12.3"
6363
async-broadcast = "0.7.2"
6464
pin-project = "1.1.10"
6565
async-stream = "0.3.6"
66+
educe = { version = "0.6.0", features = ["PartialEq"] }
6667

6768
[dev-dependencies]
6869
assert-json-diff = "2.0.2"

justfile

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ test-unit:
4545
cargo test
4646

4747
# run clippy
48-
clippy:
48+
clippy: fmt
4949
cargo clippy --all-targets --all-features --fix --allow-dirty -- -W clippy::pedantic
5050

5151
# compile for musl (for docker image)
@@ -94,7 +94,7 @@ start-dev: _cleanup-out-dir _create-out-dir _download-kubectl
9494
kind delete cluster --name dev || true
9595
kind create cluster --image=kindest/node:v{{KUBE_VERSION}} --config testdata/kind-config.yaml
9696
just install-capi
97-
kubectl wait pods --for=condition=Ready --timeout=300s --all --all-namespaces
97+
kubectl wait pods --for=condition=Ready --timeout=500s --all --all-namespaces
9898

9999
# Stop the local dev environment
100100
stop-dev:
@@ -171,13 +171,13 @@ release-manifests: _create-out-dir _download-kustomize
171171
test-import: start-dev deploy deploy-child-cluster deploy-kindnet deploy-app && collect-test-import
172172
kubectl wait pods --for=condition=Ready --timeout=150s --all --all-namespaces
173173
kubectl wait cluster --timeout=500s --for=condition=ControlPlaneReady=true docker-demo
174-
kubectl wait clusters.fleet.cattle.io --timeout=300s --for=condition=Ready=true docker-demo
174+
kubectl wait clusters.fleet.cattle.io --timeout=500s --for=condition=Ready=true docker-demo
175175

176176
# Full e2e test of importing cluster in fleet
177177
test-import-rke2: start-dev deploy deploy-child-rke2-cluster deploy-calico-gitrepo deploy-app
178178
kubectl wait pods --for=condition=Ready --timeout=150s --all --all-namespaces
179179
kubectl wait cluster --timeout=500s --for=condition=ControlPlaneReady=true docker-demo
180-
kubectl wait clusters.fleet.cattle.io --timeout=300s --for=condition=Ready=true docker-demo
180+
kubectl wait clusters.fleet.cattle.io --timeout=500s --for=condition=Ready=true docker-demo
181181

182182
collect-test-import:
183183
-just collect-artifacts dev
@@ -206,11 +206,15 @@ collect-artifacts cluster:
206206
# Full e2e test of importing cluster and ClusterClass in fleet
207207
[private]
208208
_test-import-all:
209-
kubectl wait clustergroups.fleet.cattle.io -n clusterclass --timeout=300s --for=create --for=condition=Ready=true quick-start
209+
kubectl wait clustergroups.fleet.cattle.io -n clusterclass --timeout=500s --for=create quick-start
210+
kubectl wait clustergroups.fleet.cattle.io -n clusterclass --timeout=500s --for=condition=Ready=true quick-start
210211
# Verify that cluster group created for cluster referencing clusterclass in a different namespace
211-
kubectl wait bundlenamespacemappings.fleet.cattle.io --timeout=300s --for=create -n clusterclass default
212-
kubectl wait clustergroups.fleet.cattle.io --timeout=300s --for=create --for=jsonpath='{.status.clusterCount}=1' --for=condition=Ready=true quick-start.clusterclass
213-
kubectl wait clusters.fleet.cattle.io --timeout=300s --for=create --for=condition=Ready=true capi-quickstart
212+
kubectl wait bundlenamespacemappings.fleet.cattle.io --timeout=500s --for=create -n clusterclass default
213+
kubectl wait clustergroups.fleet.cattle.io --timeout=500s --for=create quick-start.clusterclass
214+
kubectl wait clustergroups.fleet.cattle.io --timeout=500s --for=jsonpath='{.status.clusterCount}=1' quick-start.clusterclass
215+
kubectl wait clustergroups.fleet.cattle.io --timeout=500s --for=condition=Ready=true quick-start.clusterclass
216+
kubectl wait clusters.fleet.cattle.io --timeout=500s --for=create capi-quickstart
217+
kubectl wait clusters.fleet.cattle.io --timeout=500s --for=condition=Ready=true capi-quickstart
214218

215219
[private]
216220
_test-delete-all:

src/api/bundle_namespace_mapping.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use fleet_api_rs::fleet_bundle_namespace_mapping::{
22
BundleNamespaceMappingBundleSelector, BundleNamespaceMappingNamespaceSelector,
33
};
44
use kube::{
5-
api::{ObjectMeta, TypeMeta},
65
Resource,
6+
api::{ObjectMeta, TypeMeta},
77
};
88
use serde::{Deserialize, Serialize};
99

10+
use crate::api::comparable::ResourceDiff;
11+
1012
mod mapping {
1113
use kube::CustomResource;
1214
use schemars::JsonSchema;
@@ -32,3 +34,9 @@ pub struct BundleNamespaceMapping {
3234
pub bundle_selector: BundleNamespaceMappingBundleSelector,
3335
pub namespace_selector: BundleNamespaceMappingNamespaceSelector,
3436
}
37+
38+
impl ResourceDiff for BundleNamespaceMapping {
39+
fn diff(&self, other: &Self) -> bool {
40+
self != other
41+
}
42+
}

src/api/capi_cluster.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ use fleet_api_rs::{
88
fleet_clustergroup::{ClusterGroupSelector, ClusterGroupSpec},
99
};
1010
use kube::{
11-
api::{ObjectMeta, TypeMeta},
1211
CustomResource, Resource, ResourceExt as _,
12+
api::{ObjectMeta, TypeMeta},
1313
};
1414
#[cfg(feature = "agent-initiated")]
1515
use rand::distr::{Alphanumeric, SampleString as _};
@@ -20,7 +20,7 @@ use super::{
2020
bundle_namespace_mapping::BundleNamespaceMapping,
2121
fleet_addon_config::ClusterConfig,
2222
fleet_cluster,
23-
fleet_clustergroup::{ClusterGroup, CLUSTER_CLASS_LABEL, CLUSTER_CLASS_NAMESPACE_LABEL},
23+
fleet_clustergroup::{CLUSTER_CLASS_LABEL, CLUSTER_CLASS_NAMESPACE_LABEL, ClusterGroup},
2424
};
2525

2626
#[cfg(feature = "agent-initiated")]

src/api/comparable.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Trait for resources that can be compared
2+
pub(crate) trait ResourceDiff: kube::ResourceExt {
3+
fn diff(&self, other: &Self) -> bool {
4+
self.meta() != other.meta()
5+
}
6+
}

src/api/fleet_addon_config.rs

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
use std::{fmt::Display, str::FromStr};
22

3+
use crate::api::comparable::ResourceDiff;
4+
use educe::Educe;
35
use fleet_api_rs::fleet_cluster::{ClusterAgentEnvVars, ClusterAgentTolerations};
46
use k8s_openapi::{
57
api::core::v1::{ConfigMap, ObjectReference},
68
apimachinery::pkg::apis::meta::v1::{Condition, LabelSelector},
79
};
810
use kube::{
11+
CustomResource, KubeSchema, Resource,
912
api::{ObjectMeta, TypeMeta},
1013
core::{ParseExpressionError, Selector},
11-
CustomResource, KubeSchema, Resource,
1214
};
1315
use schemars::JsonSchema;
14-
use serde::{ser, Deserialize, Serialize};
15-
use serde_with::{serde_as, DisplayFromStr};
16+
use serde::{Deserialize, Serialize, ser};
17+
use serde_with::{DisplayFromStr, serde_as};
1618
use serde_yaml::Value;
1719

1820
pub const AGENT_NAMESPACE: &str = "fleet-addon-agent";
1921
pub const EXPERIMENTAL_OCI_STORAGE: &str = "EXPERIMENTAL_OCI_STORAGE";
2022
pub const EXPERIMENTAL_HELM_OPS: &str = "EXPERIMENTAL_HELM_OPS";
2123

2224
/// This provides a config for fleet addon functionality
23-
#[derive(CustomResource, Deserialize, Serialize, Clone, Default, Debug, KubeSchema)]
25+
#[derive(CustomResource, Deserialize, Serialize, Clone, Default, Debug, KubeSchema, PartialEq)]
2426
#[kube(
2527
kind = "FleetAddonConfig",
2628
group = "addons.cluster.x-k8s.io",
@@ -63,6 +65,11 @@ impl Default for FleetAddonConfig {
6365
}
6466
}
6567
}
68+
impl ResourceDiff for FleetAddonConfig {
69+
fn diff(&self, _: &Self) -> bool {
70+
true
71+
}
72+
}
6673

6774
#[derive(Deserialize, Serialize, Clone, Default, Debug, JsonSchema)]
6875
#[serde(rename_all = "camelCase")]
@@ -73,7 +80,7 @@ pub struct FleetAddonConfigStatus {
7380
pub conditions: Vec<Condition>,
7481
}
7582

76-
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
83+
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, PartialEq)]
7784
#[serde(rename_all = "camelCase")]
7885
pub struct ClusterClassConfig {
7986
/// Setting to disable setting owner references on the created resources
@@ -95,7 +102,7 @@ impl Default for ClusterClassConfig {
95102
}
96103
}
97104

98-
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
105+
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, PartialEq)]
99106
#[serde(rename_all = "camelCase")]
100107
pub struct ClusterConfig {
101108
/// Apply a `ClusterGroup` for a `ClusterClass` referenced from a different namespace.
@@ -152,13 +159,21 @@ pub struct FleetSettings {
152159
pub data: Option<FleetSettingsSpec>,
153160
}
154161

162+
impl ResourceDiff for FleetSettings {
163+
fn diff(&self, other: &Self) -> bool {
164+
self.data != other.data
165+
}
166+
}
167+
155168
#[serde_as]
156-
#[derive(Serialize, Deserialize, Default, Clone, Debug)]
169+
#[derive(Serialize, Deserialize, Default, Clone, Debug, Educe)]
170+
#[educe(PartialEq)]
157171
pub struct FleetSettingsSpec {
158172
#[serde(default)]
159173
#[serde_as(as = "DisplayFromStr")]
160174
pub fleet: FleetChartValues,
161175

176+
#[educe(PartialEq(ignore))]
162177
#[serde(flatten)]
163178
pub other: Value,
164179
}
@@ -238,7 +253,7 @@ impl ClusterConfig {
238253
}
239254

240255
/// `NamingStrategy` is controlling Fleet cluster naming
241-
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Default)]
256+
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Default, PartialEq)]
242257
pub struct NamingStrategy {
243258
/// Specify a prefix for the Cluster name, applied to created Fleet cluster
244259
pub prefix: Option<String>,
@@ -264,7 +279,7 @@ impl Default for ClusterConfig {
264279
}
265280
}
266281

267-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
282+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
268283
#[serde(rename_all = "camelCase")]
269284
pub struct FleetConfig {
270285
/// fleet server url configuration options
@@ -290,7 +305,7 @@ impl Default for FleetConfig {
290305

291306
/// Feature toggles for enabling or disabling experimental functionality.
292307
/// This struct controls access to specific experimental features.
293-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
308+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
294309
#[serde(rename_all = "camelCase")]
295310
pub struct FeatureGates {
296311
/// Enables experimental OCI storage support.
@@ -360,7 +375,7 @@ impl Default for FeatureGates {
360375

361376
/// `FeaturesConfigMap` references a `ConfigMap` where to apply feature flags.
362377
/// If a `ConfigMap` is referenced, the controller will update it instead of upgrading the Fleet chart.
363-
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
378+
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
364379
#[serde(rename_all = "camelCase")]
365380
pub struct FeaturesConfigMap {
366381
// The reference to a ConfigMap resource
@@ -369,7 +384,7 @@ pub struct FeaturesConfigMap {
369384
}
370385

371386
/// `FleetChartValues` represents Fleet chart values.
372-
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
387+
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
373388
#[serde(rename_all = "camelCase")]
374389
pub struct FleetChartValues {
375390
pub extra_env: Option<Vec<EnvironmentVariable>>,
@@ -378,14 +393,14 @@ pub struct FleetChartValues {
378393
}
379394

380395
/// `EnvironmentVariable` is a simple name/value pair.
381-
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
396+
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
382397
#[serde(rename_all = "camelCase")]
383398
pub struct EnvironmentVariable {
384399
pub name: String,
385400
pub value: String,
386401
}
387402

388-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
403+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
389404
#[serde(rename_all = "camelCase")]
390405
pub struct FleetInstall {
391406
/// Chart version to install
@@ -421,14 +436,14 @@ impl Default for Install {
421436
}
422437
}
423438

424-
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
439+
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
425440
#[serde(rename_all = "camelCase")]
426441
pub enum Server {
427442
InferLocal(bool),
428443
Custom(InstallOptions),
429444
}
430445

431-
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
446+
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
432447
#[serde(rename_all = "camelCase")]
433448
pub struct InstallOptions {
434449
pub api_server_ca_config_ref: Option<ObjectReference>,
@@ -450,7 +465,7 @@ impl NamingStrategy {
450465
}
451466

452467
/// Selectors is controlling Fleet import strategy settings.
453-
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Default)]
468+
#[derive(Serialize, Deserialize, Clone, Debug, JsonSchema, Default, PartialEq)]
454469
#[serde(rename_all = "camelCase")]
455470
pub struct Selectors {
456471
/// Namespace label selector. If set, only clusters in the namespace matching label selector will be imported.

src/api/fleet_cluster.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
use fleet_api_rs::fleet_cluster::{ClusterSpec, ClusterStatus};
22
use kube::{
3+
Resource, ResourceExt,
34
api::{ObjectMeta, TypeMeta},
4-
Resource,
55
};
66
use serde::{Deserialize, Serialize};
77

8+
use crate::api::comparable::ResourceDiff;
9+
use std::collections::HashSet;
10+
811
#[derive(Resource, Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
912
#[resource(inherit = fleet_api_rs::fleet_cluster::Cluster)]
1013
pub struct Cluster {
@@ -14,3 +17,60 @@ pub struct Cluster {
1417
pub spec: ClusterSpec,
1518
pub status: Option<ClusterStatus>,
1619
}
20+
21+
impl ResourceDiff for Cluster {
22+
fn diff(&self, other: &Self) -> bool {
23+
// Resource was just created
24+
if other.status.is_none() {
25+
return true;
26+
}
27+
28+
let template_values_equal = self
29+
.spec
30+
.template_values
31+
.as_ref()
32+
.unwrap_or(&std::collections::BTreeMap::new())
33+
.iter()
34+
.all(|(k, v)| {
35+
other
36+
.spec
37+
.template_values
38+
.as_ref()
39+
.unwrap_or(&std::collections::BTreeMap::new())
40+
.get(k)
41+
== Some(v)
42+
});
43+
44+
let spec_equal = template_values_equal
45+
&& self.spec.agent_namespace == other.spec.agent_namespace
46+
&& self.spec.host_network == other.spec.host_network
47+
&& self.spec.agent_env_vars == other.spec.agent_env_vars
48+
&& self.spec.agent_tolerations == other.spec.agent_tolerations;
49+
50+
if !spec_equal {
51+
return true;
52+
}
53+
54+
let annotations_equal = self
55+
.annotations()
56+
.iter()
57+
.all(|(k, v)| other.annotations().get(k) == Some(v));
58+
let labels_equal = self
59+
.labels()
60+
.iter()
61+
.all(|(k, v)| other.labels().get(k) == Some(v));
62+
63+
let owner_uids: HashSet<String> = other
64+
.owner_references()
65+
.iter()
66+
.map(|r| &r.uid)
67+
.cloned()
68+
.collect();
69+
let owner_references_equal = self
70+
.owner_references()
71+
.iter()
72+
.all(|self_ref| owner_uids.contains(&self_ref.uid));
73+
74+
!annotations_equal || !labels_equal || !owner_references_equal
75+
}
76+
}

src/api/fleet_cluster_registration_token.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ use fleet_api_rs::fleet_cluster_registration_token::{
22
ClusterRegistrationTokenSpec, ClusterRegistrationTokenStatus,
33
};
44
use kube::{
5-
api::{ObjectMeta, TypeMeta},
65
Resource,
6+
api::{ObjectMeta, TypeMeta},
77
};
88
use serde::{Deserialize, Serialize};
99

10+
use crate::api::comparable::ResourceDiff;
11+
1012
#[derive(Resource, Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
1113
#[resource(inherit = fleet_api_rs::fleet_cluster_registration_token::ClusterRegistrationToken)]
1214
pub struct ClusterRegistrationToken {
@@ -16,3 +18,9 @@ pub struct ClusterRegistrationToken {
1618
pub spec: ClusterRegistrationTokenSpec,
1719
pub status: Option<ClusterRegistrationTokenStatus>,
1820
}
21+
22+
impl ResourceDiff for ClusterRegistrationToken {
23+
fn diff(&self, other: &Self) -> bool {
24+
self.spec != other.spec
25+
}
26+
}

0 commit comments

Comments
 (0)