mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 04:26:45 +00:00
Providing namespace metadata additional labels and annotations (#80)
This commit is contained in:
@@ -25,9 +25,18 @@ import (
|
||||
// +kubebuilder:validation:Minimum=1
|
||||
type NamespaceQuota uint
|
||||
|
||||
type NamespaceMetadata struct {
|
||||
// +nullable
|
||||
AdditionalLabels map[string]string `json:"additionalLabels"`
|
||||
// +nullable
|
||||
AdditionalAnnotations map[string]string `json:"additionalAnnotations"`
|
||||
}
|
||||
|
||||
// TenantSpec defines the desired state of Tenant
|
||||
type TenantSpec struct {
|
||||
Owner string `json:"owner"`
|
||||
// +kubebuilder:validation:Optional
|
||||
NamespacesMetadata NamespaceMetadata `json:"namespacesMetadata"`
|
||||
// +kubebuilder:validation:Required
|
||||
StorageClasses StorageClassList `json:"storageClasses"`
|
||||
IngressClasses IngressClassList `json:"ingressClasses"`
|
||||
|
||||
@@ -64,6 +64,35 @@ func (in NamespaceList) DeepCopy() NamespaceList {
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamespaceMetadata) DeepCopyInto(out *NamespaceMetadata) {
|
||||
*out = *in
|
||||
if in.AdditionalLabels != nil {
|
||||
in, out := &in.AdditionalLabels, &out.AdditionalLabels
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
if in.AdditionalAnnotations != nil {
|
||||
in, out := &in.AdditionalAnnotations, &out.AdditionalAnnotations
|
||||
*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 NamespaceMetadata.
|
||||
func (in *NamespaceMetadata) DeepCopy() *NamespaceMetadata {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamespaceMetadata)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in StorageClassList) DeepCopyInto(out *StorageClassList) {
|
||||
{
|
||||
@@ -145,6 +174,7 @@ func (in *TenantList) DeepCopyObject() runtime.Object {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
in.NamespacesMetadata.DeepCopyInto(&out.NamespacesMetadata)
|
||||
if in.StorageClasses != nil {
|
||||
in, out := &in.StorageClasses, &out.StorageClasses
|
||||
*out = make(StorageClassList, len(*in))
|
||||
|
||||
@@ -139,6 +139,22 @@ spec:
|
||||
namespaceQuota:
|
||||
minimum: 1
|
||||
type: integer
|
||||
namespacesMetadata:
|
||||
properties:
|
||||
additionalAnnotations:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
additionalLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
nullable: true
|
||||
type: object
|
||||
required:
|
||||
- additionalAnnotations
|
||||
- additionalLabels
|
||||
type: object
|
||||
networkPolicies:
|
||||
items:
|
||||
description: NetworkPolicySpec provides the specification of a NetworkPolicy
|
||||
|
||||
@@ -393,29 +393,46 @@ func (r *TenantReconciler) syncLimitRanges(tenant *capsulev1alpha1.Tenant) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *TenantReconciler) syncNamespace(namespace string, ingressClasses []string, storageClasses []string, tenantLabel string, wg *sync.WaitGroup, channel chan error) {
|
||||
func (r *TenantReconciler) syncNamespace(namespace string, ingressClasses []string, storageClasses []string, nsMetadata capsulev1alpha1.NamespaceMetadata, tenantLabel string, wg *sync.WaitGroup, channel chan error) {
|
||||
defer wg.Done()
|
||||
|
||||
t := &corev1.Namespace{}
|
||||
if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace}, t); err != nil {
|
||||
ns := &corev1.Namespace{}
|
||||
if err := r.Client.Get(context.TODO(), types.NamespacedName{Name: namespace}, ns); err != nil {
|
||||
channel <- err
|
||||
}
|
||||
|
||||
channel <- retry.RetryOnConflict(retry.DefaultBackoff, func() error {
|
||||
if t.Annotations == nil {
|
||||
t.Annotations = make(map[string]string)
|
||||
a := ns.GetAnnotations()
|
||||
if a == nil {
|
||||
a = make(map[string]string)
|
||||
}
|
||||
t.Annotations[capsulev1alpha1.AvailableIngressClassesAnnotation] = strings.Join(ingressClasses, ",")
|
||||
t.Annotations[capsulev1alpha1.AvailableStorageClassesAnnotation] = strings.Join(storageClasses, ",")
|
||||
if t.Labels == nil {
|
||||
t.Labels = make(map[string]string)
|
||||
a[capsulev1alpha1.AvailableIngressClassesAnnotation] = strings.Join(ingressClasses, ",")
|
||||
a[capsulev1alpha1.AvailableStorageClassesAnnotation] = strings.Join(storageClasses, ",")
|
||||
if aa := nsMetadata.AdditionalAnnotations; aa != nil {
|
||||
for k, v := range aa {
|
||||
a[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
l := ns.GetLabels()
|
||||
if l == nil {
|
||||
l = make(map[string]string)
|
||||
}
|
||||
capsuleLabel, err := capsulev1alpha1.GetTypeLabel(&capsulev1alpha1.Tenant{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Labels[capsuleLabel] = tenantLabel
|
||||
return r.Client.Update(context.TODO(), t, &client.UpdateOptions{})
|
||||
l[capsuleLabel] = tenantLabel
|
||||
if al := nsMetadata.AdditionalLabels; al != nil {
|
||||
for k, v := range al {
|
||||
l[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
ns.SetLabels(l)
|
||||
ns.SetAnnotations(a)
|
||||
|
||||
return r.Client.Update(context.TODO(), ns, &client.UpdateOptions{})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -427,7 +444,7 @@ func (r *TenantReconciler) syncNamespaces(tenant *capsulev1alpha1.Tenant) (err e
|
||||
wg.Add(tenant.Status.Namespaces.Len())
|
||||
|
||||
for _, ns := range tenant.Status.Namespaces {
|
||||
go r.syncNamespace(ns, tenant.Spec.IngressClasses, tenant.Spec.StorageClasses, tenant.GetName(), wg, ch)
|
||||
go r.syncNamespace(ns, tenant.Spec.IngressClasses, tenant.Spec.StorageClasses, tenant.Spec.NamespacesMetadata, tenant.GetName(), wg, ch)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@@ -35,13 +35,14 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
Name: "tenant-assigned-custom-group",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "alice",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "alice",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
|
||||
@@ -41,8 +41,9 @@ var _ = Describe("when Tenant handles Ingress classes", func() {
|
||||
Name: "ingress-class",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "ingress",
|
||||
StorageClasses: []string{},
|
||||
Owner: "ingress",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{
|
||||
"nginx",
|
||||
"haproxy",
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
//+build e2e
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/clastix/capsule/api/v1alpha1"
|
||||
)
|
||||
|
||||
var _ = Describe("creating a Namespace for a Tenant with additional metadata", func() {
|
||||
tnt := &v1alpha1.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "tenant-metadata",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "gatsby",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{
|
||||
AdditionalLabels: map[string]string{
|
||||
"k8s.io/custom-label": "foo",
|
||||
"clastix.io/custom-label": "bar",
|
||||
},
|
||||
AdditionalAnnotations: map[string]string{
|
||||
"k8s.io/custom-annotation": "bizz",
|
||||
"clastix.io/custom-annotation": "buzz",
|
||||
},
|
||||
},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
Expect(k8sClient.Create(context.TODO(), tnt)).Should(Succeed())
|
||||
})
|
||||
JustAfterEach(func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
|
||||
})
|
||||
It("should contains additional Namespace metadata", func() {
|
||||
ns := NewNamespace("namespace-metadata")
|
||||
NamespaceCreationShouldSucceed(ns, tnt, defaultTimeoutInterval)
|
||||
NamespaceShouldBeManagedByTenant(ns, tnt, defaultTimeoutInterval)
|
||||
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed())
|
||||
By("checking additional labels", func() {
|
||||
for _, l := range tnt.Spec.NamespacesMetadata.AdditionalLabels {
|
||||
Expect(ns.Labels).Should(ContainElement(l))
|
||||
}
|
||||
})
|
||||
By("checking additional annotations", func() {
|
||||
for _, a := range tnt.Spec.NamespacesMetadata.AdditionalAnnotations {
|
||||
Expect(ns.Annotations).Should(ContainElement(a))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -38,6 +38,7 @@ var _ = Describe("creating a Namespace as Tenant owner", func() {
|
||||
Owner: "alice",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
|
||||
@@ -35,13 +35,14 @@ var _ = Describe("creating a Namespace over-quota", func() {
|
||||
Name: "overquota-tenant",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "bob",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 3,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "bob",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 3,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
|
||||
@@ -39,7 +39,8 @@ var _ = Describe("when Tenant owner interacts with the webhooks", func() {
|
||||
Name: "tenant-owner",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "ruby",
|
||||
Owner: "ruby",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{
|
||||
"cephfs",
|
||||
"glusterfs",
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
/*
|
||||
Copyright 2020 Clastix Labs.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@@ -32,13 +35,14 @@ var _ = Describe("creating a Namespace with --protected-namespace-regex enabled"
|
||||
Name: "tenantprotectednamespace",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "alice",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "alice",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
|
||||
@@ -42,9 +42,10 @@ var _ = Describe("exceeding Tenant resource quota", func() {
|
||||
Name: "tenant-resources-changes",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "bobby",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
Owner: "bobby",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{
|
||||
{
|
||||
Limits: []corev1.LimitRangeItem{
|
||||
|
||||
@@ -35,13 +35,14 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", func()
|
||||
Name: "tenant-non-owned",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "undefined",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "undefined",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
|
||||
@@ -35,13 +35,14 @@ var _ = Describe("creating a Namespace with Tenant selector", func() {
|
||||
Name: "tenant-one",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "john",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "john",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
t2 := &v1alpha1.Tenant{
|
||||
@@ -49,13 +50,14 @@ var _ = Describe("creating a Namespace with Tenant selector", func() {
|
||||
Name: "tenant-two",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "john",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
Owner: "john",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{},
|
||||
NamespaceQuota: 10,
|
||||
NodeSelector: map[string]string{},
|
||||
ResourceQuota: []corev1.ResourceQuotaSpec{},
|
||||
},
|
||||
}
|
||||
JustBeforeEach(func() {
|
||||
|
||||
@@ -38,7 +38,8 @@ var _ = Describe("when Tenant handles Storage classes", func() {
|
||||
Name: "storage-class",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "storage",
|
||||
Owner: "storage",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{
|
||||
"cephfs",
|
||||
"glusterfs",
|
||||
|
||||
@@ -40,9 +40,10 @@ var _ = Describe("changing Tenant managed Kubernetes resources", func() {
|
||||
Name: "tenant-resources-changes",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "laura",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
Owner: "laura",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{
|
||||
{
|
||||
Limits: []corev1.LimitRangeItem{
|
||||
|
||||
@@ -41,9 +41,10 @@ var _ = Describe("creating namespaces within a Tenant with resources", func() {
|
||||
Name: "tenant-resources",
|
||||
},
|
||||
Spec: v1alpha1.TenantSpec{
|
||||
Owner: "john",
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
Owner: "john",
|
||||
NamespacesMetadata: v1alpha1.NamespaceMetadata{},
|
||||
StorageClasses: []string{},
|
||||
IngressClasses: []string{},
|
||||
LimitRanges: []corev1.LimitRangeSpec{
|
||||
{
|
||||
Limits: []corev1.LimitRangeItem{
|
||||
|
||||
Reference in New Issue
Block a user