@@ -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)"
127130type 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".
212243type SelectionScope string
213244
214245const (
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.
0 commit comments