Skip to content

Commit 00f4502

Browse files
committed
fix: reject image items in a (Cluster)ReplicatedImageSet spec.filter
1 parent 9e135db commit 00f4502

9 files changed

Lines changed: 77 additions & 15 deletions

File tree

api/kuik/v1alpha1/clusterreplicatedimageset_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55
)
66

77
// ClusterReplicatedImageSetSpec defines the desired state of ClusterReplicatedImageSet.
8+
// +kubebuilder:validation:XValidation:rule="!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i, !has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i, !has(i.image))))",message="spec.filter image items are not supported on ClusterReplicatedImageSet; image selection is per-upstream via spec.upstreams[].imageFilter"
89
type ClusterReplicatedImageSetSpec struct {
910
ReplicatedImageSetBase `json:",inline"`
1011

11-
// Filter selects which pods, namespaces and images this resource applies
12-
// to. It replaces the deprecated imageFilter.
12+
// Filter selects which pods and namespaces this resource applies to (label,
13+
// annotation and namespace dimensions). The image dimension is not supported
14+
// here: image selection is per-upstream via spec.upstreams[].imageFilter.
1315
// +optional
1416
Filter ClusterFilter `json:"filter,omitempty"`
1517
}

api/kuik/v1alpha1/replicatedimageset_types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ type ReplicatedImageSetBase struct {
2020
}
2121

2222
// ReplicatedImageSetSpec defines the desired state of ReplicatedImageSet.
23+
// +kubebuilder:validation:XValidation:rule="!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i, !has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i, !has(i.image))))",message="spec.filter image items are not supported on ReplicatedImageSet; image selection is per-upstream via spec.upstreams[].imageFilter"
2324
type ReplicatedImageSetSpec struct {
2425
ReplicatedImageSetBase `json:",inline"`
2526

26-
// Filter selects which pods and images this resource applies to. It
27-
// replaces the deprecated imageFilter.
27+
// Filter selects which pods this resource applies to (label and annotation
28+
// dimensions). The image dimension is not supported here: image selection is
29+
// per-upstream via spec.upstreams[].imageFilter.
2830
// +optional
2931
Filter Filter `json:"filter,omitempty"`
3032
}

config/crd/bases/kuik.enix.io_clusterreplicatedimagesets.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ spec:
4545
properties:
4646
filter:
4747
description: |-
48-
Filter selects which pods, namespaces and images this resource applies
49-
to. It replaces the deprecated imageFilter.
48+
Filter selects which pods and namespaces this resource applies to (label,
49+
annotation and namespace dimensions). The image dimension is not supported
50+
here: image selection is per-upstream via spec.upstreams[].imageFilter.
5051
properties:
5152
exclude:
5253
items:
@@ -193,6 +194,12 @@ spec:
193194
maxItems: 32
194195
type: array
195196
type: object
197+
x-kubernetes-validations:
198+
- message: spec.filter image items are not supported on ClusterReplicatedImageSet;
199+
image selection is per-upstream via spec.upstreams[].imageFilter
200+
rule: '!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i,
201+
!has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i,
202+
!has(i.image))))'
196203
status:
197204
description: ClusterReplicatedImageSetStatus defines the observed state
198205
of ClusterReplicatedImageSet.

config/crd/bases/kuik.enix.io_replicatedimagesets.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ spec:
4444
properties:
4545
filter:
4646
description: |-
47-
Filter selects which pods and images this resource applies to. It
48-
replaces the deprecated imageFilter.
47+
Filter selects which pods this resource applies to (label and annotation
48+
dimensions). The image dimension is not supported here: image selection is
49+
per-upstream via spec.upstreams[].imageFilter.
4950
properties:
5051
exclude:
5152
items:
@@ -188,6 +189,12 @@ spec:
188189
maxItems: 32
189190
type: array
190191
type: object
192+
x-kubernetes-validations:
193+
- message: spec.filter image items are not supported on ReplicatedImageSet;
194+
image selection is per-upstream via spec.upstreams[].imageFilter
195+
rule: '!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i,
196+
!has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i,
197+
!has(i.image))))'
191198
status:
192199
description: ReplicatedImageSetStatus defines the observed state of ReplicatedImageSet.
193200
type: object

