Skip to content

Commit 938a947

Browse files
chore: backport 3/5/2026 (#1265)
2 parents 1ee8850 + c92d4c3 commit 938a947

14 files changed

+1771
-28
lines changed

apis/placement/v1beta1/clusterresourceplacement_types.go

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ type ClusterResourcePlacement struct {
124124
}
125125

126126
// PlacementSpec defines the desired state of ClusterResourcePlacement and ResourcePlacement.
127+
// +kubebuilder:validation:XValidation:rule="size(self.resourceSelectors.filter(x, x.kind == 'Namespace' && x.group == \"\" && x.version == 'v1' && has(x.selectionScope) && x.selectionScope == 'NamespaceWithResourceSelectors')) <= 1",message="only one namespace selector with NamespaceWithResourceSelectors mode is allowed"
128+
// +kubebuilder:validation:XValidation:rule="size(self.resourceSelectors.filter(x, x.kind == 'Namespace' && x.group == \"\" && x.version == 'v1' && has(x.selectionScope) && x.selectionScope == 'NamespaceWithResourceSelectors')) == 0 || (size(self.resourceSelectors.filter(x, x.kind == 'Namespace' && x.group == \"\" && x.version == 'v1' && has(x.selectionScope) && x.selectionScope == 'NamespaceWithResourceSelectors' && has(x.name) && size(x.name) > 0 && !has(x.labelSelector))) == 1)",message="namespace selector with NamespaceWithResourceSelectors mode must select by name (not by label)"
129+
// +kubebuilder:validation:XValidation:rule="size(self.resourceSelectors.filter(x, x.kind == 'Namespace' && x.group == \"\" && x.version == 'v1' && has(x.selectionScope) && x.selectionScope == 'NamespaceWithResourceSelectors')) == 0 || size(self.resourceSelectors.filter(x, x.kind == 'Namespace' && x.group == \"\" && x.version == 'v1')) == 1",message="when using NamespaceWithResourceSelectors mode, only one namespace selector is allowed (cannot mix with other namespace selectors)"
127130
type PlacementSpec struct {
128131
// ResourceSelectors is an array of selectors used to select cluster scoped resources. The selectors are `ORed`.
129132
// You can have 1-100 selectors.
@@ -185,7 +188,32 @@ type ResourceSelectorTerm struct {
185188
Version string `json:"version"`
186189

187190
// Kind of the to be selected resource.
188-
// Note: When `Kind` is `namespace`, by default ALL the resources under the selected namespaces are selected.
191+
//
192+
// Special behavior when Kind is `namespace` (ClusterResourcePlacement only):
193+
// Note: ResourcePlacement cannot select namespaces since it is namespace-scoped and selects resources within a namespace.
194+
//
195+
// For ClusterResourcePlacement, you can use SelectionScope to control what gets selected:
196+
// - NamespaceOnly: Only the namespace object itself
197+
// - NamespaceWithResources: The namespace AND all resources within it (default)
198+
// - NamespaceWithResourceSelectors: The namespace AND resources specified by additional selectors
199+
//
200+
// When SelectionScope is NamespaceWithResourceSelectors, you can define additional ResourceSelectorTerms
201+
// (after the namespace selector) to specify which resources to include. These additional selectors can
202+
// target both namespace-scoped resources (within the selected namespace) and cluster-scoped resources.
203+
//
204+
// Important requirements for NamespaceWithResourceSelectors mode:
205+
// - Exactly one namespace selector with this mode is allowed
206+
// - The namespace selector must select by name (not by label)
207+
// - Only one namespace selector is allowed when using this mode (cannot mix with other namespace selectors)
208+
// - All requirements are validated via CEL at API validation time
209+
// - If the selected namespace is deleted after CRP creation, the controller will report an error condition
210+
//
211+
// Example using NamespaceWithResourceSelectors:
212+
// - Namespace selector: {Group: "", Version: "v1", Kind: "Namespace", Name: "prod", SelectionScope: "NamespaceWithResourceSelectors"}
213+
// - Additional selector: {Group: "apps", Version: "v1", Kind: "Deployment", LabelSelector: {app: "frontend"}}
214+
// - Third selector: {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", Name: "admin"}
215+
// This selects: the "prod" namespace, all Deployments with label app=frontend in "prod", and the "admin" ClusterRole.
216+
//
189217
// +kubebuilder:validation:Required
190218
Kind string `json:"kind"`
191219

@@ -202,21 +230,80 @@ type ResourceSelectorTerm struct {
202230
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
203231

204232
// SelectionScope defines the scope of resource selections when the Kind is `namespace`.
205-
// +kubebuilder:validation:Enum=NamespaceOnly;NamespaceWithResources
233+
// This field is only applicable when Kind is "Namespace" and is ignored for other resource kinds.
234+
// See the Kind field documentation for detailed examples and usage patterns.
235+
// +kubebuilder:validation:Enum=NamespaceOnly;NamespaceWithResources;NamespaceWithResourceSelectors
206236
// +kubebuilder:default=NamespaceWithResources
207237
// +kubebuilder:validation:Optional
208238
SelectionScope SelectionScope `json:"selectionScope,omitempty"`
209239
}
210240

211-
// SelectionScope defines the scope of resource selections.
241+
// SelectionScope defines the scope of resource selections when selecting namespaces.
242+
// This only applies when a ResourceSelectorTerm has Kind="Namespace".
212243
type SelectionScope string
213244

214245
const (
215-
// NamespaceOnly means only the namespace itself is selected.
246+
// NamespaceOnly means only the namespace object itself is selected.
247+
//
248+
// Use case: When you want to create/manage only the namespace without any resources inside it.
249+
// Example: Creating a namespace with specific labels/annotations on member clusters.
216250
NamespaceOnly SelectionScope = "NamespaceOnly"
217251

218-
// NamespaceWithResources means all the resources under the namespace including namespace itself are selected.
252+
// NamespaceWithResources means the namespace and ALL resources within it are selected.
253+
// This is the default behavior for backward compatibility.
254+
//
255+
// Use case: When you want to replicate an entire namespace with all its contents to member clusters.
256+
// Example: Copying a complete application stack (deployments, services, configmaps, etc.) across clusters.
257+
//
258+
// Note: This is the default value. When you select a namespace without specifying SelectionScope,
259+
// this mode is used automatically.
219260
NamespaceWithResources SelectionScope = "NamespaceWithResources"
261+
262+
// NamespaceWithResourceSelectors allows fine-grained selection of specific resources within a namespace.
263+
// The namespace itself is always selected, and you can optionally specify which resources to include
264+
// by adding additional ResourceSelectorTerm entries after the namespace selector.
265+
//
266+
// Use cases:
267+
// 1. Select only specific resource types from a namespace (e.g., only Deployments and Services)
268+
// 2. Select resources matching certain labels within a namespace
269+
// 3. Include specific cluster-scoped resources along with namespace-scoped resources
270+
//
271+
// How "additional selectors" work:
272+
// - Exactly one namespace selector with NamespaceWithResourceSelectors mode is required
273+
// - This selector must select a namespace by name (label selectors not allowed)
274+
// - ADDITIONAL selectors specify which resources to include:
275+
// - Namespace-scoped resources are filtered to only those within the selected namespace
276+
// - Cluster-scoped resources are included as specified (not limited to the namespace)
277+
// - If no additional selectors are provided, only the namespace object itself is selected
278+
//
279+
// Example 1 - Select specific deployments from a namespace:
280+
// Selector 1: {Group: "", Version: "v1", Kind: "Namespace", Name: "production", SelectionScope: "NamespaceWithResourceSelectors"}
281+
// Selector 2: {Group: "apps", Version: "v1", Kind: "Deployment", LabelSelector: {tier: "frontend"}}
282+
// Result: The "production" namespace + all Deployments labeled tier=frontend within "production"
283+
//
284+
// Example 2 - Select namespace with multiple resource types:
285+
// Selector 1: {Group: "", Version: "v1", Kind: "Namespace", Name: "app", SelectionScope: "NamespaceWithResourceSelectors"}
286+
// Selector 2: {Group: "apps", Version: "v1", Kind: "Deployment"}
287+
// Selector 3: {Group: "", Version: "v1", Kind: "Service"}
288+
// Result: The "app" namespace + ALL Deployments and Services within "app"
289+
//
290+
// Example 3 - Include cluster-scoped resources:
291+
// Selector 1: {Group: "", Version: "v1", Kind: "Namespace", Name: "app", SelectionScope: "NamespaceWithResourceSelectors"}
292+
// Selector 2: {Group: "apps", Version: "v1", Kind: "Deployment"}
293+
// Selector 3: {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", Name: "app-admin"}
294+
// Result: The "app" namespace + ALL Deployments in "app" + the "app-admin" ClusterRole
295+
//
296+
// Important constraints:
297+
// - Exactly ONE namespace selector with NamespaceWithResourceSelectors mode is allowed
298+
// - The namespace selector must select by name (label selectors not allowed)
299+
// - Only ONE namespace selector total is allowed when using this mode (cannot mix with other namespace selectors)
300+
// - All constraints are enforced via CEL at API validation time
301+
//
302+
// Runtime behavior:
303+
// - If the selected namespace is deleted after the CRP is created, the controller will detect this during
304+
// the next reconciliation and report an error condition in the CRP status
305+
// - The CRP will transition to a failed state until the namespace is recreated or the CRP is updated
306+
NamespaceWithResourceSelectors SelectionScope = "NamespaceWithResourceSelectors"
220307
)
221308

222309
// PlacementPolicy contains the rules to select target member clusters to place the selected resources.

config/crd/bases/placement.kubernetes-fleet.io_clusterresourceoverrides.yaml

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,31 @@ spec:
432432
kind:
433433
description: |-
434434
Kind of the to be selected resource.
435-
Note: When `Kind` is `namespace`, by default ALL the resources under the selected namespaces are selected.
435+
436+
Special behavior when Kind is `namespace` (ClusterResourcePlacement only):
437+
Note: ResourcePlacement cannot select namespaces since it is namespace-scoped and selects resources within a namespace.
438+
439+
For ClusterResourcePlacement, you can use SelectionScope to control what gets selected:
440+
- NamespaceOnly: Only the namespace object itself
441+
- NamespaceWithResources: The namespace AND all resources within it (default)
442+
- NamespaceWithResourceSelectors: The namespace AND resources specified by additional selectors
443+
444+
When SelectionScope is NamespaceWithResourceSelectors, you can define additional ResourceSelectorTerms
445+
(after the namespace selector) to specify which resources to include. These additional selectors can
446+
target both namespace-scoped resources (within the selected namespace) and cluster-scoped resources.
447+
448+
Important requirements for NamespaceWithResourceSelectors mode:
449+
- Exactly one namespace selector with this mode is allowed
450+
- The namespace selector must select by name (not by label)
451+
- Only one namespace selector is allowed when using this mode (cannot mix with other namespace selectors)
452+
- All requirements are validated via CEL at API validation time
453+
- If the selected namespace is deleted after CRP creation, the controller will report an error condition
454+
455+
Example using NamespaceWithResourceSelectors:
456+
- Namespace selector: {Group: "", Version: "v1", Kind: "Namespace", Name: "prod", SelectionScope: "NamespaceWithResourceSelectors"}
457+
- Additional selector: {Group: "apps", Version: "v1", Kind: "Deployment", LabelSelector: {app: "frontend"}}
458+
- Third selector: {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", Name: "admin"}
459+
This selects: the "prod" namespace, all Deployments with label app=frontend in "prod", and the "admin" ClusterRole.
436460
type: string
437461
labelSelector:
438462
description: |-
@@ -487,11 +511,14 @@ spec:
487511
type: string
488512
selectionScope:
489513
default: NamespaceWithResources
490-
description: SelectionScope defines the scope of resource selections
491-
when the Kind is `namespace`.
514+
description: |-
515+
SelectionScope defines the scope of resource selections when the Kind is `namespace`.
516+
This field is only applicable when Kind is "Namespace" and is ignored for other resource kinds.
517+
See the Kind field documentation for detailed examples and usage patterns.
492518
enum:
493519
- NamespaceOnly
494520
- NamespaceWithResources
521+
- NamespaceWithResourceSelectors
495522
type: string
496523
version:
497524
description: Version of the to be selected resource.
@@ -805,7 +832,31 @@ spec:
805832
kind:
806833
description: |-
807834
Kind of the to be selected resource.
808-
Note: When `Kind` is `namespace`, by default ALL the resources under the selected namespaces are selected.
835+
836+
Special behavior when Kind is `namespace` (ClusterResourcePlacement only):
837+
Note: ResourcePlacement cannot select namespaces since it is namespace-scoped and selects resources within a namespace.
838+
839+
For ClusterResourcePlacement, you can use SelectionScope to control what gets selected:
840+
- NamespaceOnly: Only the namespace object itself
841+
- NamespaceWithResources: The namespace AND all resources within it (default)
842+
- NamespaceWithResourceSelectors: The namespace AND resources specified by additional selectors
843+
844+
When SelectionScope is NamespaceWithResourceSelectors, you can define additional ResourceSelectorTerms
845+
(after the namespace selector) to specify which resources to include. These additional selectors can
846+
target both namespace-scoped resources (within the selected namespace) and cluster-scoped resources.
847+
848+
Important requirements for NamespaceWithResourceSelectors mode:
849+
- Exactly one namespace selector with this mode is allowed
850+
- The namespace selector must select by name (not by label)
851+
- Only one namespace selector is allowed when using this mode (cannot mix with other namespace selectors)
852+
- All requirements are validated via CEL at API validation time
853+
- If the selected namespace is deleted after CRP creation, the controller will report an error condition
854+
855+
Example using NamespaceWithResourceSelectors:
856+
- Namespace selector: {Group: "", Version: "v1", Kind: "Namespace", Name: "prod", SelectionScope: "NamespaceWithResourceSelectors"}
857+
- Additional selector: {Group: "apps", Version: "v1", Kind: "Deployment", LabelSelector: {app: "frontend"}}
858+
- Third selector: {Group: "rbac.authorization.k8s.io", Version: "v1", Kind: "ClusterRole", Name: "admin"}
859+
This selects: the "prod" namespace, all Deployments with label app=frontend in "prod", and the "admin" ClusterRole.
809860
type: string
810861
labelSelector:
811862
description: |-
@@ -860,11 +911,14 @@ spec:
860911
type: string
861912
selectionScope:
862913
default: NamespaceWithResources
863-
description: SelectionScope defines the scope of resource selections
864-
when the Kind is `namespace`.
914+
description: |-
915+
SelectionScope defines the scope of resource selections when the Kind is `namespace`.
916+
This field is only applicable when Kind is "Namespace" and is ignored for other resource kinds.
917+
See the Kind field documentation for detailed examples and usage patterns.
865918
enum:
866919
- NamespaceOnly
867920
- NamespaceWithResources
921+
- NamespaceWithResourceSelectors
868922
type: string
869923
version:
870924
description: Version of the to be selected resource.

0 commit comments

Comments
 (0)