mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 18:09:58 +00:00
feat: Add additionalMetadataList Support for Conditional Metadata Assignment (#1339)
* feat: Add support for additionalMetadataList Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> * docs: change description Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> * fix: missing bracket Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> * fix: removed duplicated if statement Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> * chore: adjustments after review Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> * chore: Sync `syncNamespaceMetadata` method Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> --------- Signed-off-by: Deofex <28751252+Deofex@users.noreply.github.com> Signed-off-by: Deofex 28751252+Deofex@users.noreply.github.com
This commit is contained in:
@@ -13,6 +13,8 @@ type NamespaceOptions struct {
|
||||
Quota *int32 `json:"quota,omitempty"`
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
|
||||
AdditionalMetadata *api.AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
|
||||
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant via a list. Optional.
|
||||
AdditionalMetadataList []api.AdditionalMetadataSelectorSpec `json:"additionalMetadataList,omitempty"`
|
||||
// Define the labels that a Tenant Owner cannot set for their Namespace resources.
|
||||
ForbiddenLabels api.ForbiddenListSpec `json:"forbiddenLabels,omitempty"`
|
||||
// Define the annotations that a Tenant Owner cannot set for their Namespace resources.
|
||||
|
||||
@@ -293,6 +293,13 @@ func (in *NamespaceOptions) DeepCopyInto(out *NamespaceOptions) {
|
||||
*out = new(api.AdditionalMetadataSpec)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AdditionalMetadataList != nil {
|
||||
in, out := &in.AdditionalMetadataList, &out.AdditionalMetadataList
|
||||
*out = make([]api.AdditionalMetadataSelectorSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.ForbiddenLabels.DeepCopyInto(&out.ForbiddenLabels)
|
||||
in.ForbiddenAnnotations.DeepCopyInto(&out.ForbiddenAnnotations)
|
||||
}
|
||||
|
||||
@@ -1373,6 +1373,71 @@ spec:
|
||||
type: string
|
||||
type: object
|
||||
type: object
|
||||
additionalMetadataList:
|
||||
description: Specifies additional labels and annotations the Capsule
|
||||
operator places on any Namespace resource in the Tenant via
|
||||
a list. Optional.
|
||||
items:
|
||||
properties:
|
||||
annotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
labels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
namespaceSelector:
|
||||
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
|
||||
type: array
|
||||
forbiddenAnnotations:
|
||||
description: Define the annotations that a Tenant Owner cannot
|
||||
set for their Namespace resources.
|
||||
|
||||
@@ -6,6 +6,7 @@ package tenant
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -42,36 +43,39 @@ func (r *Manager) syncNamespaces(ctx context.Context, tenant *capsulev1beta2.Ten
|
||||
return
|
||||
}
|
||||
|
||||
//nolint:gocognit,nakedret
|
||||
func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
var res controllerutil.OperationResult
|
||||
|
||||
err = retry.RetryOnConflict(retry.DefaultBackoff, func() (conflictErr error) {
|
||||
ns := &corev1.Namespace{}
|
||||
if conflictErr = r.Get(ctx, types.NamespacedName{Name: namespace}, ns); err != nil {
|
||||
return
|
||||
return conflictErr
|
||||
}
|
||||
|
||||
capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
|
||||
|
||||
res, conflictErr = controllerutil.CreateOrUpdate(ctx, r.Client, ns, func() error {
|
||||
annotations := make(map[string]string)
|
||||
labels := map[string]string{
|
||||
"kubernetes.io/metadata.name": namespace,
|
||||
capsuleLabel: tnt.GetName(),
|
||||
}
|
||||
annotations := buildNamespaceAnnotationsForTenant(tnt)
|
||||
labels := buildNamespaceLabelsForTenant(tnt)
|
||||
|
||||
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Annotations {
|
||||
annotations[k] = v
|
||||
if opts := tnt.Spec.NamespaceOptions; opts != nil && len(opts.AdditionalMetadataList) > 0 {
|
||||
for _, md := range opts.AdditionalMetadataList {
|
||||
ok, err := utils.IsNamespaceSelectedBySelector(ns, md.NamespaceSelector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
maps.Copy(labels, md.Labels)
|
||||
maps.Copy(annotations, md.Annotations)
|
||||
}
|
||||
}
|
||||
|
||||
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
|
||||
labels[k] = v
|
||||
}
|
||||
}
|
||||
labels["kubernetes.io/metadata.name"] = namespace
|
||||
labels[capsuleLabel] = tnt.GetName()
|
||||
|
||||
if tnt.Spec.Cordoned {
|
||||
ns.Labels[utils.CordonedLabel] = "true"
|
||||
@@ -79,76 +83,22 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
|
||||
delete(ns.Labels, utils.CordonedLabel)
|
||||
}
|
||||
|
||||
if tnt.Spec.NodeSelector != nil {
|
||||
annotations = utils.BuildNodeSelector(tnt, annotations)
|
||||
}
|
||||
|
||||
if tnt.Spec.IngressOptions.AllowedClasses != nil {
|
||||
if len(tnt.Spec.IngressOptions.AllowedClasses.Exact) > 0 {
|
||||
annotations[AvailableIngressClassesAnnotation] = strings.Join(tnt.Spec.IngressOptions.AllowedClasses.Exact, ",")
|
||||
}
|
||||
|
||||
if len(tnt.Spec.IngressOptions.AllowedClasses.Regex) > 0 {
|
||||
annotations[AvailableIngressClassesRegexpAnnotation] = tnt.Spec.IngressOptions.AllowedClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if tnt.Spec.StorageClasses != nil {
|
||||
if len(tnt.Spec.StorageClasses.Exact) > 0 {
|
||||
annotations[AvailableStorageClassesAnnotation] = strings.Join(tnt.Spec.StorageClasses.Exact, ",")
|
||||
}
|
||||
|
||||
if len(tnt.Spec.StorageClasses.Regex) > 0 {
|
||||
annotations[AvailableStorageClassesRegexpAnnotation] = tnt.Spec.StorageClasses.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if tnt.Spec.ContainerRegistries != nil {
|
||||
if len(tnt.Spec.ContainerRegistries.Exact) > 0 {
|
||||
annotations[AllowedRegistriesAnnotation] = strings.Join(tnt.Spec.ContainerRegistries.Exact, ",")
|
||||
}
|
||||
|
||||
if len(tnt.Spec.ContainerRegistries.Regex) > 0 {
|
||||
annotations[AllowedRegistriesRegexpAnnotation] = tnt.Spec.ContainerRegistries.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok {
|
||||
annotations[api.ForbiddenNamespaceLabelsAnnotation] = value
|
||||
}
|
||||
|
||||
if value, ok := tnt.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok {
|
||||
annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = value
|
||||
}
|
||||
|
||||
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok {
|
||||
annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = value
|
||||
}
|
||||
|
||||
if value, ok := tnt.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok {
|
||||
annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = value
|
||||
}
|
||||
|
||||
if ns.Annotations == nil {
|
||||
ns.SetAnnotations(annotations)
|
||||
} else {
|
||||
for k, v := range annotations {
|
||||
ns.Annotations[k] = v
|
||||
}
|
||||
maps.Copy(ns.Annotations, annotations)
|
||||
}
|
||||
|
||||
if ns.Labels == nil {
|
||||
ns.SetLabels(labels)
|
||||
} else {
|
||||
for k, v := range labels {
|
||||
ns.Labels[k] = v
|
||||
}
|
||||
maps.Copy(ns.Labels, labels)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return
|
||||
return conflictErr
|
||||
})
|
||||
|
||||
r.emitEvent(tnt, namespace, res, "Ensuring Namespace metadata", err)
|
||||
@@ -156,6 +106,71 @@ func (r *Manager) syncNamespaceMetadata(ctx context.Context, namespace string, t
|
||||
return err
|
||||
}
|
||||
|
||||
func buildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
|
||||
if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
|
||||
maps.Copy(annotations, md.AdditionalMetadata.Annotations)
|
||||
}
|
||||
|
||||
if tnt.Spec.NodeSelector != nil {
|
||||
annotations = utils.BuildNodeSelector(tnt, annotations)
|
||||
}
|
||||
|
||||
if ic := tnt.Spec.IngressOptions.AllowedClasses; ic != nil {
|
||||
if len(ic.Exact) > 0 {
|
||||
annotations[AvailableIngressClassesAnnotation] = strings.Join(ic.Exact, ",")
|
||||
}
|
||||
|
||||
if len(ic.Regex) > 0 {
|
||||
annotations[AvailableIngressClassesRegexpAnnotation] = ic.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if sc := tnt.Spec.StorageClasses; sc != nil {
|
||||
if len(sc.Exact) > 0 {
|
||||
annotations[AvailableStorageClassesAnnotation] = strings.Join(sc.Exact, ",")
|
||||
}
|
||||
|
||||
if len(sc.Regex) > 0 {
|
||||
annotations[AvailableStorageClassesRegexpAnnotation] = sc.Regex
|
||||
}
|
||||
}
|
||||
|
||||
if cr := tnt.Spec.ContainerRegistries; cr != nil {
|
||||
if len(cr.Exact) > 0 {
|
||||
annotations[AllowedRegistriesAnnotation] = strings.Join(cr.Exact, ",")
|
||||
}
|
||||
|
||||
if len(cr.Regex) > 0 {
|
||||
annotations[AllowedRegistriesRegexpAnnotation] = cr.Regex
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range []string{
|
||||
api.ForbiddenNamespaceLabelsAnnotation,
|
||||
api.ForbiddenNamespaceLabelsRegexpAnnotation,
|
||||
api.ForbiddenNamespaceAnnotationsAnnotation,
|
||||
api.ForbiddenNamespaceAnnotationsRegexpAnnotation,
|
||||
} {
|
||||
if value, ok := tnt.Annotations[key]; ok {
|
||||
annotations[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return annotations
|
||||
}
|
||||
|
||||
func buildNamespaceLabelsForTenant(tnt *capsulev1beta2.Tenant) map[string]string {
|
||||
labels := make(map[string]string)
|
||||
|
||||
if md := tnt.Spec.NamespaceOptions; md != nil && md.AdditionalMetadata != nil {
|
||||
maps.Copy(labels, md.AdditionalMetadata.Labels)
|
||||
}
|
||||
|
||||
return labels
|
||||
}
|
||||
|
||||
func (r *Manager) ensureNamespaceCount(ctx context.Context, tenant *capsulev1beta2.Tenant) error {
|
||||
return retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
tenant.Status.Size = uint(len(tenant.Status.Namespaces))
|
||||
|
||||
@@ -38,8 +38,10 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
|
||||
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
|
||||
AdditionalMetadata: &api.AdditionalMetadataSpec{
|
||||
Labels: map[string]string{
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
"capsule.clastix.io/tenant": "tenan-override",
|
||||
"kubernetes.io/metadata.name": "namespace-override",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"k8s.io/custom-annotation": "bizz",
|
||||
@@ -68,6 +70,9 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadata.Labels {
|
||||
if k == "capsule.clastix.io/tenant" || k == "kubernetes.io/metadata.name" {
|
||||
continue // this label is managed and shouldn't be set by the user
|
||||
}
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
@@ -75,6 +80,18 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking managed labels", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
if ok, _ = HaveKeyWithValue("capsule.clastix.io/tenant", tnt.GetName()).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
if ok, _ = HaveKeyWithValue("kubernetes.io/metadata.name", ns.GetName()).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking additional annotations", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
@@ -88,3 +105,161 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", f
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("creating a Namespace for a Tenant with additional metadata list", func() {
|
||||
tnt := &capsulev1beta2.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tenant-metadata",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "cap",
|
||||
Kind: "dummy",
|
||||
Name: "tenant-metadata",
|
||||
UID: "tenant-metadata",
|
||||
},
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: capsulev1beta2.OwnerListSpec{
|
||||
{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
NamespaceOptions: &capsulev1beta2.NamespaceOptions{
|
||||
AdditionalMetadataList: []api.AdditionalMetadataSelectorSpec{
|
||||
{
|
||||
Labels: map[string]string{
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"k8s.io/custom-annotation": "bizz",
|
||||
"clastix.io/custom-annotation": "buzz",
|
||||
},
|
||||
},
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "matching_namespace_label",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"matching_namespace_label_value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"k8s.io/custom-label_2": "foo",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"k8s.io/custom-annotation_2": "bizz",
|
||||
},
|
||||
},
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "nonmatching_namespace_label",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"nonmatching_namespace_label_value"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Labels: map[string]string{
|
||||
"k8s.io/custom-label_3": "foo",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
"k8s.io/custom-annotation_3": "bizz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
JustBeforeEach(func() {
|
||||
EventuallyCreation(func() error {
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
|
||||
})
|
||||
|
||||
It("should contain additional Namespace metadata", func() {
|
||||
labels := map[string]string{
|
||||
"matching_namespace_label": "matching_namespace_label_value",
|
||||
}
|
||||
ns := NewNamespace("", labels)
|
||||
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
|
||||
TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName()))
|
||||
|
||||
By("checking additional labels from entry without node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[0].Labels {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking additional labels from entry with matching node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[1].Labels {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking additional labels from entry with non-matching node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[2].Labels {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Labels); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeFalse())
|
||||
})
|
||||
By("checking additional annotations from entry without node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[0].Annotations {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking additional annotations from entry with matching node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[1].Annotations {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
|
||||
})
|
||||
By("checking additional annotations from entry with non-matching node selector", func() {
|
||||
Eventually(func() (ok bool) {
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
for k, v := range tnt.Spec.NamespaceOptions.AdditionalMetadataList[2].Annotations {
|
||||
if ok, _ = HaveKeyWithValue(k, v).Match(ns.Annotations); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}, defaultTimeoutInterval, defaultPollInterval).Should(BeFalse())
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,9 +3,19 @@
|
||||
|
||||
package api
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type AdditionalMetadataSpec struct {
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type AdditionalMetadataSelectorSpec struct {
|
||||
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
@@ -10,9 +10,44 @@ package api
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadataSelectorSpec) DeepCopyInto(out *AdditionalMetadataSelectorSpec) {
|
||||
*out = *in
|
||||
if in.NamespaceSelector != nil {
|
||||
in, out := &in.NamespaceSelector, &out.NamespaceSelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Labels != nil {
|
||||
in, out := &in.Labels, &out.Labels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*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 AdditionalMetadataSelectorSpec.
|
||||
func (in *AdditionalMetadataSelectorSpec) DeepCopy() *AdditionalMetadataSelectorSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AdditionalMetadataSelectorSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AdditionalMetadataSpec) DeepCopyInto(out *AdditionalMetadataSpec) {
|
||||
*out = *in
|
||||
@@ -47,7 +82,7 @@ func (in *AdditionalRoleBindingsSpec) DeepCopyInto(out *AdditionalRoleBindingsSp
|
||||
*out = *in
|
||||
if in.Subjects != nil {
|
||||
in, out := &in.Subjects, &out.Subjects
|
||||
*out = make([]v1.Subject, len(*in))
|
||||
*out = make([]rbacv1.Subject, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
20
pkg/utils/namespace_selector.go
Normal file
20
pkg/utils/namespace_selector.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
func IsNamespaceSelectedBySelector(ns *corev1.Namespace, selector *metav1.LabelSelector) (bool, error) {
|
||||
if selector == nil {
|
||||
return true, nil // If selector is nil, all namespaces match
|
||||
}
|
||||
|
||||
labelSelector, err := metav1.LabelSelectorAsSelector(selector)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return labelSelector.Matches(labels.Set(ns.Labels)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user