diff --git a/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml b/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml index 8d7413d4..0c68c60d 100644 --- a/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml +++ b/deploy/charts/trust-manager/templates/crd-trust.cert-manager.io_bundles.yaml @@ -288,14 +288,47 @@ spec: NamespaceSelector will, if set, only sync the target resource in Namespaces which match the selector. 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 matches on the set of labels that must be present on a - Namespace for the Bundle target to be synced there. + 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 secret: description: |- Secret is the target Secret that all Bundle source data will be synced to. diff --git a/docs/api/api.md b/docs/api/api.md index 053e7744..16a1ee31 100644 --- a/docs/api/api.md +++ b/docs/api/api.md @@ -45,9 +45,6 @@ import "github.com/cert-manager/trust-manager/pkg/apis/trust/v1alpha1" - [type KeySelector](<#KeySelector>) - [func \(in \*KeySelector\) DeepCopy\(\) \*KeySelector](<#KeySelector.DeepCopy>) - [func \(in \*KeySelector\) DeepCopyInto\(out \*KeySelector\)](<#KeySelector.DeepCopyInto>) -- [type NamespaceSelector](<#NamespaceSelector>) - - [func \(in \*NamespaceSelector\) DeepCopy\(\) \*NamespaceSelector](<#NamespaceSelector.DeepCopy>) - - [func \(in \*NamespaceSelector\) DeepCopyInto\(out \*NamespaceSelector\)](<#NamespaceSelector.DeepCopyInto>) - [type PKCS12](<#PKCS12>) - [func \(in \*PKCS12\) DeepCopy\(\) \*PKCS12](<#PKCS12.DeepCopy>) - [func \(in \*PKCS12\) DeepCopyInto\(out \*PKCS12\)](<#PKCS12.DeepCopyInto>) @@ -208,7 +205,7 @@ func (in *Bundle) DeepCopyObject() runtime.Object DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -## type [BundleCondition]() +## type [BundleCondition]() BundleCondition contains condition information for a Bundle. @@ -403,7 +400,7 @@ func (in *BundleSpec) DeepCopyInto(out *BundleSpec) DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non\-nil. -## type [BundleStatus]() +## type [BundleStatus]() BundleStatus defines the observed state of the Bundle. @@ -468,7 +465,7 @@ type BundleTarget struct { // NamespaceSelector will, if set, only sync the target resource in // Namespaces which match the selector. // +optional - NamespaceSelector *NamespaceSelector `json:"namespaceSelector,omitempty"` + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` } ``` @@ -527,7 +524,7 @@ func (in *JKS) DeepCopyInto(out *JKS) DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non\-nil. -## type [KeySelector]() +## type [KeySelector]() KeySelector is a reference to a key for some map data object. @@ -557,38 +554,6 @@ func (in *KeySelector) DeepCopyInto(out *KeySelector) DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non\-nil. - -## type [NamespaceSelector]() - -NamespaceSelector defines selectors to match on Namespaces. - -```go -type NamespaceSelector struct { - // MatchLabels matches on the set of labels that must be present on a - // Namespace for the Bundle target to be synced there. - // +optional - MatchLabels map[string]string `json:"matchLabels,omitempty"` -} -``` - - -### func \(\*NamespaceSelector\) [DeepCopy]() - -```go -func (in *NamespaceSelector) DeepCopy() *NamespaceSelector -``` - -DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector. - - -### func \(\*NamespaceSelector\) [DeepCopyInto]() - -```go -func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) -``` - -DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non\-nil. - ## type [PKCS12]() @@ -607,7 +572,7 @@ type PKCS12 struct { ``` -### func \(\*PKCS12\) [DeepCopy]() +### func \(\*PKCS12\) [DeepCopy]() ```go func (in *PKCS12) DeepCopy() *PKCS12 @@ -616,7 +581,7 @@ func (in *PKCS12) DeepCopy() *PKCS12 DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PKCS12. -### func \(\*PKCS12\) [DeepCopyInto]() +### func \(\*PKCS12\) [DeepCopyInto]() ```go func (in *PKCS12) DeepCopyInto(out *PKCS12) @@ -625,7 +590,7 @@ func (in *PKCS12) DeepCopyInto(out *PKCS12) DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non\-nil. -## type [SourceObjectKeySelector]() +## type [SourceObjectKeySelector]() SourceObjectKeySelector is a reference to a source object and its \`data\` key\(s\) in the trust Namespace. \+structType=atomic @@ -655,7 +620,7 @@ type SourceObjectKeySelector struct { ``` -### func \(\*SourceObjectKeySelector\) [DeepCopy]() +### func \(\*SourceObjectKeySelector\) [DeepCopy]() ```go func (in *SourceObjectKeySelector) DeepCopy() *SourceObjectKeySelector @@ -664,7 +629,7 @@ func (in *SourceObjectKeySelector) DeepCopy() *SourceObjectKeySelector DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceObjectKeySelector. -### func \(\*SourceObjectKeySelector\) [DeepCopyInto]() +### func \(\*SourceObjectKeySelector\) [DeepCopyInto]() ```go func (in *SourceObjectKeySelector) DeepCopyInto(out *SourceObjectKeySelector) diff --git a/pkg/apis/trust/v1alpha1/types_bundle.go b/pkg/apis/trust/v1alpha1/types_bundle.go index 9613b7ea..e8e9d1cc 100644 --- a/pkg/apis/trust/v1alpha1/types_bundle.go +++ b/pkg/apis/trust/v1alpha1/types_bundle.go @@ -119,7 +119,7 @@ type BundleTarget struct { // NamespaceSelector will, if set, only sync the target resource in // Namespaces which match the selector. // +optional - NamespaceSelector *NamespaceSelector `json:"namespaceSelector,omitempty"` + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` } // AdditionalFormats specifies any additional formats to write to the target @@ -160,14 +160,6 @@ type PKCS12 struct { Password *string `json:"password,omitempty"` } -// NamespaceSelector defines selectors to match on Namespaces. -type NamespaceSelector struct { - // MatchLabels matches on the set of labels that must be present on a - // Namespace for the Bundle target to be synced there. - // +optional - MatchLabels map[string]string `json:"matchLabels,omitempty"` -} - // SourceObjectKeySelector is a reference to a source object and its `data` key(s) // in the trust Namespace. // +structType=atomic diff --git a/pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go index 6264b3ae..b6e43412 100644 --- a/pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/trust/v1alpha1/zz_generated.deepcopy.go @@ -230,7 +230,7 @@ func (in *BundleTarget) DeepCopyInto(out *BundleTarget) { } if in.NamespaceSelector != nil { in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(NamespaceSelector) + *out = new(v1.LabelSelector) (*in).DeepCopyInto(*out) } } @@ -281,28 +281,6 @@ func (in *KeySelector) DeepCopy() *KeySelector { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) { - *out = *in - if in.MatchLabels != nil { - in, out := &in.MatchLabels, &out.MatchLabels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector. -func (in *NamespaceSelector) DeepCopy() *NamespaceSelector { - if in == nil { - return nil - } - out := new(NamespaceSelector) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PKCS12) DeepCopyInto(out *PKCS12) { *out = *in diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go index 892eb3bb..506bbdfe 100644 --- a/pkg/bundle/bundle.go +++ b/pkg/bundle/bundle.go @@ -370,9 +370,12 @@ func (b *bundle) reconcileBundle(ctx context.Context, req ctrl.Request) (result func (b *bundle) bundleTargetNamespaceSelector(bundleObj *trustapi.Bundle) (labels.Selector, error) { nsSelector := bundleObj.Spec.Target.NamespaceSelector - if nsSelector == nil || nsSelector.MatchLabels == nil { + // LabelSelectorAsSelector returns a Selector selecting nothing if LabelSelector is nil, + // while our current default is to select everything. But this is subject to change. + // See https://github.com/cert-manager/trust-manager/issues/39 + if nsSelector == nil { return labels.Everything(), nil } - return metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: nsSelector.MatchLabels}) + return metav1.LabelSelectorAsSelector(nsSelector) } diff --git a/pkg/webhook/validation.go b/pkg/webhook/validation.go index c5f21059..84c369d6 100644 --- a/pkg/webhook/validation.go +++ b/pkg/webhook/validation.go @@ -22,7 +22,7 @@ import ( "strconv" "github.com/go-logr/logr" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -111,6 +111,9 @@ func (v *validator) validate(obj runtime.Object) (admission.Warnings, error) { if len(configMap.Key) > 0 && configMap.IncludeAllKeys { el = append(el, field.Invalid(path, fmt.Sprintf("key: %s, includeAllKeys: %t", configMap.Key, configMap.IncludeAllKeys), "source configMap key cannot be defined when includeAllKeys is true")) } + + errs := validation.ValidateLabelSelector(configMap.Selector, validation.LabelSelectorValidationOptions{}, path.Child("selector")) + el = append(el, errs...) } if secret := source.Secret; secret != nil { @@ -130,6 +133,9 @@ func (v *validator) validate(obj runtime.Object) (admission.Warnings, error) { if len(secret.Key) > 0 && secret.IncludeAllKeys { el = append(el, field.Invalid(path, fmt.Sprintf("key: %s, includeAllKeys: %t", secret.Key, secret.IncludeAllKeys), "source secret key cannot be defined when includeAllKeys is true")) } + + errs := validation.ValidateLabelSelector(secret.Selector, validation.LabelSelectorValidationOptions{}, path.Child("selector")) + el = append(el, errs...) } if source.InLine != nil { @@ -227,11 +233,8 @@ func (v *validator) validate(obj runtime.Object) (admission.Warnings, error) { } } - if nsSel := bundle.Spec.Target.NamespaceSelector; nsSel != nil && len(nsSel.MatchLabels) > 0 { - if _, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: nsSel.MatchLabels}); err != nil { - el = append(el, field.Invalid(path.Child("target", "namespaceSelector", "matchLabels"), nsSel.MatchLabels, err.Error())) - } - } + errs := validation.ValidateLabelSelector(bundle.Spec.Target.NamespaceSelector, validation.LabelSelectorValidationOptions{}, path.Child("target", "namespaceSelector")) + el = append(el, errs...) return warnings, el.ToAggregate() diff --git a/pkg/webhook/validation_test.go b/pkg/webhook/validation_test.go index 68fe2cb7..32e4ecdd 100644 --- a/pkg/webhook/validation_test.go +++ b/pkg/webhook/validation_test.go @@ -255,7 +255,7 @@ func Test_validate(t *testing.T) { }, Target: trustapi.BundleTarget{ ConfigMap: &trustapi.KeySelector{Key: "test-1"}, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"@@@@": ""}, }, }, @@ -270,7 +270,7 @@ func Test_validate(t *testing.T) { }, }, expErr: ptr.To(field.ErrorList{ - field.Invalid(field.NewPath("spec", "target", "namespaceSelector", "matchLabels"), map[string]string{"@@@@": ""}, `key: Invalid value: "@@@@": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`), + field.Invalid(field.NewPath("spec", "target", "namespaceSelector", "matchLabels"), "@@@@", `name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName', or 'my.name', or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`), }.ToAggregate().Error()), }, "a Bundle with a duplicate target JKS key should fail validation and return a denied response": { @@ -291,7 +291,7 @@ func Test_validate(t *testing.T) { ConfigMap: &trustapi.KeySelector{ Key: "bar", }, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, @@ -317,7 +317,7 @@ func Test_validate(t *testing.T) { ConfigMap: &trustapi.KeySelector{ Key: "bar", }, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, @@ -334,7 +334,7 @@ func Test_validate(t *testing.T) { }, Target: trustapi.BundleTarget{ ConfigMap: &trustapi.KeySelector{Key: "test-1"}, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, @@ -372,7 +372,7 @@ func Test_validate(t *testing.T) { ConfigMap: &trustapi.KeySelector{ Key: "bar", }, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, @@ -398,7 +398,7 @@ func Test_validate(t *testing.T) { ConfigMap: &trustapi.KeySelector{ Key: "bar", }, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, @@ -416,7 +416,7 @@ func Test_validate(t *testing.T) { }, Target: trustapi.BundleTarget{ ConfigMap: &trustapi.KeySelector{Key: "test-1"}, - NamespaceSelector: &trustapi.NamespaceSelector{ + NamespaceSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, }, }, diff --git a/test/gen/bundle.go b/test/gen/bundle.go index ef00d0e8..cd965c0c 100644 --- a/test/gen/bundle.go +++ b/test/gen/bundle.go @@ -79,7 +79,7 @@ func SetBundleResourceVersion(resourceVersion string) BundleModifier { // target namespace selector. func SetBundleTargetNamespaceSelectorMatchLabels(matchLabels map[string]string) BundleModifier { return func(bundle *trustapi.Bundle) { - bundle.Spec.Target.NamespaceSelector = &trustapi.NamespaceSelector{ + bundle.Spec.Target.NamespaceSelector = &metav1.LabelSelector{ MatchLabels: matchLabels, } } diff --git a/test/integration/bundle/suite.go b/test/integration/bundle/suite.go index 93efb13c..00e26928 100644 --- a/test/integration/bundle/suite.go +++ b/test/integration/bundle/suite.go @@ -645,7 +645,7 @@ var _ = Describe("Integration", func() { // add a label selector to the Bundle which should exclude all namespaces Expect(komega.Update(testBundle, func() { - testBundle.Spec.Target.NamespaceSelector = &trustapi.NamespaceSelector{ + testBundle.Spec.Target.NamespaceSelector = &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, } })()).To(Succeed()) diff --git a/test/smoke/suite_test.go b/test/smoke/suite_test.go index 556267c7..b4e1bcd8 100644 --- a/test/smoke/suite_test.go +++ b/test/smoke/suite_test.go @@ -153,7 +153,7 @@ func testBundleCommon(ctx context.Context, cl client.Client, testBundle *trustap By("Setting Namespace Selector should remove Secrets from Namespaces that do not have a match") Expect(cl.Get(ctx, client.ObjectKey{Name: testBundle.Name}, testBundle)).NotTo(HaveOccurred()) - testBundle.Spec.Target.NamespaceSelector = &trustapi.NamespaceSelector{ + testBundle.Spec.Target.NamespaceSelector = &metav1.LabelSelector{ MatchLabels: map[string]string{"foo": "bar"}, } Expect(cl.Update(ctx, testBundle)).NotTo(HaveOccurred())