diff --git a/Makefile b/Makefile index e2f2b9a4..2a137fda 100644 --- a/Makefile +++ b/Makefile @@ -153,6 +153,7 @@ dev-setup: --set 'crds.exclusive=true'\ --set 'crds.createConfig=true'\ --set "webhooks.exclusive=true"\ + --set "webhooks.hooks.nodes.enabled=true"\ --set "webhooks.service.url=$${WEBHOOK_URL}" \ --set "webhooks.service.caBundle=$${CA_BUNDLE}" \ capsule \ @@ -243,9 +244,12 @@ e2e-install-deps: @$(KUBECTL) apply --force-conflicts --server-side=true -f https://github.com/$(API_GW_LOOKUP)/releases/download/$(API_GW_VERSION)/standard-install.yaml e2e-build: kind + $(MAKE) e2e-build-cluster + $(MAKE) e2e-install + +e2e-build-cluster: kind $(KIND) create cluster --wait=60s --name $(CLUSTER_NAME) --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) $(MAKE) e2e-install-deps - $(MAKE) e2e-install .PHONY: e2e-install e2e-install: ko-build-all diff --git a/api/v1beta1/namespace_options.go b/api/v1beta1/namespace_options.go index 5128dbd9..3a9a2040 100644 --- a/api/v1beta1/namespace_options.go +++ b/api/v1beta1/namespace_options.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) type NamespaceOptions struct { @@ -18,11 +19,11 @@ type NamespaceOptions struct { } func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { - if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsAnnotation]; ok { + if _, ok := in.Annotations[meta.ForbiddenNamespaceLabelsAnnotation]; ok { return true } - if _, ok := in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; ok { + if _, ok := in.Annotations[meta.ForbiddenNamespaceLabelsRegexpAnnotation]; ok { return true } @@ -30,11 +31,11 @@ func (in *Tenant) hasForbiddenNamespaceLabelsAnnotations() bool { } func (in *Tenant) hasForbiddenNamespaceAnnotationsAnnotations() bool { - if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; ok { + if _, ok := in.Annotations[meta.ForbiddenNamespaceAnnotationsAnnotation]; ok { return true } - if _, ok := in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { + if _, ok := in.Annotations[meta.ForbiddenNamespaceAnnotationsRegexpAnnotation]; ok { return true } @@ -47,8 +48,8 @@ func (in *Tenant) ForbiddenUserNamespaceLabels() *api.ForbiddenListSpec { } return &api.ForbiddenListSpec{ - Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceLabelsAnnotation], ","), - Regex: in.Annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation], + Exact: strings.Split(in.Annotations[meta.ForbiddenNamespaceLabelsAnnotation], ","), + Regex: in.Annotations[meta.ForbiddenNamespaceLabelsRegexpAnnotation], } } @@ -58,7 +59,7 @@ func (in *Tenant) ForbiddenUserNamespaceAnnotations() *api.ForbiddenListSpec { } return &api.ForbiddenListSpec{ - Exact: strings.Split(in.Annotations[api.ForbiddenNamespaceAnnotationsAnnotation], ","), - Regex: in.Annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation], + Exact: strings.Split(in.Annotations[meta.ForbiddenNamespaceAnnotationsAnnotation], ","), + Regex: in.Annotations[meta.ForbiddenNamespaceAnnotationsRegexpAnnotation], } } diff --git a/api/v1beta2/capsuleconfiguration_types.go b/api/v1beta2/capsuleconfiguration_types.go index 65d2c686..69b10865 100644 --- a/api/v1beta2/capsuleconfiguration_types.go +++ b/api/v1beta2/capsuleconfiguration_types.go @@ -41,6 +41,11 @@ type CapsuleConfigurationSpec struct { // when not using an already provided CA and certificate, or when these are managed externally with Vault, or cert-manager. // +kubebuilder:default=true EnableTLSReconciler bool `json:"enableTLSReconciler"` //nolint:tagliatelle + // Define entities which can act as Administrators in the capsule construct + // These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label + // for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor + // be ignored by capsule. + Administrators api.UserListSpec `json:"administrators,omitempty"` } type NodeMetadata struct { diff --git a/api/v1beta2/resourcepool_func.go b/api/v1beta2/resourcepool_func.go index 746b93ae..229ac8e4 100644 --- a/api/v1beta2/resourcepool_func.go +++ b/api/v1beta2/resourcepool_func.go @@ -5,6 +5,7 @@ package v1beta2 import ( "errors" + "fmt" "sort" corev1 "k8s.io/api/core/v1" @@ -13,6 +14,10 @@ import ( "github.com/projectcapsule/capsule/pkg/api" ) +func (r *ResourcePool) GetQuotaName() string { + return fmt.Sprintf("capsule-pool-%s", r.GetName()) +} + func (r *ResourcePool) AssignNamespaces(namespaces []corev1.Namespace) { var l []string diff --git a/api/v1beta2/resourcepool_func_test.go b/api/v1beta2/resourcepool_func_test.go index 9fe6ef65..a5bcfc6a 100644 --- a/api/v1beta2/resourcepool_func_test.go +++ b/api/v1beta2/resourcepool_func_test.go @@ -12,7 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/stretchr/testify/assert" ) diff --git a/api/v1beta2/resourcepoolclaim_func.go b/api/v1beta2/resourcepoolclaim_func.go index e0ded520..b18e14cc 100644 --- a/api/v1beta2/resourcepoolclaim_func.go +++ b/api/v1beta2/resourcepoolclaim_func.go @@ -6,7 +6,7 @@ package v1beta2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // Indicate the claim is bound to a resource pool. diff --git a/api/v1beta2/resourcepoolclaim_func_test.go b/api/v1beta2/resourcepoolclaim_func_test.go index 01e969f7..bfe0af08 100644 --- a/api/v1beta2/resourcepoolclaim_func_test.go +++ b/api/v1beta2/resourcepoolclaim_func_test.go @@ -6,7 +6,7 @@ package v1beta2 import ( "testing" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) diff --git a/api/v1beta2/tenant_conversion_hub.go b/api/v1beta2/tenant_conversion_hub.go index 9e008832..28ddf2c8 100644 --- a/api/v1beta2/tenant_conversion_hub.go +++ b/api/v1beta2/tenant_conversion_hub.go @@ -12,6 +12,7 @@ import ( capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1" "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) func (in *Tenant) ConvertFrom(raw conversion.Hub) error { @@ -26,27 +27,29 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { } in.ObjectMeta = src.ObjectMeta - in.Spec.Owners = make(OwnerListSpec, 0, len(src.Spec.Owners)) + in.Spec.Owners = make(api.OwnerListSpec, 0, len(src.Spec.Owners)) for index, owner := range src.Spec.Owners { - proxySettings := make([]ProxySettings, 0, len(owner.ProxyOperations)) + proxySettings := make([]api.ProxySettings, 0, len(owner.ProxyOperations)) for _, proxyOp := range owner.ProxyOperations { - ops := make([]ProxyOperation, 0, len(proxyOp.Operations)) + ops := make([]api.ProxyOperation, 0, len(proxyOp.Operations)) for _, op := range proxyOp.Operations { - ops = append(ops, ProxyOperation(op)) + ops = append(ops, api.ProxyOperation(op)) } - proxySettings = append(proxySettings, ProxySettings{ - Kind: ProxyServiceKind(proxyOp.Kind), + proxySettings = append(proxySettings, api.ProxySettings{ + Kind: api.ProxyServiceKind(proxyOp.Kind), Operations: ops, }) } - in.Spec.Owners = append(in.Spec.Owners, OwnerSpec{ - Kind: OwnerKind(owner.Kind), - Name: owner.Name, + in.Spec.Owners = append(in.Spec.Owners, api.OwnerSpec{ + UserSpec: api.UserSpec{ + Kind: api.OwnerKind(owner.Kind), + Name: owner.Name, + }, ClusterRoles: owner.GetRoles(*src, index), ProxyOperations: proxySettings, }) @@ -59,28 +62,28 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata - if value, found := annotations[api.ForbiddenNamespaceLabelsAnnotation]; found { + if value, found := annotations[meta.ForbiddenNamespaceLabelsAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenLabels.Exact = strings.Split(value, ",") - delete(annotations, api.ForbiddenNamespaceLabelsAnnotation) + delete(annotations, meta.ForbiddenNamespaceLabelsAnnotation) } - if value, found := annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation]; found { + if value, found := annotations[meta.ForbiddenNamespaceLabelsRegexpAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenLabels.Regex = value - delete(annotations, api.ForbiddenNamespaceLabelsRegexpAnnotation) + delete(annotations, meta.ForbiddenNamespaceLabelsRegexpAnnotation) } - if value, found := annotations[api.ForbiddenNamespaceAnnotationsAnnotation]; found { + if value, found := annotations[meta.ForbiddenNamespaceAnnotationsAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenAnnotations.Exact = strings.Split(value, ",") - delete(annotations, api.ForbiddenNamespaceAnnotationsAnnotation) + delete(annotations, meta.ForbiddenNamespaceAnnotationsAnnotation) } - if value, found := annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { + if value, found := annotations[meta.ForbiddenNamespaceAnnotationsRegexpAnnotation]; found { in.Spec.NamespaceOptions.ForbiddenAnnotations.Regex = value - delete(annotations, api.ForbiddenNamespaceAnnotationsRegexpAnnotation) + delete(annotations, meta.ForbiddenNamespaceAnnotationsRegexpAnnotation) } } @@ -144,10 +147,10 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error { in.Spec.Cordoned = value } - if _, found := annotations[api.ProtectedTenantAnnotation]; found { + if _, found := annotations[meta.ProtectedTenantAnnotation]; found { in.Spec.PreventDeletion = true - delete(annotations, api.ProtectedTenantAnnotation) + delete(annotations, meta.ProtectedTenantAnnotation) } in.SetAnnotations(annotations) @@ -215,19 +218,19 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { dst.Spec.NamespaceOptions.AdditionalMetadata = nsOpts.AdditionalMetadata if exact := nsOpts.ForbiddenAnnotations.Exact; len(exact) > 0 { - annotations[api.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") + annotations[meta.ForbiddenNamespaceAnnotationsAnnotation] = strings.Join(exact, ",") } if regex := nsOpts.ForbiddenAnnotations.Regex; len(regex) > 0 { - annotations[api.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex + annotations[meta.ForbiddenNamespaceAnnotationsRegexpAnnotation] = regex } if exact := nsOpts.ForbiddenLabels.Exact; len(exact) > 0 { - annotations[api.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") + annotations[meta.ForbiddenNamespaceLabelsAnnotation] = strings.Join(exact, ",") } if regex := nsOpts.ForbiddenLabels.Regex; len(regex) > 0 { - annotations[api.ForbiddenNamespaceLabelsRegexpAnnotation] = regex + annotations[meta.ForbiddenNamespaceLabelsRegexpAnnotation] = regex } } @@ -264,7 +267,7 @@ func (in *Tenant) ConvertTo(raw conversion.Hub) error { } if in.Spec.PreventDeletion { - annotations[api.ProtectedTenantAnnotation] = "true" //nolint:goconst + annotations[meta.ProtectedTenantAnnotation] = "true" //nolint:goconst } if in.Spec.Cordoned { diff --git a/api/v1beta2/tenant_func.go b/api/v1beta2/tenant_func.go index 6df874b1..be83b919 100644 --- a/api/v1beta2/tenant_func.go +++ b/api/v1beta2/tenant_func.go @@ -37,14 +37,14 @@ func (in *Tenant) AssignNamespaces(namespaces []corev1.Namespace) { in.Status.Size = uint(len(l)) } -func (in *Tenant) GetOwnerProxySettings(name string, kind OwnerKind) []ProxySettings { +func (in *Tenant) GetOwnerProxySettings(name string, kind api.OwnerKind) []api.ProxySettings { return in.Spec.Owners.FindOwner(name, kind).ProxyOperations } // GetClusterRolePermissions returns a map where the clusterRole is the key // and the value is a list of permission subjects (kind and name) that reference that role. // These mappings are gathered from the owners and additionalRolebindings spec. -func (in *Tenant) GetSubjectsByClusterRoles(ignoreOwnerKind []OwnerKind) (rolePerms map[string][]rbacv1.Subject) { +func (in *Tenant) GetSubjectsByClusterRoles(ignoreOwnerKind []api.OwnerKind) (rolePerms map[string][]rbacv1.Subject) { rolePerms = make(map[string][]rbacv1.Subject) // Helper to add permissions for a given clusterRole @@ -97,7 +97,7 @@ func (in *Tenant) GetSubjectsByClusterRoles(ignoreOwnerKind []OwnerKind) (rolePe } // Get the permissions for a tenant ordered by groups and users. -func (in *Tenant) GetClusterRolesBySubject(ignoreOwnerKind []OwnerKind) (maps map[string]map[string]api.TenantSubjectRoles) { +func (in *Tenant) GetClusterRolesBySubject(ignoreOwnerKind []api.OwnerKind) (maps map[string]map[string]api.TenantSubjectRoles) { maps = make(map[string]map[string]api.TenantSubjectRoles) // Initialize a nested map for kind ("User", "Group") and name diff --git a/api/v1beta2/tenant_func_test.go b/api/v1beta2/tenant_func_test.go index de384768..bbefd42f 100644 --- a/api/v1beta2/tenant_func_test.go +++ b/api/v1beta2/tenant_func_test.go @@ -13,20 +13,26 @@ import ( var tenant = &Tenant{ Spec: TenantSpec{ - Owners: []OwnerSpec{ + Owners: []api.OwnerSpec{ { - Kind: "User", - Name: "user1", + UserSpec: api.UserSpec{ + Kind: "User", + Name: "user1", + }, ClusterRoles: []string{"cluster-admin", "read-only"}, }, { - Kind: "Group", - Name: "group1", + UserSpec: api.UserSpec{ + Kind: "Group", + Name: "group1", + }, ClusterRoles: []string{"edit"}, }, { - Kind: ServiceAccountOwner, - Name: "service", + UserSpec: api.UserSpec{ + Kind: api.ServiceAccountOwner, + Name: "service", + }, ClusterRoles: []string{"read-only"}, }, }, @@ -96,7 +102,7 @@ func TestGetSubjectsByClusterRoles(t *testing.T) { } // Ignore SubjectTypes (Ignores ServiceAccounts) - ignored := tenant.GetSubjectsByClusterRoles([]OwnerKind{"ServiceAccount"}) + ignored := tenant.GetSubjectsByClusterRoles([]api.OwnerKind{"ServiceAccount"}) expectedIgnored := map[string][]rbacv1.Subject{ "cluster-admin": { {Kind: "User", Name: "user1"}, @@ -156,7 +162,7 @@ func TestGetClusterRolesBySubject(t *testing.T) { } delete(expected, "ServiceAccount") - ignored := tenant.GetClusterRolesBySubject([]OwnerKind{"ServiceAccount"}) + ignored := tenant.GetClusterRolesBySubject([]api.OwnerKind{"ServiceAccount"}) if !reflect.DeepEqual(ignored, expected) { t.Errorf("Expected %v, but got %v", expected, ignored) diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go index d47b48e9..e10738bd 100644 --- a/api/v1beta2/tenant_status.go +++ b/api/v1beta2/tenant_status.go @@ -6,7 +6,7 @@ package v1beta2 import ( k8stypes "k8s.io/apimachinery/pkg/types" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // +kubebuilder:validation:Enum=Cordoned;Active diff --git a/api/v1beta2/tenant_types.go b/api/v1beta2/tenant_types.go index 999ae5b5..b8b65173 100644 --- a/api/v1beta2/tenant_types.go +++ b/api/v1beta2/tenant_types.go @@ -13,7 +13,7 @@ import ( type TenantSpec struct { // Specifies the owners of the Tenant. // Optional - Owners OwnerListSpec `json:"owners,omitempty"` + Owners api.OwnerListSpec `json:"owners,omitempty"` // Specifies options for the Namespaces, such as additional metadata or maximum number of namespaces allowed for that Tenant. Once the namespace quota assigned to the Tenant has been reached, the Tenant owner cannot create further namespaces. Optional. NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"` // Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional. diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index f90228c7..feee1def 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -9,7 +9,7 @@ package v1beta2 import ( "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" corev1 "k8s.io/api/core/v1" "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,27 +36,6 @@ func (in *AdditionalRoleBindingsSpec) DeepCopy() *AdditionalRoleBindingsSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) { - { - in := &in - *out = make(ByKindAndName, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ByKindAndName. -func (in ByKindAndName) DeepCopy() ByKindAndName { - if in == nil { - return nil - } - out := new(ByKindAndName) - in.DeepCopyInto(out) - return *out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CapsuleConfiguration) DeepCopyInto(out *CapsuleConfiguration) { *out = *in @@ -139,6 +118,11 @@ func (in *CapsuleConfigurationSpec) DeepCopyInto(out *CapsuleConfigurationSpec) *out = new(NodeMetadata) (*in).DeepCopyInto(*out) } + if in.Administrators != nil { + in, out := &in.Administrators, &out.Administrators + *out = make(api.UserListSpec, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CapsuleConfigurationSpec. @@ -426,68 +410,6 @@ func (in *ObjectReferenceStatus) DeepCopy() *ObjectReferenceStatus { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { - { - in := &in - *out = make(OwnerListSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec. -func (in OwnerListSpec) DeepCopy() OwnerListSpec { - if in == nil { - return nil - } - out := new(OwnerListSpec) - in.DeepCopyInto(out) - return *out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { - *out = *in - if in.ClusterRoles != nil { - in, out := &in.ClusterRoles, &out.ClusterRoles - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.ProxyOperations != nil { - in, out := &in.ProxyOperations, &out.ProxyOperations - *out = make([]ProxySettings, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - 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 OwnerSpec. -func (in *OwnerSpec) DeepCopy() *OwnerSpec { - if in == nil { - return nil - } - out := new(OwnerSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in ProcessedItems) DeepCopyInto(out *ProcessedItems) { { @@ -507,26 +429,6 @@ func (in ProcessedItems) DeepCopy() ProcessedItems { return *out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { - *out = *in - if in.Operations != nil { - in, out := &in.Operations, &out.Operations - *out = make([]ProxyOperation, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings. -func (in *ProxySettings) DeepCopy() *ProxySettings { - if in == nil { - return nil - } - out := new(ProxySettings) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RawExtension) DeepCopyInto(out *RawExtension) { *out = *in @@ -1141,7 +1043,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in if in.Owners != nil { in, out := &in.Owners, &out.Owners - *out = make(OwnerListSpec, len(*in)) + *out = make(api.OwnerListSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/charts/capsule/README.md b/charts/capsule/README.md index 6ca60685..c08722b9 100644 --- a/charts/capsule/README.md +++ b/charts/capsule/README.md @@ -112,6 +112,7 @@ The following Values have changed key or Value: | manager.image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | | manager.kind | string | `"Deployment"` | Set the controller deployment mode as `Deployment` or `DaemonSet`. | | manager.livenessProbe | object | `{"httpGet":{"path":"/healthz","port":10080}}` | Configure the liveness probe using Deployment probe spec | +| manager.options.administrators | list | `[]` | Define entities which can act as Administrators in the capsule construct These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. | | manager.options.allowServiceAccountPromotion | bool | `false` | ServiceAccounts within tenant namespaces can be promoted to owners of the given tenant this can be achieved by labeling the serviceaccount and then they are considered owners. This can only be done by other owners of the tenant. However ServiceAccounts which have been promoted to owner can not promote further serviceAccounts. | | manager.options.annotations | object | `{}` | Additional annotations to add to the CapsuleConfiguration resource | | manager.options.capsuleConfiguration | string | `"default"` | Change the default name of the capsule configuration name | diff --git a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml index 2046d1ee..b10491ca 100644 --- a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml +++ b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml @@ -40,6 +40,30 @@ spec: spec: description: CapsuleConfigurationSpec defines the Capsule configuration. properties: + administrators: + description: |- + Define entities which can act as Administrators in the capsule construct + These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label + for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor + be ignored by capsule. + items: + properties: + kind: + description: Kind of entity. Possible values are "User", "Group", + and "ServiceAccount" + enum: + - User + - Group + - ServiceAccount + type: string + name: + description: Name of the entity. + type: string + required: + - kind + - name + type: object + type: array allowServiceAccountPromotion: default: false description: |- diff --git a/charts/capsule/crds/capsule.clastix.io_tenants.yaml b/charts/capsule/crds/capsule.clastix.io_tenants.yaml index dc327c28..2668047a 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenants.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenants.yaml @@ -2061,8 +2061,8 @@ spec: type: string type: array kind: - description: Kind of tenant owner. Possible values are "User", - "Group", and "ServiceAccount" + description: Kind of entity. Possible values are "User", "Group", + and "ServiceAccount" enum: - User - Group @@ -2074,7 +2074,7 @@ spec: description: Additional Labels for the synchronized rolebindings type: object name: - description: Name of tenant owner. + description: Name of the entity. type: string proxySettings: description: Proxy settings for tenant owner. diff --git a/charts/capsule/values.schema.json b/charts/capsule/values.schema.json index f1ba5b12..29a2656f 100644 --- a/charts/capsule/values.schema.json +++ b/charts/capsule/values.schema.json @@ -319,6 +319,10 @@ "options": { "type": "object", "properties": { + "administrators": { + "description": "Define entities which can act as Administrators in the capsule construct These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants.", + "type": "array" + }, "allowServiceAccountPromotion": { "description": "ServiceAccounts within tenant namespaces can be promoted to owners of the given tenant this can be achieved by labeling the serviceaccount and then they are considered owners. This can only be done by other owners of the tenant. However ServiceAccounts which have been promoted to owner can not promote further serviceAccounts.", "type": "boolean" diff --git a/charts/capsule/values.yaml b/charts/capsule/values.yaml index f59081d7..9469a216 100644 --- a/charts/capsule/values.yaml +++ b/charts/capsule/values.yaml @@ -178,6 +178,13 @@ manager: workers: 1 # -- Set the log verbosity of the capsule with a value from 1 to 5 logLevel: '3' + # -- Define entities which can act as Administrators in the capsule construct + # These entities are automatically owners for all existing tenants. Meaning they can add namespaces to any tenant. However they must be specific by using the capsule label + # for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor + # be ignored by capsule. May also be handy in GitOps scenarios where certain service accounts need to be able to manage namespaces for all tenants. + administrators: [] + # - kind: User + # name: alice # -- Names of the users considered as Capsule users. userNames: [] # -- Names of the groups considered as Capsule users. diff --git a/cmd/main.go b/cmd/main.go index 877ff341..30e4fc85 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -31,37 +31,38 @@ import ( capsulev1beta1 "github.com/projectcapsule/capsule/api/v1beta1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - configcontroller "github.com/projectcapsule/capsule/controllers/config" - podlabelscontroller "github.com/projectcapsule/capsule/controllers/pod" - "github.com/projectcapsule/capsule/controllers/pv" - rbaccontroller "github.com/projectcapsule/capsule/controllers/rbac" - "github.com/projectcapsule/capsule/controllers/resourcepools" - "github.com/projectcapsule/capsule/controllers/resources" - servicelabelscontroller "github.com/projectcapsule/capsule/controllers/servicelabels" - tenantcontroller "github.com/projectcapsule/capsule/controllers/tenant" - tlscontroller "github.com/projectcapsule/capsule/controllers/tls" - utilscontroller "github.com/projectcapsule/capsule/controllers/utils" + configcontroller "github.com/projectcapsule/capsule/internal/controllers/cfg" + podlabelscontroller "github.com/projectcapsule/capsule/internal/controllers/pod" + "github.com/projectcapsule/capsule/internal/controllers/pv" + rbaccontroller "github.com/projectcapsule/capsule/internal/controllers/rbac" + "github.com/projectcapsule/capsule/internal/controllers/resourcepools" + "github.com/projectcapsule/capsule/internal/controllers/resources" + servicelabelscontroller "github.com/projectcapsule/capsule/internal/controllers/servicelabels" + tenantcontroller "github.com/projectcapsule/capsule/internal/controllers/tenant" + tlscontroller "github.com/projectcapsule/capsule/internal/controllers/tls" + utilscontroller "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/internal/metrics" + "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/defaults" + "github.com/projectcapsule/capsule/internal/webhook/gateway" + "github.com/projectcapsule/capsule/internal/webhook/ingress" + "github.com/projectcapsule/capsule/internal/webhook/namespace" + namespacemutation "github.com/projectcapsule/capsule/internal/webhook/namespace/mutation" + namespacevalidation "github.com/projectcapsule/capsule/internal/webhook/namespace/validation" + "github.com/projectcapsule/capsule/internal/webhook/networkpolicy" + "github.com/projectcapsule/capsule/internal/webhook/node" + "github.com/projectcapsule/capsule/internal/webhook/pod" + "github.com/projectcapsule/capsule/internal/webhook/pvc" + "github.com/projectcapsule/capsule/internal/webhook/resourcepool" + "github.com/projectcapsule/capsule/internal/webhook/route" + "github.com/projectcapsule/capsule/internal/webhook/service" + "github.com/projectcapsule/capsule/internal/webhook/serviceaccounts" + tenantmutation "github.com/projectcapsule/capsule/internal/webhook/tenant/mutation" + tenantvalidation "github.com/projectcapsule/capsule/internal/webhook/tenant/validation" + tntresource "github.com/projectcapsule/capsule/internal/webhook/tenantresource" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" "github.com/projectcapsule/capsule/pkg/indexer" - "github.com/projectcapsule/capsule/pkg/metrics" - "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/defaults" - "github.com/projectcapsule/capsule/pkg/webhook/gateway" - "github.com/projectcapsule/capsule/pkg/webhook/ingress" - namespacemutation "github.com/projectcapsule/capsule/pkg/webhook/namespace/mutation" - namespacevalidation "github.com/projectcapsule/capsule/pkg/webhook/namespace/validation" - "github.com/projectcapsule/capsule/pkg/webhook/networkpolicy" - "github.com/projectcapsule/capsule/pkg/webhook/node" - "github.com/projectcapsule/capsule/pkg/webhook/pod" - "github.com/projectcapsule/capsule/pkg/webhook/pvc" - "github.com/projectcapsule/capsule/pkg/webhook/resourcepool" - "github.com/projectcapsule/capsule/pkg/webhook/route" - "github.com/projectcapsule/capsule/pkg/webhook/service" - "github.com/projectcapsule/capsule/pkg/webhook/serviceaccounts" - tenantmutation "github.com/projectcapsule/capsule/pkg/webhook/tenant/mutation" - tenantvalidation "github.com/projectcapsule/capsule/pkg/webhook/tenant/validation" - tntresource "github.com/projectcapsule/capsule/pkg/webhook/tenantresource" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) var ( @@ -92,7 +93,7 @@ func main() { var enableLeaderElection, version bool - var metricsAddr, namespace, configurationName string + var metricsAddr, ns, configurationName string var webhookPort int @@ -125,7 +126,7 @@ func main() { os.Exit(0) } - if namespace = os.Getenv("NAMESPACE"); len(namespace) == 0 { + if ns = os.Getenv("NAMESPACE"); len(ns) == 0 { setupLog.Error(fmt.Errorf("unable to determinate the Namespace Capsule is running on"), "unable to start manager") os.Exit(1) } @@ -179,7 +180,7 @@ func main() { tlsReconciler := &tlscontroller.Reconciler{ Client: directClient, Log: ctrl.Log.WithName("controllers").WithName("TLS"), - Namespace: namespace, + Namespace: ns, Configuration: directCfg, } @@ -190,7 +191,7 @@ func main() { tlsCert := &corev1.Secret{} - if err = directClient.Get(ctx, types.NamespacedName{Namespace: namespace, Name: directCfg.TLSSecretName()}, tlsCert); err != nil { + if err = directClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: directCfg.TLSSecretName()}, tlsCert); err != nil { setupLog.Error(err, "unable to get Capsule TLS secret") os.Exit(1) } @@ -233,21 +234,50 @@ func main() { webhooksList := append( make([]webhook.Webhook, 0), route.Pod(pod.ImagePullPolicy(), pod.ContainerRegistry(cfg), pod.PriorityClass(), pod.RuntimeClass()), - route.Namespace(utils.InCapsuleGroups(cfg, namespacevalidation.PatchHandler(cfg), namespacevalidation.QuotaHandler(), namespacevalidation.FreezeHandler(cfg), namespacevalidation.PrefixHandler(cfg), namespacevalidation.UserMetadataHandler())), route.Ingress(ingress.Class(cfg, kubeVersion), ingress.Hostnames(cfg), ingress.Collision(cfg), ingress.Wildcard()), route.PVC(pvc.Validating(), pvc.PersistentVolumeReuse()), route.Service(service.Handler()), route.TenantResourceObjects(utils.InCapsuleGroups(cfg, tntresource.WriteOpsHandler())), route.NetworkPolicy(utils.InCapsuleGroups(cfg, networkpolicy.Handler())), - route.TenantMutating(tenantmutation.MetaHandler()), - route.TenantValidating(tenantvalidation.NameHandler(), tenantvalidation.RoleBindingRegexHandler(), tenantvalidation.IngressClassRegexHandler(), tenantvalidation.StorageClassRegexHandler(), tenantvalidation.ContainerRegistryRegexHandler(), tenantvalidation.HostnameRegexHandler(), tenantvalidation.FreezedEmitter(), tenantvalidation.ServiceAccountNameHandler(), tenantvalidation.ForbiddenAnnotationsRegexHandler(), tenantvalidation.ProtectedHandler()), route.Cordoning(tenantvalidation.CordoningHandler(cfg)), route.Node(utils.InCapsuleGroups(cfg, node.UserMetadataHandler(cfg, kubeVersion))), route.ServiceAccounts(serviceaccounts.Handler(cfg)), - route.NamespacePatch(utils.InCapsuleGroups(cfg, namespacemutation.CordoningLabelHandler(cfg), namespacemutation.OwnerReferenceHandler(cfg), namespacemutation.MetadataHandler(cfg))), route.CustomResources(tenantvalidation.ResourceCounterHandler(manager.GetClient())), route.Gateway(gateway.Class(cfg)), route.Defaults(defaults.Handler(cfg, kubeVersion)), + route.TenantMutation( + tenantmutation.MetaHandler(), + ), + route.TenantValidation( + tenantvalidation.NameHandler(), + tenantvalidation.RoleBindingRegexHandler(), + tenantvalidation.IngressClassRegexHandler(), + tenantvalidation.StorageClassRegexHandler(), + tenantvalidation.ContainerRegistryRegexHandler(), + tenantvalidation.HostnameRegexHandler(), + tenantvalidation.FreezedEmitter(), + tenantvalidation.ServiceAccountNameHandler(), + tenantvalidation.ForbiddenAnnotationsRegexHandler(), + tenantvalidation.ProtectedHandler(), + ), + route.NamespaceValidation( + namespace.NamespaceHandler( + cfg, + namespacevalidation.PatchHandler(cfg), + namespacevalidation.FreezeHandler(cfg), + namespacevalidation.QuotaHandler(), + namespacevalidation.PrefixHandler(cfg), + namespacevalidation.UserMetadataHandler(), + ), + ), + route.NamespaceMutation( + namespace.NamespaceHandler( + cfg, + namespacemutation.OwnerReferenceHandler(cfg), + namespacemutation.CordoningLabelHandler(cfg), + namespacemutation.MetadataHandler(cfg), + ), + ), route.ResourcePoolMutation((resourcepool.PoolMutationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepool")))), route.ResourcePoolValidation((resourcepool.PoolValidationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepool")))), route.ResourcePoolClaimMutation((resourcepool.ClaimMutationHandler(ctrl.Log.WithName("webhooks").WithName("resourcepoolclaims")))), diff --git a/controllers/tenant/annotations.go b/controllers/tenant/annotations.go deleted file mode 100644 index f933af24..00000000 --- a/controllers/tenant/annotations.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package tenant - -const ( - AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" - AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" - AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" - AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" - AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" - AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" -) diff --git a/e2e/additional_role_bindings_test.go b/e2e/additional_role_bindings_test.go index 1e5ee1a0..1a1a1812 100644 --- a/e2e/additional_role_bindings_test.go +++ b/e2e/additional_role_bindings_test.go @@ -22,10 +22,12 @@ var _ = Describe("creating a Namespace with an additional Role Binding", Label(" Name: "additional-role-binding", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "dale", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "dale", + Kind: "User", + }, }, }, AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ @@ -56,13 +58,13 @@ var _ = Describe("creating a Namespace with an additional Role Binding", Label(" It("should be assigned to each Namespace", func() { for _, ns := range []string{"rb-1", "rb-2", "rb-3"} { ns := NewNamespace(ns) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) var rb *rbacv1.RoleBinding Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) rb, err = cs.RbacV1().RoleBindings(ns.Name).Get(context.Background(), fmt.Sprintf("capsule-%s-2-%s", tnt.Name, "crds-rolebinding"), metav1.GetOptions{}) return err }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) diff --git a/e2e/administrators_test.go b/e2e/administrators_test.go new file mode 100644 index 00000000..e27f0b59 --- /dev/null +++ b/e2e/administrators_test.go @@ -0,0 +1,182 @@ +// Copyright 2020-2023 Project Capsule Authors. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" +) + +var _ = Describe("Administrators", Label("namespace", "permissions"), func() { + originConfig := &capsulev1beta2.CapsuleConfiguration{} + + tnt1 := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tnt-admins-1", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: api.OwnerListSpec{ + { + UserSpec: api.UserSpec{ + Name: "paul", + Kind: "User", + }, + }, + }, + }, + } + + tnt2 := &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "tnt-admins-2", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: api.OwnerListSpec{ + { + UserSpec: api.UserSpec{ + Name: "george", + Kind: "User", + }, + }, + }, + }, + } + + admin := api.UserSpec{ + Name: "admin", + Kind: "User", + } + + JustBeforeEach(func() { + Expect(k8sClient.Get(context.Background(), client.ObjectKey{Name: defaultConfigurationName}, originConfig)).To(Succeed()) + + for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { + EventuallyCreation(func() error { + tnt.ResourceVersion = "" + + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + + } + + ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { + configuration.Spec.Administrators = []api.UserSpec{admin} + }) + + }) + + JustAfterEach(func() { + for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} { + Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + } + + Eventually(func() error { + c := &capsulev1beta2.CapsuleConfiguration{} + if err := k8sClient.Get(context.Background(), client.ObjectKey{Name: originConfig.Name}, c); err != nil { + return err + } + // Apply the initial configuration from originConfig to c + c.Spec = originConfig.Spec + return k8sClient.Update(context.Background(), c) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + + }) + + It("capsule is triggered for administrators based on namespace label", func() { + By("creating namespace with faulty label", func() { + ns := NewNamespace("", map[string]string{ + meta.TenantLabel: "something-random", + }) + + NamespaceCreation(ns, admin, defaultTimeoutInterval).ShouldNot(Succeed()) + }) + + By("creating namespace with no label", func() { + ns := NewNamespace("") + NamespaceCreation(ns, admin, defaultTimeoutInterval).Should(Succeed()) + + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed()) + Expect(len(ns.OwnerReferences)).To(Equal(0)) + }) + + By("creating namespace with no label", func() { + ns := NewNamespace("") + NamespaceCreation(ns, admin, defaultTimeoutInterval).Should(Succeed()) + + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed()) + Expect(len(ns.OwnerReferences)).To(Equal(0)) + + PatchTenantLabelForNamespace(tnt1, ns, ownerClient(admin), defaultTimeoutInterval).Should(Succeed()) + + NamespaceIsPartOfTenant(tnt1, ns) + }) + }) + + It("assignment from administrator should work for all tenants", func() { + ns1 := NewNamespace("", map[string]string{ + meta.TenantLabel: tnt1.GetName(), + }) + + By("creating namespace", func() { + NamespaceCreation(ns1, admin, defaultTimeoutInterval).Should(Succeed()) + }) + + By("verifing tenant state", func() { + TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElements(ns1.GetName())) + + t := &capsulev1beta2.Tenant{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt1.GetName()}, t)).Should(Succeed()) + Expect(t.Status.Size).To(Equal(uint(1))) + + instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns1.GetName(), UID: ns1.GetUID()}) + Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil") + + condition := instance.Conditions.GetConditionByType(meta.ReadyCondition) + Expect(condition).NotTo(BeNil(), "Condition instance should not be nil") + + Expect(instance.Name).To(Equal(ns1.GetName())) + Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected namespace condition status to be True") + Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected namespace condition type to be Ready") + Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected namespace condition reason to be Succeeded") + }) + + ns2 := NewNamespace("", map[string]string{ + meta.TenantLabel: tnt2.GetName(), + }) + + By("creating namespace", func() { + NamespaceCreation(ns2, admin, defaultTimeoutInterval).Should(Succeed()) + }) + + By("verifing tenant state", func() { + TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElements(ns2.GetName())) + + t := &capsulev1beta2.Tenant{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt2.GetName()}, t)).Should(Succeed()) + + Expect(t.Status.Size).To(Equal(uint(1))) + + instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns2.GetName(), UID: ns2.GetUID()}) + Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil") + + condition := instance.Conditions.GetConditionByType(meta.ReadyCondition) + Expect(condition).NotTo(BeNil(), "Condition instance should not be nil") + + Expect(instance.Name).To(Equal(ns2.GetName())) + Expect(condition.Status).To(Equal(metav1.ConditionTrue), "Expected namespace condition status to be True") + Expect(condition.Type).To(Equal(meta.ReadyCondition), "Expected namespace condition type to be Ready") + Expect(condition.Reason).To(Equal(meta.SucceededReason), "Expected namespace condition reason to be Succeeded") + }) + + }) +}) diff --git a/e2e/allowed_external_ips_test.go b/e2e/allowed_external_ips_test.go index 9d8833b2..b6b5399b 100644 --- a/e2e/allowed_external_ips_test.go +++ b/e2e/allowed_external_ips_test.go @@ -22,10 +22,12 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena Name: "allowed-external-ip", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "google", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "google", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -51,7 +53,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena It("should fail creating an evil service", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -76,7 +78,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena }, } EventuallyCreation(func() error { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).ShouldNot(Succeed()) @@ -84,7 +86,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena It("should allow the first CIDR block", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -109,7 +111,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena }, } EventuallyCreation(func() error { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).Should(Succeed()) @@ -117,7 +119,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena It("should allow the /32 CIDR block", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -141,7 +143,7 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena }, } EventuallyCreation(func() error { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).Should(Succeed()) diff --git a/e2e/container_registry_test.go b/e2e/container_registry_test.go index 9444a601..b3e8bfb6 100644 --- a/e2e/container_registry_test.go +++ b/e2e/container_registry_test.go @@ -33,10 +33,12 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re Name: "container-registry", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "matt", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "matt", + Kind: "User", + }, }, }, ContainerRegistries: &api.AllowedListSpec{ @@ -72,7 +74,7 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re It("should add labels to Namespace", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Eventually(func() (ok bool) { Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.Name}, ns)).Should(Succeed()) ok, _ = HaveKeyWithValue("capsule.clastix.io/allowed-registries", "docker.io,myregistry.azurecr.io").Match(ns.Annotations) @@ -104,9 +106,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -133,9 +135,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -173,9 +175,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -227,9 +229,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) role := &rbacv1.Role{ @@ -316,9 +318,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -359,9 +361,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -402,9 +404,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) role := &rbacv1.Role{ @@ -491,9 +493,9 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -519,7 +521,7 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re It("should allow using an exact match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -535,7 +537,7 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -544,7 +546,7 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re It("should allow using a regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -560,7 +562,7 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) return err diff --git a/e2e/custom_capsule_group_test.go b/e2e/custom_capsule_group_test.go index 5714f995..adf34266 100644 --- a/e2e/custom_capsule_group_test.go +++ b/e2e/custom_capsule_group_test.go @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-group", Label("config"), func() { @@ -22,14 +23,18 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro Name: "tenant-assigned-custom-group", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alice", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alice", + Kind: "User", + }, }, { - Name: "bob", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "bob", + Kind: "User", + }, }, }, }, @@ -64,7 +69,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro }) ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed and be available in Tenant namespaces list with multiple groups", func() { @@ -74,7 +79,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) @@ -85,7 +90,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) @@ -97,7 +102,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed and be available in Tenant namespaces list with default single user", func() { @@ -109,7 +114,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) It("should succeed and be available in Tenant namespaces list with default single user", func() { @@ -121,7 +126,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[1].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should fail when group is ignored", func() { @@ -133,7 +138,7 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) }) diff --git a/e2e/custom_resource_quota_test.go b/e2e/custom_resource_quota_test.go index 4968329d..ff4d1dca 100644 --- a/e2e/custom_resource_quota_test.go +++ b/e2e/custom_resource_quota_test.go @@ -19,6 +19,7 @@ import ( "k8s.io/client-go/kubernetes/scheme" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("when Tenant limits custom Resource Quota", Label("resourcequota"), func() { @@ -30,10 +31,12 @@ var _ = Describe("when Tenant limits custom Resource Quota", Label("resourcequot }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "resource", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "resource", + Kind: "User", + }, }, }, }, @@ -102,7 +105,7 @@ var _ = Describe("when Tenant limits custom Resource Quota", Label("resourcequot for _, i := range []int{1, 2, 3} { ns := NewNamespace(fmt.Sprintf("limiting-resources-ns-%d", i)) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) obj := &unstructured.Unstructured{ diff --git a/e2e/disable_externalname_test.go b/e2e/disable_externalname_test.go index f0963009..32fde23a 100644 --- a/e2e/disable_externalname_test.go +++ b/e2e/disable_externalname_test.go @@ -23,10 +23,12 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan Name: "disable-external-service", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "google", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "google", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -50,7 +52,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan It("should fail creating a service with ExternalService type", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { svc := &corev1.Service{ @@ -73,7 +75,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).Should(Succeed()) @@ -99,7 +101,7 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).ShouldNot(Succeed()) diff --git a/e2e/disable_ingress_wildcard_test.go b/e2e/disable_ingress_wildcard_test.go index bdf68d0d..4b80491c 100644 --- a/e2e/disable_ingress_wildcard_test.go +++ b/e2e/disable_ingress_wildcard_test.go @@ -16,6 +16,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" "github.com/projectcapsule/capsule/pkg/utils" ) @@ -28,10 +29,12 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "scott", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "scott", + Kind: "User", + }, }, }, }, @@ -58,7 +61,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) ok := &extensionsv1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -138,7 +141,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) ok := &networkingv1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ @@ -218,7 +221,7 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) ok := &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ diff --git a/e2e/disable_loadbalancer_test.go b/e2e/disable_loadbalancer_test.go index 95d64083..76916924 100644 --- a/e2e/disable_loadbalancer_test.go +++ b/e2e/disable_loadbalancer_test.go @@ -23,10 +23,12 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant Name: "disable-loadbalancer-service", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "amazon", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "amazon", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -50,7 +52,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant It("should fail creating a service with LoadBalancer type", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { svc := &corev1.Service{ @@ -73,7 +75,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) @@ -101,7 +103,7 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) diff --git a/e2e/disable_node_ports_test.go b/e2e/disable_node_ports_test.go index 14df0aae..0e288af5 100644 --- a/e2e/disable_node_ports_test.go +++ b/e2e/disable_node_ports_test.go @@ -23,10 +23,12 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", L Name: "disable-node-ports", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "google", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "google", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -49,7 +51,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", L It("should fail creating a service with NodePort type", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -71,7 +73,7 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", L }, } EventuallyCreation(func() error { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).ShouldNot(Succeed()) diff --git a/e2e/dynamic_tenant_owner_clusterroles_test.go b/e2e/dynamic_tenant_owner_clusterroles_test.go index cf6688df..3e0f8a09 100644 --- a/e2e/dynamic_tenant_owner_clusterroles_test.go +++ b/e2e/dynamic_tenant_owner_clusterroles_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("defining dynamic Tenant Owner Cluster Roles", Label("tenant"), func() { @@ -19,15 +20,19 @@ var _ = Describe("defining dynamic Tenant Owner Cluster Roles", Label("tenant"), Name: "dynamic-tenant-owner-clusterroles", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Kind: "User", - Name: "michonne", + UserSpec: api.UserSpec{ + Kind: "User", + Name: "michonne", + }, ClusterRoles: []string{"editor", "manager"}, }, { - Name: "kingdom", - Kind: "Group", + UserSpec: api.UserSpec{ + Name: "kingdom", + Kind: "Group", + }, ClusterRoles: []string{"readonly"}, }, }, @@ -49,7 +54,7 @@ var _ = Describe("defining dynamic Tenant Owner Cluster Roles", Label("tenant"), It("namespace should contains the dynamic rolebindings", func() { for _, ns := range []string{"dynamnic-roles-1", "dynamnic-roles-2", "dynamnic-roles-3"} { ns := NewNamespace(ns) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(CheckForOwnerRoleBindings(ns, tnt.Spec.Owners[0], map[string]bool{"editor": false, "manager": false}), defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) diff --git a/e2e/enable_loadbalancer_test.go b/e2e/enable_loadbalancer_test.go index bcee59b7..d7ab6201 100644 --- a/e2e/enable_loadbalancer_test.go +++ b/e2e/enable_loadbalancer_test.go @@ -23,10 +23,12 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" Name: "enable-loadbalancer-service", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "netflix", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "netflix", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -50,7 +52,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" It("should succeed creating a service with LoadBalancer type", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { svc := &corev1.Service{ @@ -73,7 +75,7 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant" }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) diff --git a/e2e/enable_node_ports_test.go b/e2e/enable_node_ports_test.go index 720015e5..ec6fe631 100644 --- a/e2e/enable_node_ports_test.go +++ b/e2e/enable_node_ports_test.go @@ -13,6 +13,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a nodePort service when it is enabled for Tenant", Label("tenant"), func() { @@ -21,10 +22,12 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", La Name: "enable-node-ports", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "google", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "google", + Kind: "User", + }, }, }, }, @@ -42,7 +45,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", La It("should allow creating a service with NodePort type", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) svc := &corev1.Service{ ObjectMeta: metav1.ObjectMeta{ @@ -64,7 +67,7 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", La }, } EventuallyCreation(func() error { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{}) return err }).Should(Succeed()) diff --git a/e2e/forbidden_annotations_regex_test.go b/e2e/forbidden_annotations_regex_test.go index 49761f7c..c5577535 100644 --- a/e2e/forbidden_annotations_regex_test.go +++ b/e2e/forbidden_annotations_regex_test.go @@ -66,10 +66,12 @@ var _ = Describe("creating a tenant with various forbidden regexes", Label("tena Name: "namespace", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alice", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alice", + Kind: "User", + }, }, }, }, diff --git a/e2e/force_tenant_prefix_tenant_scope_test.go b/e2e/force_tenant_prefix_tenant_scope_test.go index 3aaaae13..3462a488 100644 --- a/e2e/force_tenant_prefix_tenant_scope_test.go +++ b/e2e/force_tenant_prefix_tenant_scope_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Tenant scope", Label("tenant", "config"), func() { @@ -20,10 +21,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te }, Spec: capsulev1beta2.TenantSpec{ ForceTenantPrefix: &[]bool{true}[0], - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -34,10 +37,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te }, Spec: capsulev1beta2.TenantSpec{ ForceTenantPrefix: &[]bool{false}[0], - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -70,17 +75,17 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t1.GetName(), } ns := NewNamespace("awesome", labels) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should fail using prefix without capsule.clastix.io/tenant label, where the user owns more than one Tenant, for a tenant with ForceTenantPrefix true and global ForceTenantPrefix false", func() { ns := NewNamespace("awesome-namespace") - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should fail using prefix without capsule.clastix.io/tenant label, where the user owns more than one Tenant, for a tenant with ForceTenantPrefix false and global ForceTenantPrefix true", func() { ns := NewNamespace("awesome-namespace") - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed and be assigned with prefix and label, for a tenant with ForceTenantPrefix true and global ForceTenantPrefix false", func() { @@ -88,7 +93,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t1.GetName(), } ns := NewNamespace("awesome-tenant", labels) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) @@ -101,7 +106,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t1.GetName(), } ns := NewNamespace("awesome", labels) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed and be assigned with prefix and label, for a tenant with ForceTenantPrefix true and global ForceTenantPrefix true", func() { @@ -112,7 +117,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t1.GetName(), } ns := NewNamespace("awesome-tenant", labels) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) @@ -122,7 +127,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te configuration.Spec.ForceTenantPrefix = true }) ns := NewNamespace("awesome-namespace") - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed when not using prefix, with tenant label for a tenant with ForceTenantPrefix false and global ForceTenantPrefix false", func() { @@ -130,7 +135,7 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t2.GetName(), } ns := NewNamespace("awesome", labels) - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) It("should succeed when not using prefix, with tenant label for a tenant with ForceTenantPrefix false and global ForceTenantPrefix true", func() { @@ -141,6 +146,6 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te "capsule.clastix.io/tenant": t2.GetName(), } ns := NewNamespace("awesome", labels) - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) }) diff --git a/e2e/force_tenant_prefix_test.go b/e2e/force_tenant_prefix_test.go index 85b477b4..62a4d4d5 100644 --- a/e2e/force_tenant_prefix_test.go +++ b/e2e/force_tenant_prefix_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Label("tenant"), func() { @@ -19,10 +20,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Lab Name: "awesome", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -32,10 +35,12 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Lab Name: "awesome-tenant", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -66,20 +71,20 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Lab It("should fail when non using prefix", func() { ns := NewNamespace("awesome") - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) It("should succeed using prefix", func() { ns := NewNamespace("awesome-namespace") - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) It("should succeed and assigned according to closest match", func() { ns1 := NewNamespace("awesome-tenant") ns2 := NewNamespace("awesome-tenant-namespace") - NamespaceCreation(ns1, t1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - NamespaceCreation(ns2, t2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns1, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) TenantNamespaceList(t2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/gateway_class_test.go b/e2e/gateway_class_test.go index ab03b5c4..40c864a7 100644 --- a/e2e/gateway_class_test.go +++ b/e2e/gateway_class_test.go @@ -5,11 +5,12 @@ package e2e import ( "context" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes/scheme" @@ -49,10 +50,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("gateway"), func() Name: "tnt-with-default-gateway-class-and-label-selector", }, Spec: capsulev1beta2.TenantSpec{ - Owners: []capsulev1beta2.OwnerSpec{ + Owners: []api.OwnerSpec{ { - Name: "gateway-default-and-label-selector", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gateway-default-and-label-selector", + Kind: "User", + }, }, }, GatewayOptions: capsulev1beta2.GatewayOptions{ @@ -75,10 +78,12 @@ var _ = Describe("when Tenant handles Gateway classes", Label("gateway"), func() Name: "tnt-with-label-selector-only", }, Spec: capsulev1beta2.TenantSpec{ - Owners: []capsulev1beta2.OwnerSpec{ + Owners: []api.OwnerSpec{ { - Name: "gateway-with-label-selector-only", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gateway-with-label-selector-only", + Kind: "User", + }, }, }, GatewayOptions: capsulev1beta2.GatewayOptions{ @@ -122,7 +127,7 @@ var _ = Describe("when Tenant handles Gateway classes", Label("gateway"), func() }) It("should block Gateway", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("providing unauthorized gatewayClassName", func() { @@ -173,7 +178,7 @@ var _ = Describe("when Tenant handles Gateway classes", Label("gateway"), func() }) It("should allow Gateway", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("providing authorized class", func() { Eventually(func() (err error) { @@ -222,7 +227,7 @@ var _ = Describe("when Tenant handles Gateway classes", Label("gateway"), func() }) It("should fail on invalid configuration", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntWithoutDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithoutDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithoutDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("providing empty GatewayClassName", func() { Eventually(func() (err error) { diff --git a/e2e/globaltenantresource_test.go b/e2e/globaltenantresource_test.go index 25ac3895..eaecad1f 100644 --- a/e2e/globaltenantresource_test.go +++ b/e2e/globaltenantresource_test.go @@ -32,10 +32,12 @@ var _ = Describe("Creating a GlobalTenantResource object", func() { }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "solar-user", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "solar-user", + Kind: "User", + }, }, }, }, @@ -49,10 +51,12 @@ var _ = Describe("Creating a GlobalTenantResource object", func() { }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "wind-user", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "wind-user", + Kind: "User", + }, }, }, }, @@ -187,13 +191,13 @@ var _ = Describe("Creating a GlobalTenantResource object", func() { By("creating solar Namespaces", func() { for _, ns := range solarNs { - NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) } }) By("creating wind Namespaces", func() { for _, ns := range windNs { - NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, wind.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, wind.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) } }) diff --git a/e2e/imagepullpolicy_multiple_test.go b/e2e/imagepullpolicy_multiple_test.go index 1597dda9..f9cefb89 100644 --- a/e2e/imagepullpolicy_multiple_test.go +++ b/e2e/imagepullpolicy_multiple_test.go @@ -22,10 +22,12 @@ var _ = Describe("enforcing some defined ImagePullPolicy", Label("tenant", "imag Name: "image-pull-policies", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alex", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alex", + Kind: "User", + }, }, }, ImagePullPolicies: []api.ImagePullPolicySpec{"Always", "IfNotPresent"}, @@ -45,9 +47,9 @@ var _ = Describe("enforcing some defined ImagePullPolicy", Label("tenant", "imag It("should just allow the defined policies", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) role := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ diff --git a/e2e/imagepullpolicy_single_test.go b/e2e/imagepullpolicy_single_test.go index b2c0e084..7cd7515b 100644 --- a/e2e/imagepullpolicy_single_test.go +++ b/e2e/imagepullpolicy_single_test.go @@ -22,10 +22,12 @@ var _ = Describe("enforcing a defined ImagePullPolicy", Label("tenant", "images" Name: "image-pull-policy", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "axel", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "axel", + Kind: "User", + }, }, }, ImagePullPolicies: []api.ImagePullPolicySpec{"Always"}, @@ -45,9 +47,9 @@ var _ = Describe("enforcing a defined ImagePullPolicy", Label("tenant", "images" It("should just allow the defined policy", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) role := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ diff --git a/e2e/ingress_class_extensions_test.go b/e2e/ingress_class_extensions_test.go index 2a6abed4..45c01e64 100644 --- a/e2e/ingress_class_extensions_test.go +++ b/e2e/ingress_class_extensions_test.go @@ -25,10 +25,12 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", Name: "ingress-class-extensions-v1beta1", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -62,9 +64,9 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", It("should block a non allowed class for extensions/v1beta1", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("non-specifying at all", func() { @@ -144,9 +146,9 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", It("should allow enabled class using the deprecated annotation", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact { @@ -189,9 +191,9 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) for _, c := range tnt.Spec.IngressOptions.AllowedClasses.Exact { @@ -216,10 +218,10 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", It("should allow enabled Ingress by regex using the deprecated annotation", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) ingressClass := "oil-ingress" - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { @@ -250,10 +252,10 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1", It("should allow enabled Ingress by regex using the ingressClassName field", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) ingressClass := "oil-haproxy" - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { diff --git a/e2e/ingress_class_networking_test.go b/e2e/ingress_class_networking_test.go index a9b53514..822920a5 100644 --- a/e2e/ingress_class_networking_test.go +++ b/e2e/ingress_class_networking_test.go @@ -30,10 +30,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Name: "ic-selector-networking-v1", }, Spec: capsulev1beta2.TenantSpec{ - Owners: []capsulev1beta2.OwnerSpec{ + Owners: []api.OwnerSpec{ { - Name: "ingress-selector", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-selector", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -59,10 +61,12 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Name: "ic-default-networking-v1", }, Spec: capsulev1beta2.TenantSpec{ - Owners: []capsulev1beta2.OwnerSpec{ + Owners: []api.OwnerSpec{ { - Name: "ingress-default", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-default", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -161,9 +165,9 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("non-specifying at all", func() { @@ -243,9 +247,9 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact { @@ -282,9 +286,9 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) for _, c := range tntNoDefault.Spec.IngressOptions.AllowedClasses.Exact { @@ -319,10 +323,10 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) ingressClass := "oil-ingress" - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { @@ -357,10 +361,10 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) ingressClass := "oil-haproxy" - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { @@ -428,9 +432,9 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -481,9 +485,9 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" } ns := NewNamespace("") - cs := ownerClient(tntNoDefault.Spec.Owners[0]) + cs := ownerClient(tntNoDefault.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -495,7 +499,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" It("should mutate to default tenant IngressClass (class not does not exist)", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) i := &networkingv1.Ingress{ @@ -534,7 +538,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) i := &networkingv1.Ingress{ @@ -576,7 +580,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) i := &networkingv1.Ingress{ @@ -622,7 +626,7 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1" Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) i := &networkingv1.Ingress{ diff --git a/e2e/ingress_hostnames_collision_cluster_scope_test.go b/e2e/ingress_hostnames_collision_cluster_scope_test.go index 256178fd..59c188ec 100644 --- a/e2e/ingress_hostnames_collision_cluster_scope_test.go +++ b/e2e/ingress_hostnames_collision_cluster_scope_test.go @@ -25,10 +25,12 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", Lab Name: "hostnames-collision-cluster-one", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress-tenant-one", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-tenant-one", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -41,10 +43,12 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", Lab Name: "hostnames-collision-cluster-two", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress-tenant-two", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-tenant-two", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -142,13 +146,13 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", Lab It("should ensure Cluster scope for Ingress hostname and path collision", func() { ns1 := NewNamespace("") - cs1 := ownerClient(tnt1.Spec.Owners[0]) - NamespaceCreation(ns1, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + cs1 := ownerClient(tnt1.Spec.Owners[0].UserSpec) + NamespaceCreation(ns1, tnt1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) ns2 := NewNamespace("") - cs2 := ownerClient(tnt2.Spec.Owners[0]) - NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + cs2 := ownerClient(tnt2.Spec.Owners[0].UserSpec) + NamespaceCreation(ns2, tnt2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) By("testing networking.k8s.io", func() { diff --git a/e2e/ingress_hostnames_collision_disabled_test.go b/e2e/ingress_hostnames_collision_disabled_test.go index 4777124f..c6b31384 100644 --- a/e2e/ingress_hostnames_collision_disabled_test.go +++ b/e2e/ingress_hostnames_collision_disabled_test.go @@ -25,10 +25,12 @@ var _ = Describe("when disabling Ingress hostnames collision", Label("ingress"), Name: "hostnames-collision-disabled", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress-disabled", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-disabled", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -119,9 +121,9 @@ var _ = Describe("when disabling Ingress hostnames collision", Label("ingress"), It("should not check any kind of collision", func() { ns1 := NewNamespace("") ns2 := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) - NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) + NamespaceCreation(ns1, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/ingress_hostnames_collision_namespace_scope_test.go b/e2e/ingress_hostnames_collision_namespace_scope_test.go index ce6be35a..b5fce227 100644 --- a/e2e/ingress_hostnames_collision_namespace_scope_test.go +++ b/e2e/ingress_hostnames_collision_namespace_scope_test.go @@ -25,10 +25,12 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", L Name: "hostnames-collision-namespace", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress-namespace", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-namespace", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -119,9 +121,9 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", L It("should ensure Namespace scope for Ingress hostname and path collision", func() { ns1 := NewNamespace("") ns2 := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) - NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) + NamespaceCreation(ns1, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/ingress_hostnames_collision_tenant_scope_test.go b/e2e/ingress_hostnames_collision_tenant_scope_test.go index 3a070c44..0dd2c520 100644 --- a/e2e/ingress_hostnames_collision_tenant_scope_test.go +++ b/e2e/ingress_hostnames_collision_tenant_scope_test.go @@ -25,10 +25,12 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", Labe Name: "hostnames-collision-tenant", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ingress-tenant", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ingress-tenant", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -121,10 +123,10 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", Labe ns2 := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns1, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns1.GetName())) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns2.GetName())) diff --git a/e2e/ingress_hostnames_test.go b/e2e/ingress_hostnames_test.go index c694ddb4..af105c2f 100644 --- a/e2e/ingress_hostnames_test.go +++ b/e2e/ingress_hostnames_test.go @@ -25,10 +25,12 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func Name: "ingress-hostnames", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "hostname", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "hostname", + Kind: "User", + }, }, }, IngressOptions: capsulev1beta2.IngressOptions{ @@ -122,9 +124,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should block an empty hostname", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing networking.k8s.io", func() { @@ -144,9 +146,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should block a non allowed Hostname", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing networking.k8s.io", func() { @@ -166,9 +168,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should block a non allowed Hostname", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing extensions", func() { @@ -188,9 +190,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should allow Hostnames in list", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing networking.k8s.io", func() { @@ -212,9 +214,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should allow Hostnames in list", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing extensions", func() { @@ -236,9 +238,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should allow Hostnames in regex", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing networking.k8s.io", func() { @@ -260,9 +262,9 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func It("should allow Hostnames in regex", func() { ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("testing extensions", func() { diff --git a/e2e/missing_tenant_test.go b/e2e/missing_tenant_test.go index de98dc0e..b9e3e938 100644 --- a/e2e/missing_tenant_test.go +++ b/e2e/missing_tenant_test.go @@ -11,22 +11,25 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace creation with no Tenant assigned", Label("tenant"), func() { It("should fail", func() { tnt := &capsulev1beta2.Tenant{ Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "missing", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "missing", + Kind: "User", + }, }, }, }, } ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).ShouldNot(Succeed()) }) diff --git a/e2e/namespace_additional_metadata_test.go b/e2e/namespace_additional_metadata_test.go index 282f0d93..033515c3 100644 --- a/e2e/namespace_additional_metadata_test.go +++ b/e2e/namespace_additional_metadata_test.go @@ -15,7 +15,7 @@ import ( capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("creating a Namespace for a Tenant with additional metadata", Label("namespace", "metadata"), func() { @@ -32,10 +32,12 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -71,7 +73,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L }) ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("checking additional labels", func() { @@ -187,7 +189,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L "matching_namespace_label": "matching_namespace_label_value", } ns := NewNamespace("", labels) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("checking templated annotations", func() { @@ -318,7 +320,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L } ns := NewNamespace("", labels) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("checking additional labels", func() { diff --git a/e2e/namespace_capsule_label_test.go b/e2e/namespace_capsule_label_test.go index 9e09238e..da18d448 100644 --- a/e2e/namespace_capsule_label_test.go +++ b/e2e/namespace_capsule_label_test.go @@ -13,6 +13,8 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), func() { @@ -21,10 +23,12 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), Name: "capsule-labels", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "charlie", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "charlie", + Kind: "User", + }, }, }, }, @@ -47,10 +51,10 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), NewNamespace(""), } for _, ns := range namespaces { - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Eventually(func() (ok bool) { Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, ns)).Should(Succeed()) - ok, _ = HaveKeyWithValue("capsule.clastix.io/tenant", tnt.Name).Match(ns.Labels) + ok, _ = HaveKeyWithValue(meta.TenantLabel, tnt.Name).Match(ns.Labels) return }, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue()) } diff --git a/e2e/namespace_hijacking_test.go b/e2e/namespace_hijacking_test.go index 038998cc..b1c95992 100644 --- a/e2e/namespace_hijacking_test.go +++ b/e2e/namespace_hijacking_test.go @@ -13,24 +13,29 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) -var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), func() { - tnt := &capsulev1beta2.Tenant{ +var _ = Describe("creating several Namespaces for a Tenant", Label("namespace", "hijack"), func() { + tnt_1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "capsule-ns-attack-1", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "charlie", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, { - Kind: "ServiceAccount", - Name: "system:serviceaccount:attacker-system:attacker", + UserSpec: api.UserSpec{ + Kind: "ServiceAccount", + Name: "system:serviceaccount:attacker-system:attacker", + }, }, }, }, @@ -41,28 +46,29 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), Name: "kube-system", }, } + JustBeforeEach(func() { EventuallyCreation(func() (err error) { - tnt.ResourceVersion = "" - err = k8sClient.Create(context.TODO(), tnt) + tnt_1.ResourceVersion = "" + err = k8sClient.Create(context.TODO(), tnt_1) return }).Should(Succeed()) }) JustAfterEach(func() { - Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) + Expect(k8sClient.Delete(context.TODO(), tnt_1)).Should(Succeed()) }) It("Can't hijack offlimits namespace (Ownerreferences)", func() { tenant := &capsulev1beta2.Tenant{} - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed()) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt_1.Name}, tenant)).Should(Succeed()) // Get the namespace Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) - for _, owner := range tnt.Spec.Owners { - cs := ownerClient(owner) + for _, owner := range tnt_1.Spec.Owners { + cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{"metadata":{"ownerReferences":[{"apiVersion":"%s/%s","kind":"Tenant","name":"%s","uid":"%s"}]}}`, capsulev1beta2.GroupVersion.Group, capsulev1beta2.GroupVersion.Version, tenant.GetName(), tenant.GetUID())) @@ -74,13 +80,13 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), It("Can't hijack offlimits namespace (Labels)", func() { tenant := &capsulev1beta2.Tenant{} - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed()) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt_1.Name}, tenant)).Should(Succeed()) // Get the namespace Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) - for _, owner := range tnt.Spec.Owners { - cs := ownerClient(owner) + for _, owner := range tnt_1.Spec.Owners { + cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{"metadata":{"labels":{"%s":"%s"}}}`, "capsule.clastix.io/tenant", tenant.GetName())) @@ -91,13 +97,13 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), It("Can't hijack offlimits namespace (Annotations)", func() { tenant := &capsulev1beta2.Tenant{} - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed()) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt_1.Name}, tenant)).Should(Succeed()) // Get the namespace Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: kubeSystem.GetName()}, kubeSystem)).Should(Succeed()) - for _, owner := range tnt.Spec.Owners { - cs := ownerClient(owner) + for _, owner := range tnt_1.Spec.Owners { + cs := ownerClient(owner.UserSpec) patch := []byte(fmt.Sprintf(`{"metadata":{"annotations":{"%s":"%s"}}}`, "capsule.clastix.io/tenant", tenant.GetName())) @@ -107,16 +113,16 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"), }) It("Owners can create and attempt to patch new namespaces but patches should not be applied", func() { - for _, owner := range tnt.Spec.Owners { - cs := ownerClient(owner) + for _, owner := range tnt_1.Spec.Owners { + cs := ownerClient(owner.UserSpec) // Each owner creates a new namespace ns := NewNamespace("") - NamespaceCreation(ns, owner, defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, owner.UserSpec, defaultTimeoutInterval).Should(Succeed()) // Attempt to patch the owner references of the new namespace tenant := &capsulev1beta2.Tenant{} - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.Name}, tenant)).Should(Succeed()) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt_1.Name}, tenant)).Should(Succeed()) randomUID := types.UID(fmt.Sprintf("%d", rand.Int())) randomName := fmt.Sprintf("random-tenant-%d", rand.Int()) diff --git a/e2e/namespace_metadata_controller_test.go b/e2e/namespace_metadata_controller_test.go index 7a3f0389..fce8fb21 100644 --- a/e2e/namespace_metadata_controller_test.go +++ b/e2e/namespace_metadata_controller_test.go @@ -29,10 +29,12 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, NamespaceOptions: &capsulev1beta2.NamespaceOptions{ @@ -61,7 +63,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L It("should contain Namespace metadata after tenant update", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("checking labels", func() { diff --git a/e2e/namespace_metadata_webhook_test.go b/e2e/namespace_metadata_webhook_test.go index 4fa1e377..5aba0f22 100644 --- a/e2e/namespace_metadata_webhook_test.go +++ b/e2e/namespace_metadata_webhook_test.go @@ -1,5 +1,3 @@ -//go:build e2e - // Copyright 2020-2023 Project Capsule Authors. // SPDX-License-Identifier: Apache-2.0 @@ -31,10 +29,12 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, NodeSelector: map[string]string{ @@ -66,7 +66,7 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L It("should contain additional Namespace metadata", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("checking additional labels", func() { diff --git a/e2e/namespace_status_test.go b/e2e/namespace_status_test.go index 821a4e57..768cc341 100644 --- a/e2e/namespace_status_test.go +++ b/e2e/namespace_status_test.go @@ -12,7 +12,8 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("creating namespace with status lifecycle", Label("namespace", "status"), func() { @@ -21,10 +22,12 @@ var _ = Describe("creating namespace with status lifecycle", Label("namespace", Name: "tenant-status", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -43,15 +46,15 @@ var _ = Describe("creating namespace with status lifecycle", Label("namespace", It("verify namespace lifecycle (functionality)", func() { ns1 := NewNamespace("") By("creating first namespace", func() { - NamespaceCreation(ns1, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns1, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns1.GetName())) t := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).Should(Succeed()) - Expect(tnt.Status.Size).To(Equal(uint(1))) + Expect(t.Status.Size).To(Equal(uint(1))) - instance := tnt.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns1.GetName(), UID: ns1.GetUID()}) + instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns1.GetName(), UID: ns1.GetUID()}) Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil") condition := instance.Conditions.GetConditionByType(meta.ReadyCondition) @@ -65,15 +68,15 @@ var _ = Describe("creating namespace with status lifecycle", Label("namespace", ns2 := NewNamespace("") By("creating second namespace", func() { - NamespaceCreation(ns2, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns2.GetName())) t := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).Should(Succeed()) - Expect(tnt.Status.Size).To(Equal(uint(2))) + Expect(t.Status.Size).To(Equal(uint(2))) - instance := tnt.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns2.GetName(), UID: ns2.GetUID()}) + instance := t.Status.GetInstance(&capsulev1beta2.TenantStatusNamespaceItem{Name: ns2.GetName(), UID: ns2.GetUID()}) Expect(instance).NotTo(BeNil(), "Namespace instance should not be nil") condition := instance.Conditions.GetConditionByType(meta.ReadyCondition) diff --git a/e2e/namespace_user_metadata_test.go b/e2e/namespace_user_metadata_test.go index db76a015..e2c72ebe 100644 --- a/e2e/namespace_user_metadata_test.go +++ b/e2e/namespace_user_metadata_test.go @@ -33,10 +33,12 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation Regex: "^gatsby-.*$", }, }, - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -56,12 +58,12 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation By("specifying non-forbidden labels", func() { ns := NewNamespace("") ns.SetLabels(map[string]string{"bim": "baz"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) By("specifying non-forbidden annotations", func() { ns := NewNamespace("") ns.SetAnnotations(map[string]string{"bim": "baz"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) }) @@ -69,22 +71,22 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation By("specifying forbidden labels using exact match", func() { ns := NewNamespace("") ns.SetLabels(map[string]string{"foo": "bar"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden labels using regex match", func() { ns := NewNamespace("") ns.SetLabels(map[string]string{"gatsby-foo": "bar"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using exact match", func() { ns := NewNamespace("") ns.SetAnnotations(map[string]string{"foo": "bar"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using regex match", func() { ns := NewNamespace("") ns.SetAnnotations(map[string]string{"gatsby-foo": "bar"}) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) }) @@ -130,12 +132,12 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation Expect(k8sClient.Create(context.Background(), roleBinding)).To(Succeed()) } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) By("specifying forbidden labels using exact match", func() { ns := NewNamespace("forbidden-labels-exact-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) rbacPatch(ns.GetName()) Consistently(func() error { if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { @@ -152,7 +154,7 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation By("specifying forbidden labels using regex match", func() { ns := NewNamespace("forbidden-labels-regex-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) rbacPatch(ns.GetName()) Consistently(func() error { if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { @@ -169,7 +171,7 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation By("specifying forbidden annotations using exact match", func() { ns := NewNamespace("forbidden-annotations-exact-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) rbacPatch(ns.GetName()) Consistently(func() error { if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { @@ -186,7 +188,7 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation By("specifying forbidden annotations using regex match", func() { ns := NewNamespace("forbidden-annotations-regex-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) rbacPatch(ns.GetName()) Consistently(func() error { if err := k8sClient.Get(context.Background(), types.NamespacedName{Name: ns.GetName()}, ns); err != nil { diff --git a/e2e/new_namespace_test.go b/e2e/new_namespace_test.go index d3c77609..f4c4c352 100644 --- a/e2e/new_namespace_test.go +++ b/e2e/new_namespace_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespaces as different type of Tenant owners", Label("namespace"), func() { @@ -19,18 +20,24 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", Lab Name: "tenant-assigned", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alice", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alice", + Kind: "User", + }, }, { - Name: "bob", - Kind: "Group", + UserSpec: api.UserSpec{ + Name: "bob", + Kind: "Group", + }, }, { - Name: "system:serviceaccount:new-namespace-sa:default", - Kind: "ServiceAccount", + UserSpec: api.UserSpec{ + Name: "system:serviceaccount:new-namespace-sa:default", + Kind: "ServiceAccount", + }, }, }, }, @@ -48,7 +55,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", Lab It("should be available in Tenant namespaces list and RoleBindings should be present when created", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) for _, owner := range tnt.Spec.Owners { @@ -57,7 +64,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", Lab }) It("should be available in Tenant namespaces list and RoleBindings should present when created as Group", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[1], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[1].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) for _, owner := range tnt.Spec.Owners { @@ -66,7 +73,7 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", Lab }) It("should be available in Tenant namespaces list and RoleBindings should present when created as ServiceAccount", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[2], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[2].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElements(ns.GetName())) for _, owner := range tnt.Spec.Owners { diff --git a/e2e/node_user_metadata_test.go b/e2e/node_user_metadata_test.go index 617815c5..1d78da81 100644 --- a/e2e/node_user_metadata_test.go +++ b/e2e/node_user_metadata_test.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) var _ = Describe("modifying node labels and annotations", Label("config", "nodes"), func() { @@ -27,10 +27,12 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes Name: "tenant-node-user-metadata-forbidden", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -145,7 +147,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Labels["bim"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -156,7 +158,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Labels["bim"] = "bom" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -167,7 +169,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Annotations["bim"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -178,7 +180,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Annotations["bim"] = "bom" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -212,7 +214,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Labels["bar"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -223,7 +225,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Labels["gatsby-foo"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -234,7 +236,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Labels["foo"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -245,7 +247,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Annotations["bar"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -256,7 +258,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Annotations["gatsby-foo"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err @@ -267,7 +269,7 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes EventuallyCreation(func() error { return ModifyNode(func(node *corev1.Node) error { node.Annotations["foo"] = "baz" - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err := cs.CoreV1().Nodes().Update(context.Background(), node, metav1.UpdateOptions{}) return err diff --git a/e2e/overquota_namespace_test.go b/e2e/overquota_namespace_test.go index 3f838d98..20b44ec1 100644 --- a/e2e/overquota_namespace_test.go +++ b/e2e/overquota_namespace_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/utils/ptr" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace in over-quota of three", Label("namespace"), func() { @@ -20,10 +21,12 @@ var _ = Describe("creating a Namespace in over-quota of three", Label("namespace Name: "over-quota-tenant", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "bob", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "bob", + Kind: "User", + }, }, }, NamespaceOptions: &capsulev1beta2.NamespaceOptions{ @@ -45,14 +48,16 @@ var _ = Describe("creating a Namespace in over-quota of three", Label("namespace By("creating three Namespaces", func() { for _, name := range []string{"bob-dev", "bob-staging", "bob-production"} { ns := NewNamespace(name) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) } }) - ns := NewNamespace("") - cs := ownerClient(tnt.Spec.Owners[0]) - _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) - Expect(err).ShouldNot(Succeed()) + By("creating additional namespace", func() { + ns := NewNamespace("") + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) + _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) + Expect(err).ShouldNot(Succeed()) + }) }) }) diff --git a/e2e/owner_webhooks_test.go b/e2e/owner_webhooks_test.go index e2e35e9d..9a56d775 100644 --- a/e2e/owner_webhooks_test.go +++ b/e2e/owner_webhooks_test.go @@ -25,10 +25,12 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" Name: "tenant-owner", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "ruby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "ruby", + Kind: "User", + }, }, }, StorageClasses: &api.DefaultAllowedListSpec{ @@ -103,7 +105,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" It("should disallow deletions", func() { By("blocking Capsule Limit ranges", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) lr := &corev1.LimitRange{} @@ -112,12 +114,12 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" return k8sClient.Get(context.TODO(), types.NamespacedName{Name: n, Namespace: ns.GetName()}, lr) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) Expect(cs.CoreV1().LimitRanges(ns.GetName()).Delete(context.TODO(), lr.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Network Policy", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) np := &networkingv1.NetworkPolicy{} @@ -126,12 +128,12 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" return k8sClient.Get(context.TODO(), types.NamespacedName{Name: n, Namespace: ns.GetName()}, np) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) Expect(cs.NetworkingV1().NetworkPolicies(ns.GetName()).Delete(context.TODO(), np.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) By("blocking Capsule Resource Quota", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) rq := &corev1.ResourceQuota{} @@ -140,7 +142,7 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" return k8sClient.Get(context.TODO(), types.NamespacedName{Name: n, Namespace: ns.GetName()}, rq) }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) Expect(cs.NetworkingV1().NetworkPolicies(ns.GetName()).Delete(context.TODO(), rq.Name, metav1.DeleteOptions{})).ShouldNot(Succeed()) }) }) @@ -148,33 +150,33 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" It("should allow", func() { By("listing Limit Range", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err = cs.CoreV1().LimitRanges(ns.GetName()).List(context.TODO(), metav1.ListOptions{}) return }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Network Policy", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err = cs.NetworkingV1().NetworkPolicies(ns.GetName()).List(context.TODO(), metav1.ListOptions{}) return }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) }) By("listing Resource Quota", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) Eventually(func() (err error) { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) _, err = cs.NetworkingV1().NetworkPolicies(ns.GetName()).List(context.TODO(), metav1.ListOptions{}) return }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) @@ -183,10 +185,10 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant" It("should allow all actions to Tenant owner Network Policy", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) np := &networkingv1.NetworkPolicy{ ObjectMeta: metav1.ObjectMeta{ Name: "custom-network-policy", diff --git a/e2e/pod_metadata_test.go b/e2e/pod_metadata_test.go index 64873795..1da7e2b2 100644 --- a/e2e/pod_metadata_test.go +++ b/e2e/pod_metadata_test.go @@ -22,10 +22,12 @@ var _ = Describe("adding metadata to Pod objects", Label("pod"), func() { Name: "pod-metadata", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, PodOptions: &api.PodOptions{ @@ -57,7 +59,7 @@ var _ = Describe("adding metadata to Pod objects", Label("pod"), func() { It("should apply them to Pod", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) fmt.Sprint("namespace created") //TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) fmt.Sprint("tenant contains list namespace") @@ -79,7 +81,7 @@ var _ = Describe("adding metadata to Pod objects", Label("pod"), func() { } EventuallyCreation(func() (err error) { - _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + _, err = ownerClient(tnt.Spec.Owners[0].UserSpec).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return }).Should(Succeed()) diff --git a/e2e/pod_priority_class_test.go b/e2e/pod_priority_class_test.go index 699fadd8..cea121d5 100644 --- a/e2e/pod_priority_class_test.go +++ b/e2e/pod_priority_class_test.go @@ -27,10 +27,12 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Name: "priority-class-defaults", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "paul", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "paul", + Kind: "User", + }, }, }, PriorityClasses: &api.DefaultAllowedListSpec{ @@ -51,10 +53,12 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Name: "priority-class-no-defaults", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "george", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "george", + Kind: "User", + }, }, }, PriorityClasses: &api.DefaultAllowedListSpec{ @@ -139,7 +143,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { It("should block non allowed Priority Class", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -156,7 +160,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { }, } - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -194,9 +198,9 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { } ns := NewNamespace("") - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -220,7 +224,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Expect(k8sClient.Create(context.TODO(), pc)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -237,7 +241,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { }, } - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -246,7 +250,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { It("should allow regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) for i, pc := range []string{"pc-bronze", "pc-silver", "pc-gold"} { class := &schedulingv1.PriorityClass{ @@ -315,9 +319,9 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { } ns := NewNamespace("") - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -343,9 +347,9 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { } ns := NewNamespace("") - cs := ownerClient(tntWithDefaults.Spec.Owners[0]) + cs := ownerClient(tntWithDefaults.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) EventuallyCreation(func() error { @@ -363,7 +367,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Expect(k8sClient.Create(context.TODO(), class)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) pod := corev1.Pod{ @@ -401,7 +405,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) pod := &corev1.Pod{ @@ -438,7 +442,7 @@ var _ = Describe("enforcing a Priority Class", Label("pod"), func() { Expect(k8sClient.Create(context.TODO(), global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) pod := &corev1.Pod{ diff --git a/e2e/pod_runtime_class_test.go b/e2e/pod_runtime_class_test.go index 47b482fc..4b93a2d7 100644 --- a/e2e/pod_runtime_class_test.go +++ b/e2e/pod_runtime_class_test.go @@ -25,10 +25,12 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { Name: "runtime-class", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "george", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "george", + Kind: "User", + }, }, }, RuntimeClasses: &api.DefaultAllowedListSpec{ @@ -72,7 +74,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }() ns := NewNamespace("rt-disallow") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) runtimeName := "disallowed" pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -89,7 +91,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -110,7 +112,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }() ns := NewNamespace("rt-exact-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) runtimeName := "legacy" pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ @@ -127,7 +129,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return err @@ -137,7 +139,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { It("should allow regex match", func() { ns := NewNamespace("rc-regex-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) for i, rt := range []string{"hardened-crio", "hardened-containerd", "hardened-dockerd"} { runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "") @@ -165,7 +167,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) @@ -179,7 +181,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { It("should allow selector match", func() { ns := NewNamespace("rc-selector-match") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) for i, rt := range []string{"customer-containerd", "customer-crio", "customer-dockerd"} { runtimeName := strings.Join([]string{rt, "-", strconv.Itoa(i)}, "") @@ -211,7 +213,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) @@ -225,7 +227,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { It("should auto assign the default", func() { ns := NewNamespace("rc-default") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) runtime := &nodev1.RuntimeClass{ ObjectMeta: metav1.ObjectMeta{ @@ -253,7 +255,7 @@ var _ = Describe("enforcing a Runtime Class", Label("pod"), func() { }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) var createdPod *corev1.Pod diff --git a/e2e/preventing_pv_cross_tenant_mount_test.go b/e2e/preventing_pv_cross_tenant_mount_test.go index 1699fb64..d374a8f8 100644 --- a/e2e/preventing_pv_cross_tenant_mount_test.go +++ b/e2e/preventing_pv_cross_tenant_mount_test.go @@ -16,6 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant", "storage"), func() { @@ -24,10 +25,12 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant Name: "pv-one", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "jessica", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "jessica", + Kind: "User", + }, }, }, }, @@ -38,10 +41,12 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant Name: "pv-two", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "leto", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "leto", + Kind: "User", + }, }, }, }, @@ -65,7 +70,7 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant It("should add labels to PersistentVolume and prevent cross-Tenant mount", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt1.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt1, defaultTimeoutInterval).Should(ContainElement(ns.Name)) pvc := corev1.PersistentVolumeClaim{ @@ -168,7 +173,7 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant Expect(k8sClient.Delete(context.Background(), &pod, &client.DeleteOptions{GracePeriodSeconds: ptr.To(int64(0))})).ToNot(HaveOccurred()) ns2 := NewNamespace("") - NamespaceCreation(ns2, tnt2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns2, tnt2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt2, defaultTimeoutInterval).Should(ContainElement(ns2.Name)) Consistently(func() error { diff --git a/e2e/protected_namespace_regex_test.go b/e2e/protected_namespace_regex_test.go index 641aeee6..e72bfa43 100644 --- a/e2e/protected_namespace_regex_test.go +++ b/e2e/protected_namespace_regex_test.go @@ -12,6 +12,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Namespace with a protected Namespace regex enabled", Label("namespace"), func() { @@ -22,10 +23,12 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled" Name: "tenant-protected-namespace", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alice", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alice", + Kind: "User", + }, }, }, }, @@ -61,13 +64,13 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled" ns := NewNamespace("test-ok") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) It("should fail using a value non matching the regex", func() { ns := NewNamespace("test-system") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) ModifyCapsuleConfigurationOpts(func(configuration *capsulev1beta2.CapsuleConfiguration) { configuration.Spec.ProtectedNamespaceRegexpString = "" diff --git a/e2e/resource_quota_exceeded_test.go b/e2e/resource_quota_exceeded_test.go index ad14dd8d..88aad1fb 100644 --- a/e2e/resource_quota_exceeded_test.go +++ b/e2e/resource_quota_exceeded_test.go @@ -27,10 +27,12 @@ var _ = Describe("exceeding a Tenant resource quota", Label("resourcequota"), fu Name: "tenant-resources-changes", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "bobby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "bobby", + Kind: "User", + }, }, }, LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ @@ -115,7 +117,7 @@ var _ = Describe("exceeding a Tenant resource quota", Label("resourcequota"), fu By("creating the Namespaces", func() { for _, i := range nsl { ns := NewNamespace(i) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) } }) @@ -125,7 +127,7 @@ var _ = Describe("exceeding a Tenant resource quota", Label("resourcequota"), fu }) It("should block new Pods", func() { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) for _, namespace := range nsl { Eventually(func() (err error) { d := &appsv1.Deployment{ @@ -188,7 +190,7 @@ var _ = Describe("exceeding a Tenant resource quota", Label("resourcequota"), fu }, }, } - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns).Create(context.Background(), pod, metav1.CreateOptions{}) return err diff --git a/e2e/resourcepool_test.go b/e2e/resourcepool_test.go index 92b17f4b..6d6208b6 100644 --- a/e2e/resourcepool_test.go +++ b/e2e/resourcepool_test.go @@ -18,7 +18,7 @@ import ( capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/utils" ) @@ -205,7 +205,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) @@ -324,7 +324,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) @@ -391,7 +391,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) @@ -437,7 +437,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} Eventually(func() error { return k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: "ns-2-default-pool", }, rq) }, "30s", "1s").ShouldNot(Succeed(), "Expected ResourceQuota to be deleted from namespace %s", "ns-2-default-pool") @@ -505,7 +505,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} Eventually(func() error { return k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns, }, rq) }, "30s", "1s").ShouldNot(Succeed(), "Expected ResourceQuota to be deleted from namespace %s", ns) @@ -644,7 +644,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) @@ -676,7 +676,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} Eventually(func() error { return k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns, }, rq) }, "30s", "1s").ShouldNot(Succeed(), "Expected ResourceQuota to be deleted from namespace %s", ns) @@ -802,7 +802,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: "ns-1-pool-unordered"}, rq) Expect(err).Should(Succeed()) @@ -1171,7 +1171,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) @@ -1255,7 +1255,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() { rq := &corev1.ResourceQuota{} err := k8sClient.Get(context.TODO(), client.ObjectKey{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: ns}, rq) Expect(err).Should(Succeed()) diff --git a/e2e/resourcepoolclaim_test.go b/e2e/resourcepoolclaim_test.go index fc92672a..2433211a 100644 --- a/e2e/resourcepoolclaim_test.go +++ b/e2e/resourcepoolclaim_test.go @@ -16,7 +16,7 @@ import ( capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() { @@ -28,10 +28,12 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() { }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "wind-user", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "wind-user", + Kind: "User", + }, }, }, }, diff --git a/e2e/sa_owner_promotion_test.go b/e2e/sa_owner_promotion_test.go index 42d7d2de..f9fd8628 100644 --- a/e2e/sa_owner_promotion_test.go +++ b/e2e/sa_owner_promotion_test.go @@ -18,9 +18,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - ctrlrbac "github.com/projectcapsule/capsule/controllers/rbac" + ctrlrbac "github.com/projectcapsule/capsule/internal/controllers/rbac" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label("promotion"), func() { @@ -31,10 +31,12 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" Name: "tenant-owner-promotion", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "alice", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "alice", + Kind: "User", + }, }, }, AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{ @@ -84,7 +86,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" }) ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) time.Sleep(250 * time.Millisecond) // Create a ServiceAccount inside the tenant namespace @@ -177,7 +179,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" }) ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) time.Sleep(250 * time.Millisecond) // Create a ServiceAccount inside the tenant namespace @@ -238,7 +240,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label(" }) ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) time.Sleep(250 * time.Millisecond) // Create a ServiceAccount inside the tenant namespace diff --git a/e2e/sa_prevent_privilege_escalation_test.go b/e2e/sa_prevent_privilege_escalation_test.go index 1270b831..1206d13f 100644 --- a/e2e/sa_prevent_privilege_escalation_test.go +++ b/e2e/sa_prevent_privilege_escalation_test.go @@ -18,6 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", Label("tenant"), func() { @@ -26,10 +27,12 @@ var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", La Name: "sa-privilege-escalation", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "mario", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "mario", + Kind: "User", + }, }, }, NodeSelector: map[string]string{ @@ -45,7 +48,7 @@ var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", La return k8sClient.Create(context.TODO(), tnt) }).Should(Succeed()) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) }) diff --git a/e2e/scalability_test.go b/e2e/scalability_test.go index 2e5dd204..3ba902b3 100644 --- a/e2e/scalability_test.go +++ b/e2e/scalability_test.go @@ -14,7 +14,8 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) var _ = Describe("verify scalability", Label("scalability"), func() { @@ -23,10 +24,12 @@ var _ = Describe("verify scalability", Label("scalability"), func() { Name: "tenant-scalability", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -111,7 +114,7 @@ var _ = Describe("verify scalability", Label("scalability"), func() { namespaces := make([]*corev1.Namespace, 0, amount) for i := 0; i < amount; i++ { ns := NewNamespace(fmt.Sprintf("scale-%d", i)) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) // Expect size bumped to i+1 and instance present diff --git a/e2e/selecting_non_owned_tenant_test.go b/e2e/selecting_non_owned_tenant_test.go index dc388544..fe8e72f5 100644 --- a/e2e/selecting_non_owned_tenant_test.go +++ b/e2e/selecting_non_owned_tenant_test.go @@ -6,6 +6,7 @@ package e2e import ( "context" + "github.com/projectcapsule/capsule/pkg/api" "github.com/projectcapsule/capsule/pkg/utils" . "github.com/onsi/ginkgo/v2" @@ -22,10 +23,12 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", Label(" Name: "tenant-non-owned", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "undefined", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "undefined", + Kind: "User", + }, }, }, }, @@ -53,7 +56,7 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", Label(" }) }) - cs := ownerClient(capsulev1beta2.OwnerSpec{Name: "dale", Kind: "User"}) + cs := ownerClient(api.UserSpec{Name: "dale", Kind: "User"}) _, err := cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) Expect(err).To(HaveOccurred()) }) diff --git a/e2e/selecting_tenant_fail_test.go b/e2e/selecting_tenant_fail_test.go index ed8d3d15..f8a2a003 100644 --- a/e2e/selecting_tenant_fail_test.go +++ b/e2e/selecting_tenant_fail_test.go @@ -11,18 +11,21 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) -var _ = Describe("creating a Namespace without a Tenant selector when user owns multiple Tenants", Label("tenant"), func() { +var _ = Describe("creating a Namespace without a Tenant selector when user owns multiple Tenants", Label("tenant", "assignment"), func() { t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-one", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -32,10 +35,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns Name: "tenant-two", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -45,10 +50,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns Name: "tenant-three", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "Group", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "Group", + }, }, }, }, @@ -58,10 +65,12 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns Name: "tenant-four", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "Group", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "Group", + }, }, }, }, @@ -72,16 +81,16 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns By("user owns 2 tenants", func() { EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t1) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) Expect(k8sClient.Delete(context.TODO(), t1)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), t2)).Should(Succeed()) }) By("group owns 2 tenants", func() { EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t3) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t4) }).Should(Succeed()) - NamespaceCreation(ns, t3.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - NamespaceCreation(ns, t4.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t3.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t4.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) Expect(k8sClient.Delete(context.TODO(), t3)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), t4)).Should(Succeed()) }) @@ -91,10 +100,10 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t3) }).Should(Succeed()) EventuallyCreation(func() error { return k8sClient.Create(context.TODO(), t4) }).Should(Succeed()) - NamespaceCreation(ns, t1.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - NamespaceCreation(ns, t3.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) - NamespaceCreation(ns, t4.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t3.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) + NamespaceCreation(ns, t4.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) Expect(k8sClient.Delete(context.TODO(), t1)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), t2)).Should(Succeed()) Expect(k8sClient.Delete(context.TODO(), t3)).Should(Succeed()) diff --git a/e2e/selecting_tenant_with_label_test.go b/e2e/selecting_tenant_with_label_test.go index 69647d2c..96bae7f6 100644 --- a/e2e/selecting_tenant_with_label_test.go +++ b/e2e/selecting_tenant_with_label_test.go @@ -6,25 +6,30 @@ package e2e import ( "context" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" ) -var _ = Describe("creating a Namespace with Tenant selector when user owns multiple tenants", Label("tenant"), func() { +var _ = Describe("creating a Namespace with Tenant selector when user owns multiple tenants", Label("tenant", "assignment"), func() { t1 := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "tenant-one", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -34,10 +39,12 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi Name: "tenant-two", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, @@ -45,9 +52,11 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi JustBeforeEach(func() { EventuallyCreation(func() error { + t1.ResourceVersion = "" return k8sClient.Create(context.TODO(), t1) }).Should(Succeed()) EventuallyCreation(func() error { + t2.ResourceVersion = "" return k8sClient.Create(context.TODO(), t2) }).Should(Succeed()) }) @@ -59,13 +68,150 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi It("should be assigned to the selected Tenant", func() { ns := NewNamespace("") By("assigning to the Namespace the Capsule Tenant label", func() { - l, err := utils.GetTypeLabel(&capsulev1beta2.Tenant{}) - Expect(err).ToNot(HaveOccurred()) ns.Labels = map[string]string{ - l: t2.Name, + meta.TenantLabel: t2.Name, } }) - NamespaceCreation(ns, t2.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) - TenantNamespaceList(t2, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceCreation(ns, t2.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + NamespaceIsPartOfTenant(t2, ns) + }) + + It("prevent reassignment via labels from owners", func() { + ns := NewNamespace("") + By("assigning to the Namespace the Capsule Tenant label", func() { + ns.Labels = map[string]string{ + meta.TenantLabel: t1.Name, + } + + NamespaceCreation(ns, t1.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, ns) + }) + + By("assigning to the Namespace the Capsule Tenant label (Attempt Label Patch)", func() { + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + meta.TenantLabel: t2.Name, + }, + }, + } + + err := PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, new) + }) + + By("assigning to the Namespace the Capsule Tenant label (Attempt Ownerreference Patch)", func() { + ref, err := GetTenantOwnerReferenceAsPatch(t2) + Expect(err).NotTo(HaveOccurred()) + + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + meta.TenantLabel: t1.Name, + }, + "ownerReferences": []map[string]interface{}{ref}, + }, + } + + err = PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, new) + }) + + By("assigning to the Namespace the Capsule Tenant label (Attempt Ownerreference Patch) - Without Label", func() { + ref, err := GetTenantOwnerReferenceAsPatch(t2) + Expect(err).NotTo(HaveOccurred()) + + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{}, + "ownerReferences": []map[string]interface{}{ref}, + }, + } + + err = PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, ns) + }) + + By("assigning to the Namespace the Capsule Tenant label (Empty Ownerreferences)", func() { + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + meta.TenantLabel: t2.Name, + }, + "ownerReferences": []string{}, + }, + } + + err := PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, new) + }) + + By("assigning to the Namespace the Capsule Tenant label (Empty Ownerreferences) - Without Label", func() { + ns.Labels = map[string]string{} + + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{}, + "ownerReferences": []string{}, + }, + } + + err := PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, ns) + }) + + By("assigning to the Namespace the Capsule Tenant label (2nd Tenant Label + Ownerreference)", func() { + ref, err := GetTenantOwnerReferenceAsPatch(t2) + Expect(err).NotTo(HaveOccurred()) + + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]string{ + meta.TenantLabel: t2.Name, + }, + "ownerReferences": []map[string]interface{}{ref}, + }, + } + + err = PatchNamespace(ns, ownerClient(t2.Spec.Owners[0].UserSpec), patch) + Expect(err).NotTo(HaveOccurred()) + + new := &corev1.Namespace{} + k8sClient.Get(context.TODO(), types.NamespacedName{Name: ns.GetName()}, new) + + TenantNamespaceList(t1, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) + NamespaceIsPartOfTenant(t1, new) + }) }) }) diff --git a/e2e/service_forbidden_metadata_test.go b/e2e/service_forbidden_metadata_test.go index 952941a7..b6c4c7fd 100644 --- a/e2e/service_forbidden_metadata_test.go +++ b/e2e/service_forbidden_metadata_test.go @@ -32,10 +32,12 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Regex: "^gatsby-.*$", }, }, - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, }, @@ -54,7 +56,7 @@ var _ = Describe("creating a Service with user-specified labels and annotations" It("should allow", func() { By("specifying non-forbidden labels", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -62,11 +64,11 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "non-forbidden-labels", }) svc.SetLabels(map[string]string{"bim": "baz"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) By("specifying non-forbidden annotations", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -74,14 +76,14 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "non-forbidden-annotations", }) svc.SetAnnotations(map[string]string{"bim": "baz"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) }) }) It("should fail when creating a Service", func() { By("specifying forbidden labels using exact match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -89,11 +91,11 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "forbidden-labels-exact", }) svc.SetLabels(map[string]string{"foo": "bar"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden labels using regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -101,11 +103,11 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "forbidden-labels-regex", }) svc.SetLabels(map[string]string{"gatsby-foo": "bar"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using exact match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -113,11 +115,11 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "forbidden-annotations-exact", }) svc.SetAnnotations(map[string]string{"foo": "bar"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) By("specifying forbidden annotations using regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ @@ -125,23 +127,23 @@ var _ = Describe("creating a Service with user-specified labels and annotations" Name: "forbidden-annotations-regex", }) svc.SetAnnotations(map[string]string{"gatsby-foo": "bar"}) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).ShouldNot(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).ShouldNot(Succeed()) }) }) It("should fail when updating a Service", func() { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) By("specifying forbidden labels using exact match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ Namespace: ns.GetName(), Name: "forbidden-labels-exact-match", }) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Consistently(func() error { svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{}) if err != nil { @@ -156,14 +158,14 @@ var _ = Describe("creating a Service with user-specified labels and annotations" }) By("specifying forbidden labels using regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ Namespace: ns.GetName(), Name: "forbidden-labels-regex-match", }) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Consistently(func() error { svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{}) if err != nil { @@ -179,14 +181,14 @@ var _ = Describe("creating a Service with user-specified labels and annotations" }) By("specifying forbidden annotations using exact match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ Namespace: ns.GetName(), Name: "forbidden-annotations-exact-match", }) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Consistently(func() error { svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{}) if err != nil { @@ -202,14 +204,14 @@ var _ = Describe("creating a Service with user-specified labels and annotations" }) By("specifying forbidden annotations using regex match", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := NewService(types.NamespacedName{ Namespace: ns.GetName(), Name: "forbidden-annotations-regex-match", }) - ServiceCreation(svc, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + ServiceCreation(svc, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) Consistently(func() error { svc, err := cs.CoreV1().Services(svc.Namespace).Get(context.Background(), svc.GetName(), metav1.GetOptions{}) if err != nil { diff --git a/e2e/service_metadata_test.go b/e2e/service_metadata_test.go index 160ca420..f1e58ff4 100644 --- a/e2e/service_metadata_test.go +++ b/e2e/service_metadata_test.go @@ -29,10 +29,12 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service" Name: "service-metadata", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "gatsby", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "gatsby", + Kind: "User", + }, }, }, ServiceOptions: &api.ServiceOptions{ @@ -64,7 +66,7 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service" It("should apply them to Service", func() { ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) svc := &corev1.Service{ @@ -101,13 +103,13 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service" }, }, } - _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + _, err = ownerClient(tnt.Spec.Owners[0].UserSpec).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return }).Should(Succeed()) EventuallyCreation(func() (err error) { - _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Services(ns.GetName()).Create(context.Background(), svc, metav1.CreateOptions{}) + _, err = ownerClient(tnt.Spec.Owners[0].UserSpec).CoreV1().Services(ns.GetName()).Create(context.Background(), svc, metav1.CreateOptions{}) return }).Should(Succeed()) @@ -147,7 +149,7 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service" } ns := NewNamespace("") - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) // Waiting for the reconciliation of required RBAC EventuallyCreation(func() (err error) { @@ -164,7 +166,7 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service" }, }, } - _, err = ownerClient(tnt.Spec.Owners[0]).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) + _, err = ownerClient(tnt.Spec.Owners[0].UserSpec).CoreV1().Pods(ns.GetName()).Create(context.Background(), pod, metav1.CreateOptions{}) return }).Should(Succeed()) diff --git a/e2e/storage_class_test.go b/e2e/storage_class_test.go index c76520fb..798a4a08 100644 --- a/e2e/storage_class_test.go +++ b/e2e/storage_class_test.go @@ -32,10 +32,12 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage Name: "storage-class-selector", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "selector", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "selector", + Kind: "User", + }, }, }, StorageClasses: &api.DefaultAllowedListSpec{ @@ -59,10 +61,12 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage Name: "storage-class-default", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "default", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "default", + Kind: "User", + }, }, }, StorageClasses: &api.DefaultAllowedListSpec{ @@ -137,12 +141,12 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage k8sClient.Create(context.TODO(), tntNoDefaults) ns := NewNamespace("") - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("non-specifying it", func() { Eventually(func() (err error) { - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) p := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "denied-pvc", @@ -162,7 +166,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage }) By("specifying a forbidden one", func() { Eventually(func() (err error) { - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) p := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: "mighty-storage", @@ -209,7 +213,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage }, } - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) EventuallyCreation(func() error { _, err := cs.CoreV1().PersistentVolumeClaims(ns.GetName()).Create(context.Background(), p, metav1.CreateOptions{}) @@ -222,9 +226,9 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage It("should allow", func() { ns := NewNamespace("") - cs := ownerClient(tntNoDefaults.Spec.Owners[0]) + cs := ownerClient(tntNoDefaults.Spec.Owners[0].UserSpec) - NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntNoDefaults.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntNoDefaults, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) By("using exact matches", func() { for _, c := range tntNoDefaults.Spec.StorageClasses.Exact { @@ -309,7 +313,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage It("should mutate to default tenant StorageClass (class does not exists)", func() { ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) p := &corev1.PersistentVolumeClaim{ @@ -337,7 +341,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage Expect(k8sClient.Create(context.TODO(), &class)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) p := &corev1.PersistentVolumeClaim{ @@ -369,7 +373,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) p := &corev1.PersistentVolumeClaim{ @@ -400,7 +404,7 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "storage Expect(k8sClient.Create(context.TODO(), &global)).Should(Succeed()) ns := NewNamespace("") - NamespaceCreation(ns, tntWithDefault.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tntWithDefault.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tntWithDefault, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) p := &corev1.PersistentVolumeClaim{ diff --git a/e2e/suite_test.go b/e2e/suite_test.go index b0610b5a..02c81614 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -4,15 +4,20 @@ package e2e import ( + "context" + "fmt" "testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -20,6 +25,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to @@ -28,9 +34,12 @@ import ( var ( cfg *rest.Config k8sClient client.Client - testEnv *envtest.Environment + + testEnv *envtest.Environment ) +var log = ctrl.Log.WithName("e2e-tests") + func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -60,11 +69,42 @@ var _ = BeforeSuite(func() { }) var _ = AfterSuite(func() { + Eventually(func() error { + var nsList corev1.NamespaceList + + // List all namespaces with env=e2e + if err := k8sClient.List( + context.TODO(), + &nsList, + client.MatchingLabels{"env": "e2e"}, + ); err != nil { + return err + } + + // If none left, we’re done + if len(nsList.Items) == 0 { + return nil + } + + // Try deleting all; if any delete fails with something other than NotFound, + // return the error so Eventually keeps retrying. + for i := range nsList.Items { + ns := &nsList.Items[i] + if err := k8sClient.Delete(context.TODO(), ns); err != nil && !apierrors.IsNotFound(err) { + return err + } + } + + // Return a non-nil error to tell Eventually "not done yet" + return fmt.Errorf("still have %d namespaces with env=e2e", len(nsList.Items)) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + By("tearing down the test environment") + Expect(testEnv.Stop()).ToNot(HaveOccurred()) }) -func ownerClient(owner capsulev1beta2.OwnerSpec) (cs kubernetes.Interface) { +func ownerClient(owner api.UserSpec) (cs kubernetes.Interface) { c, err := config.GetConfig() Expect(err).ToNot(HaveOccurred()) c.Impersonate.Groups = []string{"projectcapsule.dev", owner.Name} diff --git a/e2e/tenant_cordoning_test.go b/e2e/tenant_cordoning_test.go index 2cceb45a..54c294f3 100644 --- a/e2e/tenant_cordoning_test.go +++ b/e2e/tenant_cordoning_test.go @@ -7,7 +7,8 @@ import ( "context" "time" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -24,10 +25,12 @@ var _ = Describe("cordoning a Tenant", Label("tenant"), func() { Name: "tenant-cordoning", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "jim", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "jim", + Kind: "User", + }, }, }, }, @@ -43,7 +46,7 @@ var _ = Describe("cordoning a Tenant", Label("tenant"), func() { Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed()) }) It("should block or allow operations", func() { - cs := ownerClient(tnt.Spec.Owners[0]) + cs := ownerClient(tnt.Spec.Owners[0].UserSpec) ns := NewNamespace("") @@ -74,7 +77,7 @@ var _ = Describe("cordoning a Tenant", Label("tenant"), func() { }) By("creating a Namespace", func() { - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) EventuallyCreation(func() error { _, err := cs.CoreV1().Pods(ns.Name).Create(context.Background(), pod, metav1.CreateOptions{}) diff --git a/e2e/tenant_metadata_test.go b/e2e/tenant_metadata_test.go index fe31c5c5..2c473fa0 100644 --- a/e2e/tenant_metadata_test.go +++ b/e2e/tenant_metadata_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) func getLabels(tnt capsulev1beta2.Tenant) (map[string]string, error) { @@ -32,10 +33,12 @@ var _ = Describe("adding metadata to a Tenant", Label("tenant"), func() { }, }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "jim", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "jim", + Kind: "User", + }, }, }, }, diff --git a/e2e/tenant_name_webhook_test.go b/e2e/tenant_name_webhook_test.go index f7bc0569..b1683d8e 100644 --- a/e2e/tenant_name_webhook_test.go +++ b/e2e/tenant_name_webhook_test.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("creating a Tenant with wrong name", Label("tenant"), func() { @@ -19,10 +20,12 @@ var _ = Describe("creating a Tenant with wrong name", Label("tenant"), func() { Name: "non_rfc_dns_1123", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, diff --git a/e2e/tenant_protected_webhook_test.go b/e2e/tenant_protected_webhook_test.go index 3637805d..2790dae8 100644 --- a/e2e/tenant_protected_webhook_test.go +++ b/e2e/tenant_protected_webhook_test.go @@ -12,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" ) var _ = Describe("Deleting a tenant with protected annotation", Label("tenant"), func() { @@ -21,10 +22,12 @@ var _ = Describe("Deleting a tenant with protected annotation", Label("tenant"), }, Spec: capsulev1beta2.TenantSpec{ PreventDeletion: true, - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, }, diff --git a/e2e/tenant_resources_changes_test.go b/e2e/tenant_resources_changes_test.go index 37ddd4e4..815e2894 100644 --- a/e2e/tenant_resources_changes_test.go +++ b/e2e/tenant_resources_changes_test.go @@ -27,10 +27,12 @@ var _ = Describe("changing Tenant managed Kubernetes resources", Label("tenant") Name: "tenant-resources-changes", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "laura", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "laura", + Kind: "User", + }, }, }, LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ @@ -162,7 +164,7 @@ var _ = Describe("changing Tenant managed Kubernetes resources", Label("tenant") By("creating the Namespaces", func() { for _, i := range nsl { ns := NewNamespace(i) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) } }) diff --git a/e2e/tenant_resources_test.go b/e2e/tenant_resources_test.go index 498493ff..df451ef2 100644 --- a/e2e/tenant_resources_test.go +++ b/e2e/tenant_resources_test.go @@ -27,10 +27,12 @@ var _ = Describe("creating namespaces within a Tenant with resources", Label("te Name: "tenant-resources", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "john", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "john", + Kind: "User", + }, }, }, LimitRanges: api.LimitRangesSpec{Items: []corev1.LimitRangeSpec{ @@ -161,7 +163,7 @@ var _ = Describe("creating namespaces within a Tenant with resources", Label("te By("creating the Namespaces", func() { for _, i := range nsl { ns := NewNamespace(i) - NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) TenantNamespaceList(tnt, defaultTimeoutInterval).Should(ContainElement(ns.GetName())) } }) diff --git a/e2e/tenantresource_test.go b/e2e/tenantresource_test.go index c24f57de..3233b1b3 100644 --- a/e2e/tenantresource_test.go +++ b/e2e/tenantresource_test.go @@ -30,10 +30,12 @@ var _ = Describe("Creating a TenantResource object", Label("tenantresource"), fu Name: "energy-solar", }, Spec: capsulev1beta2.TenantSpec{ - Owners: capsulev1beta2.OwnerListSpec{ + Owners: api.OwnerListSpec{ { - Name: "solar-user", - Kind: "User", + UserSpec: api.UserSpec{ + Name: "solar-user", + Kind: "User", + }, }, }, }, @@ -192,7 +194,7 @@ var _ = Describe("Creating a TenantResource object", Label("tenantresource"), fu By("creating solar Namespaces", func() { for _, ns := range append(solarNs, "solar-system") { - NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed()) + NamespaceCreation(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, solar.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) } }) @@ -316,7 +318,7 @@ var _ = Describe("Creating a TenantResource object", Label("tenantresource"), fu By("checking replicated object cannot be deleted by a Tenant Owner", func() { for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { - cs := ownerClient(solar.Spec.Owners[0]) + cs := ownerClient(solar.Spec.Owners[0].UserSpec) Consistently(func() error { return cs.CoreV1().Secrets("solar-three").Delete(context.TODO(), name, metav1.DeleteOptions{}) @@ -326,7 +328,7 @@ var _ = Describe("Creating a TenantResource object", Label("tenantresource"), fu By("checking replicated object cannot be update by a Tenant Owner", func() { for _, name := range []string{"dummy-secret", "raw-secret-1", "raw-secret-2", "raw-secret-3"} { - cs := ownerClient(solar.Spec.Owners[0]) + cs := ownerClient(solar.Spec.Owners[0].UserSpec) Consistently(func() error { secret, err := cs.CoreV1().Secrets("solar-three").Get(context.TODO(), name, metav1.GetOptions{}) diff --git a/e2e/utils_test.go b/e2e/utils_test.go index bd07d982..463e2983 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -5,6 +5,7 @@ package e2e import ( "context" + "encoding/json" "fmt" "reflect" "strings" @@ -24,6 +25,8 @@ import ( "k8s.io/client-go/kubernetes" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) const ( @@ -46,7 +49,7 @@ func NewService(svc types.NamespacedName) *corev1.Service { } } -func ServiceCreation(svc *corev1.Service, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion { +func ServiceCreation(svc *corev1.Service, owner api.UserSpec, timeout time.Duration) AsyncAssertion { cs := ownerClient(owner) return Eventually(func() (err error) { _, err = cs.CoreV1().Services(svc.Namespace).Create(context.TODO(), svc, metav1.CreateOptions{}) @@ -59,7 +62,9 @@ func NewNamespace(name string, labels ...map[string]string) *corev1.Namespace { name = rand.String(10) } - var namespaceLabels map[string]string + namespaceLabels := make(map[string]string) + namespaceLabels["env"] = "e2e" + if len(labels) > 0 { namespaceLabels = labels[0] } @@ -72,7 +77,7 @@ func NewNamespace(name string, labels ...map[string]string) *corev1.Namespace { } } -func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, timeout time.Duration) AsyncAssertion { +func NamespaceCreation(ns *corev1.Namespace, owner api.UserSpec, timeout time.Duration) AsyncAssertion { cs := ownerClient(owner) return Eventually(func() (err error) { _, err = cs.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) @@ -80,9 +85,165 @@ func NamespaceCreation(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, tim }, timeout, defaultPollInterval) } -func TenantNamespaceList(t *capsulev1beta2.Tenant, timeout time.Duration) AsyncAssertion { +func NamespaceIsPartOfTenant( + tnt *capsulev1beta2.Tenant, + ns *corev1.Namespace, +) func() error { + + return func() error { + t := &capsulev1beta2.Tenant{} + if err := k8sClient.Get( + context.TODO(), + types.NamespacedName{Name: tnt.GetName()}, + t, + ); err != nil { + return fmt.Errorf("failed to get tenant: %w", err) + } + + // reuse existing helper + namespaces := TenantNamespaceList(t, defaultTimeoutInterval) + if ok, _ := ContainElements(ns.GetName()).Match(namespaces); ok { + return fmt.Errorf( + "expected tenant %s to contain namespace %s, but got: %v", + t.GetName(), ns.GetName(), namespaces, + ) + } + + // reuse your existing method + instance := t.Status.GetInstance( + &capsulev1beta2.TenantStatusNamespaceItem{ + Name: ns.GetName(), + UID: ns.GetUID(), + }) + + if instance == nil { + return fmt.Errorf( + "tenant %s does not contain instance for namespace %s (uid=%s)", + t.GetName(), ns.GetName(), ns.GetUID(), + ) + } + + return nil + } +} + +func GetTenantOwnerReference( + tnt *capsulev1beta2.Tenant, +) (metav1.OwnerReference, error) { + + t := &capsulev1beta2.Tenant{} + if err := k8sClient.Get( + context.TODO(), + types.NamespacedName{Name: tnt.GetName()}, + t, + ); err != nil { + return metav1.OwnerReference{}, fmt.Errorf("failed to get tenant: %w", err) + } + + gvk := capsulev1beta2.GroupVersion.WithKind("Tenant") + return metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().String(), + Kind: gvk.Kind, + Name: t.GetName(), + UID: t.GetUID(), + }, nil +} + +func GetTenantOwnerReferenceAsPatch( + tnt *capsulev1beta2.Tenant, +) (map[string]interface{}, error) { + ownerRef, err := GetTenantOwnerReference(tnt) + if err != nil { + return nil, err + } + + return map[string]interface{}{ + "apiVersion": ownerRef.APIVersion, + "kind": ownerRef.Kind, + "name": ownerRef.Name, + "uid": string(ownerRef.UID), + }, nil + +} + +func PatchTenantLabelForNamespace(tnt *capsulev1beta2.Tenant, ns *corev1.Namespace, cs kubernetes.Interface, timeout time.Duration) AsyncAssertion { + return Eventually(func() (err error) { + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "labels": map[string]interface{}{ + meta.TenantLabel: tnt.GetName(), + }, + }, + } + + return PatchNamespace(ns, cs, patch) + }, timeout, defaultPollInterval) +} + +func PatchNamespace(ns *corev1.Namespace, cs kubernetes.Interface, patch map[string]interface{}) error { + patchBytes, err := json.Marshal(patch) + if err != nil { + return err + } + + _, err = cs.CoreV1().Namespaces().Patch( + context.Background(), + ns.GetName(), + types.MergePatchType, + patchBytes, + metav1.PatchOptions{}, + ) + + return err +} + +func PatchTenantOwnerReferenceForNamespace( + tnt *capsulev1beta2.Tenant, + ns *corev1.Namespace, + cs kubernetes.Interface, + timeout time.Duration, +) AsyncAssertion { + return Eventually(func() error { + // Build ownerRef for the tenant + ownerRef := metav1.OwnerReference{ + APIVersion: capsulev1beta2.GroupVersion.String(), + Kind: "Tenant", + Name: tnt.GetName(), + UID: tnt.GetUID(), + } + + patch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "ownerReferences": []map[string]interface{}{ + { + "apiVersion": ownerRef.APIVersion, + "kind": ownerRef.Kind, + "name": ownerRef.Name, + "uid": string(ownerRef.UID), + }, + }, + }, + } + + patchBytes, err := json.Marshal(patch) + Expect(err).ToNot(HaveOccurred()) + + _, err = cs.CoreV1().Namespaces().Patch( + context.Background(), + ns.GetName(), + types.StrategicMergePatchType, + patchBytes, + metav1.PatchOptions{}, + ) + + return err + }, timeout, defaultPollInterval) +} + +func TenantNamespaceList(tnt *capsulev1beta2.Tenant, timeout time.Duration) AsyncAssertion { + t := &capsulev1beta2.Tenant{} return Eventually(func() []string { - Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: t.GetName()}, t)).Should(Succeed()) + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, t)).Should(Succeed()) return t.Status.Namespaces }, timeout, defaultPollInterval) } @@ -108,7 +269,7 @@ func ModifyCapsuleConfigurationOpts(fn func(configuration *capsulev1beta2.Capsul Expect(k8sClient.Update(context.Background(), config)).ToNot(HaveOccurred()) } -func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta2.OwnerSpec, roles map[string]bool) func() error { +func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner api.OwnerSpec, roles map[string]bool) func() error { if roles == nil { roles = map[string]bool{ "admin": false, @@ -125,7 +286,7 @@ func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner capsulev1beta2.OwnerS var ownerName string - if owner.Kind == capsulev1beta2.ServiceAccountOwner { + if owner.Kind == api.ServiceAccountOwner { parts := strings.Split(owner.Name, ":") ownerName = parts[3] diff --git a/go.mod b/go.mod index 74b06dac..fe5ea7fc 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/projectcapsule/capsule -go 1.24.0 - -toolchain go1.25.4 +go 1.25.4 require ( github.com/go-logr/logr v1.4.3 @@ -16,11 +14,11 @@ require ( go.uber.org/automaxprocs v1.6.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.18.0 - k8s.io/api v0.34.1 - k8s.io/apiextensions-apiserver v0.34.1 - k8s.io/apimachinery v0.34.1 - k8s.io/apiserver v0.34.1 - k8s.io/client-go v0.34.1 + k8s.io/api v0.34.2 + k8s.io/apiextensions-apiserver v0.34.2 + k8s.io/apimachinery v0.34.2 + k8s.io/apiserver v0.34.2 + k8s.io/client-go v0.34.2 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/cluster-api v1.11.3 sigs.k8s.io/controller-runtime v0.22.4 @@ -38,20 +36,20 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.22.0 // indirect - github.com/go-openapi/jsonreference v0.21.1 // indirect - github.com/go-openapi/swag v0.24.1 // indirect - github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.24.0 // indirect - github.com/go-openapi/swag/fileutils v0.24.0 // indirect - github.com/go-openapi/swag/jsonname v0.24.0 // indirect - github.com/go-openapi/swag/jsonutils v0.24.0 // indirect - github.com/go-openapi/swag/loading v0.24.0 // indirect - github.com/go-openapi/swag/mangling v0.24.0 // indirect - github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.24.0 // indirect - github.com/go-openapi/swag/typeutils v0.24.0 // indirect - github.com/go-openapi/swag/yamlutils v0.24.0 // indirect + github.com/go-openapi/jsonpointer v0.22.3 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/swag v0.25.3 // indirect + github.com/go-openapi/swag/cmdutils v0.25.3 // indirect + github.com/go-openapi/swag/conv v0.25.3 // indirect + github.com/go-openapi/swag/fileutils v0.25.3 // indirect + github.com/go-openapi/swag/jsonname v0.25.3 // indirect + github.com/go-openapi/swag/jsonutils v0.25.3 // indirect + github.com/go-openapi/swag/loading v0.25.3 // indirect + github.com/go-openapi/swag/mangling v0.25.3 // indirect + github.com/go-openapi/swag/netutils v0.25.3 // indirect + github.com/go-openapi/swag/stringutils v0.25.3 // indirect + github.com/go-openapi/swag/typeutils v0.25.3 // indirect + github.com/go-openapi/swag/yamlutils v0.25.3 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect @@ -60,32 +58,30 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.2 // indirect + github.com/prometheus/procfs v0.19.2 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.44.0 // indirect - golang.org/x/oauth2 v0.31.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/term v0.35.0 // indirect - golang.org/x/text v0.29.0 // indirect - golang.org/x/time v0.13.0 // indirect - golang.org/x/tools v0.36.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.38.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/protobuf v1.36.9 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 3aaa7cfd..4ce804d8 100644 --- a/go.sum +++ b/go.sum @@ -23,8 +23,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0= github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4= -github.com/coredns/corefile-migration v1.0.27 h1:WIIw5sU0LfGgoGnhdrYdVcto/aWmJoGA/C62iwkU0JM= -github.com/coredns/corefile-migration v1.0.27/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= +github.com/coredns/corefile-migration v1.0.29 h1:g4cPYMXXDDs9uLE2gFYrJaPBuUAR07eEMGyh9JBE13w= +github.com/coredns/corefile-migration v1.0.29/go.mod h1:56DPqONc3njpVPsdilEnfijCwNGC3/kTJLl7i7SPavY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -43,44 +43,58 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= -github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= -github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA= -github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8= -github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= -github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= -github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= -github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik= -github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c= -github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak= -github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90= -github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= -github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q= -github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts= -github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0= -github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc= -github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk= -github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk= -github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc= -github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= -github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM= -github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w= -github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw= -github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI= -github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c= -github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8= +github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= +github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= +github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= +github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/swag v0.25.3 h1:FAa5wJXyDtI7yUztKDfZxDrSx+8WTg31MfCQ9s3PV+s= +github.com/go-openapi/swag v0.25.3/go.mod h1:tX9vI8Mj8Ny+uCEk39I1QADvIPI7lkndX4qCsEqhkS8= +github.com/go-openapi/swag/cmdutils v0.25.3 h1:EIwGxN143JCThNHnqfqs85R8lJcJG06qjJRZp3VvjLI= +github.com/go-openapi/swag/cmdutils v0.25.3/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.3 h1:PcB18wwfba7MN5BVlBIV+VxvUUeC2kEuCEyJ2/t2X7E= +github.com/go-openapi/swag/conv v0.25.3/go.mod h1:n4Ibfwhn8NJnPXNRhBO5Cqb9ez7alBR40JS4rbASUPU= +github.com/go-openapi/swag/fileutils v0.25.3 h1:P52Uhd7GShkeU/a1cBOuqIcHMHBrA54Z2t5fLlE85SQ= +github.com/go-openapi/swag/fileutils v0.25.3/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.3 h1:U20VKDS74HiPaLV7UZkztpyVOw3JNVsit+w+gTXRj0A= +github.com/go-openapi/swag/jsonname v0.25.3/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.3 h1:kV7wer79KXUM4Ea4tBdAVTU842Rg6tWstX3QbM4fGdw= +github.com/go-openapi/swag/jsonutils v0.25.3/go.mod h1:ILcKqe4HC1VEZmJx51cVuZQ6MF8QvdfXsQfiaCs0z9o= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3 h1:/i3E9hBujtXfHy91rjtwJ7Fgv5TuDHgnSrYjhFxwxOw= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.3/go.mod h1:8kYfCR2rHyOj25HVvxL5Nm8wkfzggddgjZm6RgjT8Ao= +github.com/go-openapi/swag/loading v0.25.3 h1:Nn65Zlzf4854MY6Ft0JdNrtnHh2bdcS/tXckpSnOb2Y= +github.com/go-openapi/swag/loading v0.25.3/go.mod h1:xajJ5P4Ang+cwM5gKFrHBgkEDWfLcsAKepIuzTmOb/c= +github.com/go-openapi/swag/mangling v0.25.3 h1:rGIrEzXaYWuUW1MkFmG3pcH+EIA0/CoUkQnIyB6TUyo= +github.com/go-openapi/swag/mangling v0.25.3/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.3 h1:XWXHZfL/65ABiv8rvGp9dtE0C6QHTYkCrNV77jTl358= +github.com/go-openapi/swag/netutils v0.25.3/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.3 h1:nAmWq1fUTWl/XiaEPwALjp/8BPZJun70iDHRNq/sH6w= +github.com/go-openapi/swag/stringutils v0.25.3/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.3 h1:2w4mEEo7DQt3V4veWMZw0yTPQibiL3ri2fdDV4t2TQc= +github.com/go-openapi/swag/typeutils v0.25.3/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.3 h1:LKTJjCn/W1ZfMec0XDL4Vxh8kyAnv1orH5F2OREDUrg= +github.com/go-openapi/swag/yamlutils v0.25.3/go.mod h1:Y7QN6Wc5DOBXK14/xeo1cQlq0EA0wvLoSv13gDQoCao= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= @@ -104,8 +118,8 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -118,8 +132,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -132,12 +148,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.25.3 h1:Ty8+Yi/ayDAGtk4XxmmfUy4GabvM+MegeB4cDLRi6nw= -github.com/onsi/ginkgo/v2 v2.25.3/go.mod h1:43uiyQC4Ed2tkOzLsEYm7hnrb7UJTWHYNsuy3bG/snE= -github.com/onsi/ginkgo/v2 v2.26.0 h1:1J4Wut1IlYZNEAWIV3ALrT9NfiaGW2cDCJQSFQMs/gE= -github.com/onsi/ginkgo/v2 v2.26.0/go.mod h1:qhEywmzWTBUY88kfO0BRvX4py7scov9yR+Az2oavUzw= -github.com/onsi/ginkgo/v2 v2.27.1 h1:0LJC8MpUSQnfnp4n/3W3GdlmJP3ENGF0ZPzjQGLPP7s= -github.com/onsi/ginkgo/v2 v2.27.1/go.mod h1:wmy3vCqiBjirARfVhAqFpYt8uvX0yaFe+GudAqqcCqA= github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= @@ -155,12 +165,12 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= +github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= @@ -182,6 +192,14 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -216,8 +234,6 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= -go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -225,63 +241,46 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= -golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= -golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= -golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= -golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= -golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= -golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= -golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -290,14 +289,12 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -308,59 +305,32 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.0 h1:L+JtP2wDbEYPUeNGbeSa/5GwFtIA662EmT2YSLOkAVE= -k8s.io/api v0.34.0/go.mod h1:YzgkIzOOlhl9uwWCZNqpw6RJy9L2FK4dlJeayUoydug= -k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= -k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= -k8s.io/apiextensions-apiserver v0.34.0 h1:B3hiB32jV7BcyKcMU5fDaDxk882YrJ1KU+ZSkA9Qxoc= -k8s.io/apiextensions-apiserver v0.34.0/go.mod h1:hLI4GxE1BDBy9adJKxUxCEHBGZtGfIg98Q+JmTD7+g0= -k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= -k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= -k8s.io/apimachinery v0.34.0 h1:eR1WO5fo0HyoQZt1wdISpFDffnWOvFLOOeJ7MgIv4z0= -k8s.io/apimachinery v0.34.0/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= -k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= -k8s.io/apiserver v0.34.0 h1:Z51fw1iGMqN7uJ1kEaynf2Aec1Y774PqU+FVWCFV3Jg= -k8s.io/apiserver v0.34.0/go.mod h1:52ti5YhxAvewmmpVRqlASvaqxt0gKJxvCeW7ZrwgazQ= -k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= -k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= -k8s.io/client-go v0.34.0 h1:YoWv5r7bsBfb0Hs2jh8SOvFbKzzxyNo0nSb0zC19KZo= -k8s.io/client-go v0.34.0/go.mod h1:ozgMnEKXkRjeMvBZdV1AijMHLTh3pbACPvK7zFR+QQY= -k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= -k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= +k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= +k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.2 h1:2/yu8suwkmES7IzwlehAovo8dDE07cFRC7KMDb1+MAE= +k8s.io/apiserver v0.34.2/go.mod h1:gqJQy2yDOB50R3JUReHSFr+cwJnL8G1dzTA0YLEqAPI= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= k8s.io/cluster-bootstrap v0.33.3 h1:u2NTxJ5CFSBFXaDxLQoOWMly8eni31psVso+caq6uwI= k8s.io/cluster-bootstrap v0.33.3/go.mod h1:p970f8u8jf273zyQ5raD8WUu2XyAl0SAWOY82o7i/ds= -k8s.io/component-base v0.34.0 h1:bS8Ua3zlJzapklsB1dZgjEJuJEeHjj8yTu1gxE2zQX8= -k8s.io/component-base v0.34.0/go.mod h1:RSCqUdvIjjrEm81epPcjQ/DS+49fADvGSCkIP3IC6vg= -k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.2 h1:HQRqK9x2sSAsd8+R4xxRirlTjowsg6fWCPwWYeSvogQ= +k8s.io/component-base v0.34.2/go.mod h1:9xw2FHJavUHBFpiGkZoKuYZ5pdtLKe97DEByaA+hHbM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f h1:wyRlmLgBSXi3kgawro8klrMRljXeRo1HFkQRs+meYfs= -k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/cluster-api v1.11.1 h1:7CyGCTxv1p3Y2kRe1ljTj/w4TcdIdWNj0CTBc4i1aBo= -sigs.k8s.io/cluster-api v1.11.1/go.mod h1:zyrjgJ5RbXhwKcAdUlGPNK5YOHpcmxXvur+5I8lkMUQ= -sigs.k8s.io/cluster-api v1.11.2 h1:uAczaBavU5Y6aDgyoXWtq28k1kalpSZnVItwXHusw1c= -sigs.k8s.io/cluster-api v1.11.2/go.mod h1:C1gJVAjMXRG+M+djjGYNkoi5kBMhFnOUI9QqZDAtMms= sigs.k8s.io/cluster-api v1.11.3 h1:apxfugbP1X8AG7THCM74CTarCOW4H2oOc6hlbm1hY80= sigs.k8s.io/cluster-api v1.11.3/go.mod h1:CA471SACi81M8DzRKTlWpHV33G0cfWEj7sC4fALFVok= -sigs.k8s.io/controller-runtime v0.22.1 h1:Ah1T7I+0A7ize291nJZdS1CabF/lB4E++WizgV24Eqg= -sigs.k8s.io/controller-runtime v0.22.1/go.mod h1:FwiwRjkRPbiN+zp2QRp7wlTCzbUXxZ/D4OzuQUDwBHY= -sigs.k8s.io/controller-runtime v0.22.2 h1:cK2l8BGWsSWkXz09tcS4rJh95iOLney5eawcK5A33r4= -sigs.k8s.io/controller-runtime v0.22.2/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= -sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= -sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= -sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= -sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= diff --git a/controllers/config/manager.go b/internal/controllers/cfg/manager.go similarity index 95% rename from controllers/config/manager.go rename to internal/controllers/cfg/manager.go index c80352ed..a34a436d 100644 --- a/controllers/config/manager.go +++ b/internal/controllers/cfg/manager.go @@ -13,7 +13,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" "github.com/projectcapsule/capsule/pkg/configuration" ) diff --git a/controllers/pod/errors.go b/internal/controllers/pod/errors.go similarity index 100% rename from controllers/pod/errors.go rename to internal/controllers/pod/errors.go diff --git a/controllers/pod/metadata.go b/internal/controllers/pod/metadata.go similarity index 100% rename from controllers/pod/metadata.go rename to internal/controllers/pod/metadata.go diff --git a/controllers/pv/controller.go b/internal/controllers/pv/controller.go similarity index 92% rename from controllers/pv/controller.go rename to internal/controllers/pv/controller.go index 6537b7c5..5982005d 100644 --- a/controllers/pv/controller.go +++ b/internal/controllers/pv/controller.go @@ -18,9 +18,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - webhookutils "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type Controller struct { @@ -79,7 +79,7 @@ func (c *Controller) Reconcile(ctx context.Context, request reconcile.Request) ( return reconcile.Result{}, nil } - tnt, err := webhookutils.TenantByStatusNamespace(ctx, c.client, persistentVolume.Spec.ClaimRef.Namespace) + tnt, err := tenant.TenantByStatusNamespace(ctx, c.client, persistentVolume.Spec.ClaimRef.Namespace) if err != nil { log.Error(err, "unable to retrieve Tenant from the claimRef") diff --git a/controllers/rbac/const.go b/internal/controllers/rbac/const.go similarity index 100% rename from controllers/rbac/const.go rename to internal/controllers/rbac/const.go diff --git a/controllers/rbac/manager.go b/internal/controllers/rbac/manager.go similarity index 88% rename from controllers/rbac/manager.go rename to internal/controllers/rbac/manager.go index ab42bb1a..188ad704 100644 --- a/controllers/rbac/manager.go +++ b/internal/controllers/rbac/manager.go @@ -13,6 +13,7 @@ import ( rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authentication/serviceaccount" "k8s.io/client-go/util/retry" "k8s.io/client-go/util/workqueue" ctrl "sigs.k8s.io/controller-runtime" @@ -23,9 +24,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/meta" ) type Manager struct { @@ -112,6 +114,32 @@ func (r *Manager) EnsureClusterRoleBindingsProvisioner(ctx context.Context) erro crb.RoleRef = provisionerClusterRoleBinding.RoleRef crb.Subjects = nil + for _, entity := range r.Configuration.Administrators() { + switch entity.Kind { + case api.UserOwner: + crb.Subjects = append(crb.Subjects, rbacv1.Subject{ + Kind: rbacv1.UserKind, + Name: entity.Name, + }) + case api.GroupOwner: + crb.Subjects = append(crb.Subjects, rbacv1.Subject{ + Kind: rbacv1.GroupKind, + Name: entity.Name, + }) + case api.ServiceAccountOwner: + namespace, name, err := serviceaccount.SplitUsername(entity.Name) + if err != nil { + return err + } + + crb.Subjects = append(crb.Subjects, rbacv1.Subject{ + Kind: rbacv1.ServiceAccountKind, + Name: name, + Namespace: namespace, + }) + } + } + for _, group := range r.Configuration.UserGroups() { crb.Subjects = append(crb.Subjects, rbacv1.Subject{ Kind: rbacv1.GroupKind, diff --git a/controllers/resourcepools/claim_controller.go b/internal/controllers/resourcepools/claim_controller.go similarity index 97% rename from controllers/resourcepools/claim_controller.go rename to internal/controllers/resourcepools/claim_controller.go index ef911306..8b3f4f1a 100644 --- a/controllers/resourcepools/claim_controller.go +++ b/internal/controllers/resourcepools/claim_controller.go @@ -23,10 +23,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/internal/metrics" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" - "github.com/projectcapsule/capsule/pkg/metrics" + "github.com/projectcapsule/capsule/pkg/api/meta" ) type resourceClaimController struct { diff --git a/controllers/resourcepools/manager.go b/internal/controllers/resourcepools/manager.go similarity index 88% rename from controllers/resourcepools/manager.go rename to internal/controllers/resourcepools/manager.go index df7973b8..6a14adf7 100644 --- a/controllers/resourcepools/manager.go +++ b/internal/controllers/resourcepools/manager.go @@ -10,8 +10,8 @@ import ( "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/manager" - "github.com/projectcapsule/capsule/controllers/utils" - "github.com/projectcapsule/capsule/pkg/metrics" + "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/internal/metrics" ) func Add( diff --git a/controllers/resourcepools/pool_controller.go b/internal/controllers/resourcepools/pool_controller.go similarity index 98% rename from controllers/resourcepools/pool_controller.go rename to internal/controllers/resourcepools/pool_controller.go index 1d1bc396..7229b217 100644 --- a/controllers/resourcepools/pool_controller.go +++ b/internal/controllers/resourcepools/pool_controller.go @@ -26,10 +26,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - ctrlutils "github.com/projectcapsule/capsule/controllers/utils" + ctrlutils "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/internal/metrics" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" - "github.com/projectcapsule/capsule/pkg/metrics" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/utils" ) @@ -514,7 +514,7 @@ func (r *resourcePoolController) syncResourceQuota( target := &corev1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ - Name: utils.PoolResourceQuotaName(pool), + Name: pool.GetQuotaName(), Namespace: namespace.GetName(), }, } @@ -744,7 +744,7 @@ func (r *resourcePoolController) garbageCollectNamespace( return fmt.Errorf("failed to check namespace existence: %w", err) } - name := utils.PoolResourceQuotaName(pool) + name := pool.GetQuotaName() // Attempt to delete the ResourceQuota target := &corev1.ResourceQuota{ diff --git a/controllers/resources/global.go b/internal/controllers/resources/global.go similarity index 99% rename from controllers/resources/global.go rename to internal/controllers/resources/global.go index 76f9b70e..611e29e5 100644 --- a/controllers/resources/global.go +++ b/internal/controllers/resources/global.go @@ -23,7 +23,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" ) type Global struct { diff --git a/controllers/resources/namespaced.go b/internal/controllers/resources/namespaced.go similarity index 98% rename from controllers/resources/namespaced.go rename to internal/controllers/resources/namespaced.go index 6aec6872..ab796142 100644 --- a/controllers/resources/namespaced.go +++ b/internal/controllers/resources/namespaced.go @@ -20,7 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" ) type Namespaced struct { diff --git a/controllers/resources/processor.go b/internal/controllers/resources/processor.go similarity index 100% rename from controllers/resources/processor.go rename to internal/controllers/resources/processor.go diff --git a/controllers/servicelabels/abstract.go b/internal/controllers/servicelabels/abstract.go similarity index 100% rename from controllers/servicelabels/abstract.go rename to internal/controllers/servicelabels/abstract.go diff --git a/controllers/servicelabels/endpoint_slices.go b/internal/controllers/servicelabels/endpoint_slices.go similarity index 100% rename from controllers/servicelabels/endpoint_slices.go rename to internal/controllers/servicelabels/endpoint_slices.go diff --git a/controllers/servicelabels/errors.go b/internal/controllers/servicelabels/errors.go similarity index 100% rename from controllers/servicelabels/errors.go rename to internal/controllers/servicelabels/errors.go diff --git a/controllers/servicelabels/service.go b/internal/controllers/servicelabels/service.go similarity index 100% rename from controllers/servicelabels/service.go rename to internal/controllers/servicelabels/service.go diff --git a/controllers/tenant/limitranges.go b/internal/controllers/tenant/limitranges.go similarity index 83% rename from controllers/tenant/limitranges.go rename to internal/controllers/tenant/limitranges.go index ebf182c4..e727afad 100644 --- a/controllers/tenant/limitranges.go +++ b/internal/controllers/tenant/limitranges.go @@ -1,6 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 +//nolint:dupl package tenant import ( @@ -14,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // Ensuring all the LimitRange are applied to each Namespace handled by the Tenant. @@ -40,17 +41,6 @@ func (r *Manager) syncLimitRanges(ctx context.Context, tenant *capsulev1beta2.Te } func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta2.Tenant, namespace string, keys []string) (err error) { - // getting LimitRange labels for the mutateFn - var tenantLabel, limitRangeLabel string - - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { - return err - } - - if limitRangeLabel, err = utils.GetTypeLabel(&corev1.LimitRange{}); err != nil { - return err - } - if err = r.pruningResources(ctx, namespace, keys, &corev1.LimitRange{}); err != nil { return err } @@ -71,8 +61,8 @@ func (r *Manager) syncLimitRange(ctx context.Context, tenant *capsulev1beta2.Ten labels = map[string]string{} } - labels[tenantLabel] = tenant.Name - labels[limitRangeLabel] = strconv.Itoa(i) + labels[meta.TenantLabel] = tenant.Name + labels[meta.LimitRangeLabel] = strconv.Itoa(i) target.SetLabels(labels) target.Spec = spec diff --git a/controllers/tenant/manager.go b/internal/controllers/tenant/manager.go similarity index 96% rename from controllers/tenant/manager.go rename to internal/controllers/tenant/manager.go index 813cd17f..a49ff295 100644 --- a/controllers/tenant/manager.go +++ b/internal/controllers/tenant/manager.go @@ -24,9 +24,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/controllers/utils" - meta "github.com/projectcapsule/capsule/pkg/meta" - "github.com/projectcapsule/capsule/pkg/metrics" + "github.com/projectcapsule/capsule/internal/controllers/utils" + "github.com/projectcapsule/capsule/internal/metrics" + meta "github.com/projectcapsule/capsule/pkg/api/meta" ) type Manager struct { diff --git a/controllers/tenant/metadata.go b/internal/controllers/tenant/metadata.go similarity index 80% rename from controllers/tenant/metadata.go rename to internal/controllers/tenant/metadata.go index f5803ad8..505ad18a 100644 --- a/controllers/tenant/metadata.go +++ b/internal/controllers/tenant/metadata.go @@ -7,7 +7,7 @@ import ( "context" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // Sets a label on the Tenant object with it's name. @@ -17,7 +17,7 @@ func (r *Manager) ensureMetadata(ctx context.Context, tnt *capsulev1beta2.Tenant tnt.Labels = make(map[string]string) } - if v, ok := tnt.Labels[capsuleapi.TenantNameLabel]; !ok || v != tnt.Name { + if v, ok := tnt.Labels[meta.TenantNameLabel]; !ok || v != tnt.Name { if err := r.Update(ctx, tnt); err != nil { return err, false } diff --git a/controllers/tenant/metrics.go b/internal/controllers/tenant/metrics.go similarity index 97% rename from controllers/tenant/metrics.go rename to internal/controllers/tenant/metrics.go index 3d2f1c4e..46b5669b 100644 --- a/controllers/tenant/metrics.go +++ b/internal/controllers/tenant/metrics.go @@ -7,7 +7,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // Exposing Status Metrics for tenant. diff --git a/controllers/tenant/namespaces.go b/internal/controllers/tenant/namespaces.go similarity index 91% rename from controllers/tenant/namespaces.go rename to internal/controllers/tenant/namespaces.go index b01cc2ae..6ee6b4ab 100644 --- a/controllers/tenant/namespaces.go +++ b/internal/controllers/tenant/namespaces.go @@ -20,8 +20,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/utils" ) @@ -240,39 +239,39 @@ func buildNamespaceAnnotationsForTenant(tnt *capsulev1beta2.Tenant) map[string]s if ic := tnt.Spec.IngressOptions.AllowedClasses; ic != nil { if len(ic.Exact) > 0 { - annotations[AvailableIngressClassesAnnotation] = strings.Join(ic.Exact, ",") + annotations[meta.AvailableIngressClassesAnnotation] = strings.Join(ic.Exact, ",") } if len(ic.Regex) > 0 { - annotations[AvailableIngressClassesRegexpAnnotation] = ic.Regex + annotations[meta.AvailableIngressClassesRegexpAnnotation] = ic.Regex } } if sc := tnt.Spec.StorageClasses; sc != nil { if len(sc.Exact) > 0 { - annotations[AvailableStorageClassesAnnotation] = strings.Join(sc.Exact, ",") + annotations[meta.AvailableStorageClassesAnnotation] = strings.Join(sc.Exact, ",") } if len(sc.Regex) > 0 { - annotations[AvailableStorageClassesRegexpAnnotation] = sc.Regex + annotations[meta.AvailableStorageClassesRegexpAnnotation] = sc.Regex } } if cr := tnt.Spec.ContainerRegistries; cr != nil { if len(cr.Exact) > 0 { - annotations[AllowedRegistriesAnnotation] = strings.Join(cr.Exact, ",") + annotations[meta.AllowedRegistriesAnnotation] = strings.Join(cr.Exact, ",") } if len(cr.Regex) > 0 { - annotations[AllowedRegistriesRegexpAnnotation] = cr.Regex + annotations[meta.AllowedRegistriesRegexpAnnotation] = cr.Regex } } for _, key := range []string{ - api.ForbiddenNamespaceLabelsAnnotation, - api.ForbiddenNamespaceLabelsRegexpAnnotation, - api.ForbiddenNamespaceAnnotationsAnnotation, - api.ForbiddenNamespaceAnnotationsRegexpAnnotation, + meta.ForbiddenNamespaceLabelsAnnotation, + meta.ForbiddenNamespaceLabelsRegexpAnnotation, + meta.ForbiddenNamespaceAnnotationsAnnotation, + meta.ForbiddenNamespaceAnnotationsRegexpAnnotation, } { if value, ok := tnt.Annotations[key]; ok { annotations[key] = value diff --git a/controllers/tenant/networkpolicies.go b/internal/controllers/tenant/networkpolicies.go similarity index 83% rename from controllers/tenant/networkpolicies.go rename to internal/controllers/tenant/networkpolicies.go index 78cd2ce0..b937d57f 100644 --- a/controllers/tenant/networkpolicies.go +++ b/internal/controllers/tenant/networkpolicies.go @@ -1,6 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 +//nolint:dupl package tenant import ( @@ -14,7 +15,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // Ensuring all the NetworkPolicies are applied to each Namespace handled by the Tenant. @@ -43,16 +44,6 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta2. if err = r.pruningResources(ctx, namespace, keys, &networkingv1.NetworkPolicy{}); err != nil { return err } - // getting NetworkPolicy labels for the mutateFn - var tenantLabel, networkPolicyLabel string - - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { - return err - } - - if networkPolicyLabel, err = utils.GetTypeLabel(&networkingv1.NetworkPolicy{}); err != nil { - return err - } for i, spec := range tenant.Spec.NetworkPolicies.Items { //nolint:dupl target := &networkingv1.NetworkPolicy{ @@ -70,8 +61,8 @@ func (r *Manager) syncNetworkPolicy(ctx context.Context, tenant *capsulev1beta2. labels = map[string]string{} } - labels[tenantLabel] = tenant.Name - labels[networkPolicyLabel] = strconv.Itoa(i) + labels[meta.TenantLabel] = tenant.Name + labels[meta.NetworkPolicyLabel] = strconv.Itoa(i) target.SetLabels(labels) target.Spec = spec diff --git a/controllers/tenant/resourcequotas.go b/internal/controllers/tenant/resourcequotas.go similarity index 95% rename from controllers/tenant/resourcequotas.go rename to internal/controllers/tenant/resourcequotas.go index 2e18161f..01063f2d 100644 --- a/controllers/tenant/resourcequotas.go +++ b/internal/controllers/tenant/resourcequotas.go @@ -23,6 +23,7 @@ import ( capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/utils" ) @@ -40,17 +41,6 @@ import ( // In case of Namespace-scoped Resource Budget, we're just replicating the resources across all registered Namespaces. func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2.Tenant) (err error) { //nolint:gocognit - // getting ResourceQuota labels for the mutateFn - var tenantLabel, typeLabel string - - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { - return err - } - - if typeLabel, err = utils.GetTypeLabel(&corev1.ResourceQuota{}); err != nil { - return err - } - // Remove prior metrics, to avoid cleaning up for metrics of deleted ResourceQuotas r.Metrics.DeleteTenantResourceMetrics(tenant.Name) // Expose the namespace quota and usage as metrics for the tenant @@ -77,13 +67,13 @@ func (r *Manager) syncResourceQuotas(ctx context.Context, tenant *capsulev1beta2 // Requirement to list ResourceQuota of the current Tenant var tntRequirement *labels.Requirement - if tntRequirement, scopeErr = labels.NewRequirement(tenantLabel, selection.Equals, []string{tenant.Name}); scopeErr != nil { + if tntRequirement, scopeErr = labels.NewRequirement(meta.TenantLabel, selection.Equals, []string{tenant.Name}); scopeErr != nil { r.Log.Error(scopeErr, "Cannot build ResourceQuota Tenant requirement") } // Requirement to list ResourceQuota for the current index var indexRequirement *labels.Requirement - if indexRequirement, scopeErr = labels.NewRequirement(typeLabel, selection.Equals, []string{strconv.Itoa(index)}); scopeErr != nil { + if indexRequirement, scopeErr = labels.NewRequirement(meta.ResourceQuotaLabel, selection.Equals, []string{strconv.Itoa(index)}); scopeErr != nil { r.Log.Error(scopeErr, "Cannot build ResourceQuota index requirement") } // Listing all the ResourceQuota according to the said requirements. diff --git a/controllers/tenant/resourcequotas_quota.go b/internal/controllers/tenant/resourcequotas_quota.go similarity index 100% rename from controllers/tenant/resourcequotas_quota.go rename to internal/controllers/tenant/resourcequotas_quota.go diff --git a/controllers/tenant/rolebindings.go b/internal/controllers/tenant/rolebindings.go similarity index 89% rename from controllers/tenant/rolebindings.go rename to internal/controllers/tenant/rolebindings.go index dde840fe..2f172438 100644 --- a/controllers/tenant/rolebindings.go +++ b/internal/controllers/tenant/rolebindings.go @@ -16,12 +16,12 @@ import ( capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" ) // ownerClusterRoleBindings generates a Capsule AdditionalRoleBinding object for the Owner dynamic clusterrole in order // to take advantage of the additional role binding feature. -func (r *Manager) ownerClusterRoleBindings(owner capsulev1beta2.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { +func (r *Manager) ownerClusterRoleBindings(owner api.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec { var subject rbacv1.Subject if owner.Kind == "ServiceAccount" { @@ -94,16 +94,6 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta2.T } func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsulev1beta2.Tenant, ns string, keys []string, hashFn func(binding api.AdditionalRoleBindingsSpec) string) (err error) { - var tenantLabel, roleBindingLabel string - - if tenantLabel, err = utils.GetTypeLabel(&capsulev1beta2.Tenant{}); err != nil { - return err - } - - if roleBindingLabel, err = utils.GetTypeLabel(&rbacv1.RoleBinding{}); err != nil { - return err - } - if err = r.pruningResources(ctx, ns, keys, &rbacv1.RoleBinding{}); err != nil { return err } @@ -138,8 +128,8 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule target.Labels = roleBinding.Labels } - target.Labels[tenantLabel] = tenant.Name - target.Labels[roleBindingLabel] = roleBindingHashLabel + target.Labels[meta.TenantLabel] = tenant.Name + target.Labels[meta.RolebindingLabel] = roleBindingHashLabel if roleBinding.Annotations != nil { target.Annotations = roleBinding.Annotations diff --git a/controllers/tenant/utils.go b/internal/controllers/tenant/utils.go similarity index 100% rename from controllers/tenant/utils.go rename to internal/controllers/tenant/utils.go diff --git a/controllers/tls/errors.go b/internal/controllers/tls/errors.go similarity index 100% rename from controllers/tls/errors.go rename to internal/controllers/tls/errors.go diff --git a/controllers/tls/manager.go b/internal/controllers/tls/manager.go similarity index 99% rename from controllers/tls/manager.go rename to internal/controllers/tls/manager.go index e93dafd0..3c99d1e3 100644 --- a/controllers/tls/manager.go +++ b/internal/controllers/tls/manager.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/projectcapsule/capsule/controllers/utils" + "github.com/projectcapsule/capsule/internal/controllers/utils" "github.com/projectcapsule/capsule/pkg/cert" "github.com/projectcapsule/capsule/pkg/configuration" ) diff --git a/controllers/utils/name_matching.go b/internal/controllers/utils/name_matching.go similarity index 100% rename from controllers/utils/name_matching.go rename to internal/controllers/utils/name_matching.go diff --git a/controllers/utils/options.go b/internal/controllers/utils/options.go similarity index 100% rename from controllers/utils/options.go rename to internal/controllers/utils/options.go diff --git a/pkg/metrics/claim_recorder.go b/internal/metrics/claim_recorder.go similarity index 100% rename from pkg/metrics/claim_recorder.go rename to internal/metrics/claim_recorder.go diff --git a/pkg/metrics/pool_recorder.go b/internal/metrics/pool_recorder.go similarity index 100% rename from pkg/metrics/pool_recorder.go rename to internal/metrics/pool_recorder.go diff --git a/pkg/metrics/tenant_recorder.go b/internal/metrics/tenant_recorder.go similarity index 100% rename from pkg/metrics/tenant_recorder.go rename to internal/metrics/tenant_recorder.go diff --git a/pkg/metrics/utils.go b/internal/metrics/utils.go similarity index 100% rename from pkg/metrics/utils.go rename to internal/metrics/utils.go diff --git a/pkg/webhook/defaults/errors.go b/internal/webhook/defaults/errors.go similarity index 100% rename from pkg/webhook/defaults/errors.go rename to internal/webhook/defaults/errors.go diff --git a/pkg/webhook/defaults/gateway.go b/internal/webhook/defaults/gateway.go similarity index 94% rename from pkg/webhook/defaults/gateway.go rename to internal/webhook/defaults/gateway.go index d18adfba..e2d7b3ba 100644 --- a/pkg/webhook/defaults/gateway.go +++ b/internal/webhook/defaults/gateway.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" - capsulegateway "github.com/projectcapsule/capsule/pkg/webhook/gateway" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulegateway "github.com/projectcapsule/capsule/internal/webhook/gateway" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) func mutateGatewayDefaults(ctx context.Context, req admission.Request, c client.Client, decoder admission.Decoder, recorder record.EventRecorder, namespce string) *admission.Response { diff --git a/pkg/webhook/defaults/handler.go b/internal/webhook/defaults/handler.go similarity index 97% rename from pkg/webhook/defaults/handler.go rename to internal/webhook/defaults/handler.go index 3887066d..37327a6a 100644 --- a/pkg/webhook/defaults/handler.go +++ b/internal/webhook/defaults/handler.go @@ -12,8 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" ) type handler struct { diff --git a/pkg/webhook/defaults/ingress.go b/internal/webhook/defaults/ingress.go similarity index 94% rename from pkg/webhook/defaults/ingress.go rename to internal/webhook/defaults/ingress.go index 3db4f7ac..35eda2c1 100644 --- a/pkg/webhook/defaults/ingress.go +++ b/internal/webhook/defaults/ingress.go @@ -16,8 +16,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsuleingress "github.com/projectcapsule/capsule/pkg/webhook/ingress" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsuleingress "github.com/projectcapsule/capsule/internal/webhook/ingress" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) func mutateIngressDefaults(ctx context.Context, req admission.Request, version *version.Version, c client.Client, decoder admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { diff --git a/pkg/webhook/defaults/pods.go b/internal/webhook/defaults/pods.go similarity index 95% rename from pkg/webhook/defaults/pods.go rename to internal/webhook/defaults/pods.go index ad49713e..1cae1fc1 100644 --- a/pkg/webhook/defaults/pods.go +++ b/internal/webhook/defaults/pods.go @@ -15,8 +15,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Client, decoder admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { @@ -27,7 +28,7 @@ func mutatePodDefaults(ctx context.Context, req admission.Request, c client.Clie pod.SetNamespace(namespace) - tnt, tErr := utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + tnt, tErr := tenant.TenantByStatusNamespace(ctx, c, pod.Namespace) if tErr != nil { return utils.ErroredResponse(tErr) } else if tnt == nil { diff --git a/pkg/webhook/defaults/storage.go b/internal/webhook/defaults/storage.go similarity index 91% rename from pkg/webhook/defaults/storage.go rename to internal/webhook/defaults/storage.go index 0423e2fe..df28a3ac 100644 --- a/pkg/webhook/defaults/storage.go +++ b/internal/webhook/defaults/storage.go @@ -15,7 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Client, decoder admission.Decoder, recorder record.EventRecorder, namespace string) *admission.Response { @@ -30,7 +31,7 @@ func mutatePVCDefaults(ctx context.Context, req admission.Request, c client.Clie var tnt *capsulev1beta2.Tenant - tnt, err = utils.TenantByStatusNamespace(ctx, c, pvc.Namespace) + tnt, err = tenant.TenantByStatusNamespace(ctx, c, pvc.Namespace) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/gateway/errors.go b/internal/webhook/gateway/errors.go similarity index 95% rename from pkg/webhook/gateway/errors.go rename to internal/webhook/gateway/errors.go index 33a15672..12289a9c 100644 --- a/pkg/webhook/gateway/errors.go +++ b/internal/webhook/gateway/errors.go @@ -6,8 +6,8 @@ package gateway import ( "fmt" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type gatewayClassForbiddenError struct { diff --git a/pkg/webhook/gateway/utils.go b/internal/webhook/gateway/utils.go similarity index 100% rename from pkg/webhook/gateway/utils.go rename to internal/webhook/gateway/utils.go diff --git a/pkg/webhook/gateway/validate_class.go b/internal/webhook/gateway/validate_class.go similarity index 96% rename from pkg/webhook/gateway/validate_class.go rename to internal/webhook/gateway/validate_class.go index 47cec014..f3477665 100644 --- a/pkg/webhook/gateway/validate_class.go +++ b/internal/webhook/gateway/validate_class.go @@ -15,9 +15,9 @@ import ( gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type class struct { diff --git a/pkg/webhook/handler.go b/internal/webhook/handler.go similarity index 65% rename from pkg/webhook/handler.go rename to internal/webhook/handler.go index 85fdc433..38dd53b2 100644 --- a/pkg/webhook/handler.go +++ b/internal/webhook/handler.go @@ -18,3 +18,9 @@ type Handler interface { OnDelete(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) Func OnUpdate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) Func } + +type TypedHandler[T client.Object] interface { + OnCreate(c client.Client, obj T, decoder admission.Decoder, recorder record.EventRecorder) Func + OnUpdate(c client.Client, obj T, old T, decoder admission.Decoder, recorder record.EventRecorder) Func + OnDelete(c client.Client, obj T, decoder admission.Decoder, recorder record.EventRecorder) Func +} diff --git a/pkg/webhook/ingress/errors.go b/internal/webhook/ingress/errors.go similarity index 98% rename from pkg/webhook/ingress/errors.go rename to internal/webhook/ingress/errors.go index c20e9b9b..f48f4dd4 100644 --- a/pkg/webhook/ingress/errors.go +++ b/internal/webhook/ingress/errors.go @@ -7,8 +7,8 @@ import ( "fmt" "strings" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type ingressClassForbiddenError struct { diff --git a/pkg/webhook/ingress/types.go b/internal/webhook/ingress/types.go similarity index 100% rename from pkg/webhook/ingress/types.go rename to internal/webhook/ingress/types.go diff --git a/pkg/webhook/ingress/utils.go b/internal/webhook/ingress/utils.go similarity index 100% rename from pkg/webhook/ingress/utils.go rename to internal/webhook/ingress/utils.go diff --git a/pkg/webhook/ingress/validate_class.go b/internal/webhook/ingress/validate_class.go similarity index 96% rename from pkg/webhook/ingress/validate_class.go rename to internal/webhook/ingress/validate_class.go index 420abc37..c7dc38ed 100644 --- a/pkg/webhook/ingress/validate_class.go +++ b/internal/webhook/ingress/validate_class.go @@ -15,9 +15,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type class struct { diff --git a/pkg/webhook/ingress/validate_collision.go b/internal/webhook/ingress/validate_collision.go similarity index 97% rename from pkg/webhook/ingress/validate_collision.go rename to internal/webhook/ingress/validate_collision.go index 9558fb38..7ca977d6 100644 --- a/pkg/webhook/ingress/validate_collision.go +++ b/internal/webhook/ingress/validate_collision.go @@ -19,11 +19,11 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" "github.com/projectcapsule/capsule/pkg/configuration" "github.com/projectcapsule/capsule/pkg/indexer/ingress" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type collision struct { diff --git a/pkg/webhook/ingress/validate_hostnames.go b/internal/webhook/ingress/validate_hostnames.go similarity index 96% rename from pkg/webhook/ingress/validate_hostnames.go rename to internal/webhook/ingress/validate_hostnames.go index c62db954..fd73b3cd 100644 --- a/pkg/webhook/ingress/validate_hostnames.go +++ b/internal/webhook/ingress/validate_hostnames.go @@ -15,9 +15,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type hostnames struct { diff --git a/pkg/webhook/ingress/validate_wildcard.go b/internal/webhook/ingress/validate_wildcard.go similarity index 95% rename from pkg/webhook/ingress/validate_wildcard.go rename to internal/webhook/ingress/validate_wildcard.go index 30fa6bc3..4691dceb 100644 --- a/pkg/webhook/ingress/validate_wildcard.go +++ b/internal/webhook/ingress/validate_wildcard.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type wildcard struct{} diff --git a/internal/webhook/namespace/handler.go b/internal/webhook/namespace/handler.go new file mode 100644 index 00000000..3b7f581e --- /dev/null +++ b/internal/webhook/namespace/handler.go @@ -0,0 +1,157 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package namespace + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +func NamespaceHandler(configuration configuration.Configuration, handlers ...webhook.TypedHandler[*corev1.Namespace]) webhook.Handler { + return &adminHandler{ + cfg: configuration, + handlers: handlers, + } +} + +type adminHandler struct { + cfg configuration.Configuration + handlers []webhook.TypedHandler[*corev1.Namespace] +} + +//nolint:dupl +func (h *adminHandler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := tenant.GetTenantByLabels(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil && userIsAdmin { + return nil + } + + for _, hndl := range h.handlers { + if response := hndl.OnCreate(c, ns, decoder, recorder)(ctx, req); response != nil { + return response + } + } + + return nil + } +} + +//nolint:dupl +func (h *adminHandler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := tenant.GetTenantByLabels(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil && userIsAdmin { + return nil + } + + for _, hndl := range h.handlers { + if response := hndl.OnDelete(c, ns, decoder, recorder)(ctx, req); response != nil { + return response + } + } + + return nil + } +} + +func (h *adminHandler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + oldNs := &corev1.Namespace{} + if err := decoder.DecodeRaw(req.OldObject, oldNs); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, oldNs.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + //nolint:nestif + if userIsAdmin { + if tnt == nil { + tnt, err = tenant.GetTenantByLabels(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + } + } else { + owned, err := tenant.NamespaceIsOwned(ctx, c, h.cfg, oldNs, tnt, req.UserInfo) + if err != nil { + return utils.ErroredResponse(err) + } + + if !owned { + recorder.Eventf(oldNs, corev1.EventTypeWarning, "NamespacePatch", "Namespace %s can not be patched", oldNs.GetName()) + + response := admission.Denied("Denied patch request for this namespace") + + return &response + } + } + + for _, hndl := range h.handlers { + if response := hndl.OnUpdate(c, ns, oldNs, decoder, recorder)(ctx, req); response != nil { + return response + } + } + + return nil + } +} diff --git a/pkg/webhook/namespace/mutation/cordoning.go b/internal/webhook/namespace/mutation/cordoning.go similarity index 68% rename from pkg/webhook/namespace/mutation/cordoning.go rename to internal/webhook/namespace/mutation/cordoning.go index 81e6780c..22995f41 100644 --- a/pkg/webhook/namespace/mutation/cordoning.go +++ b/internal/webhook/namespace/mutation/cordoning.go @@ -16,49 +16,52 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/meta" capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type cordoningLabelHandler struct { cfg configuration.Configuration } -func CordoningLabelHandler(cfg configuration.Configuration) capsulewebhook.Handler { +func CordoningLabelHandler(cfg configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { return &cordoningLabelHandler{ cfg: cfg, } } -func (h *cordoningLabelHandler) OnCreate(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *cordoningLabelHandler) OnCreate(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (h *cordoningLabelHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *cordoningLabelHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (h *cordoningLabelHandler) OnUpdate(c client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { +func (h *cordoningLabelHandler) OnUpdate( + c client.Client, + ns *corev1.Namespace, + old *corev1.Namespace, + decoder admission.Decoder, + _ record.EventRecorder, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - response := h.syncNamespaceCordonLabel(ctx, c, req, ns) - - return response + return h.handle(ctx, c, req, ns) } } -func (h *cordoningLabelHandler) syncNamespaceCordonLabel(ctx context.Context, c client.Client, req admission.Request, ns *corev1.Namespace) *admission.Response { +func (h *cordoningLabelHandler) handle( + ctx context.Context, + c client.Client, + req admission.Request, + ns *corev1.Namespace, +) *admission.Response { tnt := &capsulev1beta2.Tenant{} ln, err := capsuleutils.GetTypeLabel(tnt) diff --git a/pkg/webhook/namespace/mutation/metadata.go b/internal/webhook/namespace/mutation/metadata.go similarity index 69% rename from pkg/webhook/namespace/mutation/metadata.go rename to internal/webhook/namespace/mutation/metadata.go index 0dce73e1..7e2591d1 100644 --- a/pkg/webhook/namespace/mutation/metadata.go +++ b/internal/webhook/namespace/mutation/metadata.go @@ -14,30 +14,24 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" ) type metadataHandler struct { cfg configuration.Configuration } -func MetadataHandler(cfg configuration.Configuration) capsulewebhook.Handler { +func MetadataHandler(cfg configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { return &metadataHandler{ cfg: cfg, } } -func (h *metadataHandler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (h *metadataHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return &response - } - - tenant, errResponse := getNamespaceTenant(ctx, client, ns, req, h.cfg, recorder) + tenant, errResponse := utils.GetNamespaceTenant(ctx, client, ns, req, h.cfg, recorder) if errResponse != nil { return errResponse } @@ -85,14 +79,14 @@ func (h *metadataHandler) OnCreate(client client.Client, decoder admission.Decod } } -func (h *metadataHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *metadataHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (h *metadataHandler) OnUpdate(_ client.Client, _ admission.Decoder, _ record.EventRecorder) capsulewebhook.Func { - return func(_ context.Context, _ admission.Request) *admission.Response { +func (h *metadataHandler) OnUpdate(client.Client, *corev1.Namespace, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { return nil } } diff --git a/internal/webhook/namespace/mutation/ownerreference.go b/internal/webhook/namespace/mutation/ownerreference.go new file mode 100644 index 00000000..f8a8ffc7 --- /dev/null +++ b/internal/webhook/namespace/mutation/ownerreference.go @@ -0,0 +1,145 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package mutation + +import ( + "context" + "encoding/json" + "net/http" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" +) + +type ownerReferenceHandler struct { + cfg configuration.Configuration +} + +func OwnerReferenceHandler(cfg configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { + return &ownerReferenceHandler{ + cfg: cfg, + } +} + +func (h *ownerReferenceHandler) OnCreate(c client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, errResponse := utils.GetNamespaceTenant(ctx, c, ns, req, h.cfg, recorder) + if errResponse != nil { + return errResponse + } + + if tnt == nil { + response := admission.Denied("Unable to assign namespace to tenant. Please use " + meta.TenantLabel + " label when creating a namespace") + + return &response + } + + if err := tenant.AddTenantLabelForNamespace(ns, tnt); err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return &response + } + + response := patchResponseForOwnerRef(c, tnt.DeepCopy(), ns, recorder) + + return &response + } +} + +func (h *ownerReferenceHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (h *ownerReferenceHandler) OnUpdate(c client.Client, newNs *corev1.Namespace, oldNs *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, oldNs.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + o, err := json.Marshal(newNs.DeepCopy()) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + var refs []metav1.OwnerReference + + for _, ref := range oldNs.OwnerReferences { + if tenant.IsTenantOwnerReferenceForTenant(ref, tnt) { + refs = append(refs, ref) + } + } + + for _, ref := range newNs.OwnerReferences { + if !tenant.IsTenantOwnerReference(ref) { + refs = append(refs, ref) + } + } + + newNs.OwnerReferences = refs + + if err := tenant.AddTenantLabelForNamespace(newNs, tnt); err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return &response + } + + obj, err := json.Marshal(newNs) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + response := admission.PatchResponseFromRaw(o, obj) + + return &response + } +} + +func patchResponseForOwnerRef( + c client.Client, + tenant *capsulev1beta2.Tenant, + ns *corev1.Namespace, + recorder record.EventRecorder, +) admission.Response { + o, err := json.Marshal(ns.DeepCopy()) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + if err = controllerutil.SetOwnerReference(tenant, ns, c.Scheme()); err != nil { + recorder.Eventf(tenant, corev1.EventTypeWarning, "Error", "Namespace %s cannot be assigned to the desired Tenant", ns.GetName()) + + return admission.Errored(http.StatusInternalServerError, err) + } + + recorder.Eventf(tenant, corev1.EventTypeNormal, "NamespaceCreationWebhook", "Namespace %s has been assigned to the desired Tenant", ns.GetName()) + + obj, err := json.Marshal(ns) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + return admission.PatchResponseFromRaw(o, obj) +} diff --git a/pkg/webhook/namespace/validation/errors.go b/internal/webhook/namespace/validation/errors.go similarity index 100% rename from pkg/webhook/namespace/validation/errors.go rename to internal/webhook/namespace/validation/errors.go diff --git a/internal/webhook/namespace/validation/freezed.go b/internal/webhook/namespace/validation/freezed.go new file mode 100644 index 00000000..fe3498c7 --- /dev/null +++ b/internal/webhook/namespace/validation/freezed.go @@ -0,0 +1,112 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +type freezedHandler struct { + cfg configuration.Configuration +} + +func FreezeHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { + return &freezedHandler{cfg: configuration} +} + +func (r *freezedHandler) OnCreate( + c client.Client, + ns *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if tnt.Spec.Cordoned { + recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName()) + + response := admission.Denied("the selected Tenant is freezed") + + return &response + } + + return nil + } +} + +func (r *freezedHandler) OnDelete( + c client.Client, + ns *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, r.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name) + + response := admission.Denied("the selected Tenant is freezed") + + return &response + } + + return nil + } +} + +func (r *freezedHandler) OnUpdate( + c client.Client, + ns *corev1.Namespace, + old *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, r.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName()) + + response := admission.Denied("the selected Tenant is freezed") + + return &response + } + + return nil + } +} diff --git a/pkg/webhook/namespace/validation/patch.go b/internal/webhook/namespace/validation/patch.go similarity index 51% rename from pkg/webhook/namespace/validation/patch.go rename to internal/webhook/namespace/validation/patch.go index 5531ea39..18dfe741 100644 --- a/pkg/webhook/namespace/validation/patch.go +++ b/internal/webhook/namespace/validation/patch.go @@ -15,61 +15,52 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/configuration" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/users" ) type patchHandler struct { - configuration configuration.Configuration + cfg configuration.Configuration } -func PatchHandler(configuration configuration.Configuration) capsulewebhook.Handler { - return &patchHandler{configuration: configuration} +func PatchHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { + return &patchHandler{cfg: configuration} } -func (r *patchHandler) OnCreate(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (r *patchHandler) OnCreate(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *patchHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (r *patchHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *patchHandler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (r *patchHandler) OnUpdate( + c client.Client, + ns *corev1.Namespace, + old *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - // Decode Namespace - ns := &corev1.Namespace{} - if err := decoder.DecodeRaw(req.OldObject, ns); err != nil { - return utils.ErroredResponse(err) - } - - // Get Tenant Label - ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return &response - } - - // Extract Tenant from namespace e := fmt.Sprintf("namespace/%s can not be patched", ns.Name) - if label, ok := ns.Labels[ln]; ok { + if label, ok := ns.Labels[meta.TenantLabel]; ok { // retrieving the selected Tenant tnt := &capsulev1beta2.Tenant{} - if err = c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { + if err := c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { response := admission.Errored(http.StatusBadRequest, err) return &response } - ok, err := utils.IsTenantOwner(ctx, c, tnt, req.UserInfo, r.configuration.AllowServiceAccountPromotion()) + ok, err := users.IsTenantOwner(ctx, c, r.cfg, tnt, req.UserInfo) if err != nil { response := admission.Errored(http.StatusBadRequest, err) diff --git a/internal/webhook/namespace/validation/prefix.go b/internal/webhook/namespace/validation/prefix.go new file mode 100644 index 00000000..daed58ca --- /dev/null +++ b/internal/webhook/namespace/validation/prefix.go @@ -0,0 +1,84 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "context" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" +) + +type prefixHandler struct { + cfg configuration.Configuration +} + +func PrefixHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { + return &prefixHandler{ + cfg: configuration, + } +} + +func (r *prefixHandler) OnCreate( + clt client.Client, + ns *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, clt, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if exp, _ := r.cfg.ProtectedNamespaceRegexp(); exp != nil { + if matched := exp.MatchString(ns.GetName()); matched { + response := admission.Denied(fmt.Sprintf("Creating namespaces with name matching %s regexp is not allowed; please, reach out to the system administrators", exp.String())) + + return &response + } + } + + if r.cfg.ForceTenantPrefix() { + if tnt.Spec.ForceTenantPrefix != nil && !*tnt.Spec.ForceTenantPrefix { + return nil + } + + if e := fmt.Sprintf("%s-%s", tnt.GetName(), ns.GetName()); !strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) { + recorder.Eventf(tnt, corev1.EventTypeWarning, "InvalidTenantPrefix", "Namespace %s does not match the expected prefix for the current Tenant", ns.GetName()) + + response := admission.Denied(fmt.Sprintf("The namespace doesn't match the tenant prefix, expected %s", e)) + + return &response + } + } + + return nil + } +} + +func (r *prefixHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (r *prefixHandler) OnUpdate(client.Client, *corev1.Namespace, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} diff --git a/internal/webhook/namespace/validation/quota.go b/internal/webhook/namespace/validation/quota.go new file mode 100644 index 00000000..fbbd8924 --- /dev/null +++ b/internal/webhook/namespace/validation/quota.go @@ -0,0 +1,71 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" +) + +type quotaHandler struct{} + +func QuotaHandler() capsulewebhook.TypedHandler[*corev1.Namespace] { + return "aHandler{} +} + +func (r *quotaHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return r.handle(client, ns, recorder, ctx, req) + } +} + +func (r *quotaHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} + +func (r *quotaHandler) OnUpdate(client client.Client, ns *corev1.Namespace, _ *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return r.handle(client, ns, recorder, ctx, req) + } +} + +func (r *quotaHandler) handle(c client.Client, ns *corev1.Namespace, recorder record.EventRecorder, ctx context.Context, req admission.Request) *admission.Response { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if tnt.IsFull() { + // Checking if the Namespace already exists. + // If this is the case, no need to return the quota exceeded error: + // the Kubernetes API Server will return an AlreadyExists error, + // adhering more to the native Kubernetes experience. + if err := c.Get(ctx, types.NamespacedName{Name: ns.Name}, &corev1.Namespace{}); err == nil { + return nil + } + + recorder.Eventf(tnt, corev1.EventTypeWarning, "NamespaceQuotaExceded", "Namespace %s cannot be attached, quota exceeded for the current Tenant", ns.GetName()) + + response := admission.Denied(NewNamespaceQuotaExceededError().Error()) + + return &response + } + + return nil +} diff --git a/pkg/webhook/namespace/validation/user_metadata.go b/internal/webhook/namespace/validation/user_metadata.go similarity index 70% rename from pkg/webhook/namespace/validation/user_metadata.go rename to internal/webhook/namespace/validation/user_metadata.go index 74c64f91..331d44ea 100644 --- a/pkg/webhook/namespace/validation/user_metadata.go +++ b/internal/webhook/namespace/validation/user_metadata.go @@ -8,42 +8,36 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type userMetadataHandler struct{} -func UserMetadataHandler() capsulewebhook.Handler { +func UserMetadataHandler() capsulewebhook.TypedHandler[*corev1.Namespace] { return &userMetadataHandler{} } -func (r *userMetadataHandler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (r *userMetadataHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { ns := &corev1.Namespace{} if err := decoder.Decode(req, ns); err != nil { return utils.ErroredResponse(err) } - tnt := &capsulev1beta2.Tenant{} + tnt, err := tenant.GetTenantByOwnerreferences(ctx, client, ns.OwnerReferences) + if err != nil { + return utils.ErroredResponse(err) + } - for _, objectRef := range ns.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(objectRef) { - continue - } - - // retrieving the selected Tenant - if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { - return utils.ErroredResponse(err) - } + if tnt == nil { + return nil } if tnt.Spec.NamespaceOptions != nil { @@ -70,35 +64,21 @@ func (r *userMetadataHandler) OnCreate(client client.Client, decoder admission.D } } -func (r *userMetadataHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} - -func (r *userMetadataHandler) OnUpdate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (r *userMetadataHandler) OnUpdate( + client client.Client, + newNs *corev1.Namespace, + oldNs *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - oldNs := &corev1.Namespace{} - if err := decoder.DecodeRaw(req.OldObject, oldNs); err != nil { + tnt, err := tenant.GetTenantByOwnerreferences(ctx, client, newNs.OwnerReferences) + if err != nil { return utils.ErroredResponse(err) } - newNs := &corev1.Namespace{} - if err := decoder.Decode(req, newNs); err != nil { - return utils.ErroredResponse(err) - } - - tnt := &capsulev1beta2.Tenant{} - - for _, objectRef := range newNs.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(objectRef) { - continue - } - - // retrieving the selected Tenant - if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { - return utils.ErroredResponse(err) - } + if tnt == nil { + return nil } if len(tnt.Spec.NodeSelector) > 0 { @@ -183,3 +163,9 @@ func (r *userMetadataHandler) OnUpdate(client client.Client, decoder admission.D return nil } } + +func (r *userMetadataHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { + return func(context.Context, admission.Request) *admission.Response { + return nil + } +} diff --git a/pkg/webhook/networkpolicy/validating.go b/internal/webhook/networkpolicy/validating.go similarity index 94% rename from pkg/webhook/networkpolicy/validating.go rename to internal/webhook/networkpolicy/validating.go index ffac96ca..fa8293da 100644 --- a/pkg/webhook/networkpolicy/validating.go +++ b/internal/webhook/networkpolicy/validating.go @@ -12,9 +12,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type handler struct{} diff --git a/pkg/webhook/node/errors.go b/internal/webhook/node/errors.go similarity index 100% rename from pkg/webhook/node/errors.go rename to internal/webhook/node/errors.go diff --git a/pkg/webhook/node/user_metadata.go b/internal/webhook/node/user_metadata.go similarity index 96% rename from pkg/webhook/node/user_metadata.go rename to internal/webhook/node/user_metadata.go index 8c787684..0493c42e 100644 --- a/pkg/webhook/node/user_metadata.go +++ b/internal/webhook/node/user_metadata.go @@ -13,9 +13,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type userMetadataHandler struct { diff --git a/pkg/webhook/pod/containerregistry.go b/internal/webhook/pod/containerregistry.go similarity index 78% rename from pkg/webhook/pod/containerregistry.go rename to internal/webhook/pod/containerregistry.go index e84862ee..1c5cb1a9 100644 --- a/pkg/webhook/pod/containerregistry.go +++ b/internal/webhook/pod/containerregistry.go @@ -7,15 +7,15 @@ import ( "context" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type containerRegistryHandler struct { @@ -59,19 +59,15 @@ func (h *containerRegistryHandler) validate( return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), - }); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, c, pod.GetNamespace()) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - tnt := tntList.Items[0] - if tnt.Spec.ContainerRegistries == nil { return nil } @@ -101,14 +97,14 @@ func (h *containerRegistryHandler) verifyContainerRegistry( recorder record.EventRecorder, req admission.Request, image string, - tnt capsulev1beta2.Tenant, + tnt *capsulev1beta2.Tenant, ) *admission.Response { var valid, matched bool reg := NewRegistry(image, h.configuration) if len(reg.Registry()) == 0 { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) + recorder.Eventf(tnt, corev1.EventTypeWarning, "MissingFQCI", "Pod %s/%s is not using a fully qualified container image, cannot enforce registry the current Tenant", req.Namespace, req.Name, reg.Registry()) response := admission.Denied(NewContainerRegistryForbidden(image, *tnt.Spec.ContainerRegistries).Error()) @@ -120,7 +116,7 @@ func (h *containerRegistryHandler) verifyContainerRegistry( matched = tnt.Spec.ContainerRegistries.RegexMatch(reg.Registry()) if !valid && !matched { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry()) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenContainerRegistry", "Pod %s/%s is using a container hosted on registry %s that is forbidden for the current Tenant", req.Namespace, req.Name, reg.Registry()) response := admission.Denied(NewContainerRegistryForbidden(reg.FQCI(), *tnt.Spec.ContainerRegistries).Error()) diff --git a/pkg/webhook/pod/containerregistry_errors.go b/internal/webhook/pod/containerregistry_errors.go similarity index 100% rename from pkg/webhook/pod/containerregistry_errors.go rename to internal/webhook/pod/containerregistry_errors.go diff --git a/pkg/webhook/pod/containerregistry_registry.go b/internal/webhook/pod/containerregistry_registry.go similarity index 100% rename from pkg/webhook/pod/containerregistry_registry.go rename to internal/webhook/pod/containerregistry_registry.go diff --git a/pkg/webhook/pod/imagepullpolicy.go b/internal/webhook/pod/imagepullpolicy.go similarity index 80% rename from pkg/webhook/pod/imagepullpolicy.go rename to internal/webhook/pod/imagepullpolicy.go index 1eb773d7..bd9750f4 100644 --- a/pkg/webhook/pod/imagepullpolicy.go +++ b/internal/webhook/pod/imagepullpolicy.go @@ -7,14 +7,14 @@ import ( "context" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type imagePullPolicy struct{} @@ -53,20 +53,16 @@ func (h *imagePullPolicy) validate( return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", pod.Namespace), - }); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, c, pod.GetNamespace()) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - tnt := tntList.Items[0] - - policy := NewPullPolicy(&tnt) + policy := NewPullPolicy(tnt) if policy == nil { return nil } @@ -98,10 +94,10 @@ func (h *imagePullPolicy) verifyPullPolicy( policy PullPolicy, usedPullPolicy string, container string, - tnt capsulev1beta2.Tenant, + tnt *capsulev1beta2.Tenant, ) *admission.Response { if !policy.IsPolicySupported(usedPullPolicy) { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenPullPolicy", "Pod %s/%s pull policy %s is forbidden for the current Tenant", req.Namespace, req.Name, usedPullPolicy) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenPullPolicy", "Pod %s/%s pull policy %s is forbidden for the current Tenant", req.Namespace, req.Name, usedPullPolicy) response := admission.Denied(NewImagePullPolicyForbidden(usedPullPolicy, container, policy.AllowedPullPolicies()).Error()) diff --git a/pkg/webhook/pod/imagepullpolicy_errors.go b/internal/webhook/pod/imagepullpolicy_errors.go similarity index 100% rename from pkg/webhook/pod/imagepullpolicy_errors.go rename to internal/webhook/pod/imagepullpolicy_errors.go diff --git a/pkg/webhook/pod/imagepullpolicy_pullpolicy.go b/internal/webhook/pod/imagepullpolicy_pullpolicy.go similarity index 100% rename from pkg/webhook/pod/imagepullpolicy_pullpolicy.go rename to internal/webhook/pod/imagepullpolicy_pullpolicy.go diff --git a/pkg/webhook/pod/priorityclass.go b/internal/webhook/pod/priorityclass.go similarity index 91% rename from pkg/webhook/pod/priorityclass.go rename to internal/webhook/pod/priorityclass.go index 1bca1917..d3231af7 100644 --- a/pkg/webhook/pod/priorityclass.go +++ b/internal/webhook/pod/priorityclass.go @@ -12,8 +12,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type priorityClass struct{} @@ -29,7 +30,7 @@ func (h *priorityClass) OnCreate(c client.Client, decoder admission.Decoder, rec return utils.ErroredResponse(err) } - tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + tnt, err := tenant.TenantByStatusNamespace(ctx, c, pod.Namespace) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/pod/priorityclass_errors.go b/internal/webhook/pod/priorityclass_errors.go similarity index 92% rename from pkg/webhook/pod/priorityclass_errors.go rename to internal/webhook/pod/priorityclass_errors.go index cde702d9..5d682351 100644 --- a/pkg/webhook/pod/priorityclass_errors.go +++ b/internal/webhook/pod/priorityclass_errors.go @@ -6,8 +6,8 @@ package pod import ( "fmt" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type podPriorityClassForbiddenError struct { diff --git a/pkg/webhook/pod/runtimeclass.go b/internal/webhook/pod/runtimeclass.go similarity index 91% rename from pkg/webhook/pod/runtimeclass.go rename to internal/webhook/pod/runtimeclass.go index 70eb995f..333583d2 100644 --- a/pkg/webhook/pod/runtimeclass.go +++ b/internal/webhook/pod/runtimeclass.go @@ -14,8 +14,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type runtimeClass struct{} @@ -61,7 +62,7 @@ func (h *runtimeClass) validate(ctx context.Context, c client.Client, decoder ad return utils.ErroredResponse(err) } - tnt, err := utils.TenantByStatusNamespace(ctx, c, pod.Namespace) + tnt, err := tenant.TenantByStatusNamespace(ctx, c, pod.Namespace) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/pod/runtimeclass_errors.go b/internal/webhook/pod/runtimeclass_errors.go similarity index 92% rename from pkg/webhook/pod/runtimeclass_errors.go rename to internal/webhook/pod/runtimeclass_errors.go index cf1c7571..55e4d997 100644 --- a/pkg/webhook/pod/runtimeclass_errors.go +++ b/internal/webhook/pod/runtimeclass_errors.go @@ -6,8 +6,8 @@ package pod import ( "fmt" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type podRuntimeClassForbiddenError struct { diff --git a/pkg/webhook/pvc/errors.go b/internal/webhook/pvc/errors.go similarity index 97% rename from pkg/webhook/pvc/errors.go rename to internal/webhook/pvc/errors.go index 69575c89..c1165634 100644 --- a/pkg/webhook/pvc/errors.go +++ b/internal/webhook/pvc/errors.go @@ -6,8 +6,8 @@ package pvc import ( "fmt" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/webhook/utils" ) type storageClassNotValidError struct { diff --git a/pkg/webhook/pvc/pv.go b/internal/webhook/pvc/pv.go similarity index 90% rename from pkg/webhook/pvc/pv.go rename to internal/webhook/pvc/pv.go index 4415b2c0..ee171629 100644 --- a/pkg/webhook/pvc/pv.go +++ b/internal/webhook/pvc/pv.go @@ -15,8 +15,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type PV struct { @@ -41,7 +42,7 @@ func (p PV) OnCreate(client client.Client, decoder admission.Decoder, _ record.E return utils.ErroredResponse(err) } - tnt, err := utils.TenantByStatusNamespace(ctx, client, pvc.GetNamespace()) + tnt, err := tenant.TenantByStatusNamespace(ctx, client, pvc.GetNamespace()) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/pvc/validating.go b/internal/webhook/pvc/validating.go similarity index 91% rename from pkg/webhook/pvc/validating.go rename to internal/webhook/pvc/validating.go index 98e7993b..41039021 100644 --- a/pkg/webhook/pvc/validating.go +++ b/internal/webhook/pvc/validating.go @@ -13,8 +13,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type validating struct{} @@ -30,7 +31,7 @@ func (h *validating) OnCreate(c client.Client, decoder admission.Decoder, record return utils.ErroredResponse(err) } - tnt, err := utils.TenantByStatusNamespace(ctx, c, pvc.Namespace) + tnt, err := tenant.TenantByStatusNamespace(ctx, c, pvc.Namespace) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/resourcepool/claim_mutating.go b/internal/webhook/resourcepool/claim_mutating.go similarity index 95% rename from pkg/webhook/resourcepool/claim_mutating.go rename to internal/webhook/resourcepool/claim_mutating.go index 8b4d3049..81bc28cb 100644 --- a/pkg/webhook/resourcepool/claim_mutating.go +++ b/internal/webhook/resourcepool/claim_mutating.go @@ -16,9 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/meta" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" ) type claimMutationHandler struct { diff --git a/pkg/webhook/resourcepool/claim_validating.go b/internal/webhook/resourcepool/claim_validating.go similarity index 95% rename from pkg/webhook/resourcepool/claim_validating.go rename to internal/webhook/resourcepool/claim_validating.go index 252f4a36..bd42a51f 100644 --- a/pkg/webhook/resourcepool/claim_validating.go +++ b/internal/webhook/resourcepool/claim_validating.go @@ -14,8 +14,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type claimValidationHandler struct { diff --git a/pkg/webhook/resourcepool/pool_mutating.go b/internal/webhook/resourcepool/pool_mutating.go similarity index 95% rename from pkg/webhook/resourcepool/pool_mutating.go rename to internal/webhook/resourcepool/pool_mutating.go index df061fb3..f5d9e1f3 100644 --- a/pkg/webhook/resourcepool/pool_mutating.go +++ b/internal/webhook/resourcepool/pool_mutating.go @@ -17,8 +17,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type poolMutationHandler struct { diff --git a/pkg/webhook/resourcepool/pool_validation.go b/internal/webhook/resourcepool/pool_validation.go similarity index 95% rename from pkg/webhook/resourcepool/pool_validation.go rename to internal/webhook/resourcepool/pool_validation.go index 2135fb92..2c07570a 100644 --- a/pkg/webhook/resourcepool/pool_validation.go +++ b/internal/webhook/resourcepool/pool_validation.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type poolValidationHandler struct { diff --git a/pkg/webhook/route/cordoning.go b/internal/webhook/route/cordoning.go similarity index 86% rename from pkg/webhook/route/cordoning.go rename to internal/webhook/route/cordoning.go index 13774f45..4510b89d 100644 --- a/pkg/webhook/route/cordoning.go +++ b/internal/webhook/route/cordoning.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type cordoning struct { diff --git a/pkg/webhook/route/customresources.go b/internal/webhook/route/customresources.go similarity index 87% rename from pkg/webhook/route/customresources.go rename to internal/webhook/route/customresources.go index 2d7bce1d..a67485d5 100644 --- a/pkg/webhook/route/customresources.go +++ b/internal/webhook/route/customresources.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type customResourcesHandler struct { diff --git a/pkg/webhook/route/defaults.go b/internal/webhook/route/defaults.go similarity index 86% rename from pkg/webhook/route/defaults.go rename to internal/webhook/route/defaults.go index 54489e3a..b716840f 100644 --- a/pkg/webhook/route/defaults.go +++ b/internal/webhook/route/defaults.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type defaults struct { diff --git a/pkg/webhook/route/gateway.go b/internal/webhook/route/gateway.go similarity index 86% rename from pkg/webhook/route/gateway.go rename to internal/webhook/route/gateway.go index 66c3f6b2..efa6f2cd 100644 --- a/pkg/webhook/route/gateway.go +++ b/internal/webhook/route/gateway.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type gateway struct { diff --git a/pkg/webhook/route/ingresses.go b/internal/webhook/route/ingresses.go similarity index 86% rename from pkg/webhook/route/ingresses.go rename to internal/webhook/route/ingresses.go index aaf234a8..484ea096 100644 --- a/pkg/webhook/route/ingresses.go +++ b/internal/webhook/route/ingresses.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type ingress struct { diff --git a/pkg/webhook/route/namespaces.go b/internal/webhook/route/namespaces.go similarity index 72% rename from pkg/webhook/route/namespaces.go rename to internal/webhook/route/namespaces.go index 6ee7b9ed..bfc1cc8d 100644 --- a/pkg/webhook/route/namespaces.go +++ b/internal/webhook/route/namespaces.go @@ -4,14 +4,14 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type namespace struct { handlers []capsulewebhook.Handler } -func Namespace(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { +func NamespaceValidation(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { return &namespace{handlers: handler} } @@ -27,7 +27,7 @@ type namespacePatch struct { handlers []capsulewebhook.Handler } -func NamespacePatch(handlers ...capsulewebhook.Handler) capsulewebhook.Webhook { +func NamespaceMutation(handlers ...capsulewebhook.Handler) capsulewebhook.Webhook { return &namespacePatch{handlers: handlers} } diff --git a/pkg/webhook/route/networkpolicies.go b/internal/webhook/route/networkpolicies.go similarity index 87% rename from pkg/webhook/route/networkpolicies.go rename to internal/webhook/route/networkpolicies.go index 27c78a48..ec830d4e 100644 --- a/pkg/webhook/route/networkpolicies.go +++ b/internal/webhook/route/networkpolicies.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type networkPolicy struct { diff --git a/pkg/webhook/route/node.go b/internal/webhook/route/node.go similarity index 85% rename from pkg/webhook/route/node.go rename to internal/webhook/route/node.go index c50445dc..34237dbe 100644 --- a/pkg/webhook/route/node.go +++ b/internal/webhook/route/node.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type node struct { diff --git a/pkg/webhook/route/ownerreference.go b/internal/webhook/route/ownerreference.go similarity index 86% rename from pkg/webhook/route/ownerreference.go rename to internal/webhook/route/ownerreference.go index b2c68022..bfdef7cd 100644 --- a/pkg/webhook/route/ownerreference.go +++ b/internal/webhook/route/ownerreference.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type webhook struct { diff --git a/pkg/webhook/route/pods.go b/internal/webhook/route/pods.go similarity index 85% rename from pkg/webhook/route/pods.go rename to internal/webhook/route/pods.go index 9ed8e91f..051f34df 100644 --- a/pkg/webhook/route/pods.go +++ b/internal/webhook/route/pods.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type pod struct { diff --git a/pkg/webhook/route/pvc.go b/internal/webhook/route/pvc.go similarity index 85% rename from pkg/webhook/route/pvc.go rename to internal/webhook/route/pvc.go index 142bea23..253cae93 100644 --- a/pkg/webhook/route/pvc.go +++ b/internal/webhook/route/pvc.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type pvc struct { diff --git a/pkg/webhook/route/resourcepool.go b/internal/webhook/route/resourcepool.go similarity index 95% rename from pkg/webhook/route/resourcepool.go rename to internal/webhook/route/resourcepool.go index cfbe0410..d387c567 100644 --- a/pkg/webhook/route/resourcepool.go +++ b/internal/webhook/route/resourcepool.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type poolmutation struct { diff --git a/pkg/webhook/route/serviceaccounts.go b/internal/webhook/route/serviceaccounts.go similarity index 87% rename from pkg/webhook/route/serviceaccounts.go rename to internal/webhook/route/serviceaccounts.go index bb8c30ca..2ac4d668 100644 --- a/pkg/webhook/route/serviceaccounts.go +++ b/internal/webhook/route/serviceaccounts.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type serviceaccounts struct { diff --git a/pkg/webhook/route/services.go b/internal/webhook/route/services.go similarity index 86% rename from pkg/webhook/route/services.go rename to internal/webhook/route/services.go index a93b02b5..f731602d 100644 --- a/pkg/webhook/route/services.go +++ b/internal/webhook/route/services.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type service struct { diff --git a/pkg/webhook/route/tenantresource_objs.go b/internal/webhook/route/tenantresource_objs.go similarity index 87% rename from pkg/webhook/route/tenantresource_objs.go rename to internal/webhook/route/tenantresource_objs.go index c745dbf0..12f82d51 100644 --- a/pkg/webhook/route/tenantresource_objs.go +++ b/internal/webhook/route/tenantresource_objs.go @@ -4,7 +4,7 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type tntResourceObjs struct { diff --git a/pkg/webhook/route/tenants.go b/internal/webhook/route/tenants.go similarity index 78% rename from pkg/webhook/route/tenants.go rename to internal/webhook/route/tenants.go index 3fe98525..00e34030 100644 --- a/pkg/webhook/route/tenants.go +++ b/internal/webhook/route/tenants.go @@ -4,14 +4,14 @@ package route import ( - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" ) type tenantValidating struct { handlers []capsulewebhook.Handler } -func TenantValidating(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { +func TenantValidation(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { return &tenantValidating{handlers: handler} } @@ -27,7 +27,7 @@ type tenantMutating struct { handlers []capsulewebhook.Handler } -func TenantMutating(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { +func TenantMutation(handler ...capsulewebhook.Handler) capsulewebhook.Webhook { return &tenantMutating{handlers: handler} } diff --git a/pkg/webhook/router.go b/internal/webhook/router.go similarity index 100% rename from pkg/webhook/router.go rename to internal/webhook/router.go diff --git a/pkg/webhook/service/errors.go b/internal/webhook/service/errors.go similarity index 100% rename from pkg/webhook/service/errors.go rename to internal/webhook/service/errors.go diff --git a/pkg/webhook/service/validating.go b/internal/webhook/service/validating.go similarity index 73% rename from pkg/webhook/service/validating.go rename to internal/webhook/service/validating.go index c99ed21f..974b7b12 100644 --- a/pkg/webhook/service/validating.go +++ b/internal/webhook/service/validating.go @@ -10,15 +10,14 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type handler struct{} @@ -51,21 +50,17 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", svc.GetNamespace()), - }); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, clt, svc.GetNamespace()) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } - tnt := tntList.Items[0] - if svc.Spec.Type == corev1.ServiceTypeNodePort && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.NodePort { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenNodePort", "Service %s/%s cannot be type of NodePort for the current Tenant", req.Namespace, req.Name) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenNodePort", "Service %s/%s cannot be type of NodePort for the current Tenant", req.Namespace, req.Name) response := admission.Denied(NewNodePortDisabledError().Error()) @@ -73,7 +68,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder } if svc.Spec.Type == corev1.ServiceTypeExternalName && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.ExternalName { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenExternalName", "Service %s/%s cannot be type of ExternalName for the current Tenant", req.Namespace, req.Name) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenExternalName", "Service %s/%s cannot be type of ExternalName for the current Tenant", req.Namespace, req.Name) response := admission.Denied(NewExternalNameDisabledError().Error()) @@ -81,7 +76,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder } if svc.Spec.Type == corev1.ServiceTypeLoadBalancer && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.LoadBalancer { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenLoadBalancer", "Service %s/%s cannot be type of LoadBalancer for the current Tenant", req.Namespace, req.Name) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenLoadBalancer", "Service %s/%s cannot be type of LoadBalancer for the current Tenant", req.Namespace, req.Name) response := admission.Denied(NewLoadBalancerDisabled().Error()) @@ -92,7 +87,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder err := api.ValidateForbidden(svc.Annotations, tnt.Spec.ServiceOptions.ForbiddenAnnotations) if err != nil { err = errors.Wrap(err, "service annotations validation failed") - recorder.Eventf(&tnt, corev1.EventTypeWarning, api.ForbiddenAnnotationReason, err.Error()) + recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenAnnotationReason, err.Error()) response := admission.Denied(err.Error()) return &response @@ -101,7 +96,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder err = api.ValidateForbidden(svc.Labels, tnt.Spec.ServiceOptions.ForbiddenLabels) if err != nil { err = errors.Wrap(err, "service labels validation failed") - recorder.Eventf(&tnt, corev1.EventTypeWarning, api.ForbiddenLabelReason, err.Error()) + recorder.Eventf(tnt, corev1.EventTypeWarning, api.ForbiddenLabelReason, err.Error()) response := admission.Denied(err.Error()) return &response @@ -132,7 +127,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder ip := net.ParseIP(externalIP) if !ipInCIDR(ip) { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenExternalServiceIP", "Service %s/%s external IP %s is forbidden for the current Tenant", req.Namespace, req.Name, ip.String()) + recorder.Eventf(tnt, corev1.EventTypeWarning, "ForbiddenExternalServiceIP", "Service %s/%s external IP %s is forbidden for the current Tenant", req.Namespace, req.Name, ip.String()) response := admission.Denied(NewExternalServiceIPForbidden(tnt.Spec.ServiceOptions.ExternalServiceIPs.Allowed).Error()) diff --git a/pkg/webhook/serviceaccounts/validating.go b/internal/webhook/serviceaccounts/validating.go similarity index 78% rename from pkg/webhook/serviceaccounts/validating.go rename to internal/webhook/serviceaccounts/validating.go index 43e77e37..2cf50571 100644 --- a/pkg/webhook/serviceaccounts/validating.go +++ b/internal/webhook/serviceaccounts/validating.go @@ -7,16 +7,16 @@ import ( "context" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/meta" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" + "github.com/projectcapsule/capsule/pkg/utils/users" ) type handler struct { @@ -51,14 +51,12 @@ func (r *handler) handle(ctx context.Context, clt client.Client, decoder admissi return utils.ErroredResponse(err) } - tntList := &capsulev1beta2.TenantList{} - if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", sa.GetNamespace()), - }); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, clt, sa.GetNamespace()) + if err != nil { return utils.ErroredResponse(err) } - if len(tntList.Items) == 0 { + if tnt == nil { return nil } @@ -76,7 +74,7 @@ func (r *handler) handle(ctx context.Context, clt client.Client, decoder admissi } // We don't want to allow promoted serviceaccounts to promote other serviceaccounts - allowed, err := utils.IsTenantOwner(ctx, clt, &tntList.Items[0], req.UserInfo, false) + allowed, err := users.IsTenantOwner(ctx, clt, r.cfg, tnt, req.UserInfo) if err != nil { return utils.ErroredResponse(err) } diff --git a/pkg/webhook/tenant/mutation/metadata.go b/internal/webhook/tenant/mutation/metadata.go similarity index 85% rename from pkg/webhook/tenant/mutation/metadata.go rename to internal/webhook/tenant/mutation/metadata.go index f098252d..c7062a06 100644 --- a/pkg/webhook/tenant/mutation/metadata.go +++ b/internal/webhook/tenant/mutation/metadata.go @@ -13,9 +13,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsuleapi "github.com/projectcapsule/capsule/pkg/api" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" ) type metaHandler struct{} @@ -49,7 +49,7 @@ func (h *metaHandler) handle(decoder admission.Decoder, req admission.Request) * } labels := tenant.GetLabels() - if val, ok := labels[capsuleapi.TenantNameLabel]; ok && val == tenant.Name { + if val, ok := labels[meta.TenantNameLabel]; ok && val == tenant.Name { return nil } @@ -57,7 +57,7 @@ func (h *metaHandler) handle(decoder admission.Decoder, req admission.Request) * labels = make(map[string]string) } - labels[capsuleapi.TenantNameLabel] = tenant.Name + labels[meta.TenantNameLabel] = tenant.Name tenant.SetLabels(labels) marshaled, err := json.Marshal(tenant) diff --git a/pkg/webhook/tenant/validation/containerregistry_regex.go b/internal/webhook/tenant/validation/containerregistry_regex.go similarity index 93% rename from pkg/webhook/tenant/validation/containerregistry_regex.go rename to internal/webhook/tenant/validation/containerregistry_regex.go index a89d421f..dfb67c28 100644 --- a/pkg/webhook/tenant/validation/containerregistry_regex.go +++ b/internal/webhook/tenant/validation/containerregistry_regex.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type containerRegistryRegexHandler struct{} diff --git a/internal/webhook/tenant/validation/cordoning.go b/internal/webhook/tenant/validation/cordoning.go new file mode 100644 index 00000000..a9b47de3 --- /dev/null +++ b/internal/webhook/tenant/validation/cordoning.go @@ -0,0 +1,70 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "context" + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +type cordoningHandler struct { + configuration configuration.Configuration +} + +func CordoningHandler(configuration configuration.Configuration) capsulewebhook.Handler { + return &cordoningHandler{ + configuration: configuration, + } +} + +func (h *cordoningHandler) OnCreate(c client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.cordonHandler(ctx, c, req, recorder) + } +} + +func (h *cordoningHandler) OnDelete(c client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.cordonHandler(ctx, c, req, recorder) + } +} + +func (h *cordoningHandler) OnUpdate(c client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + return h.cordonHandler(ctx, c, req, recorder) + } +} + +func (h *cordoningHandler) cordonHandler(ctx context.Context, c client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response { + tnt, err := tenant.TenantByStatusNamespace(ctx, c, req.Namespace) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, h.configuration, req.UserInfo.Username, req.UserInfo.Groups) { + recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) + + response := admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName())) + + return &response + } + + return nil +} diff --git a/pkg/webhook/tenant/validation/custom_resource_quota.go b/internal/webhook/tenant/validation/custom_resource_quota.go similarity index 89% rename from pkg/webhook/tenant/validation/custom_resource_quota.go rename to internal/webhook/tenant/validation/custom_resource_quota.go index b0eeb3d4..4224831d 100644 --- a/pkg/webhook/tenant/validation/custom_resource_quota.go +++ b/internal/webhook/tenant/validation/custom_resource_quota.go @@ -9,7 +9,6 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" @@ -17,8 +16,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type resourceCounterHandler struct { @@ -134,17 +134,14 @@ func (r *resourceCounterHandler) OnUpdate(client.Client, admission.Decoder, reco } func (r *resourceCounterHandler) getTenantName(ctx context.Context, clt client.Client, req admission.Request) (string, error) { - tntList := &capsulev1beta2.TenantList{} - - if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace), - }); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, clt, req.Namespace) + if err != nil { return "", err } - if len(tntList.Items) == 0 { + if tnt == nil { return "", nil } - return tntList.Items[0].GetName(), nil + return tnt.GetName(), nil } diff --git a/pkg/webhook/tenant/validation/custom_resource_quota_errors.go b/internal/webhook/tenant/validation/custom_resource_quota_errors.go similarity index 100% rename from pkg/webhook/tenant/validation/custom_resource_quota_errors.go rename to internal/webhook/tenant/validation/custom_resource_quota_errors.go diff --git a/pkg/webhook/tenant/validation/forbidden_annotations_regex.go b/internal/webhook/tenant/validation/forbidden_annotations_regex.go similarity index 94% rename from pkg/webhook/tenant/validation/forbidden_annotations_regex.go rename to internal/webhook/tenant/validation/forbidden_annotations_regex.go index 8c2002b7..c1db9856 100644 --- a/pkg/webhook/tenant/validation/forbidden_annotations_regex.go +++ b/internal/webhook/tenant/validation/forbidden_annotations_regex.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type forbiddenAnnotationsRegexHandler struct{} diff --git a/pkg/webhook/tenant/validation/freezed_emitter.go b/internal/webhook/tenant/validation/freezed_emitter.go similarity index 93% rename from pkg/webhook/tenant/validation/freezed_emitter.go rename to internal/webhook/tenant/validation/freezed_emitter.go index dfe83a60..7534a0b7 100644 --- a/pkg/webhook/tenant/validation/freezed_emitter.go +++ b/internal/webhook/tenant/validation/freezed_emitter.go @@ -12,8 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type freezedEmitterHandler struct{} diff --git a/pkg/webhook/tenant/validation/hostname_regex.go b/internal/webhook/tenant/validation/hostname_regex.go similarity index 93% rename from pkg/webhook/tenant/validation/hostname_regex.go rename to internal/webhook/tenant/validation/hostname_regex.go index 1447cfee..e93a9a6e 100644 --- a/pkg/webhook/tenant/validation/hostname_regex.go +++ b/internal/webhook/tenant/validation/hostname_regex.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type hostnameRegexHandler struct{} diff --git a/pkg/webhook/tenant/validation/ingressclass_regex.go b/internal/webhook/tenant/validation/ingressclass_regex.go similarity index 93% rename from pkg/webhook/tenant/validation/ingressclass_regex.go rename to internal/webhook/tenant/validation/ingressclass_regex.go index ee1877f5..ed58de3c 100644 --- a/pkg/webhook/tenant/validation/ingressclass_regex.go +++ b/internal/webhook/tenant/validation/ingressclass_regex.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type ingressClassRegexHandler struct{} diff --git a/pkg/webhook/tenant/validation/name.go b/internal/webhook/tenant/validation/name.go similarity index 91% rename from pkg/webhook/tenant/validation/name.go rename to internal/webhook/tenant/validation/name.go index f7d60aed..6581fa5a 100644 --- a/pkg/webhook/tenant/validation/name.go +++ b/internal/webhook/tenant/validation/name.go @@ -12,8 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type nameHandler struct{} diff --git a/pkg/webhook/tenant/validation/protected.go b/internal/webhook/tenant/validation/protected.go similarity index 91% rename from pkg/webhook/tenant/validation/protected.go rename to internal/webhook/tenant/validation/protected.go index 0ce5ced5..246f69bc 100644 --- a/pkg/webhook/tenant/validation/protected.go +++ b/internal/webhook/tenant/validation/protected.go @@ -12,8 +12,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type protectedHandler struct{} diff --git a/pkg/webhook/tenant/validation/rolebindings_regex.go b/internal/webhook/tenant/validation/rolebindings_regex.go similarity index 93% rename from pkg/webhook/tenant/validation/rolebindings_regex.go rename to internal/webhook/tenant/validation/rolebindings_regex.go index fef51f50..297316b6 100644 --- a/pkg/webhook/tenant/validation/rolebindings_regex.go +++ b/internal/webhook/tenant/validation/rolebindings_regex.go @@ -15,8 +15,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type rbRegexHandler struct{} diff --git a/pkg/webhook/tenant/validation/serviceaccount_format.go b/internal/webhook/tenant/validation/serviceaccount_format.go similarity index 93% rename from pkg/webhook/tenant/validation/serviceaccount_format.go rename to internal/webhook/tenant/validation/serviceaccount_format.go index 2074c1be..dfb489ee 100644 --- a/pkg/webhook/tenant/validation/serviceaccount_format.go +++ b/internal/webhook/tenant/validation/serviceaccount_format.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type saNameHandler struct{} diff --git a/pkg/webhook/tenant/validation/storageclass_regex.go b/internal/webhook/tenant/validation/storageclass_regex.go similarity index 93% rename from pkg/webhook/tenant/validation/storageclass_regex.go rename to internal/webhook/tenant/validation/storageclass_regex.go index 4d0683f8..603fb052 100644 --- a/pkg/webhook/tenant/validation/storageclass_regex.go +++ b/internal/webhook/tenant/validation/storageclass_regex.go @@ -13,8 +13,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" ) type storageClassRegexHandler struct{} diff --git a/pkg/webhook/tenantresource/objects.go b/internal/webhook/tenantresource/objects.go similarity index 78% rename from pkg/webhook/tenantresource/objects.go rename to internal/webhook/tenantresource/objects.go index 1040b00f..c771a914 100644 --- a/pkg/webhook/tenantresource/objects.go +++ b/internal/webhook/tenantresource/objects.go @@ -15,9 +15,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/indexer/tenantresource" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type cordoningHandler struct{} @@ -45,16 +46,15 @@ func (h *cordoningHandler) OnUpdate(client client.Client, _ admission.Decoder, r } func (h *cordoningHandler) handler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response { - tntList := &capsulev1beta2.TenantList{} - - if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace)}); err != nil { + tnt, err := tenant.TenantByStatusNamespace(ctx, clt, req.Namespace) + if err != nil { return utils.ErroredResponse(err) } - // resource is not inside a Tenant namespace: - // we can avoid any kind of extra check. - if len(tntList.Items) == 0 { + + if tnt == nil { return nil } + // Checking if the object is managed by a TenantResource, local or global ors := capsulev1beta2.ObjectReferenceStatus{ ObjectReferenceAbstract: capsulev1beta2.ObjectReferenceAbstract{ @@ -76,9 +76,7 @@ func (h *cordoningHandler) handler(ctx context.Context, clt client.Client, req a } if len(local.Items) > 0 || len(global.Items) > 0 { - tnt := tntList.Items[0] - - recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantResourceWriteOp", "%s %s/%s cannot be %sd, resource is managed by the Tenant", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) + recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantResourceWriteOp", "%s %s/%s cannot be %sd, resource is managed by the Tenant", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) response := admission.Denied(fmt.Sprintf("resource %s is managed at the Tenant level", req.Name)) diff --git a/pkg/webhook/utils/error.go b/internal/webhook/utils/error.go similarity index 100% rename from pkg/webhook/utils/error.go rename to internal/webhook/utils/error.go diff --git a/pkg/webhook/utils/in_capsule_groups.go b/internal/webhook/utils/in_capsule_groups.go similarity index 79% rename from pkg/webhook/utils/in_capsule_groups.go rename to internal/webhook/utils/in_capsule_groups.go index 82786245..f8f16ec0 100644 --- a/pkg/webhook/utils/in_capsule_groups.go +++ b/internal/webhook/utils/in_capsule_groups.go @@ -10,8 +10,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + "github.com/projectcapsule/capsule/internal/webhook" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/webhook" + "github.com/projectcapsule/capsule/pkg/utils/users" ) func InCapsuleGroups(configuration configuration.Configuration, handlers ...webhook.Handler) webhook.Handler { @@ -29,7 +30,7 @@ type handler struct { //nolint:dupl func (h *handler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserNames(), h.configuration.UserGroups(), h.configuration.IgnoreUserWithGroups()) { + if !users.IsCapsuleUser(ctx, client, h.configuration, req.UserInfo.Username, req.UserInfo.Groups) { return nil } @@ -46,7 +47,7 @@ func (h *handler) OnCreate(client client.Client, decoder admission.Decoder, reco //nolint:dupl func (h *handler) OnDelete(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserNames(), h.configuration.UserGroups(), h.configuration.IgnoreUserWithGroups()) { + if !users.IsCapsuleUser(ctx, client, h.configuration, req.UserInfo.Username, req.UserInfo.Groups) { return nil } @@ -63,7 +64,7 @@ func (h *handler) OnDelete(client client.Client, decoder admission.Decoder, reco //nolint:dupl func (h *handler) OnUpdate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - if !IsCapsuleUser(ctx, req, client, h.configuration.UserNames(), h.configuration.UserGroups(), h.configuration.IgnoreUserWithGroups()) { + if !users.IsCapsuleUser(ctx, client, h.configuration, req.UserInfo.Username, req.UserInfo.Groups) { return nil } diff --git a/pkg/webhook/utils/kubernetes_version.go b/internal/webhook/utils/kubernetes_version.go similarity index 100% rename from pkg/webhook/utils/kubernetes_version.go rename to internal/webhook/utils/kubernetes_version.go diff --git a/pkg/webhook/utils/resources.go b/internal/webhook/utils/resources.go similarity index 100% rename from pkg/webhook/utils/resources.go rename to internal/webhook/utils/resources.go diff --git a/internal/webhook/utils/tenant_get.go b/internal/webhook/utils/tenant_get.go new file mode 100644 index 00000000..13b06284 --- /dev/null +++ b/internal/webhook/utils/tenant_get.go @@ -0,0 +1,90 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package utils + +import ( + "context" + "fmt" + "net/http" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" +) + +// getNamespaceTenant returns namespace owner tenant. +func GetNamespaceTenant( + ctx context.Context, + client client.Client, + ns *corev1.Namespace, + req admission.Request, + cfg configuration.Configuration, + recorder record.EventRecorder, +) (*capsulev1beta2.Tenant, *admission.Response) { + tnt, err := tenant.GetTenantByLabelsAndUser(ctx, client, cfg, ns, req.UserInfo) + if err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return nil, &response + } + + if tnt != nil { + return tnt, nil + } + + tnts, err := tenant.GetTenantByUserInfo(ctx, client, cfg, ns, req.UserInfo.Username, req.UserInfo.Groups) + if err != nil { + response := admission.Errored(http.StatusBadRequest, err) + + return nil, &response + } + + if len(tnts) == 0 { + response := admission.Denied("You do not have any Tenant assigned: please, reach out to the system administrators") + + return nil, &response + } + + if len(tnts) == 1 { + // Check if namespace needs Tenant name prefix + if !validateNamespacePrefix(ns, &tnts[0]) { + response := admission.Denied(fmt.Sprintf("The Namespace name must start with '%s-' when ForceTenantPrefix is enabled in the Tenant.", tnts[0].GetName())) + + return nil, &response + } + + return &tnts[0], nil + } + + if cfg.ForceTenantPrefix() { + for _, t := range tnts { + if strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", t.GetName())) { + return &t, nil + } + } + + response := admission.Denied("The Namespace prefix used doesn't match any available Tenant") + + return nil, &response + } + + return nil, nil +} + +func validateNamespacePrefix(ns *corev1.Namespace, tenant *capsulev1beta2.Tenant) bool { + // Check if ForceTenantPrefix is true + if tenant.Spec.ForceTenantPrefix != nil && *tenant.Spec.ForceTenantPrefix { + if !strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tenant.GetName())) { + return false + } + } + + return true +} diff --git a/pkg/webhook/webhook.go b/internal/webhook/webhook.go similarity index 100% rename from pkg/webhook/webhook.go rename to internal/webhook/webhook.go diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go deleted file mode 100644 index 913ecc32..00000000 --- a/pkg/api/annotations.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -const ( - ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels" - ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp" - ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations" - ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp" - ProtectedTenantAnnotation = "capsule.clastix.io/protected" -) diff --git a/pkg/api/meta/annotations.go b/pkg/api/meta/annotations.go new file mode 100644 index 00000000..e04840f0 --- /dev/null +++ b/pkg/api/meta/annotations.go @@ -0,0 +1,58 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package meta + +import ( + "strings" + + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + ReleaseAnnotation = "projectcapsule.dev/release" + ReleaseAnnotationTrigger = "true" + + AvailableIngressClassesAnnotation = "capsule.clastix.io/ingress-classes" + AvailableIngressClassesRegexpAnnotation = "capsule.clastix.io/ingress-classes-regexp" + AvailableStorageClassesAnnotation = "capsule.clastix.io/storage-classes" + AvailableStorageClassesRegexpAnnotation = "capsule.clastix.io/storage-classes-regexp" + AllowedRegistriesAnnotation = "capsule.clastix.io/allowed-registries" + AllowedRegistriesRegexpAnnotation = "capsule.clastix.io/allowed-registries-regexp" + + ForbiddenNamespaceLabelsAnnotation = "capsule.clastix.io/forbidden-namespace-labels" + ForbiddenNamespaceLabelsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-labels-regexp" + ForbiddenNamespaceAnnotationsAnnotation = "capsule.clastix.io/forbidden-namespace-annotations" + ForbiddenNamespaceAnnotationsRegexpAnnotation = "capsule.clastix.io/forbidden-namespace-annotations-regexp" + ProtectedTenantAnnotation = "capsule.clastix.io/protected" +) + +func ReleaseAnnotationTriggers(obj client.Object) bool { + return annotationTriggers(obj, ReleaseAnnotation, ReleaseAnnotationTrigger) +} + +func ReleaseAnnotationRemove(obj client.Object) { + annotationRemove(obj, ReleaseAnnotation) +} + +func annotationRemove(obj client.Object, anno string) { + annotations := obj.GetAnnotations() + + if _, ok := annotations[anno]; ok { + delete(annotations, anno) + + obj.SetAnnotations(annotations) + } +} + +func annotationTriggers(obj client.Object, anno string, trigger string) bool { + annotations := obj.GetAnnotations() + + if val, ok := annotations[anno]; ok { + if strings.ToLower(val) == trigger { + return true + } + } + + return false +} diff --git a/pkg/meta/conditions.go b/pkg/api/meta/conditions.go similarity index 100% rename from pkg/meta/conditions.go rename to pkg/api/meta/conditions.go diff --git a/pkg/meta/conditions_test.go b/pkg/api/meta/conditions_test.go similarity index 100% rename from pkg/meta/conditions_test.go rename to pkg/api/meta/conditions_test.go diff --git a/pkg/meta/finalizers.go b/pkg/api/meta/finalizers.go similarity index 100% rename from pkg/meta/finalizers.go rename to pkg/api/meta/finalizers.go diff --git a/pkg/meta/labels.go b/pkg/api/meta/labels.go similarity index 78% rename from pkg/meta/labels.go rename to pkg/api/meta/labels.go index 310d55b7..cf23aa3e 100644 --- a/pkg/meta/labels.go +++ b/pkg/api/meta/labels.go @@ -10,6 +10,11 @@ import ( ) const ( + TenantNameLabel = "kubernetes.io/metadata.name" + TenantLabel = "capsule.clastix.io/tenant" + + ResourcePoolLabel = "projectcapsule.dev/pool" + FreezeLabel = "projectcapsule.dev/freeze" FreezeLabelTrigger = "true" @@ -20,6 +25,11 @@ const ( CordonedLabelTrigger = "true" ManagedByCapsuleLabel = "capsule.clastix.io/managed-by" + + LimitRangeLabel = "capsule.clastix.io/limit-range" + NetworkPolicyLabel = "capsule.clastix.io/network-policy" + ResourceQuotaLabel = "capsule.clastix.io/resource-quota" + RolebindingLabel = "capsule.clastix.io/role-binding" ) func FreezeLabelTriggers(obj client.Object) bool { diff --git a/pkg/meta/labels_test.go b/pkg/api/meta/labels_test.go similarity index 100% rename from pkg/meta/labels_test.go rename to pkg/api/meta/labels_test.go diff --git a/pkg/meta/ownerreference.go b/pkg/api/meta/ownerreference.go similarity index 100% rename from pkg/meta/ownerreference.go rename to pkg/api/meta/ownerreference.go diff --git a/pkg/meta/ownerreference_test.go b/pkg/api/meta/ownerreference_test.go similarity index 100% rename from pkg/meta/ownerreference_test.go rename to pkg/api/meta/ownerreference_test.go diff --git a/pkg/meta/zz_generated.deepcopy.go b/pkg/api/meta/zz_generated.deepcopy.go similarity index 100% rename from pkg/meta/zz_generated.deepcopy.go rename to pkg/api/meta/zz_generated.deepcopy.go diff --git a/pkg/api/metadata_const.go b/pkg/api/metadata_const.go deleted file mode 100644 index ef41acd4..00000000 --- a/pkg/api/metadata_const.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package api - -const ( - TenantNameLabel = "kubernetes.io/metadata.name" -) diff --git a/api/v1beta2/owner.go b/pkg/api/owner.go similarity index 91% rename from api/v1beta2/owner.go rename to pkg/api/owner.go index 7fe7bcf8..5037a26a 100644 --- a/api/v1beta2/owner.go +++ b/pkg/api/owner.go @@ -1,13 +1,13 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api + +// +kubebuilder:object:generate=true type OwnerSpec struct { - // Kind of tenant owner. Possible values are "User", "Group", and "ServiceAccount" - Kind OwnerKind `json:"kind"` - // Name of tenant owner. - Name string `json:"name"` + UserSpec `json:",inline"` + // Defines additional cluster-roles for the specific Owner. // +kubebuilder:default={admin,capsule-namespace-deleter} ClusterRoles []string `json:"clusterRoles,omitempty"` @@ -26,6 +26,8 @@ func (k OwnerKind) String() string { return string(k) } +// +kubebuilder:object:generate=true + type ProxySettings struct { Kind ProxyServiceKind `json:"kind"` Operations []ProxyOperation `json:"operations"` diff --git a/api/v1beta2/owner_list.go b/pkg/api/owner_list.go similarity index 65% rename from api/v1beta2/owner_list.go rename to pkg/api/owner_list.go index 0d2a4f6e..3853fab4 100644 --- a/api/v1beta2/owner_list.go +++ b/pkg/api/owner_list.go @@ -1,14 +1,36 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +//nolint:dupl +package api import ( "sort" ) +// +kubebuilder:object:generate=true + type OwnerListSpec []OwnerSpec +func (o OwnerListSpec) IsOwner(name string, groups []string) bool { + for _, owner := range o { + switch owner.Kind { + case UserOwner, ServiceAccountOwner: + if name == owner.Name { + return true + } + case GroupOwner: + for _, group := range groups { + if group == owner.Name { + return true + } + } + } + } + + return false +} + func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) { sort.Sort(ByKindAndName(o)) i := sort.Search(len(o), func(i int) bool { diff --git a/api/v1beta2/owner_list_test.go b/pkg/api/owner_list_test.go similarity index 80% rename from api/v1beta2/owner_list_test.go rename to pkg/api/owner_list_test.go index 27f404fb..b7d5f9d5 100644 --- a/api/v1beta2/owner_list_test.go +++ b/pkg/api/owner_list_test.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package v1beta2 +package api import ( "testing" @@ -11,8 +11,10 @@ import ( func TestOwnerListSpec_FindOwner(t *testing.T) { bla := OwnerSpec{ - Kind: UserOwner, - Name: "bla", + UserSpec: UserSpec{ + Kind: UserOwner, + Name: "bla", + }, ProxyOperations: []ProxySettings{ { Kind: IngressClassesProxy, @@ -21,8 +23,10 @@ func TestOwnerListSpec_FindOwner(t *testing.T) { }, } bar := OwnerSpec{ - Kind: GroupOwner, - Name: "bar", + UserSpec: UserSpec{ + Kind: GroupOwner, + Name: "bar", + }, ProxyOperations: []ProxySettings{ { Kind: StorageClassesProxy, @@ -31,8 +35,10 @@ func TestOwnerListSpec_FindOwner(t *testing.T) { }, } baz := OwnerSpec{ - Kind: UserOwner, - Name: "baz", + UserSpec: UserSpec{ + Kind: UserOwner, + Name: "baz", + }, ProxyOperations: []ProxySettings{ { Kind: StorageClassesProxy, @@ -41,8 +47,10 @@ func TestOwnerListSpec_FindOwner(t *testing.T) { }, } fim := OwnerSpec{ - Kind: ServiceAccountOwner, - Name: "fim", + UserSpec: UserSpec{ + Kind: ServiceAccountOwner, + Name: "fim", + }, ProxyOperations: []ProxySettings{ { Kind: NodesProxy, @@ -51,8 +59,10 @@ func TestOwnerListSpec_FindOwner(t *testing.T) { }, } bom := OwnerSpec{ - Kind: GroupOwner, - Name: "bom", + UserSpec: UserSpec{ + Kind: GroupOwner, + Name: "bom", + }, ProxyOperations: []ProxySettings{ { Kind: StorageClassesProxy, @@ -65,8 +75,10 @@ func TestOwnerListSpec_FindOwner(t *testing.T) { }, } qip := OwnerSpec{ - Kind: ServiceAccountOwner, - Name: "qip", + UserSpec: UserSpec{ + Kind: ServiceAccountOwner, + Name: "qip", + }, ProxyOperations: []ProxySettings{ { Kind: StorageClassesProxy, diff --git a/pkg/api/users.go b/pkg/api/users.go new file mode 100644 index 00000000..353bc9a4 --- /dev/null +++ b/pkg/api/users.go @@ -0,0 +1,19 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package api + +// +kubebuilder:validation:Enum=User;Group;ServiceAccount +type UserKind string + +func (k UserKind) String() string { + return string(k) +} + +// +kubebuilder:object:generate=true +type UserSpec struct { + // Kind of entity. Possible values are "User", "Group", and "ServiceAccount" + Kind OwnerKind `json:"kind"` + // Name of the entity. + Name string `json:"name"` +} diff --git a/pkg/api/users_list.go b/pkg/api/users_list.go new file mode 100644 index 00000000..8bf88816 --- /dev/null +++ b/pkg/api/users_list.go @@ -0,0 +1,63 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +//nolint:dupl +package api + +import ( + "sort" +) + +// +kubebuilder:object:generate=true + +type UserListSpec []UserSpec + +func (u UserListSpec) IsPresent(name string, groups []string) bool { + for _, user := range u { + switch user.Kind { + case UserOwner, ServiceAccountOwner: + if name == user.Name { + return true + } + case GroupOwner: + for _, group := range groups { + if group == user.Name { + return true + } + } + } + } + + return false +} + +func (o UserListSpec) FindUser(name string, kind OwnerKind) (owner UserSpec) { + sort.Sort(ByKindName(o)) + i := sort.Search(len(o), func(i int) bool { + return o[i].Kind >= kind && o[i].Name >= name + }) + + if i < len(o) && o[i].Kind == kind && o[i].Name == name { + return o[i] + } + + return owner +} + +type ByKindName UserListSpec + +func (b ByKindName) Len() int { + return len(b) +} + +func (b ByKindName) Less(i, j int) bool { + if b[i].Kind.String() != b[j].Kind.String() { + return b[i].Kind.String() < b[j].Kind.String() + } + + return b[i].Name < b[j].Name +} + +func (b ByKindName) Swap(i, j int) { + b[i], b[j] = b[j], b[i] +} diff --git a/pkg/api/zz_generated.deepcopy.go b/pkg/api/zz_generated.deepcopy.go index 811c84d3..850cfb32 100644 --- a/pkg/api/zz_generated.deepcopy.go +++ b/pkg/api/zz_generated.deepcopy.go @@ -281,6 +281,69 @@ func (in *NetworkPolicySpec) DeepCopy() *NetworkPolicySpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in OwnerListSpec) DeepCopyInto(out *OwnerListSpec) { + { + in := &in + *out = make(OwnerListSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerListSpec. +func (in OwnerListSpec) DeepCopy() OwnerListSpec { + if in == nil { + return nil + } + out := new(OwnerListSpec) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OwnerSpec) DeepCopyInto(out *OwnerSpec) { + *out = *in + out.UserSpec = in.UserSpec + if in.ClusterRoles != nil { + in, out := &in.ClusterRoles, &out.ClusterRoles + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ProxyOperations != nil { + in, out := &in.ProxyOperations, &out.ProxyOperations + *out = make([]ProxySettings, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + 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 OwnerSpec. +func (in *OwnerSpec) DeepCopy() *OwnerSpec { + if in == nil { + return nil + } + out := new(OwnerSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PodOptions) DeepCopyInto(out *PodOptions) { *out = *in @@ -318,6 +381,26 @@ func (in *PoolExhaustionResource) DeepCopy() *PoolExhaustionResource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ProxySettings) DeepCopyInto(out *ProxySettings) { + *out = *in + if in.Operations != nil { + in, out := &in.Operations, &out.Operations + *out = make([]ProxyOperation, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProxySettings. +func (in *ProxySettings) DeepCopy() *ProxySettings { + if in == nil { + return nil + } + out := new(ProxySettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourceQuotaSpec) DeepCopyInto(out *ResourceQuotaSpec) { *out = *in @@ -420,3 +503,37 @@ func (in *ServiceOptions) DeepCopy() *ServiceOptions { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in UserListSpec) DeepCopyInto(out *UserListSpec) { + { + in := &in + *out = make(UserListSpec, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserListSpec. +func (in UserListSpec) DeepCopy() UserListSpec { + if in == nil { + return nil + } + out := new(UserListSpec) + in.DeepCopyInto(out) + return *out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UserSpec) DeepCopyInto(out *UserSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserSpec. +func (in *UserSpec) DeepCopy() *UserSpec { + if in == nil { + return nil + } + out := new(UserSpec) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/configuration/client.go b/pkg/configuration/client.go index 48211528..53e2519e 100644 --- a/pkg/configuration/client.go +++ b/pkg/configuration/client.go @@ -113,3 +113,7 @@ func (c *capsuleConfiguration) ForbiddenUserNodeAnnotations() *capsuleapi.Forbid return &c.retrievalFn().Spec.NodeMetadata.ForbiddenAnnotations } + +func (c *capsuleConfiguration) Administrators() capsuleapi.UserListSpec { + return c.retrievalFn().Spec.Administrators +} diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 0f109f19..72512fb2 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -29,4 +29,5 @@ type Configuration interface { IgnoreUserWithGroups() []string ForbiddenUserNodeLabels() *capsuleapi.ForbiddenListSpec ForbiddenUserNodeAnnotations() *capsuleapi.ForbiddenListSpec + Administrators() capsuleapi.UserListSpec } diff --git a/pkg/indexer/namespace/namespaces.go b/pkg/indexer/namespace/namespaces.go index 3c803398..a63ab373 100644 --- a/pkg/indexer/namespace/namespaces.go +++ b/pkg/indexer/namespace/namespaces.go @@ -9,7 +9,7 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type OwnerReference struct{} @@ -32,7 +32,7 @@ func (o OwnerReference) Func() client.IndexerFunc { } for _, or := range ns.OwnerReferences { - if or.APIVersion == capsulev1beta2.GroupVersion.String() { + if tenant.IsTenantOwnerReference(or) { res = append(res, or.Name) } } diff --git a/pkg/indexer/tenant/owner.go b/pkg/indexer/tenant/owner.go index e2ff6fba..8b63c9c0 100644 --- a/pkg/indexer/tenant/owner.go +++ b/pkg/indexer/tenant/owner.go @@ -9,7 +9,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type OwnerReference struct{} @@ -24,11 +24,11 @@ func (o OwnerReference) Field() string { func (o OwnerReference) Func() client.IndexerFunc { return func(object client.Object) []string { - tenant, ok := object.(*capsulev1beta2.Tenant) + tnt, ok := object.(*capsulev1beta2.Tenant) if !ok { - panic(fmt.Errorf("expected type *capsulev1beta2.Tenant, got %T", tenant)) + panic(fmt.Errorf("expected type *capsulev1beta2.Tenant, got %T", tnt)) } - return utils.GetOwnersWithKinds(tenant) + return tenant.GetOwnersWithKinds(tnt) } } diff --git a/pkg/meta/annotations.go b/pkg/meta/annotations.go deleted file mode 100644 index ffcb3977..00000000 --- a/pkg/meta/annotations.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package meta - -import ( - "strings" - - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - ReleaseAnnotation = "projectcapsule.dev/release" - ReleaseAnnotationTrigger = "true" -) - -func ReleaseAnnotationTriggers(obj client.Object) bool { - return annotationTriggers(obj, ReleaseAnnotation, ReleaseAnnotationTrigger) -} - -func ReleaseAnnotationRemove(obj client.Object) { - annotationRemove(obj, ReleaseAnnotation) -} - -func annotationRemove(obj client.Object, anno string) { - annotations := obj.GetAnnotations() - - if _, ok := annotations[anno]; ok { - delete(annotations, anno) - - obj.SetAnnotations(annotations) - } -} - -func annotationTriggers(obj client.Object, anno string, trigger string) bool { - annotations := obj.GetAnnotations() - - if val, ok := annotations[anno]; ok { - if strings.ToLower(val) == trigger { - return true - } - } - - return false -} diff --git a/pkg/utils/names.go b/pkg/utils/names.go deleted file mode 100644 index 1558786a..00000000 --- a/pkg/utils/names.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package utils - -import ( - "fmt" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" -) - -func PoolResourceQuotaName(quota *capsulev1beta2.ResourcePool) string { - return fmt.Sprintf("capsule-pool-%s", quota.Name) -} diff --git a/pkg/utils/reference.go b/pkg/utils/reference.go deleted file mode 100644 index eed75542..00000000 --- a/pkg/utils/reference.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package utils - -import ( - "strings" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" -) - -const ( - ObjectReferenceTenantKind = "Tenant" -) - -func IsTenantOwnerReference(or metav1.OwnerReference) bool { - parts := strings.Split(or.APIVersion, "/") - if len(parts) != 2 { - return false - } - - group := parts[0] - - return group == capsulev1beta2.GroupVersion.Group && or.Kind == ObjectReferenceTenantKind -} diff --git a/pkg/utils/tenant/get_by.go b/pkg/utils/tenant/get_by.go new file mode 100644 index 00000000..63ad9b7e --- /dev/null +++ b/pkg/utils/tenant/get_by.go @@ -0,0 +1,183 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + "fmt" + "sort" + "strings" + + authenticationv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +func TenantByStatusNamespace( + ctx context.Context, + c client.Client, + namespace string, +) (*capsulev1beta2.Tenant, error) { + tntList := &capsulev1beta2.TenantList{} + + if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), + }); err != nil { + return nil, err + } + + if len(tntList.Items) == 0 { + return nil, nil + } + + tnt := &capsulev1beta2.Tenant{} + *tnt = tntList.Items[0] + + return tnt, nil +} + +// getNamespaceTenant returns namespace owner tenant. +func GetTenantByOwnerreferences( + ctx context.Context, + c client.Client, + refs []v1.OwnerReference, +) (tnt *capsulev1beta2.Tenant, err error) { + for _, or := range refs { + if !IsTenantOwnerReference(or) { + continue + } + + tnt = &capsulev1beta2.Tenant{} + if err = c.Get(ctx, types.NamespacedName{Name: or.Name}, tnt); err != nil { + return nil, err + } + + return tnt, nil + } + + return nil, nil +} + +//nolint:nestif +func GetTenantByUserInfo( + ctx context.Context, + c client.Client, + cfg configuration.Configuration, + ns *corev1.Namespace, + username string, + groups []string, +) (sortedTenants, error) { + var tenants sortedTenants + + // User tenants. + userTntList := &capsulev1beta2.TenantList{} + fields := client.MatchingFields{ + ".spec.owner.ownerkind": fmt.Sprintf("User:%s", username), + } + + err := c.List(ctx, userTntList, fields) + if err != nil { + return nil, err + } + + tenants = userTntList.Items + + // ServiceAccount tenants. + if strings.HasPrefix(username, "system:serviceaccount:") { + saTntList := &capsulev1beta2.TenantList{} + fields = client.MatchingFields{ + ".spec.owner.ownerkind": fmt.Sprintf("ServiceAccount:%s", username), + } + + err = c.List(ctx, saTntList, fields) + if err != nil { + return nil, err + } + + tenants = append(tenants, saTntList.Items...) + + if cfg.AllowServiceAccountPromotion() { + if tnt, err := users.ResolveServiceAccountActor(ctx, c, ns, username, cfg); err != nil { + return nil, err + } else if tnt != nil { + tenants = append(tenants, *tnt) + } + } + } + + // Group tenants. + groupTntList := &capsulev1beta2.TenantList{} + + for _, group := range groups { + fields = client.MatchingFields{ + ".spec.owner.ownerkind": fmt.Sprintf("Group:%s", group), + } + + err = c.List(ctx, groupTntList, fields) + if err != nil { + return nil, err + } + + tenants = append(tenants, groupTntList.Items...) + } + + sort.Sort(sort.Reverse(tenants)) + + return tenants, nil +} + +// getTenantByLabels returns tenant from labels. +func GetTenantByLabels( + ctx context.Context, + c client.Client, + ns *corev1.Namespace, +) (*capsulev1beta2.Tenant, error) { + if label, ok := ns.Labels[meta.TenantLabel]; ok { + tnt := &capsulev1beta2.Tenant{} + if err := c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { + return nil, err + } + + return tnt, nil + } + + // Nothing found in the labels. + return nil, nil +} + +func GetTenantByLabelsAndUser( + ctx context.Context, + c client.Client, + cfg configuration.Configuration, + ns *corev1.Namespace, + userInfo authenticationv1.UserInfo, +) (*capsulev1beta2.Tenant, error) { + tnt, err := GetTenantByLabels(ctx, c, ns) + if err != nil { + return nil, err + } + + if tnt != nil { + ok, err := users.IsTenantOwner(ctx, c, cfg, tnt, userInfo) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf("can not assign the desired namespace to a non-owned Tenant") + } + + return tnt, nil + } + + return nil, nil +} diff --git a/pkg/utils/tenant/metdata.go b/pkg/utils/tenant/metdata.go new file mode 100644 index 00000000..17827fac --- /dev/null +++ b/pkg/utils/tenant/metdata.go @@ -0,0 +1,21 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + corev1 "k8s.io/api/core/v1" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api/meta" +) + +func AddTenantLabelForNamespace(ns *corev1.Namespace, tnt *capsulev1beta2.Tenant) error { + if ns.Labels == nil { + ns.Labels = make(map[string]string) + } + + ns.Labels[meta.TenantLabel] = tnt.GetName() + + return nil +} diff --git a/pkg/utils/tenant/owned.go b/pkg/utils/tenant/owned.go new file mode 100644 index 00000000..88a94fde --- /dev/null +++ b/pkg/utils/tenant/owned.go @@ -0,0 +1,35 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "context" + + authenticationv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +func NamespaceIsOwned( + ctx context.Context, + c client.Client, + cfg configuration.Configuration, + ns *corev1.Namespace, + tnt *capsulev1beta2.Tenant, + userInfo authenticationv1.UserInfo, +) (bool, error) { + for _, ownerRef := range ns.OwnerReferences { + if !IsTenantOwnerReferenceForTenant(ownerRef, tnt) { + continue + } + + return users.IsTenantOwner(ctx, c, cfg, tnt, userInfo) + } + + return false, nil +} diff --git a/pkg/utils/tenant/owner_reference.go b/pkg/utils/tenant/owner_reference.go new file mode 100644 index 00000000..47adede8 --- /dev/null +++ b/pkg/utils/tenant/owner_reference.go @@ -0,0 +1,58 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "strings" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" +) + +const ( + ObjectReferenceTenantKind = "Tenant" +) + +func IsTenantOwnerReference(or metav1.OwnerReference) bool { + if or.Kind != ObjectReferenceTenantKind { + return false + } + + if or.APIVersion == "" { + return false + } + + parts := strings.Split(or.APIVersion, "/") + if len(parts) != 2 { + return false + } + + group := parts[0] + + return group == capsulev1beta2.GroupVersion.Group && or.Kind == ObjectReferenceTenantKind +} + +func IsTenantOwnerReferenceForTenant(or metav1.OwnerReference, tnt *capsulev1beta2.Tenant) bool { + if tnt == nil { + return false + } + + if or.Kind != ObjectReferenceTenantKind { + return false + } + + if or.APIVersion == "" { + return false + } + + parts := strings.Split(or.APIVersion, "/") + if len(parts) != 2 { + return false + } + + group := parts[0] + + return group == capsulev1beta2.GroupVersion.Group && or.Kind == ObjectReferenceTenantKind && or.Name == tnt.GetName() && or.UID == tnt.GetUID() +} diff --git a/pkg/utils/tenant/owner_reference_test.go b/pkg/utils/tenant/owner_reference_test.go new file mode 100644 index 00000000..cc388e13 --- /dev/null +++ b/pkg/utils/tenant/owner_reference_test.go @@ -0,0 +1,323 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" +) + +func TestIsTenantOwnerReference(t *testing.T) { + capsuleGroup := capsulev1beta2.GroupVersion.Group + + tests := []struct { + name string + or metav1.OwnerReference + want bool + }{ + { + name: "valid tenant ownerRef with exact group and version", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: true, + }, + { + name: "valid tenant ownerRef with same group but different version", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: true, // we intentionally only check the group, not the version + }, + { + name: "wrong group", + or: metav1.OwnerReference{ + APIVersion: "other.group.io/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "wrong kind", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2", + Kind: "Namespace", + Name: "my-tenant", + }, + want: false, + }, + { + name: "empty APIVersion", + or: metav1.OwnerReference{ + APIVersion: "", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "APIVersion without slash (only version)", + or: metav1.OwnerReference{ + APIVersion: "v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "APIVersion with empty group", + or: metav1.OwnerReference{ + APIVersion: "/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "APIVersion with empty version", + or: metav1.OwnerReference{ + APIVersion: "", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "APIVersion with extra slash in version (still ok as long as group matches)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2/extra", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + want: false, + }, + { + name: "completely unrelated ownerRef", + or: metav1.OwnerReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Name: "cm", + }, + want: false, + }, + } + + for _, tt := range tests { + tt := tt // capture + t.Run(tt.name, func(t *testing.T) { + got := IsTenantOwnerReference(tt.or) + if got != tt.want { + t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want) + } + }) + } +} + +func TestIsTenantOwnerReferenceForTenant(t *testing.T) { + capsuleGroup := capsulev1beta2.GroupVersion.Group + + tests := []struct { + name string + or metav1.OwnerReference + want bool + tenant *capsulev1beta2.Tenant + }{ + { + name: "valid tenant ownerRef with exact group and version (same tenant)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: true, + }, + { + name: "valid tenant ownerRef with exact group and version (different tenant)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant-2", + }, + }, + want: false, + }, + { + name: "valid tenant ownerRef with same group but different version (same tenant)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: true, // we intentionally only check the group, not the version + }, + { + name: "valid tenant ownerRef with same group but different version (different tenant)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant-2", + }, + }, + want: false, // we intentionally only check the group, not the version + }, + { + name: "wrong group", + or: metav1.OwnerReference{ + APIVersion: "other.group.io/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "wrong kind", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2", + Kind: "Namespace", + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "empty APIVersion", + or: metav1.OwnerReference{ + APIVersion: "", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "empty tenant", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: nil, + want: false, + }, + { + name: "APIVersion without slash (only version)", + or: metav1.OwnerReference{ + APIVersion: "v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "APIVersion with empty group", + or: metav1.OwnerReference{ + APIVersion: "/v1beta2", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "APIVersion with empty version", + or: metav1.OwnerReference{ + APIVersion: "", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "APIVersion with extra slash in version (still ok as long as group matches)", + or: metav1.OwnerReference{ + APIVersion: capsuleGroup + "/v1beta2/extra", + Kind: ObjectReferenceTenantKind, + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + { + name: "completely unrelated ownerRef", + or: metav1.OwnerReference{ + APIVersion: "v1", + Kind: "ConfigMap", + Name: "my-tenant", + }, + tenant: &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-tenant", + }, + }, + want: false, + }, + } + + for _, tt := range tests { + tt := tt // capture + t.Run(tt.name, func(t *testing.T) { + got := IsTenantOwnerReferenceForTenant(tt.or, tt.tenant) + if got != tt.want { + t.Fatalf("IsTenantOwnerReference(%+v) = %v, want %v", tt.or, got, tt.want) + } + }) + } +} diff --git a/pkg/utils/owner.go b/pkg/utils/tenant/owners.go similarity index 96% rename from pkg/utils/owner.go rename to pkg/utils/tenant/owners.go index bae1d599..8b878f39 100644 --- a/pkg/utils/owner.go +++ b/pkg/utils/tenant/owners.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package utils +package tenant import ( "fmt" diff --git a/pkg/utils/tenant/types.go b/pkg/utils/tenant/types.go new file mode 100644 index 00000000..266e0799 --- /dev/null +++ b/pkg/utils/tenant/types.go @@ -0,0 +1,20 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package tenant + +import capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + +type sortedTenants []capsulev1beta2.Tenant + +func (s sortedTenants) Len() int { + return len(s) +} + +func (s sortedTenants) Less(i, j int) bool { + return len(s[i].GetName()) < len(s[j].GetName()) +} + +func (s sortedTenants) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} diff --git a/pkg/utils/tenant_labels.go b/pkg/utils/tenant_labels.go index 1fef285d..4f66a0d6 100644 --- a/pkg/utils/tenant_labels.go +++ b/pkg/utils/tenant_labels.go @@ -13,22 +13,23 @@ import ( "github.com/projectcapsule/capsule/api/v1beta1" "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api/meta" ) func GetTypeLabel(t runtime.Object) (label string, err error) { switch v := t.(type) { case *v1beta1.Tenant, *v1beta2.Tenant: - return "capsule.clastix.io/tenant", nil + return meta.TenantLabel, nil case *v1beta2.ResourcePool: - return "projectcapsule.dev/pool", nil + return meta.ResourcePoolLabel, nil case *corev1.LimitRange: - return "capsule.clastix.io/limit-range", nil + return meta.LimitRangeLabel, nil case *networkingv1.NetworkPolicy: - return "capsule.clastix.io/network-policy", nil + return meta.NetworkPolicyLabel, nil case *corev1.ResourceQuota: - return "capsule.clastix.io/resource-quota", nil + return meta.ResourceQuotaLabel, nil case *rbacv1.RoleBinding: - return "capsule.clastix.io/role-binding", nil + return meta.RolebindingLabel, nil default: err = fmt.Errorf("type %T is not mapped as Capsule label recognized", v) } diff --git a/pkg/utils/users/is_admin_user.go b/pkg/utils/users/is_admin_user.go new file mode 100644 index 00000000..7d1d9375 --- /dev/null +++ b/pkg/utils/users/is_admin_user.go @@ -0,0 +1,14 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package users + +import ( + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/projectcapsule/capsule/pkg/api" +) + +func IsAdminUser(req admission.Request, administrators api.UserListSpec) bool { + return administrators.IsPresent(req.UserInfo.Username, req.UserInfo.Groups) +} diff --git a/pkg/webhook/utils/is_capsule_user.go b/pkg/utils/users/is_capsule_user.go similarity index 57% rename from pkg/webhook/utils/is_capsule_user.go rename to pkg/utils/users/is_capsule_user.go index 3548cf37..1799c21a 100644 --- a/pkg/webhook/utils/is_capsule_user.go +++ b/pkg/utils/users/is_capsule_user.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package utils +package users import ( "context" @@ -11,14 +11,19 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/authentication/serviceaccount" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/utils" + "github.com/projectcapsule/capsule/pkg/configuration" ) -func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client, users []string, userGroups []string, ignoreGroups []string) bool { - groupList := utils.NewUserGroupList(req.UserInfo.Groups) +func IsCapsuleUser( + ctx context.Context, + c client.Client, + cfg configuration.Configuration, + user string, + groups []string, +) bool { + groupList := NewUserGroupList(groups) // if the user is a ServiceAccount belonging to the kube-system namespace, definitely, it's not a Capsule user // and we can skip the check in case of Capsule user group assigned to system:authenticated // (ref: https://github.com/projectcapsule/capsule/issues/234) @@ -27,15 +32,15 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client } //nolint:nestif - if sets.NewString(req.UserInfo.Groups...).Has("system:serviceaccounts") { - namespace, name, err := serviceaccount.SplitUsername(req.UserInfo.Username) + if sets.NewString(groups...).Has("system:serviceaccounts") { + namespace, name, err := serviceaccount.SplitUsername(user) if err == nil { if namespace == os.Getenv("NAMESPACE") && name == os.Getenv("SERVICE_ACCOUNT") { return false } tl := &capsulev1beta2.TenantList{} - if err := clt.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", namespace)}); err != nil { + if err := c.List(ctx, tl, client.MatchingFieldsSelector{Selector: fields.OneTermEqualSelector(".status.namespaces", namespace)}); err != nil { return false } @@ -45,10 +50,10 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client } } - for _, group := range userGroups { + for _, group := range cfg.UserGroups() { if groupList.Find(group) { - if len(ignoreGroups) > 0 { - for _, ignoreGroup := range ignoreGroups { + if len(cfg.IgnoreUserWithGroups()) > 0 { + for _, ignoreGroup := range cfg.IgnoreUserWithGroups() { if groupList.Find(ignoreGroup) { return false } @@ -59,7 +64,7 @@ func IsCapsuleUser(ctx context.Context, req admission.Request, clt client.Client } } - if len(users) > 0 && sets.New[string](users...).Has(req.UserInfo.Username) { + if len(cfg.UserNames()) > 0 && sets.New[string](cfg.UserNames()...).Has(user) { return true } diff --git a/pkg/webhook/utils/is_tenant_owner.go b/pkg/utils/users/is_tenant_owner.go similarity index 69% rename from pkg/webhook/utils/is_tenant_owner.go rename to pkg/utils/users/is_tenant_owner.go index 6ad615d1..6a0908b1 100644 --- a/pkg/webhook/utils/is_tenant_owner.go +++ b/pkg/utils/users/is_tenant_owner.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package utils +package users import ( "context" @@ -13,32 +13,27 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/meta" + "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/configuration" ) func IsTenantOwner( ctx context.Context, c client.Client, + cfg configuration.Configuration, tenant *capsulev1beta2.Tenant, userInfo authenticationv1.UserInfo, - promotedServiceAccountOwners bool, ) (bool, error) { - for _, owner := range tenant.Spec.Owners { - switch owner.Kind { - case capsulev1beta2.UserOwner, capsulev1beta2.ServiceAccountOwner: - if userInfo.Username == owner.Name { - return true, nil - } - case capsulev1beta2.GroupOwner: - for _, group := range userInfo.Groups { - if group == owner.Name { - return true, nil - } - } - } + if isOwner := tenant.Spec.Owners.IsOwner(userInfo.Username, userInfo.Groups); isOwner { + return true, nil } - if promotedServiceAccountOwners { + // Administrators are always Owners + if cfg.Administrators().IsPresent(userInfo.Username, userInfo.Groups) { + return true, nil + } + + if cfg.AllowServiceAccountPromotion() { parts := strings.Split(userInfo.Username, ":") if len(parts) != 4 { diff --git a/pkg/utils/users/serviceaccounts.go b/pkg/utils/users/serviceaccounts.go new file mode 100644 index 00000000..aae258cf --- /dev/null +++ b/pkg/utils/users/serviceaccounts.go @@ -0,0 +1,59 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package users + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apiserver/pkg/authentication/serviceaccount" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/configuration" +) + +// This function resolves the tenant based on the serviceaccount given via username +// if a serviceaccount is in a tenant namespace they will return the tenant. +func ResolveServiceAccountActor( + ctx context.Context, + c client.Client, + ns *corev1.Namespace, + username string, + cfg configuration.Configuration, +) (tnt *capsulev1beta2.Tenant, err error) { + namespace, name, err := serviceaccount.SplitUsername(username) + if err != nil { + return nil, err + } + + sa := &corev1.ServiceAccount{} + if err = c.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, sa); err != nil { + if apierrors.IsNotFound(err) { + return tnt, err + } + + return tnt, err + } + + if meta.OwnerPromotionLabelTriggers(ns) { + return tnt, err + } + + tntList := &capsulev1beta2.TenantList{} + if err = c.List(ctx, tntList, client.MatchingFieldsSelector{ + Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), + }); err != nil { + return tnt, err + } + + if len(tntList.Items) > 0 { + tnt = &tntList.Items[0] + } + + return tnt, err +} diff --git a/pkg/utils/user_group.go b/pkg/utils/users/user_group.go similarity index 97% rename from pkg/utils/user_group.go rename to pkg/utils/users/user_group.go index 2ffc90e4..956a9356 100644 --- a/pkg/utils/user_group.go +++ b/pkg/utils/users/user_group.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package utils +package users import ( "sort" diff --git a/pkg/utils/user_group_test.go b/pkg/utils/users/user_group_test.go similarity index 97% rename from pkg/utils/user_group_test.go rename to pkg/utils/users/user_group_test.go index aecb8fad..1896c941 100644 --- a/pkg/utils/user_group_test.go +++ b/pkg/utils/users/user_group_test.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package utils +package users import ( "testing" diff --git a/pkg/webhook/namespace/mutation/ownerreference.go b/pkg/webhook/namespace/mutation/ownerreference.go deleted file mode 100644 index 2ae7c492..00000000 --- a/pkg/webhook/namespace/mutation/ownerreference.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package mutation - -import ( - "context" - "encoding/json" - "net/http" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/configuration" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type ownerReferenceHandler struct { - cfg configuration.Configuration -} - -func OwnerReferenceHandler(cfg configuration.Configuration) capsulewebhook.Handler { - return &ownerReferenceHandler{ - cfg: cfg, - } -} - -func (h *ownerReferenceHandler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return h.setOwnerRef(ctx, req, client, decoder, recorder) - } -} - -func (h *ownerReferenceHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} - -func (h *ownerReferenceHandler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - oldNs := &corev1.Namespace{} - if err := decoder.DecodeRaw(req.OldObject, oldNs); err != nil { - return utils.ErroredResponse(err) - } - - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", oldNs.Name), - }); err != nil { - return utils.ErroredResponse(err) - } - - ok, err := h.namespaceIsOwned(ctx, c, oldNs, tntList, req) - if err != nil { - return utils.ErroredResponse(err) - } - - if !ok { - recorder.Eventf(oldNs, corev1.EventTypeWarning, "OfflimitNamespace", "Namespace %s can not be patched", oldNs.GetName()) - - response := admission.Denied("Denied patch request for this namespace") - - return &response - } - - newNs := &corev1.Namespace{} - if err := decoder.Decode(req, newNs); err != nil { - return utils.ErroredResponse(err) - } - - o, err := json.Marshal(newNs.DeepCopy()) - if err != nil { - response := admission.Errored(http.StatusInternalServerError, err) - - return &response - } - - var refs []metav1.OwnerReference - - for _, ref := range oldNs.OwnerReferences { - if capsuleutils.IsTenantOwnerReference(ref) { - refs = append(refs, ref) - } - } - - for _, ref := range newNs.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(ref) { - refs = append(refs, ref) - } - } - - newNs.OwnerReferences = refs - - c, err := json.Marshal(newNs) - if err != nil { - response := admission.Errored(http.StatusInternalServerError, err) - - return &response - } - - response := admission.PatchResponseFromRaw(o, c) - - return &response - } -} - -func (h *ownerReferenceHandler) namespaceIsOwned(ctx context.Context, c client.Client, ns *corev1.Namespace, tenantList *capsulev1beta2.TenantList, req admission.Request) (bool, error) { - for _, tenant := range tenantList.Items { - for _, ownerRef := range ns.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(ownerRef) { - continue - } - - ok, err := utils.IsTenantOwner(ctx, c, &tenant, req.UserInfo, h.cfg.AllowServiceAccountPromotion()) - if err != nil { - return false, err - } - - if ownerRef.UID == tenant.UID && ok { - return true, nil - } - } - } - - return false, nil -} - -func (h *ownerReferenceHandler) setOwnerRef(ctx context.Context, req admission.Request, client client.Client, decoder admission.Decoder, recorder record.EventRecorder) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return &response - } - - ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return &response - } - - tnt, errResponse := getNamespaceTenant(ctx, client, ns, req, h.cfg, recorder) - if errResponse != nil { - return errResponse - } - - if tnt == nil { - response := admission.Denied("Unable to assign namespace to tenant. Please use " + ln + " label when creating a namespace") - - return &response - } - - response := h.patchResponseForOwnerRef(tnt.DeepCopy(), ns, recorder) - - return &response -} - -func (h *ownerReferenceHandler) patchResponseForOwnerRef(tenant *capsulev1beta2.Tenant, ns *corev1.Namespace, recorder record.EventRecorder) admission.Response { - scheme := runtime.NewScheme() - _ = capsulev1beta2.AddToScheme(scheme) - _ = corev1.AddToScheme(scheme) - - o, err := json.Marshal(ns.DeepCopy()) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - if err = controllerutil.SetOwnerReference(tenant, ns, scheme); err != nil { - recorder.Eventf(tenant, corev1.EventTypeWarning, "Error", "Namespace %s cannot be assigned to the desired Tenant", ns.GetName()) - - return admission.Errored(http.StatusInternalServerError, err) - } - - recorder.Eventf(tenant, corev1.EventTypeNormal, "NamespaceCreationWebhook", "Namespace %s has been assigned to the desired Tenant", ns.GetName()) - - c, err := json.Marshal(ns) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - return admission.PatchResponseFromRaw(o, c) -} diff --git a/pkg/webhook/namespace/mutation/utils.go b/pkg/webhook/namespace/mutation/utils.go deleted file mode 100644 index 022d1e78..00000000 --- a/pkg/webhook/namespace/mutation/utils.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package mutation - -import ( - "context" - "fmt" - "net/http" - "sort" - "strings" - - v1 "k8s.io/api/authentication/v1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/meta" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type sortedTenants []capsulev1beta2.Tenant - -func (s sortedTenants) Len() int { - return len(s) -} - -func (s sortedTenants) Less(i, j int) bool { - return len(s[i].GetName()) < len(s[j].GetName()) -} - -func (s sortedTenants) Swap(i, j int) { - s[i], s[j] = s[j], s[i] -} - -// getNamespaceTenant returns namespace owner tenant. -func getNamespaceTenant( - ctx context.Context, - client client.Client, - ns *corev1.Namespace, - req admission.Request, - cfg configuration.Configuration, - recorder record.EventRecorder, -) (*capsulev1beta2.Tenant, *admission.Response) { - tenant, errResponse := getTenantByLabels(ctx, client, ns, req, cfg, recorder) - if errResponse != nil { - return nil, errResponse - } - - if tenant == nil { - tenant, errResponse = getTenantByUserInfo(ctx, ns, req.UserInfo, client, cfg) - if errResponse != nil { - return nil, errResponse - } - } - - return tenant, nil -} - -// getTenantByLabels returns tenant from labels. -func getTenantByLabels( - ctx context.Context, - client client.Client, - ns *corev1.Namespace, - req admission.Request, - cfg configuration.Configuration, - recorder record.EventRecorder, -) (*capsulev1beta2.Tenant, *admission.Response) { - ln, err := capsuleutils.GetTypeLabel(&capsulev1beta2.Tenant{}) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - // Get tenant from namespace labels. - if label, ok := ns.Labels[ln]; ok { - // retrieving the selected Tenant - tnt := &capsulev1beta2.Tenant{} - if err = client.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - ok, err := utils.IsTenantOwner(ctx, client, tnt, req.UserInfo, cfg.AllowServiceAccountPromotion()) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - if !ok { - recorder.Eventf(tnt, corev1.EventTypeWarning, "NonOwnedTenant", "Namespace %s cannot be assigned to the current Tenant", ns.GetName()) - - response := admission.Denied("Cannot assign the desired namespace to a non-owned Tenant") - - return nil, &response - } - - return tnt, nil - } - - // Nothing found in the labels. - return nil, nil -} - -// getTenantByUserInfo returns tenant list associated with admission request userinfo. -// -//nolint:nestif -func getTenantByUserInfo( - ctx context.Context, - ns *corev1.Namespace, - userInfo v1.UserInfo, - clt client.Client, - cfg configuration.Configuration, -) (*capsulev1beta2.Tenant, *admission.Response) { - var tenants sortedTenants - - // User tenants. - userTntList := &capsulev1beta2.TenantList{} - fields := client.MatchingFields{ - ".spec.owner.ownerkind": fmt.Sprintf("User:%s", userInfo.Username), - } - - err := clt.List(ctx, userTntList, fields) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - tenants = userTntList.Items - - // ServiceAccount tenants. - if strings.HasPrefix(userInfo.Username, "system:serviceaccount:") { - saTntList := &capsulev1beta2.TenantList{} - fields = client.MatchingFields{ - ".spec.owner.ownerkind": fmt.Sprintf("ServiceAccount:%s", userInfo.Username), - } - - err = clt.List(ctx, saTntList, fields) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - tenants = append(tenants, saTntList.Items...) - - if cfg.AllowServiceAccountPromotion() { - if tnt, err := resolveServiceAccountActor(ctx, ns, userInfo, clt, cfg); err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } else if tnt != nil { - tenants = append(tenants, *tnt) - } - } - } - - // Group tenants. - groupTntList := &capsulev1beta2.TenantList{} - - for _, group := range userInfo.Groups { - fields = client.MatchingFields{ - ".spec.owner.ownerkind": fmt.Sprintf("Group:%s", group), - } - - err = clt.List(ctx, groupTntList, fields) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return nil, &response - } - - tenants = append(tenants, groupTntList.Items...) - } - - sort.Sort(sort.Reverse(tenants)) - - if len(tenants) == 0 { - response := admission.Denied("You do not have any Tenant assigned: please, reach out to the system administrators") - - return nil, &response - } - - if len(tenants) == 1 { - // Check if namespace needs Tenant name prefix - if !validateNamespacePrefix(ns, &tenants[0]) { - response := admission.Denied(fmt.Sprintf("The Namespace name must start with '%s-' when ForceTenantPrefix is enabled in the Tenant.", tenants[0].GetName())) - - return nil, &response - } - - return &tenants[0], nil - } - - if cfg.ForceTenantPrefix() { - for _, tnt := range tenants { - if strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) { - return &tnt, nil - } - } - - response := admission.Denied("The Namespace prefix used doesn't match any available Tenant") - - return nil, &response - } - - return nil, nil -} - -func resolveServiceAccountActor( - ctx context.Context, - ns *corev1.Namespace, - userInfo v1.UserInfo, - clt client.Client, - cfg configuration.Configuration, -) (tnt *capsulev1beta2.Tenant, err error) { - parts := strings.Split(userInfo.Username, ":") - if len(parts) != 4 { - return tnt, err - } - - namespace, saName := parts[2], parts[3] - - sa := &corev1.ServiceAccount{} - if err = clt.Get(ctx, client.ObjectKey{Namespace: namespace, Name: saName}, sa); err != nil { - if apierrors.IsNotFound(err) { - return tnt, err - } - - return tnt, err - } - - if meta.OwnerPromotionLabelTriggers(ns) { - return tnt, err - } - - tntList := &capsulev1beta2.TenantList{} - if err = clt.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), - }); err != nil { - return tnt, err - } - - if len(tntList.Items) > 0 { - tnt = &tntList.Items[0] - } - - return tnt, err -} - -func validateNamespacePrefix(ns *corev1.Namespace, tenant *capsulev1beta2.Tenant) bool { - // Check if ForceTenantPrefix is true - if tenant.Spec.ForceTenantPrefix != nil && *tenant.Spec.ForceTenantPrefix { - if !strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tenant.GetName())) { - return false - } - } - - return true -} diff --git a/pkg/webhook/namespace/validation/freezed.go b/pkg/webhook/namespace/validation/freezed.go deleted file mode 100644 index 234c1cd0..00000000 --- a/pkg/webhook/namespace/validation/freezed.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package validation - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/configuration" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type freezedHandler struct { - configuration configuration.Configuration -} - -func FreezeHandler(configuration configuration.Configuration) capsulewebhook.Handler { - return &freezedHandler{configuration: configuration} -} - -func (r *freezedHandler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - for _, objectRef := range ns.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(objectRef) { - continue - } - - // retrieving the selected Tenant - tnt := &capsulev1beta2.Tenant{} - if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { - return utils.ErroredResponse(err) - } - - if tnt.Spec.Cordoned { - recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName()) - - response := admission.Denied("the selected Tenant is freezed") - - return &response - } - } - // creating NS that is not bounded to any Tenant - return nil - } -} - -func (r *freezedHandler) OnDelete(c client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", req.Name), - }); err != nil { - return utils.ErroredResponse(err) - } - - if len(tntList.Items) == 0 { - return nil - } - - tnt := tntList.Items[0] - - if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserNames(), r.configuration.UserGroups(), r.configuration.IgnoreUserWithGroups()) { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name) - - response := admission.Denied("the selected Tenant is freezed") - - return &response - } - - return nil - } -} - -func (r *freezedHandler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - tntList := &capsulev1beta2.TenantList{} - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", ns.Name), - }); err != nil { - return utils.ErroredResponse(err) - } - - if len(tntList.Items) == 0 { - return nil - } - - tnt := tntList.Items[0] - - if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, c, r.configuration.UserNames(), r.configuration.UserGroups(), r.configuration.IgnoreUserWithGroups()) { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName()) - - response := admission.Denied("the selected Tenant is freezed") - - return &response - } - - return nil - } -} diff --git a/pkg/webhook/namespace/validation/prefix.go b/pkg/webhook/namespace/validation/prefix.go deleted file mode 100644 index 32549d45..00000000 --- a/pkg/webhook/namespace/validation/prefix.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package validation - -import ( - "context" - "fmt" - "strings" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/configuration" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type prefixHandler struct { - configuration configuration.Configuration -} - -func PrefixHandler(configuration configuration.Configuration) capsulewebhook.Handler { - return &prefixHandler{ - configuration: configuration, - } -} - -func (r *prefixHandler) OnCreate(clt client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - if exp, _ := r.configuration.ProtectedNamespaceRegexp(); exp != nil { - if matched := exp.MatchString(ns.GetName()); matched { - response := admission.Denied(fmt.Sprintf("Creating namespaces with name matching %s regexp is not allowed; please, reach out to the system administrators", exp.String())) - - return &response - } - } - - if r.configuration.ForceTenantPrefix() { - tnt := &capsulev1beta2.Tenant{} - - for _, or := range ns.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(or) { - continue - } - - // retrieving the selected Tenant - if err := clt.Get(ctx, types.NamespacedName{Name: or.Name}, tnt); err != nil { - return utils.ErroredResponse(err) - } - - // Check for Tenant-level ForceTenantPrefix override - if tnt.Spec.ForceTenantPrefix != nil && !*tnt.Spec.ForceTenantPrefix { - return nil - } - - if e := fmt.Sprintf("%s-%s", tnt.GetName(), ns.GetName()); !strings.HasPrefix(ns.GetName(), fmt.Sprintf("%s-", tnt.GetName())) { - recorder.Eventf(tnt, corev1.EventTypeWarning, "InvalidTenantPrefix", "Namespace %s does not match the expected prefix for the current Tenant", ns.GetName()) - - response := admission.Denied(fmt.Sprintf("The namespace doesn't match the tenant prefix, expected %s", e)) - - return &response - } - } - } - - return nil - } -} - -func (r *prefixHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} - -func (r *prefixHandler) OnUpdate(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} diff --git a/pkg/webhook/namespace/validation/quota.go b/pkg/webhook/namespace/validation/quota.go deleted file mode 100644 index dc8b9031..00000000 --- a/pkg/webhook/namespace/validation/quota.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package validation - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - capsuleutils "github.com/projectcapsule/capsule/pkg/utils" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type quotaHandler struct{} - -func QuotaHandler() capsulewebhook.Handler { - return "aHandler{} -} - -func (r *quotaHandler) OnCreate(client client.Client, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - for _, objectRef := range ns.OwnerReferences { - if !capsuleutils.IsTenantOwnerReference(objectRef) { - continue - } - - // retrieving the selected Tenant - tnt := &capsulev1beta2.Tenant{} - if err := client.Get(ctx, types.NamespacedName{Name: objectRef.Name}, tnt); err != nil { - return utils.ErroredResponse(err) - } - - if tnt.IsFull() { - // Checking if the Namespace already exists. - // If this is the case, no need to return the quota exceeded error: - // the Kubernetes API Server will return an AlreadyExists error, - // adhering more to the native Kubernetes experience. - if err := client.Get(ctx, types.NamespacedName{Name: ns.Name}, &corev1.Namespace{}); err == nil { - return nil - } - - recorder.Eventf(tnt, corev1.EventTypeWarning, "NamespaceQuotaExceded", "Namespace %s cannot be attached, quota exceeded for the current Tenant", ns.GetName()) - - response := admission.Denied(NewNamespaceQuotaExceededError().Error()) - - return &response - } - } - // creating NS that is not bounded to any Tenant - return nil - } -} - -func (r *quotaHandler) OnDelete(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} - -func (r *quotaHandler) OnUpdate(client.Client, admission.Decoder, record.EventRecorder) capsulewebhook.Func { - return func(context.Context, admission.Request) *admission.Response { - return nil - } -} diff --git a/pkg/webhook/tenant/validation/cordoning.go b/pkg/webhook/tenant/validation/cordoning.go deleted file mode 100644 index 2f8fb591..00000000 --- a/pkg/webhook/tenant/validation/cordoning.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package validation - -import ( - "context" - "fmt" - "strings" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/fields" - "k8s.io/client-go/tools/record" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" - "github.com/projectcapsule/capsule/pkg/configuration" - capsulewebhook "github.com/projectcapsule/capsule/pkg/webhook" - "github.com/projectcapsule/capsule/pkg/webhook/utils" -) - -type cordoningHandler struct { - configuration configuration.Configuration -} - -func CordoningHandler(configuration configuration.Configuration) capsulewebhook.Handler { - return &cordoningHandler{ - configuration: configuration, - } -} - -func (h *cordoningHandler) OnCreate(client client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return h.cordonHandler(ctx, client, req, recorder) - } -} - -func (h *cordoningHandler) OnDelete(client client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return h.cordonHandler(ctx, client, req, recorder) - } -} - -func (h *cordoningHandler) OnUpdate(client client.Client, _ admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { - return func(ctx context.Context, req admission.Request) *admission.Response { - return h.cordonHandler(ctx, client, req, recorder) - } -} - -func (h *cordoningHandler) cordonHandler(ctx context.Context, clt client.Client, req admission.Request, recorder record.EventRecorder) *admission.Response { - tntList := &capsulev1beta2.TenantList{} - - if err := clt.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", req.Namespace), - }); err != nil { - return utils.ErroredResponse(err) - } - // resource is not inside a Tenant namespace - if len(tntList.Items) == 0 { - return nil - } - - tnt := tntList.Items[0] - if tnt.Spec.Cordoned && utils.IsCapsuleUser(ctx, req, clt, h.configuration.UserNames(), h.configuration.UserGroups(), h.configuration.IgnoreUserWithGroups()) { - recorder.Eventf(&tnt, corev1.EventTypeWarning, "TenantFreezed", "%s %s/%s cannot be %sd, current Tenant is freezed", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation))) - - response := admission.Denied(fmt.Sprintf("tenant %s is freezed: please, reach out to the system administrator", tnt.GetName())) - - return &response - } - - return nil -} diff --git a/pkg/webhook/utils/tenant_by_field.go b/pkg/webhook/utils/tenant_by_field.go deleted file mode 100644 index ea8a895d..00000000 --- a/pkg/webhook/utils/tenant_by_field.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020-2025 Project Capsule Authors -// SPDX-License-Identifier: Apache-2.0 - -package utils - -import ( - "context" - - "k8s.io/apimachinery/pkg/fields" - "sigs.k8s.io/controller-runtime/pkg/client" - - capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" -) - -func TenantByStatusNamespace(ctx context.Context, c client.Client, namespace string) (*capsulev1beta2.Tenant, error) { - tntList := &capsulev1beta2.TenantList{} - tnt := &capsulev1beta2.Tenant{} - - if err := c.List(ctx, tntList, client.MatchingFieldsSelector{ - Selector: fields.OneTermEqualSelector(".status.namespaces", namespace), - }); err != nil { - return nil, err - } - - if len(tntList.Items) == 0 { - return tnt, nil - } - - *tnt = tntList.Items[0] - - return tnt, nil -}