helm/kube-image-keeper/crds/kuik.enix.io_clusterreplicatedimagesets.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ spec:
4444
properties:
4545
filter:
4646
description: |-
47-
Filter selects which pods, namespaces and images this resource applies
48-
to. It replaces the deprecated imageFilter.
47+
Filter selects which pods and namespaces this resource applies to (label,
48+
annotation and namespace dimensions). The image dimension is not supported
49+
here: image selection is per-upstream via spec.upstreams[].imageFilter.
4950
properties:
5051
exclude:
5152
items:
@@ -192,6 +193,12 @@ spec:
192193
maxItems: 32
193194
type: array
194195
type: object
196+
x-kubernetes-validations:
197+
- message: spec.filter image items are not supported on ClusterReplicatedImageSet;
198+
image selection is per-upstream via spec.upstreams[].imageFilter
199+
rule: '!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i,
200+
!has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i,
201+
!has(i.image))))'
195202
status:
196203
description: ClusterReplicatedImageSetStatus defines the observed state
197204
of ClusterReplicatedImageSet.

helm/kube-image-keeper/crds/kuik.enix.io_replicatedimagesets.yaml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ spec:
4343
properties:
4444
filter:
4545
description: |-
46-
Filter selects which pods and images this resource applies to. It
47-
replaces the deprecated imageFilter.
46+
Filter selects which pods this resource applies to (label and annotation
47+
dimensions). The image dimension is not supported here: image selection is
48+
per-upstream via spec.upstreams[].imageFilter.
4849
properties:
4950
exclude:
5051
items:
@@ -187,6 +188,12 @@ spec:
187188
maxItems: 32
188189
type: array
189190
type: object
191+
x-kubernetes-validations:
192+
- message: spec.filter image items are not supported on ReplicatedImageSet;
193+
image selection is per-upstream via spec.upstreams[].imageFilter
194+
rule: '!has(self.filter) || ((!has(self.filter.include) || self.filter.include.all(i,
195+
!has(i.image))) && (!has(self.filter.exclude) || self.filter.exclude.all(i,
196+
!has(i.image))))'
190197
status:
191198
description: ReplicatedImageSetStatus defines the observed state of ReplicatedImageSet.
192199
type: object

internal/controller/kuik/mirror_reconciler_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,36 @@ var _ = Describe("CRD admission validation", func() {
274274
}
275275
Expect(k8sClient.Create(ctx, ism)).To(MatchError(ContainSubstring("mutually exclusive")))
276276
})
277+
278+
It("rejects an image item in a ReplicatedImageSet spec.filter (image selection is per-upstream)", func() {
279+
ris := &kuikv1alpha1.ReplicatedImageSet{
280+
ObjectMeta: metav1.ObjectMeta{Name: "ris-image-filter", Namespace: "default"},
281+
Spec: kuikv1alpha1.ReplicatedImageSetSpec{
282+
Filter: kuikv1alpha1.Filter{Include: []kuikv1alpha1.FilterItem{{Image: "nginx.*"}}},
283+
},
284+
}
285+
Expect(k8sClient.Create(ctx, ris)).To(MatchError(ContainSubstring("image selection is per-upstream")))
286+
})
287+
288+
It("rejects an image item in a ClusterReplicatedImageSet spec.filter", func() {
289+
cris := &kuikv1alpha1.ClusterReplicatedImageSet{
290+
ObjectMeta: metav1.ObjectMeta{Name: "cris-image-filter"},
291+
Spec: kuikv1alpha1.ClusterReplicatedImageSetSpec{
292+
Filter: kuikv1alpha1.ClusterFilter{Exclude: []kuikv1alpha1.ClusterFilterItem{{FilterItem: kuikv1alpha1.FilterItem{Image: "nginx.*"}}}},
293+
},
294+
}
295+
Expect(k8sClient.Create(ctx, cris)).To(MatchError(ContainSubstring("image selection is per-upstream")))
296+
})
297+
298+
It("accepts a label item in a ReplicatedImageSet spec.filter", func() {
299+
ris := &kuikv1alpha1.ReplicatedImageSet{
300+
ObjectMeta: metav1.ObjectMeta{Name: "ris-label-filter", Namespace: "default"},
301+
Spec: kuikv1alpha1.ReplicatedImageSetSpec{
302+
Filter: kuikv1alpha1.Filter{Include: []kuikv1alpha1.FilterItem{{Label: "app=foo"}}},
303+
},
304+
}
305+
Expect(k8sClient.Create(ctx, ris)).To(Succeed())
306+
})
277307
})
278308

