diff --git a/api/v1/config/crd/eno.azure.io_compositions.yaml b/api/v1/config/crd/eno.azure.io_compositions.yaml index c6fc4d42..196db13d 100644 --- a/api/v1/config/crd/eno.azure.io_compositions.yaml +++ b/api/v1/config/crd/eno.azure.io_compositions.yaml @@ -109,7 +109,59 @@ spec: properties: name: type: string + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic type: object + x-kubernetes-validations: + - message: at least one of name or labelSelector must be set + rule: has(self.name) || has(self.labelSelector) type: object status: properties: diff --git a/api/v1/config/crd/eno.azure.io_symphonies.yaml b/api/v1/config/crd/eno.azure.io_symphonies.yaml index 7242466c..15d49ea8 100644 --- a/api/v1/config/crd/eno.azure.io_symphonies.yaml +++ b/api/v1/config/crd/eno.azure.io_symphonies.yaml @@ -161,7 +161,59 @@ spec: properties: name: type: string + labelSelector: + description: |- + A label selector is a label query over a set of resources. The result of matchLabels and + matchExpressions are ANDed. An empty label selector matches all objects. A null + label selector matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic type: object + x-kubernetes-validations: + - message: at least one of name or labelSelector must be set + rule: has(self.name) || has(self.labelSelector) type: object type: array type: object diff --git a/api/v1/synthesizer.go b/api/v1/synthesizer.go index 3941d181..04055969 100644 --- a/api/v1/synthesizer.go +++ b/api/v1/synthesizer.go @@ -70,6 +70,8 @@ type PodOverrides struct { type SynthesizerStatus struct { } +// +kubebuilder:validation:XValidation:rule="has(self.name) || has(self.labelSelector)",message="at least one of name or labelSelector must be set" type SynthesizerRef struct { - Name string `json:"name,omitempty"` + Name string `json:"name,omitempty"` + LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"` } diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 5a8d0e02..277bc1f1 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -88,7 +88,7 @@ func (in *CompositionList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CompositionSpec) DeepCopyInto(out *CompositionSpec) { *out = *in - out.Synthesizer = in.Synthesizer + in.Synthesizer.DeepCopyInto(&out.Synthesizer) if in.Bindings != nil { in, out := &in.Bindings, &out.Bindings *out = make([]Binding, len(*in)) @@ -743,6 +743,11 @@ func (in *SynthesizerList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SynthesizerRef) DeepCopyInto(out *SynthesizerRef) { *out = *in + if in.LabelSelector != nil { + in, out := &in.LabelSelector, &out.LabelSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SynthesizerRef. @@ -823,7 +828,7 @@ func (in *Variation) DeepCopyInto(out *Variation) { (*out)[key] = val } } - out.Synthesizer = in.Synthesizer + in.Synthesizer.DeepCopyInto(&out.Synthesizer) if in.Bindings != nil { in, out := &in.Bindings, &out.Bindings *out = make([]Binding, len(*in)) diff --git a/internal/controllers/liveness/namespace_test.go b/internal/controllers/liveness/namespace_test.go index 1b1f8f60..77e337c9 100644 --- a/internal/controllers/liveness/namespace_test.go +++ b/internal/controllers/liveness/namespace_test.go @@ -29,6 +29,7 @@ func TestMissingNamespace(t *testing.T) { comp := &apiv1.Composition{} comp.Name = "test-composition" comp.Finalizers = []string{"eno.azure.io/cleanup"} + comp.Spec.Synthesizer = apiv1.SynthesizerRef{Name: "some-synthesizer"} testMissingNamespace(t, comp) }) diff --git a/internal/controllers/resourceslice/integration_test.go b/internal/controllers/resourceslice/integration_test.go index ea67289d..069ede0d 100644 --- a/internal/controllers/resourceslice/integration_test.go +++ b/internal/controllers/resourceslice/integration_test.go @@ -27,6 +27,7 @@ func TestResourceSliceLifecycle(t *testing.T) { comp := &apiv1.Composition{} comp.Name = "test-1" comp.Namespace = "default" + comp.Spec.Synthesizer = apiv1.SynthesizerRef{Name: "some-synthesizer"} require.NoError(t, cli.Create(ctx, comp)) testutil.Eventually(t, func() bool { diff --git a/internal/manager/manager_test.go b/internal/manager/manager_test.go index ef2cc997..dc5cf4be 100644 --- a/internal/manager/manager_test.go +++ b/internal/manager/manager_test.go @@ -200,18 +200,21 @@ func TestReconcilerLimitedScope(t *testing.T) { comp1.Name = "in-namespace-with-labels" comp1.Namespace = ns.Name comp1.Labels = map[string]string{"testkey": "testval"} + comp1.Spec.Synthesizer = apiv1.SynthesizerRef{Name: "some-synthesizer"} err = mgr.GetClient().Create(ctx, comp1) require.NoError(t, err) comp2 := &apiv1.Composition{} comp2.Name = "in-namespace-no-labels" comp2.Namespace = ns.Name + comp2.Spec.Synthesizer = apiv1.SynthesizerRef{Name: "some-synthesizer"} err = mgr.GetClient().Create(ctx, comp2) require.NoError(t, err) comp3 := &apiv1.Composition{} comp3.Name = "in-different-namespace" comp3.Namespace = "default" + comp3.Spec.Synthesizer = apiv1.SynthesizerRef{Name: "some-synthesizer"} err = mgr.GetClient().Create(ctx, comp3) require.NoError(t, err)