279309
// conflictOnFirstUpdateClient wraps a client.Client and returns a conflict

website/src/content/docs/crds.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This is particularly useful for multi-homed projects (e.g., Thanos, Prometheus,
2525
| Field | Required | Description |
2626
| --- | --- | --- |
2727
| `spec.priority` | | Controls ordering of alternatives relative to the original image and other CRs. Negative values place alternatives before the original image; positive values place them after. Default is `0` (original image first). |
28-
| `spec.filter` | | Selects which pods and namespaces (cluster-scoped only) this resource applies to. The `image` dimension is **not** supported here and is **ignored**: image selection is done per-upstream via `spec.upstreams[].imageFilter`. See [Resource filtering](/resource-filtering/). |
28+
| `spec.filter` | | Selects which pods and namespaces (cluster-scoped only) this resource applies to. The `image` dimension is **not** supported here and an `image` item is **rejected at admission**: image selection is done per-upstream via `spec.upstreams[].imageFilter`. See [Resource filtering](/resource-filtering/). |
2929
| `spec.upstreams[]` | | List of upstream image sources that should be considered equivalent. |
3030
| `spec.upstreams[].registry` || Registry where the upstream image is hosted (e.g. `docker.io`, `quay.io`). |
3131
| `spec.upstreams[].path` || Path identifying the image in the registry (e.g. `/thanosio/thanos`). |

website/src/content/docs/resource-filtering.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Items are grouped by dimension (all `image` items together, all `label` items to
3939
This makes the filter a faithful superset of the per-dimension filters it replaces: a resource applies to an `(pod, image)` pair when the image passes the image dimension, the namespace passes the namespace dimension, and the pod passes the label and annotation dimensions.
4040

4141
:::note
42-
`(Cluster)ReplicatedImageSet` is the exception: it ignores the `image` dimension of `spec.filter` and selects images per-upstream instead. See [Per-upstream image filtering](#per-upstream-image-filtering-on-clusterreplicatedimageset).
42+
`(Cluster)ReplicatedImageSet` is the exception: it has no `image` dimension in `spec.filter` (an `image` item is rejected at admission) and selects images per-upstream instead. See [Per-upstream image filtering](#per-upstream-image-filtering-on-clusterreplicatedimageset).
4343
:::
4444

4545
:::caution
@@ -71,7 +71,7 @@ The operator also exposes a cluster-wide skip list (`skipLabels` / `skipAnnotati
7171

7272
`(Cluster)ReplicatedImageSet` selects images **per upstream** via `spec.upstreams[].imageFilter`, which chooses the images each upstream entry replicates. That field is unrelated to the deprecated top-level `imageFilter` below and is **not** affected by its deprecation.
7373

74-
Because image selection is per-upstream, the **`image` dimension of the top-level `spec.filter` is not supported and is ignored** on `(Cluster)ReplicatedImageSet`: any `image` `include` / `exclude` items there have no effect. Only the `label`, `annotation` and (cluster-scoped) `namespace` dimensions of `spec.filter` apply, as a resource-wide pod / namespace gate.
74+
Because image selection is per-upstream, the **`image` dimension of the top-level `spec.filter` is not supported** on `(Cluster)ReplicatedImageSet`: an `image` `include` / `exclude` item is rejected at admission. Only the `label`, `annotation` and (cluster-scoped) `namespace` dimensions of `spec.filter` apply, as a resource-wide pod / namespace gate.
7575

7676
## Examples
7777

0 commit comments

Comments
 (0)