mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 09:59:57 +00:00
feat(tenant): add dedicated tenantowner crd (#1764)
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
7
PROJECT
7
PROJECT
@@ -64,4 +64,11 @@ resources:
|
||||
kind: ResourcePoolClaim
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
- api:
|
||||
crdVersion: v1
|
||||
domain: clastix.io
|
||||
group: capsule
|
||||
kind: TenantOwner
|
||||
path: github.com/projectcapsule/capsule/api/v1beta2
|
||||
version: v1beta2
|
||||
version: "3"
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/misc"
|
||||
)
|
||||
|
||||
// ResourcePoolSpec.
|
||||
type ResourcePoolSpec struct {
|
||||
// Selector to match the namespaces that should be managed by the GlobalResourceQuota
|
||||
Selectors []api.NamespaceSelector `json:"selectors,omitempty"`
|
||||
Selectors []misc.NamespaceSelector `json:"selectors,omitempty"`
|
||||
// Define the resourcequota served by this resourcepool.
|
||||
Quota corev1.ResourceQuotaSpec `json:"quota"`
|
||||
// The Defaults given for each namespace, the default is not counted towards the total allocation
|
||||
|
||||
@@ -46,11 +46,13 @@ func (in *Tenant) ConvertFrom(raw conversion.Hub) error {
|
||||
}
|
||||
|
||||
in.Spec.Owners = append(in.Spec.Owners, api.OwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.OwnerKind(owner.Kind),
|
||||
Name: owner.Name,
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.OwnerKind(owner.Kind),
|
||||
Name: owner.Name,
|
||||
},
|
||||
ClusterRoles: owner.GetRoles(*src, index),
|
||||
},
|
||||
ClusterRoles: owner.GetRoles(*src, index),
|
||||
ProxyOperations: proxySettings,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,15 +4,76 @@
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
)
|
||||
|
||||
func (in *Tenant) CollectOwners(ctx context.Context, c client.Client, allowPromotion bool, admins api.UserListSpec) (api.OwnerStatusListSpec, error) {
|
||||
owners := in.Spec.Owners.ToStatusOwners()
|
||||
|
||||
// Promoted ServiceAccounts
|
||||
if allowPromotion && len(in.Status.Namespaces) > 0 {
|
||||
saList := &corev1.ServiceAccountList{}
|
||||
if err := c.List(ctx, saList,
|
||||
client.MatchingLabels{
|
||||
meta.OwnerPromotionLabel: meta.OwnerPromotionLabelTrigger,
|
||||
},
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, sa := range saList.Items {
|
||||
for _, ns := range in.Status.Namespaces {
|
||||
if sa.GetNamespace() != ns {
|
||||
continue
|
||||
}
|
||||
|
||||
owners.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: serviceaccount.ServiceAccountUsernamePrefix + sa.Namespace + ":" + sa.Name,
|
||||
},
|
||||
ClusterRoles: []string{
|
||||
api.ProvisionerRoleName,
|
||||
api.DeleterRoleName,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Administrators
|
||||
for _, a := range admins {
|
||||
owners.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: a,
|
||||
ClusterRoles: []string{
|
||||
api.DeleterRoleName,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Dedicated Owner Objects
|
||||
listed, err := in.Spec.Permissions.ListMatchingOwners(ctx, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, o := range listed {
|
||||
owners.Upsert(o.Spec.CoreOwnerSpec)
|
||||
}
|
||||
|
||||
return owners, nil
|
||||
}
|
||||
|
||||
func (in *Tenant) IsFull() bool {
|
||||
// we don't have limits on assigned Namespaces
|
||||
if in.Spec.NamespaceOptions == nil || in.Spec.NamespaceOptions.Quota == nil {
|
||||
|
||||
@@ -15,25 +15,31 @@ var tenant = &Tenant{
|
||||
Spec: TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "User",
|
||||
Name: "user1",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "User",
|
||||
Name: "user1",
|
||||
},
|
||||
ClusterRoles: []string{"cluster-admin", "read-only"},
|
||||
},
|
||||
ClusterRoles: []string{"cluster-admin", "read-only"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "Group",
|
||||
Name: "group1",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "Group",
|
||||
Name: "group1",
|
||||
},
|
||||
ClusterRoles: []string{"edit"},
|
||||
},
|
||||
ClusterRoles: []string{"edit"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "service",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "service",
|
||||
},
|
||||
ClusterRoles: []string{"read-only"},
|
||||
},
|
||||
ClusterRoles: []string{"read-only"},
|
||||
},
|
||||
},
|
||||
AdditionalRoleBindings: []api.AdditionalRoleBindingsSpec{
|
||||
|
||||
@@ -6,6 +6,7 @@ package v1beta2
|
||||
import (
|
||||
k8stypes "k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
)
|
||||
|
||||
@@ -22,6 +23,8 @@ type TenantStatus struct {
|
||||
// Allowed Cluster Objects within Tenant
|
||||
TenantAvailableStatus `json:",inline"`
|
||||
|
||||
// Collected owners for this tenant
|
||||
Owners api.OwnerStatusListSpec `json:"owners,omitempty"`
|
||||
// +kubebuilder:default=Active
|
||||
// The operational state of the Tenant. Possible values are "Active", "Cordoned".
|
||||
State tenantState `json:"state"`
|
||||
|
||||
@@ -4,13 +4,19 @@
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/misc"
|
||||
)
|
||||
|
||||
// TenantSpec defines the desired state of Tenant.
|
||||
type TenantSpec struct {
|
||||
// Specify Permissions for the Tenant.
|
||||
Permissions Permissions `json:"permissions,omitempty"`
|
||||
// Specifies the owners of the Tenant.
|
||||
// Optional
|
||||
Owners api.OwnerListSpec `json:"owners,omitempty"`
|
||||
@@ -72,6 +78,21 @@ type TenantSpec struct {
|
||||
ForceTenantPrefix *bool `json:"forceTenantPrefix,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
// Matches TenantOwner objects which are promoted to owners of this tenant
|
||||
// The elements are OR operations and independent. You can see the resulting Tenant Owners
|
||||
// in the Status.Owners specification of the Tenant.
|
||||
MatchOwners []*metav1.LabelSelector `json:"matchOwners,omitempty"`
|
||||
}
|
||||
|
||||
func (p *Permissions) ListMatchingOwners(
|
||||
ctx context.Context,
|
||||
c client.Client,
|
||||
opts ...client.ListOption,
|
||||
) ([]*TenantOwner, error) {
|
||||
return misc.ListBySelectors[*TenantOwner](ctx, c, &TenantOwnerList{}, p.MatchOwners)
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:storageversion
|
||||
// +kubebuilder:subresource:status
|
||||
|
||||
53
api/v1beta2/tenantowner_types.go
Normal file
53
api/v1beta2/tenantowner_types.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package v1beta2
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// TenantOwnerSpec defines the desired state of TenantOwner.
|
||||
type TenantOwnerSpec struct {
|
||||
api.CoreOwnerSpec `json:",inline"`
|
||||
}
|
||||
|
||||
// TenantOwnerStatus defines the observed state of TenantOwner.
|
||||
type TenantOwnerStatus struct{}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// +kubebuilder:resource:scope=Cluster
|
||||
|
||||
// TenantOwner is the Schema for the tenantowners API.
|
||||
type TenantOwner struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// metadata is a standard object metadata.
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitzero"`
|
||||
|
||||
// spec defines the desired state of TenantOwner.
|
||||
// +required
|
||||
Spec TenantOwnerSpec `json:"spec"`
|
||||
|
||||
// status defines the observed state of TenantOwner.
|
||||
// +optional
|
||||
Status TenantOwnerStatus `json:"status,omitzero"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
|
||||
// TenantOwnerList contains a list of TenantOwner.
|
||||
type TenantOwnerList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitzero"`
|
||||
|
||||
Items []TenantOwner `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&TenantOwner{}, &TenantOwnerList{})
|
||||
}
|
||||
@@ -10,6 +10,7 @@ package v1beta2
|
||||
import (
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
"github.com/projectcapsule/capsule/pkg/api/misc"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/rbac/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -410,6 +411,32 @@ 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 *Permissions) DeepCopyInto(out *Permissions) {
|
||||
*out = *in
|
||||
if in.MatchOwners != nil {
|
||||
in, out := &in.MatchOwners, &out.MatchOwners
|
||||
*out = make([]*metav1.LabelSelector, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Permissions.
|
||||
func (in *Permissions) DeepCopy() *Permissions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Permissions)
|
||||
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) {
|
||||
{
|
||||
@@ -727,7 +754,7 @@ func (in *ResourcePoolSpec) DeepCopyInto(out *ResourcePoolSpec) {
|
||||
*out = *in
|
||||
if in.Selectors != nil {
|
||||
in, out := &in.Selectors, &out.Selectors
|
||||
*out = make([]api.NamespaceSelector, len(*in))
|
||||
*out = make([]misc.NamespaceSelector, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
@@ -982,6 +1009,96 @@ func (in *TenantList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantOwner) DeepCopyInto(out *TenantOwner) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
out.Status = in.Status
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantOwner.
|
||||
func (in *TenantOwner) DeepCopy() *TenantOwner {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantOwner)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantOwner) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantOwnerList) DeepCopyInto(out *TenantOwnerList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]TenantOwner, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantOwnerList.
|
||||
func (in *TenantOwnerList) DeepCopy() *TenantOwnerList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantOwnerList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TenantOwnerList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantOwnerSpec) DeepCopyInto(out *TenantOwnerSpec) {
|
||||
*out = *in
|
||||
in.CoreOwnerSpec.DeepCopyInto(&out.CoreOwnerSpec)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantOwnerSpec.
|
||||
func (in *TenantOwnerSpec) DeepCopy() *TenantOwnerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantOwnerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantOwnerStatus) DeepCopyInto(out *TenantOwnerStatus) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantOwnerStatus.
|
||||
func (in *TenantOwnerStatus) DeepCopy() *TenantOwnerStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TenantOwnerStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantResource) DeepCopyInto(out *TenantResource) {
|
||||
*out = *in
|
||||
@@ -1092,6 +1209,7 @@ func (in *TenantResourceStatus) DeepCopy() *TenantResourceStatus {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
|
||||
*out = *in
|
||||
in.Permissions.DeepCopyInto(&out.Permissions)
|
||||
if in.Owners != nil {
|
||||
in, out := &in.Owners, &out.Owners
|
||||
*out = make(api.OwnerListSpec, len(*in))
|
||||
@@ -1179,6 +1297,13 @@ func (in *TenantSpec) DeepCopy() *TenantSpec {
|
||||
func (in *TenantStatus) DeepCopyInto(out *TenantStatus) {
|
||||
*out = *in
|
||||
in.TenantAvailableStatus.DeepCopyInto(&out.TenantAvailableStatus)
|
||||
if in.Owners != nil {
|
||||
in, out := &in.Owners, &out.Owners
|
||||
*out = make(api.OwnerStatusListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Namespaces != nil {
|
||||
in, out := &in.Namespaces, &out.Namespaces
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
74
charts/capsule/crds/capsule.clastix.io_tenantowners.yaml
Normal file
74
charts/capsule/crds/capsule.clastix.io_tenantowners.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.19.0
|
||||
name: tenantowners.capsule.clastix.io
|
||||
spec:
|
||||
group: capsule.clastix.io
|
||||
names:
|
||||
kind: TenantOwner
|
||||
listKind: TenantOwnerList
|
||||
plural: tenantowners
|
||||
singular: tenantowner
|
||||
scope: Cluster
|
||||
versions:
|
||||
- name: v1beta2
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: TenantOwner is the Schema for the tenantowners API.
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: spec defines the desired state of TenantOwner.
|
||||
properties:
|
||||
clusterRoles:
|
||||
default:
|
||||
- admin
|
||||
- capsule-namespace-deleter
|
||||
description: Defines additional cluster-roles for the specific Owner.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
status:
|
||||
description: status defines the observed state of TenantOwner.
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
@@ -2140,6 +2140,65 @@ spec:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
permissions:
|
||||
description: Specify Permissions for the Tenant.
|
||||
properties:
|
||||
matchOwners:
|
||||
description: |-
|
||||
Matches TenantOwner objects which are promoted to owners of this tenant
|
||||
The elements are OR operations and independent. You can see the resulting Tenant Owners
|
||||
in the Status.Owners specification of the Tenant.
|
||||
items:
|
||||
description: |-
|
||||
A label selector is a label query over a set of resources. The result of matchLabels and
|
||||
matchExpressions are ANDed. An empty label selector matches all objects. A null
|
||||
label selector matches no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: |-
|
||||
A label selector requirement is a selector that contains values, a key, and an operator that
|
||||
relates the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: |-
|
||||
operator represents a key's relationship to a set of values.
|
||||
Valid operators are In, NotIn, Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: |-
|
||||
values is an array of string values. If the operator is In or NotIn,
|
||||
the values array must be non-empty. If the operator is Exists or DoesNotExist,
|
||||
the values array must be empty. This array is replaced during a strategic
|
||||
merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-type: atomic
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: |-
|
||||
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
|
||||
map is equivalent to an element of matchExpressions, whose key field is "key", the
|
||||
operator is "In", and the values array contains only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
x-kubernetes-map-type: atomic
|
||||
type: array
|
||||
type: object
|
||||
podOptions:
|
||||
description: Specifies options for the Pods deployed in the Tenant
|
||||
namespaces, such as additional metadata.
|
||||
@@ -2596,6 +2655,35 @@ spec:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
owners:
|
||||
description: Collected owners for this tenant
|
||||
items:
|
||||
properties:
|
||||
clusterRoles:
|
||||
default:
|
||||
- admin
|
||||
- capsule-namespace-deleter
|
||||
description: Defines additional cluster-roles for the specific
|
||||
Owner.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
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
|
||||
size:
|
||||
description: How many namespaces are assigned to the Tenant.
|
||||
type: integer
|
||||
|
||||
@@ -31,6 +31,7 @@ rules:
|
||||
- tenantresources.capsule.clastix.io
|
||||
- globaltenantresources.capsule.clastix.io
|
||||
- tenants.capsule.clastix.io
|
||||
- tenantowners.capsule.clastix.io
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
|
||||
@@ -282,7 +282,7 @@ func main() {
|
||||
tenantvalidation.ServiceAccountNameHandler(),
|
||||
tenantvalidation.ForbiddenAnnotationsRegexHandler(),
|
||||
tenantvalidation.ProtectedHandler(),
|
||||
tenantvalidation.WarningHandler(),
|
||||
tenantvalidation.WarningHandler(cfg),
|
||||
),
|
||||
route.NamespaceValidation(
|
||||
namespacevalidation.NamespaceHandler(
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("creating a Namespace with an additional Role Binding", Label("
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "dale",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "dale",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("Administrators", Label("namespace", "permissions"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "paul",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "paul",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -43,9 +45,11 @@ var _ = Describe("Administrators", Label("namespace", "permissions"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("enforcing an allowed set of Service external IPs", Label("tena
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,9 +35,11 @@ var _ = Describe("enforcing a Container Registry", Label("tenant", "images", "re
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "matt",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "matt",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,15 +25,19 @@ var _ = Describe("creating a Namespace as Tenant owner with custom --capsule-gro
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -33,9 +33,11 @@ var _ = Describe("when Tenant limits custom Resource Quota", Label("resourcequot
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "resource",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "resource",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating an ExternalName service when it is disabled for Tenan
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,9 +31,11 @@ var _ = Describe("creating an Ingress with a wildcard when it is denied for the
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "scott",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "scott",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating a LoadBalancer service when it is disabled for Tenant
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "amazon",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "amazon",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", L
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,18 +22,22 @@ var _ = Describe("defining dynamic Tenant Owner Cluster Roles", Label("tenant"),
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "User",
|
||||
Name: "michonne",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "User",
|
||||
Name: "michonne",
|
||||
},
|
||||
ClusterRoles: []string{"editor", "manager"},
|
||||
},
|
||||
ClusterRoles: []string{"editor", "manager"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "kingdom",
|
||||
Kind: "Group",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "kingdom",
|
||||
Kind: "Group",
|
||||
},
|
||||
ClusterRoles: []string{"readonly"},
|
||||
},
|
||||
ClusterRoles: []string{"readonly"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating a LoadBalancer service when it is enabled for Tenant"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "netflix",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "netflix",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("creating a nodePort service when it is enabled for Tenant", La
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "google",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -68,9 +68,11 @@ var _ = Describe("creating a tenant with various forbidden regexes", Label("tena
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,9 +23,11 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te
|
||||
ForceTenantPrefix: &[]bool{true}[0],
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -39,9 +41,11 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement at Te
|
||||
ForceTenantPrefix: &[]bool{false}[0],
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,9 +22,11 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Lab
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -37,9 +39,11 @@ var _ = Describe("creating a Namespace with Tenant name prefix enforcement", Lab
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -79,9 +79,11 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gateway-default-and-label-selector",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gateway-default-and-label-selector",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -110,9 +112,11 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gateway-with-label-selector-only",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gateway-with-label-selector-only",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -140,9 +144,11 @@ var _ = Describe("when Tenant handles Gateway classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-gateway-no-restrictions",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-gateway-no-restrictions",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -34,9 +34,11 @@ var _ = Describe("Creating a GlobalTenantResource object", func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "solar-user",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "solar-user",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -53,9 +55,11 @@ var _ = Describe("Creating a GlobalTenantResource object", func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "wind-user",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "wind-user",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("enforcing some defined ImagePullPolicy", Label("tenant", "imag
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alex",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alex",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("enforcing a defined ImagePullPolicy", Label("tenant", "images"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "axel",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "axel",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when Tenant handles Ingress classes with extensions/v1beta1",
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,9 +32,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-selector",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-selector",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -63,9 +65,11 @@ var _ = Describe("when Tenant handles Ingress classes with networking.k8s.io/v1"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-default",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-default",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", Lab
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant-one",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant-one",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -45,9 +47,11 @@ var _ = Describe("when handling Cluster scoped Ingress hostnames collision", Lab
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant-two",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant-two",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when disabling Ingress hostnames collision", Label("ingress"),
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-disabled",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-disabled",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when handling Namespace scoped Ingress hostnames collision", L
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-namespace",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-namespace",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when handling Tenant scoped Ingress hostnames collision", Labe
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ingress-tenant",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when Tenant handles Ingress hostnames", Label("ingress"), func
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "hostname",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "hostname",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -20,9 +20,11 @@ var _ = Describe("creating a Namespace creation with no Tenant assigned", Label(
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "missing",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "missing",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -34,9 +34,11 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace"),
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "charlie",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "charlie",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,15 +26,19 @@ var _ = Describe("creating several Namespaces for a Tenant", Label("namespace",
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:attacker-system:attacker",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: "ServiceAccount",
|
||||
Name: "system:serviceaccount:attacker-system:attacker",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,9 +31,11 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,9 +31,11 @@ var _ = Describe("creating a Namespace for a Tenant with additional metadata", L
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("creating namespace with status lifecycle", Label("namespace",
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,9 +35,11 @@ var _ = Describe("creating a Namespace with user-specified labels and annotation
|
||||
},
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,21 +22,27 @@ var _ = Describe("creating a Namespaces as different type of Tenant owners", Lab
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "Group",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "Group",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "system:serviceaccount:new-namespace-sa:default",
|
||||
Kind: "ServiceAccount",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "system:serviceaccount:new-namespace-sa:default",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,9 +29,11 @@ var _ = Describe("modifying node labels and annotations", Label("config", "nodes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -23,9 +23,11 @@ var _ = Describe("creating a Namespace in over-quota of three", Label("namespace
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bob",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("when Tenant owner interacts with the webhooks", Label("tenant"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ruby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "ruby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
531
e2e/owners_test.go
Normal file
531
e2e/owners_test.go
Normal file
@@ -0,0 +1,531 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
var _ = Describe("Owners", Label("tenant", "permissions", "owners"), func() {
|
||||
originConfig := &capsulev1beta2.CapsuleConfiguration{}
|
||||
|
||||
tnt1 := &capsulev1beta2.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-owners-1",
|
||||
},
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Permissions: capsulev1beta2.Permissions{
|
||||
MatchOwners: []*metav1.LabelSelector{
|
||||
{
|
||||
MatchLabels: map[string]string{
|
||||
"customer": "x",
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchLabels: map[string]string{
|
||||
"team": "devops",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-owners-1",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-owners-1-group",
|
||||
Kind: "Group",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tnt2 := &capsulev1beta2.Tenant{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-owners-2",
|
||||
},
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Permissions: capsulev1beta2.Permissions{
|
||||
MatchOwners: []*metav1.LabelSelector{
|
||||
{
|
||||
MatchLabels: map[string]string{
|
||||
"customer": "x",
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchLabels: map[string]string{
|
||||
"team": "infrastructure",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-owners-2",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-owners-2-group",
|
||||
Kind: "Group",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
Kind: "ServiceAccount",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ownersInfra := &capsulev1beta2.TenantOwner{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-owners-infra",
|
||||
Labels: map[string]string{
|
||||
"team": "infrastructure",
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.TenantOwnerSpec{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:administrators",
|
||||
},
|
||||
ClusterRoles: []string{
|
||||
"mega-admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ownersDevops := &capsulev1beta2.TenantOwner{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-owners-devops",
|
||||
Labels: map[string]string{
|
||||
"team": "devops",
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.TenantOwnerSpec{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:devops",
|
||||
},
|
||||
ClusterRoles: []string{
|
||||
"namespaced-admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ownersCommon := &capsulev1beta2.TenantOwner{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "e2e-owners-common",
|
||||
Labels: map[string]string{
|
||||
"team": "infrastructure",
|
||||
"customer": "x",
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.TenantOwnerSpec{
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{
|
||||
"service-admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
for _, tnt := range []*capsulev1beta2.TenantOwner{ownersInfra, ownersDevops, ownersCommon} {
|
||||
EventuallyCreation(func() error {
|
||||
tnt.ResourceVersion = ""
|
||||
|
||||
return k8sClient.Create(context.TODO(), tnt)
|
||||
}).Should(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
JustAfterEach(func() {
|
||||
for _, tnt := range []*capsulev1beta2.Tenant{tnt1, tnt2} {
|
||||
err := k8sClient.Delete(context.TODO(), tnt)
|
||||
Expect(client.IgnoreNotFound(err)).To(Succeed())
|
||||
}
|
||||
|
||||
for _, owners := range []*capsulev1beta2.TenantOwner{ownersInfra, ownersDevops, ownersCommon} {
|
||||
err := k8sClient.Delete(context.TODO(), owners)
|
||||
Expect(client.IgnoreNotFound(err)).To(Succeed())
|
||||
}
|
||||
})
|
||||
|
||||
It("Verify owners for", func() {
|
||||
By("checking owners (e2e-owners-1)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt1.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-1-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:devops",
|
||||
},
|
||||
ClusterRoles: []string{"namespaced-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter", "service-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-1",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-2)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt2.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-2-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:administrators",
|
||||
},
|
||||
ClusterRoles: []string{"mega-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter", "service-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-2",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("remove common tenant-owners", func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), ownersCommon)).Should(Succeed())
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-1)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt1.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-1-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:devops",
|
||||
},
|
||||
ClusterRoles: []string{"namespaced-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-1",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-2)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt2.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-2-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:administrators",
|
||||
},
|
||||
ClusterRoles: []string{"mega-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-2",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("remove admin tenant-owners", func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), ownersInfra)).Should(Succeed())
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-1)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt1.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-1-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "oidc:comp:devops",
|
||||
},
|
||||
ClusterRoles: []string{"namespaced-admin"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-1",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-2)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt2.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-2-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-2",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("remove admin tenant-owners", func() {
|
||||
Expect(k8sClient.Delete(context.TODO(), ownersDevops)).Should(Succeed())
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-1)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt1.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-1-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-1",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
|
||||
By("checking owners (e2e-owners-2)", func() {
|
||||
t := &capsulev1beta2.Tenant{}
|
||||
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt2.GetName()}, t)).Should(Succeed())
|
||||
|
||||
expectedOwners := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.GroupOwner,
|
||||
Name: "e2e-owners-2-group",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.ServiceAccountOwner,
|
||||
Name: "system:serviceaccount:capsule-system:capsule",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "e2e-owners-2",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
Expect(normalizeOwners(t.Status.Owners)).
|
||||
To(Equal(normalizeOwners(expectedOwners)))
|
||||
|
||||
VerifyTenantRoleBindings(t)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("adding metadata to Pod objects", Label("pod"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,9 +30,11 @@ var _ = Describe("enforcing a Priority Class", Label("pod", "classes"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "paul",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "paul",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -56,9 +58,11 @@ var _ = Describe("enforcing a Priority Class", Label("pod", "classes"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -85,9 +89,11 @@ var _ = Describe("enforcing a Priority Class", Label("pod", "classes"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-priority-class-no-restrictions",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-priority-class-no-restrictions",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,9 +31,11 @@ var _ = Describe("enforcing a Runtime Class", Label("pod", "classes", "current")
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "george",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -61,9 +63,11 @@ var _ = Describe("enforcing a Runtime Class", Label("pod", "classes", "current")
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: []api.OwnerSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-gateway-no-restrictions",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "e2e-gateway-no-restrictions",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jessica",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jessica",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -43,9 +45,11 @@ var _ = Describe("preventing PersistentVolume cross-tenant mount", Label("tenant
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "leto",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "leto",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating a Namespace with a protected Namespace regex enabled"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,9 +29,11 @@ var _ = Describe("exceeding a Tenant resource quota", Label("resourcequota"), fu
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bobby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "bobby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,8 +17,8 @@ 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/api/meta"
|
||||
"github.com/projectcapsule/capsule/pkg/api/misc"
|
||||
"github.com/projectcapsule/capsule/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -83,7 +83,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -522,7 +522,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -694,7 +694,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -950,7 +950,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -1292,7 +1292,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -1450,7 +1450,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -1558,7 +1558,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -1666,7 +1666,7 @@ var _ = Describe("ResourcePool Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
|
||||
@@ -17,6 +17,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/api/misc"
|
||||
)
|
||||
|
||||
var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
@@ -30,9 +31,11 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "wind-user",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "wind-user",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -99,7 +102,7 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
},
|
||||
},
|
||||
Spec: capsulev1beta2.ResourcePoolSpec{
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -302,7 +305,7 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
|
||||
DeleteBoundResources: ptr.To(false),
|
||||
},
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -492,7 +495,7 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
|
||||
DeleteBoundResources: ptr.To(false),
|
||||
},
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
@@ -521,7 +524,7 @@ var _ = Describe("ResourcePoolClaim Tests", Label("resourcepool"), func() {
|
||||
Config: capsulev1beta2.ResourcePoolSpecConfiguration{
|
||||
DeleteBoundResources: ptr.To(false),
|
||||
},
|
||||
Selectors: []api.NamespaceSelector{
|
||||
Selectors: []misc.NamespaceSelector{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
ctrlrbac "github.com/projectcapsule/capsule/internal/controllers/rbac"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
)
|
||||
@@ -33,9 +32,11 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label("
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "alice",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -280,7 +281,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label("
|
||||
|
||||
Eventually(func(g Gomega) []rbacv1.Subject {
|
||||
crb := &rbacv1.ClusterRoleBinding{}
|
||||
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ctrlrbac.ProvisionerRoleName}, crb)
|
||||
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: api.ProvisionerRoleName}, crb)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return crb.Subjects
|
||||
@@ -329,7 +330,7 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label("
|
||||
|
||||
Eventually(func(g Gomega) []rbacv1.Subject {
|
||||
crb := &rbacv1.ClusterRoleBinding{}
|
||||
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: ctrlrbac.ProvisionerRoleName}, crb)
|
||||
err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: api.ProvisionerRoleName}, crb)
|
||||
g.Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return crb.Subjects
|
||||
@@ -351,5 +352,4 @@ var _ = Describe("Promoting ServiceAccounts to Owners", Label("config"), Label("
|
||||
Expect(saClient.Delete(context.TODO(), secondNs)).To(Not(Succeed()))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -29,9 +29,11 @@ var _ = Describe("trying to escalate from a Tenant Namespace ServiceAccount", La
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "mario",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "mario",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,9 +26,11 @@ var _ = Describe("verify scalability", Label("scalability"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,9 +25,11 @@ var _ = Describe("creating a Namespace trying to select a third Tenant", Label("
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "undefined",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "undefined",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,9 +22,11 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -37,9 +39,11 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -52,9 +56,11 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -67,9 +73,11 @@ var _ = Describe("creating a Namespace without a Tenant selector when user owns
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "Group",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -26,9 +26,11 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -41,9 +43,11 @@ var _ = Describe("creating a Namespace with Tenant selector when user owns multi
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -34,9 +34,11 @@ var _ = Describe("creating a Service with user-specified labels and annotations"
|
||||
},
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -31,9 +31,11 @@ var _ = Describe("adding metadata to Service objects", Label("tenant", "service"
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "gatsby",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,9 +35,11 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "selector",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "selector",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -64,9 +66,11 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "default",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "default",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -90,9 +94,11 @@ var _ = Describe("when Tenant handles Storage classes", Label("tenant", "classes
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "no-restrictions",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "no-restrictions",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -27,9 +27,11 @@ var _ = Describe("cordoning a Tenant", Label("tenant"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jim",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jim",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,9 +35,11 @@ var _ = Describe("adding metadata to a Tenant", Label("tenant"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jim",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "jim",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,9 +22,11 @@ var _ = Describe("creating a Tenant with wrong name", Label("tenant"), func() {
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -24,9 +24,11 @@ var _ = Describe("Deleting a tenant with protected annotation", Label("tenant"),
|
||||
PreventDeletion: true,
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,9 +29,11 @@ var _ = Describe("changing Tenant managed Kubernetes resources", Label("tenant")
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "laura",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "laura",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -29,9 +29,11 @@ var _ = Describe("creating namespaces within a Tenant with resources", Label("te
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "john",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -32,9 +32,11 @@ var _ = Describe("Creating a TenantResource object", Label("tenantresource"), fu
|
||||
Spec: capsulev1beta2.TenantSpec{
|
||||
Owners: api.OwnerListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "solar-user",
|
||||
Kind: "User",
|
||||
CoreOwnerSpec: api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: "solar-user",
|
||||
Kind: "User",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -327,6 +328,66 @@ func CheckForOwnerRoleBindings(ns *corev1.Namespace, owner api.OwnerSpec, roles
|
||||
}
|
||||
}
|
||||
|
||||
func VerifyTenantRoleBindings(
|
||||
tnt *capsulev1beta2.Tenant,
|
||||
) {
|
||||
Eventually(func(g Gomega) {
|
||||
// List all RoleBindings once per namespace to avoid repeated API calls.
|
||||
for _, ns := range tnt.Status.Namespaces {
|
||||
for i, owner := range tnt.Status.Owners {
|
||||
for _, role := range owner.ClusterRoles {
|
||||
rbName := fmt.Sprintf("capsule-%s-%d-%s", tnt.Name, i, role)
|
||||
|
||||
rb := &rbacv1.RoleBinding{}
|
||||
err := k8sClient.Get(context.Background(), client.ObjectKey{
|
||||
Namespace: ns,
|
||||
Name: rbName,
|
||||
}, rb)
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred(),
|
||||
"expected RoleBinding %s/%s to exist", ns, rbName)
|
||||
|
||||
g.Expect(rb.RoleRef.Name).To(Equal(role),
|
||||
"expected RoleBinding %s/%s to have RoleRef.Name=%q",
|
||||
ns, rbName, role)
|
||||
|
||||
g.Expect(rb.Subjects).ToNot(BeEmpty(),
|
||||
"expected RoleBinding %s/%s to have at least one subject", ns, rbName)
|
||||
|
||||
foundSubject := false
|
||||
for _, s := range rb.Subjects {
|
||||
if s.Kind == string(owner.Kind) && s.Name == owner.Name {
|
||||
foundSubject = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
g.Expect(foundSubject).To(BeTrue(),
|
||||
"expected RoleBinding %s/%s to contain subject %s/%s",
|
||||
ns, rb.Name, owner.Kind, owner.Name)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}).WithTimeout(30 * time.Second).WithPolling(500 * time.Millisecond).Should(Succeed())
|
||||
}
|
||||
|
||||
func normalizeOwners(in api.OwnerStatusListSpec) api.OwnerStatusListSpec {
|
||||
// copy to avoid mutating the original
|
||||
out := make(api.OwnerStatusListSpec, len(in))
|
||||
copy(out, in)
|
||||
|
||||
// sort outer slice by kind+name
|
||||
sort.Sort(api.GetByKindAndName(out))
|
||||
|
||||
// sort roles inside each owner so role order doesn't matter
|
||||
for i := range out {
|
||||
sort.Strings(out[i].ClusterRoles)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func GetKubernetesVersion() *versionUtil.Version {
|
||||
var serverVersion *version.Info
|
||||
var err error
|
||||
|
||||
2
go.sum
2
go.sum
@@ -232,8 +232,6 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
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.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
|
||||
@@ -38,7 +38,7 @@ type Manager struct {
|
||||
|
||||
//nolint:revive
|
||||
func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, ctrlConfig utils.ControllerOptions) (err error) {
|
||||
namesPredicate := utils.NamesMatchingPredicate(ProvisionerRoleName, DeleterRoleName)
|
||||
namesPredicate := utils.NamesMatchingPredicate(api.ProvisionerRoleName, api.DeleterRoleName)
|
||||
|
||||
crErr := ctrl.NewControllerManagedBy(mgr).
|
||||
For(&rbacv1.ClusterRole{}, namesPredicate).
|
||||
@@ -63,7 +63,7 @@ func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, ctrlCo
|
||||
r.handleSAChange(ctx, e.Object)
|
||||
},
|
||||
UpdateFunc: func(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) {
|
||||
if promotionLabelsChanged(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels()) {
|
||||
if utils.LabelsChanged([]string{meta.OwnerPromotionLabel}, e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels()) {
|
||||
r.handleSAChange(ctx, e.ObjectNew)
|
||||
}
|
||||
},
|
||||
@@ -83,9 +83,9 @@ func (r *Manager) SetupWithManager(ctx context.Context, mgr ctrl.Manager, ctrlCo
|
||||
// Resource kinds and we're just interested to the ones with the said name since they're bounded together.
|
||||
func (r *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res reconcile.Result, err error) {
|
||||
switch request.Name {
|
||||
case ProvisionerRoleName:
|
||||
if err = r.EnsureClusterRole(ctx, ProvisionerRoleName); err != nil {
|
||||
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", ProvisionerRoleName)
|
||||
case api.ProvisionerRoleName:
|
||||
if err = r.EnsureClusterRole(ctx, api.ProvisionerRoleName); err != nil {
|
||||
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", api.ProvisionerRoleName)
|
||||
|
||||
break
|
||||
}
|
||||
@@ -95,9 +95,9 @@ func (r *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
|
||||
break
|
||||
}
|
||||
case DeleterRoleName:
|
||||
if err = r.EnsureClusterRole(ctx, DeleterRoleName); err != nil {
|
||||
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", DeleterRoleName)
|
||||
case api.DeleterRoleName:
|
||||
if err = r.EnsureClusterRole(ctx, api.DeleterRoleName); err != nil {
|
||||
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", api.DeleterRoleName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,12 +106,12 @@ func (r *Manager) Reconcile(ctx context.Context, request reconcile.Request) (res
|
||||
|
||||
func (r *Manager) EnsureClusterRoleBindingsProvisioner(ctx context.Context) error {
|
||||
crb := &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: ProvisionerRoleName},
|
||||
ObjectMeta: metav1.ObjectMeta{Name: api.ProvisionerRoleName},
|
||||
}
|
||||
|
||||
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
|
||||
_, err := controllerutil.CreateOrUpdate(ctx, r.Client, crb, func() error {
|
||||
crb.RoleRef = provisionerClusterRoleBinding.RoleRef
|
||||
crb.RoleRef = api.ProvisionerClusterRoleBinding.RoleRef
|
||||
crb.Subjects = nil
|
||||
|
||||
for _, entity := range r.Configuration.Administrators() {
|
||||
@@ -179,7 +179,7 @@ func (r *Manager) EnsureClusterRoleBindingsProvisioner(ctx context.Context) erro
|
||||
}
|
||||
|
||||
func (r *Manager) EnsureClusterRole(ctx context.Context, roleName string) (err error) {
|
||||
role, ok := clusterRoles[roleName]
|
||||
role, ok := api.ClusterRoles[roleName]
|
||||
if !ok {
|
||||
return fmt.Errorf("clusterRole %s is not mapped", roleName)
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (r *Manager) EnsureClusterRole(ctx context.Context, roleName string) (err e
|
||||
// since we're not creating empty CR and CRB upon Capsule installation: it's a run-once task, since the reconciliation
|
||||
// is handled by the Reconciler implemented interface.
|
||||
func (r *Manager) Start(ctx context.Context) error {
|
||||
for roleName := range clusterRoles {
|
||||
for roleName := range api.ClusterRoles {
|
||||
r.Log.V(4).Info("setting up ClusterRoles", "ClusterRole", roleName)
|
||||
|
||||
if err := r.EnsureClusterRole(ctx, roleName); err != nil {
|
||||
@@ -237,20 +237,3 @@ func (r *Manager) handleSAChange(ctx context.Context, obj client.Object) {
|
||||
r.Log.Error(err, "cannot update ClusterRoleBinding upon ServiceAccount event")
|
||||
}
|
||||
}
|
||||
|
||||
func promotionLabelsChanged(oldLabels, newLabels map[string]string) bool {
|
||||
keys := []string{
|
||||
meta.OwnerPromotionLabel,
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
oldVal, oldOK := oldLabels[key]
|
||||
newVal, newOK := newLabels[key]
|
||||
|
||||
if oldOK != newOK || oldVal != newVal {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -17,20 +17,25 @@ import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/internal/controllers/utils"
|
||||
"github.com/projectcapsule/capsule/internal/metrics"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
meta "github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
"github.com/projectcapsule/capsule/pkg/configuration"
|
||||
)
|
||||
@@ -47,7 +52,12 @@ type Manager struct {
|
||||
|
||||
func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.ControllerOptions) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&capsulev1beta2.Tenant{}).
|
||||
For(
|
||||
&capsulev1beta2.Tenant{},
|
||||
builder.WithPredicates(
|
||||
predicate.GenerationChangedPredicate{},
|
||||
),
|
||||
).
|
||||
Owns(&networkingv1.NetworkPolicy{}).
|
||||
Owns(&corev1.LimitRange{}).
|
||||
Owns(&corev1.ResourceQuota{}).
|
||||
@@ -64,29 +74,129 @@ func (r *Manager) SetupWithManager(mgr ctrl.Manager, ctrlConfig utils.Controller
|
||||
).
|
||||
Watches(
|
||||
&storagev1.StorageClass{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.enqueueAllTenants),
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableStorageClasses,
|
||||
"cannot collect storage classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&gatewayv1.GatewayClass{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.enqueueAllTenants),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&networkingv1.IngressClass{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.enqueueAllTenants),
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableGatewayClasses,
|
||||
"cannot collect gateway classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&schedulingv1.PriorityClass{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.enqueueAllTenants),
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailablePriorityClasses,
|
||||
"cannot collect priority classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&nodev1.RuntimeClass{},
|
||||
handler.EnqueueRequestsFromMapFunc(r.enqueueAllTenants),
|
||||
r.statusOnlyHandlerClasses(
|
||||
r.reconcileClassStatus,
|
||||
r.collectAvailableRuntimeClasses,
|
||||
"cannot collect runtime classes",
|
||||
),
|
||||
builder.WithPredicates(utils.UpdatedMetadataPredicate),
|
||||
).
|
||||
Watches(
|
||||
&capsulev1beta2.TenantOwner{},
|
||||
handler.TypedFuncs[client.Object, ctrl.Request]{
|
||||
CreateFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedCreateEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueForTenantsWithCondition(
|
||||
ctx,
|
||||
e.Object,
|
||||
q,
|
||||
func(tnt *capsulev1beta2.Tenant, c client.Object) bool {
|
||||
return len(tnt.Spec.Permissions.MatchOwners) > 0
|
||||
})
|
||||
},
|
||||
UpdateFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedUpdateEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueForTenantsWithCondition(
|
||||
ctx,
|
||||
e.ObjectNew,
|
||||
q,
|
||||
func(tnt *capsulev1beta2.Tenant, c client.Object) bool {
|
||||
return len(tnt.Spec.Permissions.MatchOwners) > 0
|
||||
})
|
||||
},
|
||||
|
||||
DeleteFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedDeleteEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueTenantsForTenantOwner(ctx, e.Object, q)
|
||||
},
|
||||
},
|
||||
).
|
||||
Watches(
|
||||
&corev1.ServiceAccount{},
|
||||
handler.TypedFuncs[client.Object, ctrl.Request]{
|
||||
CreateFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedCreateEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueForTenantsWithCondition(ctx, e.Object, q, func(tnt *capsulev1beta2.Tenant, c client.Object) bool {
|
||||
for _, n := range tnt.Status.Namespaces {
|
||||
if n == c.GetNamespace() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
},
|
||||
UpdateFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedUpdateEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueForTenantsWithCondition(ctx, e.ObjectNew, q, func(tnt *capsulev1beta2.Tenant, c client.Object) bool {
|
||||
for _, n := range tnt.Status.Namespaces {
|
||||
if n == c.GetNamespace() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
},
|
||||
DeleteFunc: func(
|
||||
ctx context.Context,
|
||||
e event.TypedDeleteEvent[client.Object],
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
r.enqueueForTenantsWithCondition(ctx, e.Object, q, func(tnt *capsulev1beta2.Tenant, c client.Object) bool {
|
||||
_, found := tnt.Status.Owners.FindOwner(
|
||||
serviceaccount.ServiceAccountUsernamePrefix+c.GetNamespace()+":"+c.GetName(),
|
||||
api.ServiceAccountOwner,
|
||||
)
|
||||
|
||||
return found
|
||||
})
|
||||
},
|
||||
},
|
||||
builder.WithPredicates(utils.PromotedServiceaccountPredicate),
|
||||
).
|
||||
WithOptions(controller.Options{MaxConcurrentReconciles: ctrlConfig.MaxConcurrentReconciles}).
|
||||
Complete(r)
|
||||
}
|
||||
@@ -120,6 +230,13 @@ func (r Manager) Reconcile(ctx context.Context, request ctrl.Request) (result ct
|
||||
}
|
||||
}()
|
||||
|
||||
// Collect Ownership for Status
|
||||
if err = r.collectOwners(ctx, instance); err != nil {
|
||||
err = fmt.Errorf("cannot collect available owners: %w", err)
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Ensuring Metadata.
|
||||
err, updated := r.ensureMetadata(ctx, instance)
|
||||
if err != nil {
|
||||
|
||||
@@ -15,28 +15,11 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/internal/controllers/rbac"
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
"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 api.OwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec {
|
||||
rb := r.userClusterRoleBindings(owner.UserSpec, clusterRole)
|
||||
|
||||
if owner.Labels != nil {
|
||||
rb.Labels = owner.Labels
|
||||
}
|
||||
|
||||
if owner.Annotations != nil {
|
||||
rb.Labels = owner.Annotations
|
||||
}
|
||||
|
||||
return rb
|
||||
}
|
||||
|
||||
func (r *Manager) userClusterRoleBindings(owner api.UserSpec, clusterRole string) api.AdditionalRoleBindingsSpec {
|
||||
func (r *Manager) userClusterRoleBindings(owner api.CoreOwnerSpec, clusterRole string) api.AdditionalRoleBindingsSpec {
|
||||
var subject rbacv1.Subject
|
||||
|
||||
if owner.Kind == "ServiceAccount" {
|
||||
@@ -78,12 +61,13 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta2.T
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum64())
|
||||
}
|
||||
|
||||
// getting requested Role Binding keys
|
||||
keys := make([]string, 0, len(tenant.Spec.Owners))
|
||||
keys := make([]string, 0, len(tenant.Status.Owners))
|
||||
// Generating for dynamic tenant owners cluster roles
|
||||
for _, owner := range tenant.Spec.Owners {
|
||||
for _, owner := range tenant.Status.Owners {
|
||||
for _, clusterRoleName := range owner.ClusterRoles {
|
||||
cr := r.ownerClusterRoleBindings(owner, clusterRoleName)
|
||||
cr := r.userClusterRoleBindings(owner, clusterRoleName)
|
||||
|
||||
keys = append(keys, hashFn(cr))
|
||||
}
|
||||
@@ -93,12 +77,6 @@ func (r *Manager) syncRoleBindings(ctx context.Context, tenant *capsulev1beta2.T
|
||||
keys = append(keys, hashFn(i))
|
||||
}
|
||||
|
||||
for _, i := range r.Configuration.Administrators() {
|
||||
cr := r.userClusterRoleBindings(i, rbac.DeleterRoleName)
|
||||
|
||||
keys = append(keys, hashFn(cr))
|
||||
}
|
||||
|
||||
group := new(errgroup.Group)
|
||||
|
||||
for _, ns := range tenant.Status.Namespaces {
|
||||
@@ -119,16 +97,12 @@ func (r *Manager) syncAdditionalRoleBinding(ctx context.Context, tenant *capsule
|
||||
|
||||
roleBindings := make([]api.AdditionalRoleBindingsSpec, 0)
|
||||
|
||||
for _, owner := range tenant.Spec.Owners {
|
||||
for _, owner := range tenant.Status.Owners {
|
||||
for _, clusterRoleName := range owner.ClusterRoles {
|
||||
roleBindings = append(roleBindings, r.ownerClusterRoleBindings(owner, clusterRoleName))
|
||||
roleBindings = append(roleBindings, r.userClusterRoleBindings(owner, clusterRoleName))
|
||||
}
|
||||
}
|
||||
|
||||
for _, a := range r.Configuration.Administrators() {
|
||||
roleBindings = append(roleBindings, r.userClusterRoleBindings(a, rbac.DeleterRoleName))
|
||||
}
|
||||
|
||||
roleBindings = append(roleBindings, tenant.Spec.AdditionalRoleBindings...)
|
||||
|
||||
for i, roleBinding := range roleBindings {
|
||||
|
||||
@@ -5,6 +5,7 @@ package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
nodev1 "k8s.io/api/node/v1"
|
||||
@@ -21,11 +22,86 @@ import (
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
// Sets a label on the Tenant object with it's name.
|
||||
func (r *Manager) collectOwners(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
owners, err := tnt.CollectOwners(
|
||||
ctx,
|
||||
r.Client,
|
||||
r.Configuration.AllowServiceAccountPromotion(),
|
||||
r.Configuration.Administrators(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// No Direct Update needed as status is always posted
|
||||
tnt.Status.Owners = owners
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r Manager) reconcileClassStatus(
|
||||
ctx context.Context,
|
||||
fn func(context.Context, *capsulev1beta2.Tenant) error,
|
||||
) (err error) {
|
||||
tntList := &capsulev1beta2.TenantList{}
|
||||
if err = r.List(ctx, tntList); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range tntList.Items {
|
||||
t := &tntList.Items[i]
|
||||
|
||||
// Collect Ownership for Status
|
||||
if err = fn(ctx, t); err != nil {
|
||||
err = fmt.Errorf("cannot collect available classes: %w", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err = r.updateTenantStatus(ctx, t, err); err != nil {
|
||||
err = fmt.Errorf("cannot update tenant status: %w", err)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
log := log.FromContext(ctx)
|
||||
|
||||
log.V(5).Info("collecting available storageclasses")
|
||||
|
||||
if err = r.collectAvailableStorageClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available storageclasses", "size", len(tnt.Status.Classes.StorageClasses))
|
||||
|
||||
if err = r.collectAvailablePriorityClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available priorityclasses", "size", len(tnt.Status.Classes.PriorityClasses))
|
||||
|
||||
if err = r.collectAvailableGatewayClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available gatewayclasses", "size", len(tnt.Status.Classes.GatewayClasses))
|
||||
|
||||
if err = r.collectAvailableRuntimeClasses(ctx, tnt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available runtimeclasses", "size", len(tnt.Status.Classes.RuntimeClasses))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Manager) collectAvailableStorageClasses(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
if tnt.Status.Classes.StorageClasses, err = listObjectNamesBySelector(
|
||||
ctx,
|
||||
r.Client,
|
||||
@@ -35,8 +111,10 @@ func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1b
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available storageclasses", "size", len(tnt.Status.Classes.StorageClasses))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Manager) collectAvailablePriorityClasses(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
if tnt.Status.Classes.PriorityClasses, err = listObjectNamesBySelector(
|
||||
ctx,
|
||||
r.Client,
|
||||
@@ -46,8 +124,10 @@ func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1b
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available priorityclasses", "size", len(tnt.Status.Classes.PriorityClasses))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Manager) collectAvailableGatewayClasses(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
if tnt.Status.Classes.GatewayClasses, err = listObjectNamesBySelector(
|
||||
ctx,
|
||||
r.Client,
|
||||
@@ -57,8 +137,10 @@ func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1b
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available gatewayclasses", "size", len(tnt.Status.Classes.GatewayClasses))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Manager) collectAvailableRuntimeClasses(ctx context.Context, tnt *capsulev1beta2.Tenant) (err error) {
|
||||
if tnt.Status.Classes.RuntimeClasses, err = listObjectNamesBySelector(
|
||||
ctx,
|
||||
r.Client,
|
||||
@@ -68,8 +150,6 @@ func (r *Manager) collectAvailableResources(ctx context.Context, tnt *capsulev1b
|
||||
return err
|
||||
}
|
||||
|
||||
log.V(5).Info("collected available runtimeclasses", "size", len(tnt.Status.Classes.RuntimeClasses))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -81,13 +161,7 @@ func listObjectNamesBySelector(
|
||||
allowed *api.DefaultAllowedListSpec,
|
||||
list client.ObjectList,
|
||||
opts ...client.ListOption,
|
||||
) (objects []string, err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
sort.Strings(objects)
|
||||
}
|
||||
}()
|
||||
|
||||
) ([]string, error) {
|
||||
if err := c.List(ctx, list, opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -97,8 +171,9 @@ func listObjectNamesBySelector(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allNames := make(map[string]struct{})
|
||||
objects := make([]string, 0)
|
||||
|
||||
allNames := make(map[string]struct{})
|
||||
selected := make(map[string]struct{})
|
||||
|
||||
hasSelector := false
|
||||
@@ -117,9 +192,12 @@ func listObjectNamesBySelector(
|
||||
objects = append(objects, accessor.GetName())
|
||||
}
|
||||
|
||||
sort.Strings(objects)
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// Prepare selector
|
||||
var sel labels.Selector
|
||||
if hasSelector {
|
||||
sel, err = metav1.LabelSelectorAsSelector(&allowed.LabelSelector)
|
||||
@@ -128,6 +206,7 @@ func listObjectNamesBySelector(
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate objects
|
||||
for _, obj := range objs {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
@@ -140,7 +219,6 @@ func listObjectNamesBySelector(
|
||||
|
||||
if hasSelector {
|
||||
lbls := labels.Set(accessor.GetLabels())
|
||||
|
||||
if sel.Matches(lbls) {
|
||||
selected[name] = struct{}{}
|
||||
}
|
||||
@@ -157,10 +235,6 @@ func listObjectNamesBySelector(
|
||||
continue
|
||||
}
|
||||
|
||||
if _, already := selected[name]; already {
|
||||
continue
|
||||
}
|
||||
|
||||
selected[name] = struct{}{}
|
||||
}
|
||||
|
||||
@@ -168,5 +242,7 @@ func listObjectNamesBySelector(
|
||||
objects = append(objects, name)
|
||||
}
|
||||
|
||||
sort.Strings(objects)
|
||||
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
@@ -12,14 +12,116 @@ import (
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/pkg/utils"
|
||||
)
|
||||
|
||||
func (r *Manager) statusOnlyHandlerClasses(
|
||||
fn func(ctx context.Context, perTenant func(context.Context, *capsulev1beta2.Tenant) error) error,
|
||||
perTenant func(context.Context, *capsulev1beta2.Tenant) error,
|
||||
errMsg string,
|
||||
) *handler.TypedFuncs[client.Object, reconcile.Request] {
|
||||
return &handler.TypedFuncs[client.Object, reconcile.Request]{
|
||||
CreateFunc: func(
|
||||
ctx context.Context,
|
||||
_ event.TypedCreateEvent[client.Object],
|
||||
_ workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
if err := fn(ctx, perTenant); err != nil {
|
||||
r.Log.Error(err, errMsg)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(
|
||||
ctx context.Context,
|
||||
_ event.TypedUpdateEvent[client.Object],
|
||||
_ workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
if err := fn(ctx, perTenant); err != nil {
|
||||
r.Log.Error(err, errMsg)
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(
|
||||
ctx context.Context,
|
||||
_ event.TypedDeleteEvent[client.Object],
|
||||
_ workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
if err := fn(ctx, perTenant); err != nil {
|
||||
r.Log.Error(err, errMsg)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Manager) enqueueTenantsForTenantOwner(
|
||||
ctx context.Context,
|
||||
tenantOwner client.Object,
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
) {
|
||||
var tenants capsulev1beta2.TenantList
|
||||
if err := r.List(ctx, &tenants); err != nil {
|
||||
r.Log.Error(err, "failed to list Tenants for Tenant Owner event")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
owner, ok := tenantOwner.(*capsulev1beta2.TenantOwner)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range tenants.Items {
|
||||
tnt := &tenants.Items[i]
|
||||
|
||||
if _, found := tnt.Status.Owners.FindOwner(
|
||||
owner.Spec.Name,
|
||||
owner.Spec.Kind,
|
||||
); !found {
|
||||
continue
|
||||
}
|
||||
|
||||
q.Add(reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: tnt.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Manager) enqueueForTenantsWithCondition(
|
||||
ctx context.Context,
|
||||
obj client.Object,
|
||||
q workqueue.TypedRateLimitingInterface[reconcile.Request],
|
||||
fn func(*capsulev1beta2.Tenant, client.Object) bool,
|
||||
) {
|
||||
var tenants capsulev1beta2.TenantList
|
||||
if err := r.List(ctx, &tenants); err != nil {
|
||||
r.Log.Error(err, "failed to list Tenants for class event")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for i := range tenants.Items {
|
||||
tnt := &tenants.Items[i]
|
||||
|
||||
if !fn(tnt, obj) {
|
||||
continue
|
||||
}
|
||||
|
||||
q.Add(reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: tnt.Name,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Manager) enqueueAllTenants(ctx context.Context, _ client.Object) []reconcile.Request {
|
||||
var tenants capsulev1beta2.TenantList
|
||||
if err := r.List(ctx, &tenants); err != nil {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
|
||||
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
|
||||
"github.com/projectcapsule/capsule/pkg/api/meta"
|
||||
)
|
||||
|
||||
var CapsuleConfigSpecChangedPredicate = predicate.Funcs{
|
||||
@@ -32,6 +33,31 @@ var CapsuleConfigSpecChangedPredicate = predicate.Funcs{
|
||||
GenericFunc: func(e event.GenericEvent) bool { return false },
|
||||
}
|
||||
|
||||
var PromotedServiceaccountPredicate = predicate.TypedFuncs[client.Object]{
|
||||
CreateFunc: func(e event.TypedCreateEvent[client.Object]) bool {
|
||||
v, ok := e.Object.GetLabels()[meta.OwnerPromotionLabel]
|
||||
|
||||
return ok && v == meta.OwnerPromotionLabelTrigger
|
||||
},
|
||||
|
||||
DeleteFunc: func(e event.TypedDeleteEvent[client.Object]) bool {
|
||||
v, ok := e.Object.GetLabels()[meta.OwnerPromotionLabel]
|
||||
|
||||
return ok && v == meta.OwnerPromotionLabelTrigger
|
||||
},
|
||||
|
||||
UpdateFunc: func(e event.TypedUpdateEvent[client.Object]) bool {
|
||||
oldVal, oldOK := e.ObjectOld.GetLabels()[meta.OwnerPromotionLabel]
|
||||
newVal, newOK := e.ObjectNew.GetLabels()[meta.OwnerPromotionLabel]
|
||||
|
||||
return oldOK != newOK || oldVal != newVal
|
||||
},
|
||||
|
||||
GenericFunc: func(event.TypedGenericEvent[client.Object]) bool {
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
var UpdatedMetadataPredicate = predicate.Funcs{
|
||||
CreateFunc: func(e event.CreateEvent) bool { return true },
|
||||
DeleteFunc: func(e event.DeleteEvent) bool { return true },
|
||||
@@ -57,6 +83,19 @@ func labelsEqual(a, b map[string]string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func LabelsChanged(keys []string, oldLabels, newLabels map[string]string) bool {
|
||||
for _, key := range keys {
|
||||
oldVal, oldOK := oldLabels[key]
|
||||
newVal, newOK := newLabels[key]
|
||||
|
||||
if oldOK != newOK || oldVal != newVal {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NamesMatchingPredicate(names ...string) builder.Predicates {
|
||||
return builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
|
||||
for _, name := range names {
|
||||
|
||||
@@ -132,12 +132,7 @@ func (h *handler) OnUpdate(c client.Client, decoder admission.Decoder, recorder
|
||||
}
|
||||
}
|
||||
} else {
|
||||
owned, err := tenant.NamespaceIsOwned(ctx, c, h.cfg, oldNs, tnt, req.UserInfo)
|
||||
if err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
if !owned {
|
||||
if owned := tenant.NamespaceIsOwned(ctx, c, h.cfg, oldNs, tnt, req.UserInfo); !owned {
|
||||
recorder.Eventf(oldNs, corev1.EventTypeWarning, "NamespacePatch", "Namespace %s can not be patched", oldNs.GetName())
|
||||
|
||||
response := admission.Denied("Denied patch request for this namespace")
|
||||
|
||||
@@ -6,7 +6,6 @@ package validation
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/record"
|
||||
@@ -62,14 +61,7 @@ func (h *patchHandler) OnUpdate(
|
||||
return func(ctx context.Context, req admission.Request) *admission.Response {
|
||||
e := fmt.Sprintf("namespace/%s can not be patched", ns.Name)
|
||||
|
||||
ok, err := users.IsTenantOwner(ctx, c, h.cfg, tnt, req.UserInfo)
|
||||
if err != nil {
|
||||
response := admission.Errored(http.StatusBadRequest, err)
|
||||
|
||||
return &response
|
||||
}
|
||||
|
||||
if ok {
|
||||
if ok := users.IsTenantOwnerByStatus(ctx, c, h.cfg, tnt, req.UserInfo); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
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/users"
|
||||
@@ -85,12 +84,7 @@ func (h *validating) handle(
|
||||
}
|
||||
|
||||
// We don't want to allow promoted serviceaccounts to promote other serviceaccounts
|
||||
allowed, err := users.IsTenantOwner(ctx, c, h.cfg, tnt, req.UserInfo)
|
||||
if err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
if allowed {
|
||||
if ok := users.IsTenantOwnerByStatus(ctx, c, h.cfg, tnt, req.UserInfo); ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,17 +14,27 @@ import (
|
||||
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"
|
||||
)
|
||||
|
||||
type warningHandler struct{}
|
||||
|
||||
func WarningHandler() capsulewebhook.Handler {
|
||||
return &warningHandler{}
|
||||
type warningHandler struct {
|
||||
cfg configuration.Configuration
|
||||
}
|
||||
|
||||
func (h *warningHandler) OnCreate(_ client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(_ context.Context, req admission.Request) *admission.Response {
|
||||
return h.handle(decoder, req)
|
||||
func WarningHandler(cfg configuration.Configuration) capsulewebhook.Handler {
|
||||
return &warningHandler{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *warningHandler) OnCreate(c client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(ctx context.Context, req admission.Request) *admission.Response {
|
||||
tnt := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tnt); err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
return h.handle(tnt, decoder, req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,16 +46,16 @@ func (h *warningHandler) OnDelete(client.Client, admission.Decoder, record.Event
|
||||
|
||||
func (h *warningHandler) OnUpdate(_ client.Client, decoder admission.Decoder, _ record.EventRecorder) capsulewebhook.Func {
|
||||
return func(_ context.Context, req admission.Request) *admission.Response {
|
||||
return h.handle(decoder, req)
|
||||
tnt := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tnt); err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
return h.handle(tnt, decoder, req)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *warningHandler) handle(decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
tenant := &capsulev1beta2.Tenant{}
|
||||
if err := decoder.Decode(req, tenant); err != nil {
|
||||
return utils.ErroredResponse(err)
|
||||
}
|
||||
|
||||
func (h *warningHandler) handle(tnt *capsulev1beta2.Tenant, decoder admission.Decoder, req admission.Request) *admission.Response {
|
||||
response := &admission.Response{
|
||||
AdmissionResponse: admissionv1.AdmissionResponse{
|
||||
UID: req.UID,
|
||||
@@ -53,31 +63,31 @@ func (h *warningHandler) handle(decoder admission.Decoder, req admission.Request
|
||||
},
|
||||
}
|
||||
|
||||
if len(tenant.Spec.LimitRanges.Items) > 0 {
|
||||
if len(tnt.Spec.LimitRanges.Items) > 0 {
|
||||
response.Warnings = append(response.Warnings, "Limitranges are deprecated and will be removed int the future. You need to consider to migrate to TenantReplications: https://projectcapsule.dev/docs/tenants/enforcement/#limitrange-distribution-with-tenantreplications.")
|
||||
}
|
||||
|
||||
if len(tenant.Spec.NetworkPolicies.Items) > 0 {
|
||||
if len(tnt.Spec.NetworkPolicies.Items) > 0 {
|
||||
response.Warnings = append(response.Warnings, "NetworkPolicies are deprecated and will be removed int the future. You need to consider to migrate to TenantReplications: https://projectcapsule.dev/docs/tenants/enforcement/#networkpolicy-distribution-with-tenantreplications.")
|
||||
}
|
||||
|
||||
if tenant.Spec.NamespaceOptions != nil && tenant.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
if tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.AdditionalMetadata != nil {
|
||||
response.Warnings = append(response.Warnings, "additionalMetadata is deprecated and will be removed int the future. You need to consider to migrate to AdditionalMetadataList: https://projectcapsule.dev/docs/tenants/enforcement/#additionalmetadatalist.")
|
||||
}
|
||||
|
||||
if tenant.Spec.StorageClasses != nil && tenant.Spec.StorageClasses.Regex != "" {
|
||||
if tnt.Spec.StorageClasses != nil && tnt.Spec.StorageClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select StorageClasses is deprecated and will be removed int the future.")
|
||||
}
|
||||
|
||||
if tenant.Spec.GatewayOptions.AllowedClasses != nil && tenant.Spec.GatewayOptions.AllowedClasses.Regex != "" {
|
||||
if tnt.Spec.GatewayOptions.AllowedClasses != nil && tnt.Spec.GatewayOptions.AllowedClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select GatewayClasses is deprecated and will be removed int the future.")
|
||||
}
|
||||
|
||||
if tenant.Spec.PriorityClasses != nil && tenant.Spec.PriorityClasses.Regex != "" {
|
||||
if tnt.Spec.PriorityClasses != nil && tnt.Spec.PriorityClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select PriorityClasses is deprecated and will be removed int the future.")
|
||||
}
|
||||
|
||||
if tenant.Spec.RuntimeClasses != nil && tenant.Spec.RuntimeClasses.Regex != "" {
|
||||
if tnt.Spec.RuntimeClasses != nil && tnt.Spec.RuntimeClasses.Regex != "" {
|
||||
response.Warnings = append(response.Warnings, "Using the regex property to select RuntimeClasses is deprecated and will be removed int the future.")
|
||||
}
|
||||
|
||||
|
||||
143
pkg/api/misc/selectors.go
Normal file
143
pkg/api/misc/selectors.go
Normal file
@@ -0,0 +1,143 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Selector for resources and their labels or selecting origin namespaces
|
||||
// +kubebuilder:object:generate=true
|
||||
type NamespaceSelector struct {
|
||||
// Select Items based on their labels. If the namespaceSelector is also set, the selector is applied
|
||||
// to items within the selected namespaces. Otherwise for all the items.
|
||||
*metav1.LabelSelector `json:",inline"`
|
||||
}
|
||||
|
||||
// GetMatchingNamespaces retrieves the list of namespaces that match the NamespaceSelector.
|
||||
func (s *NamespaceSelector) GetMatchingNamespaces(ctx context.Context, client client.Client) ([]corev1.Namespace, error) {
|
||||
if s.LabelSelector == nil {
|
||||
return nil, nil // No namespace selector means all namespaces
|
||||
}
|
||||
|
||||
nsSelector, err := metav1.LabelSelectorAsSelector(s.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid namespace selector: %w", err)
|
||||
}
|
||||
|
||||
namespaceList := &corev1.NamespaceList{}
|
||||
if err := client.List(ctx, namespaceList); err != nil {
|
||||
return nil, fmt.Errorf("failed to list namespaces: %w", err)
|
||||
}
|
||||
|
||||
var matchingNamespaces []corev1.Namespace
|
||||
|
||||
for _, ns := range namespaceList.Items {
|
||||
if nsSelector.Matches(labels.Set(ns.Labels)) {
|
||||
matchingNamespaces = append(matchingNamespaces, ns)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingNamespaces, nil
|
||||
}
|
||||
|
||||
// ListBySelectors lists objects of type T (using list L), then returns all items that
|
||||
// match ANY of the provided LabelSelectors. The result is unique by namespace/name.
|
||||
func ListBySelectors[T client.Object](
|
||||
ctx context.Context,
|
||||
c client.Client,
|
||||
list client.ObjectList,
|
||||
selectors []*metav1.LabelSelector,
|
||||
) ([]T, error) {
|
||||
if len(selectors) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
return nil, fmt.Errorf("list must not be nil")
|
||||
}
|
||||
|
||||
// Preallocate with upper bound (len(selectors)); nil selectors will just not be used.
|
||||
selList := make([]labels.Selector, 0, len(selectors))
|
||||
|
||||
for _, ls := range selectors {
|
||||
if ls == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(ls)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid label selector %v: %w", ls, err)
|
||||
}
|
||||
|
||||
selList = append(selList, sel)
|
||||
}
|
||||
|
||||
if len(selList) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// List all objects once
|
||||
if err := c.List(ctx, list); err != nil {
|
||||
return nil, fmt.Errorf("listing objects: %w", err)
|
||||
}
|
||||
|
||||
rawItems, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extracting list items: %w", err)
|
||||
}
|
||||
|
||||
// Deduplicate by namespace/name
|
||||
seen := make(map[client.ObjectKey]struct{}, len(rawItems))
|
||||
|
||||
// Upper bound: at most len(rawItems) will match; good enough for prealloc.
|
||||
result := make([]T, 0, len(rawItems))
|
||||
|
||||
for _, obj := range rawItems {
|
||||
typed, ok := obj.(T)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
lbls := typed.GetLabels()
|
||||
if len(lbls) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
set := labels.Set(lbls)
|
||||
|
||||
// Match against ANY selector
|
||||
matched := false
|
||||
|
||||
for _, sel := range selList {
|
||||
if sel.Matches(set) {
|
||||
matched = true
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
key := client.ObjectKeyFromObject(typed)
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
|
||||
seen[key] = struct{}{}
|
||||
|
||||
result = append(result, typed)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
32
pkg/api/misc/zz_generated.deepcopy.go
Normal file
32
pkg/api/misc/zz_generated.deepcopy.go
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
// Copyright 2020-2023 Project Capsule Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Code generated by controller-gen. DO NOT EDIT.
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) {
|
||||
*out = *in
|
||||
if in.LabelSelector != nil {
|
||||
in, out := &in.LabelSelector, &out.LabelSelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector.
|
||||
func (in *NamespaceSelector) DeepCopy() *NamespaceSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamespaceSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -6,11 +6,8 @@ package api
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type OwnerSpec struct {
|
||||
UserSpec `json:",inline"`
|
||||
CoreOwnerSpec `json:",inline"`
|
||||
|
||||
// Defines additional cluster-roles for the specific Owner.
|
||||
// +kubebuilder:default={admin,capsule-namespace-deleter}
|
||||
ClusterRoles []string `json:"clusterRoles,omitempty"`
|
||||
// Proxy settings for tenant owner.
|
||||
ProxyOperations []ProxySettings `json:"proxySettings,omitempty"`
|
||||
// Additional Labels for the synchronized rolebindings
|
||||
@@ -19,6 +16,16 @@ type OwnerSpec struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type CoreOwnerSpec struct {
|
||||
UserSpec `json:",inline"`
|
||||
|
||||
// Defines additional cluster-roles for the specific Owner.
|
||||
// +kubebuilder:default={admin,capsule-namespace-deleter}
|
||||
ClusterRoles []string `json:"clusterRoles,omitempty"`
|
||||
}
|
||||
|
||||
// +kubebuilder:validation:Enum=User;Group;ServiceAccount
|
||||
type OwnerKind string
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//nolint:dupl
|
||||
package api
|
||||
|
||||
import (
|
||||
@@ -31,6 +30,15 @@ func (o OwnerListSpec) IsOwner(name string, groups []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (o OwnerListSpec) ToStatusOwners() OwnerStatusListSpec {
|
||||
list := OwnerStatusListSpec{}
|
||||
for _, owner := range o {
|
||||
list = append(list, owner.CoreOwnerSpec)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func (o OwnerListSpec) FindOwner(name string, kind OwnerKind) (owner OwnerSpec) {
|
||||
sort.Sort(ByKindAndName(o))
|
||||
i := sort.Search(len(o), func(i int) bool {
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
|
||||
func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
bla := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "bla",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "bla",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
@@ -23,9 +25,11 @@ func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
bar := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bar",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bar",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
@@ -35,9 +39,11 @@ func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
baz := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "baz",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: UserOwner,
|
||||
Name: "baz",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
@@ -47,9 +53,11 @@ func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
fim := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "fim",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "fim",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
@@ -59,9 +67,11 @@ func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
bom := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bom",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: GroupOwner,
|
||||
Name: "bom",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
@@ -75,9 +85,11 @@ func TestOwnerListSpec_FindOwner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
qip := OwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "qip",
|
||||
CoreOwnerSpec: CoreOwnerSpec{
|
||||
UserSpec: UserSpec{
|
||||
Kind: ServiceAccountOwner,
|
||||
Name: "qip",
|
||||
},
|
||||
},
|
||||
ProxyOperations: []ProxySettings{
|
||||
{
|
||||
|
||||
136
pkg/api/owner_status_list.go
Normal file
136
pkg/api/owner_status_list.go
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// +kubebuilder:object:generate=true
|
||||
|
||||
type OwnerStatusListSpec []CoreOwnerSpec
|
||||
|
||||
func (o *OwnerStatusListSpec) Upsert(
|
||||
newOwner CoreOwnerSpec,
|
||||
) {
|
||||
owners := *o
|
||||
|
||||
// Ensure slice is sorted before binary search
|
||||
sort.Sort(GetByKindAndName(owners))
|
||||
|
||||
less := func(a, b CoreOwnerSpec) bool {
|
||||
if a.Kind.String() != b.Kind.String() {
|
||||
return a.Kind.String() < b.Kind.String()
|
||||
}
|
||||
|
||||
return a.Name < b.Name
|
||||
}
|
||||
|
||||
// Find the first index where owners[i] >= newOwner
|
||||
idx := sort.Search(len(owners), func(i int) bool {
|
||||
return !less(owners[i], newOwner)
|
||||
})
|
||||
|
||||
// If we found an exact match (same Kind + Name), merge ClusterRoles
|
||||
if idx < len(owners) && !less(owners[idx], newOwner) && !less(newOwner, owners[idx]) {
|
||||
existing := &owners[idx]
|
||||
|
||||
roleSet := make(map[string]struct{}, len(existing.ClusterRoles))
|
||||
for _, r := range existing.ClusterRoles {
|
||||
roleSet[r] = struct{}{}
|
||||
}
|
||||
|
||||
for _, r := range newOwner.ClusterRoles {
|
||||
if _, ok := roleSet[r]; !ok {
|
||||
existing.ClusterRoles = append(existing.ClusterRoles, r)
|
||||
roleSet[r] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
*o = owners
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Not found: append and keep sorted
|
||||
owners = append(owners, newOwner)
|
||||
sort.Sort(GetByKindAndName(owners))
|
||||
*o = owners
|
||||
}
|
||||
|
||||
func (o OwnerStatusListSpec) IsOwner(name string, groups []string) bool {
|
||||
var groupSet map[string]struct{}
|
||||
if len(groups) > 0 {
|
||||
groupSet = make(map[string]struct{}, len(groups))
|
||||
for _, g := range groups {
|
||||
groupSet[g] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, owner := range o {
|
||||
switch owner.Kind {
|
||||
case UserOwner, ServiceAccountOwner:
|
||||
if name == owner.Name {
|
||||
return true
|
||||
}
|
||||
case GroupOwner:
|
||||
if groupSet == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := groupSet[owner.Name]; ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (o OwnerStatusListSpec) FindOwner(name string, kind OwnerKind) (CoreOwnerSpec, bool) {
|
||||
// Sort in-place by (Kind.String(), Name).
|
||||
sort.Sort(GetByKindAndName(o))
|
||||
|
||||
targetKind := kind.String()
|
||||
n := len(o)
|
||||
|
||||
idx := sort.Search(n, func(i int) bool {
|
||||
ki := o[i].Kind.String()
|
||||
|
||||
switch {
|
||||
case ki > targetKind:
|
||||
return true
|
||||
case ki < targetKind:
|
||||
return false
|
||||
default:
|
||||
return o[i].Name >= name
|
||||
}
|
||||
})
|
||||
|
||||
if idx < n &&
|
||||
o[idx].Kind.String() == targetKind &&
|
||||
o[idx].Name == name {
|
||||
return o[idx], true
|
||||
}
|
||||
|
||||
return CoreOwnerSpec{}, false
|
||||
}
|
||||
|
||||
type GetByKindAndName OwnerStatusListSpec
|
||||
|
||||
func (b GetByKindAndName) Len() int {
|
||||
return len(b)
|
||||
}
|
||||
|
||||
func (b GetByKindAndName) 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 GetByKindAndName) Swap(i, j int) {
|
||||
b[i], b[j] = b[j], b[i]
|
||||
}
|
||||
355
pkg/api/owner_status_list_test.go
Normal file
355
pkg/api/owner_status_list_test.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/projectcapsule/capsule/pkg/api"
|
||||
)
|
||||
|
||||
func slowIsOwner(o api.OwnerStatusListSpec, name string, groups []string) bool {
|
||||
for _, owner := range o {
|
||||
switch owner.Kind {
|
||||
case api.UserOwner, api.ServiceAccountOwner:
|
||||
if name == owner.Name {
|
||||
return true
|
||||
}
|
||||
case api.GroupOwner:
|
||||
for _, group := range groups {
|
||||
if group == owner.Name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// linearFind is the obvious, slow, but correct reference implementation.
|
||||
func linearFind(o api.OwnerStatusListSpec, name string, kind api.OwnerKind) (api.CoreOwnerSpec, bool) {
|
||||
for _, x := range o {
|
||||
if x.Kind == kind && x.Name == name {
|
||||
return x, true
|
||||
}
|
||||
}
|
||||
return api.CoreOwnerSpec{}, false
|
||||
}
|
||||
|
||||
// randomName generates a simple lowercase name of length n.
|
||||
func randomName(rnd *rand.Rand, n int) string {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyz"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[rnd.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestUpsert_AddsNewOwnerToEmptyList(t *testing.T) {
|
||||
var list api.OwnerStatusListSpec
|
||||
|
||||
list.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"admin"},
|
||||
})
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected 1 owner, got %d", len(list))
|
||||
}
|
||||
got := list[0]
|
||||
if got.Kind != api.UserOwner || got.Name != "alice" {
|
||||
t.Fatalf("unexpected owner: %+v", got)
|
||||
}
|
||||
if !reflect.DeepEqual(got.ClusterRoles, []string{"admin"}) {
|
||||
t.Fatalf("unexpected roles: %#v", got.ClusterRoles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsert_MergesClusterRolesForExistingOwner(t *testing.T) {
|
||||
list := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "capsule-namespace-deleter"},
|
||||
},
|
||||
}
|
||||
|
||||
list.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"extra-sad"},
|
||||
})
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected 1 owner, got %d", len(list))
|
||||
}
|
||||
got := list[0]
|
||||
if got.Kind != api.UserOwner || got.Name != "alice" {
|
||||
t.Fatalf("unexpected owner: %+v", got)
|
||||
}
|
||||
|
||||
// Roles should be union of both sets, order: existing roles first, then new ones
|
||||
expected := []string{"admin", "capsule-namespace-deleter", "extra-sad"}
|
||||
if !reflect.DeepEqual(got.ClusterRoles, expected) {
|
||||
t.Fatalf("expected roles %v, got %v", expected, got.ClusterRoles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsert_DeduplicatesClusterRoles(t *testing.T) {
|
||||
list := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"admin", "viewer"},
|
||||
},
|
||||
}
|
||||
|
||||
list.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"viewer", "editor"},
|
||||
})
|
||||
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("expected 1 owner, got %d", len(list))
|
||||
}
|
||||
got := list[0]
|
||||
|
||||
expected := []string{"admin", "viewer", "editor"}
|
||||
if !reflect.DeepEqual(got.ClusterRoles, expected) {
|
||||
t.Fatalf("expected roles %v, got %v", expected, got.ClusterRoles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpsert_KeepsListSortedAndMergesIntoExistingInUnsortedInitialSlice(t *testing.T) {
|
||||
// Start with an unsorted slice, as could come from API/server
|
||||
list := api.OwnerStatusListSpec{
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "bob",
|
||||
},
|
||||
ClusterRoles: []string{"bob-role"},
|
||||
},
|
||||
{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"admin"},
|
||||
},
|
||||
}
|
||||
|
||||
// Upsert another alice
|
||||
list.Upsert(api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Kind: api.UserOwner,
|
||||
Name: "alice",
|
||||
},
|
||||
ClusterRoles: []string{"extra"},
|
||||
})
|
||||
|
||||
if len(list) != 2 {
|
||||
t.Fatalf("expected 2 owners (alice, bob), got %d", len(list))
|
||||
}
|
||||
|
||||
// Ensure sorted by Kind.Name: alice before bob
|
||||
// (relies on ByKindAndName order)
|
||||
sorted := make(api.OwnerStatusListSpec, len(list))
|
||||
copy(sorted, list)
|
||||
sort.Sort(api.GetByKindAndName(sorted))
|
||||
|
||||
if !reflect.DeepEqual(list, sorted) {
|
||||
t.Fatalf("expected list to be sorted by kind+name, got %#v", list)
|
||||
}
|
||||
|
||||
// Find alice and check roles
|
||||
var alice *api.CoreOwnerSpec
|
||||
for i := range list {
|
||||
if list[i].Name == "alice" {
|
||||
alice = &list[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if alice == nil {
|
||||
t.Fatalf("alice not found in list")
|
||||
}
|
||||
|
||||
expectedRoles := []string{"admin", "extra"}
|
||||
if !reflect.DeepEqual(alice.ClusterRoles, expectedRoles) {
|
||||
t.Fatalf("expected alice roles %v, got %v", expectedRoles, alice.ClusterRoles)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByKindAndNameOrdering(t *testing.T) {
|
||||
o := api.OwnerStatusListSpec{
|
||||
api.CoreOwnerSpec{UserSpec: api.UserSpec{Name: "b", Kind: api.ServiceAccountOwner}},
|
||||
api.CoreOwnerSpec{UserSpec: api.UserSpec{Name: "z", Kind: api.UserOwner}},
|
||||
api.CoreOwnerSpec{UserSpec: api.UserSpec{Name: "a", Kind: api.GroupOwner}},
|
||||
api.CoreOwnerSpec{UserSpec: api.UserSpec{Name: "a", Kind: api.UserOwner}},
|
||||
}
|
||||
|
||||
// Sort using production ordering
|
||||
got := append(api.OwnerStatusListSpec(nil), o...)
|
||||
sort.Sort(api.GetByKindAndName(got))
|
||||
|
||||
// Manually sorted expectation using the same logic.
|
||||
want := append(api.OwnerStatusListSpec(nil), o...)
|
||||
sort.Slice(want, func(i, j int) bool {
|
||||
if want[i].Kind.String() != want[j].Kind.String() {
|
||||
return want[i].Kind.String() < want[j].Kind.String()
|
||||
}
|
||||
return want[i].Name < want[j].Name
|
||||
})
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("length mismatch: got %d, want %d", len(got), len(want))
|
||||
}
|
||||
for i := range got {
|
||||
if !reflect.DeepEqual(got[i], want[i]) {
|
||||
t.Fatalf("ordering mismatch at %d: got %+v, want %+v", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindOwner_Randomized(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(42)) // fixed seed for deterministic test runs
|
||||
|
||||
ownerKinds := []api.OwnerKind{
|
||||
api.GroupOwner,
|
||||
api.UserOwner,
|
||||
api.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
const (
|
||||
numLists = 200
|
||||
maxLength = 40
|
||||
numLookupsPerList = 80
|
||||
)
|
||||
|
||||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||||
var list api.OwnerStatusListSpec
|
||||
n := rnd.Intn(maxLength)
|
||||
for i := 0; i < n; i++ {
|
||||
k := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
list = append(list, api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||||
Kind: k,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||||
var qName string
|
||||
var qKind api.OwnerKind
|
||||
|
||||
if len(list) > 0 && rnd.Float64() < 0.6 {
|
||||
// 60% of lookups: pick a real element, must be found
|
||||
pick := list[rnd.Intn(len(list))]
|
||||
qName = pick.Name
|
||||
qKind = pick.Kind
|
||||
} else {
|
||||
// 40%: random query, may or may not exist
|
||||
qName = randomName(rnd, 3+rnd.Intn(4))
|
||||
qKind = ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
}
|
||||
|
||||
listCopy := append(api.OwnerStatusListSpec(nil), list...)
|
||||
gotOwner, gotFound := listCopy.FindOwner(qName, qKind)
|
||||
wantOwner, wantFound := linearFind(list, qName, qKind)
|
||||
|
||||
if gotFound != wantFound {
|
||||
t.Fatalf("list=%d lookup=%d: found mismatch for (%q,%v): got=%v, want=%v",
|
||||
listIdx, lookupIdx, qName, qKind, gotFound, wantFound)
|
||||
}
|
||||
if gotFound && !reflect.DeepEqual(gotOwner, wantOwner) {
|
||||
t.Fatalf("list=%d lookup=%d: owner mismatch for (%q,%v):\n got= %+v\nwant= %+v",
|
||||
listIdx, lookupIdx, qName, qKind, gotOwner, wantOwner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsOwner_RandomizedMatchesSlowImplementation(t *testing.T) {
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
ownerKinds := []api.OwnerKind{
|
||||
api.UserOwner,
|
||||
api.GroupOwner,
|
||||
api.ServiceAccountOwner,
|
||||
}
|
||||
|
||||
const (
|
||||
numLists = 200
|
||||
maxOwnersPerList = 30
|
||||
numLookupsPerList = 80
|
||||
maxGroupsPerUser = 10
|
||||
)
|
||||
|
||||
for listIdx := 0; listIdx < numLists; listIdx++ {
|
||||
// Generate a random owner list (possibly with duplicates).
|
||||
var owners api.OwnerStatusListSpec
|
||||
nOwners := rnd.Intn(maxOwnersPerList)
|
||||
for i := 0; i < nOwners; i++ {
|
||||
kind := ownerKinds[rnd.Intn(len(ownerKinds))]
|
||||
owners = append(owners, api.CoreOwnerSpec{
|
||||
UserSpec: api.UserSpec{
|
||||
Name: randomName(rnd, 3+rnd.Intn(4)), // length 3–6
|
||||
Kind: kind,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for lookupIdx := 0; lookupIdx < numLookupsPerList; lookupIdx++ {
|
||||
// Generate a random userName and groups,
|
||||
// sometimes biased to hit existing owners/groups.
|
||||
var userName string
|
||||
var groups []string
|
||||
|
||||
// 50% of the time: pick an existing owner name as userName
|
||||
if len(owners) > 0 && rnd.Float64() < 0.5 {
|
||||
pick := owners[rnd.Intn(len(owners))]
|
||||
userName = pick.Name
|
||||
} else {
|
||||
userName = randomName(rnd, 3+rnd.Intn(4))
|
||||
}
|
||||
|
||||
// Random groups, sometimes including owner names
|
||||
nGroups := rnd.Intn(maxGroupsPerUser)
|
||||
for i := 0; i < nGroups; i++ {
|
||||
if len(owners) > 0 && rnd.Float64() < 0.5 {
|
||||
pick := owners[rnd.Intn(len(owners))]
|
||||
groups = append(groups, pick.Name)
|
||||
} else {
|
||||
groups = append(groups, randomName(rnd, 3+rnd.Intn(4)))
|
||||
}
|
||||
}
|
||||
|
||||
got := owners.IsOwner(userName, groups)
|
||||
want := slowIsOwner(owners, userName, groups)
|
||||
|
||||
if got != want {
|
||||
t.Fatalf("list=%d lookup=%d: mismatch\n owners=%v\n user=%q\n groups=%v\n optimized=%v\n slow=%v",
|
||||
listIdx, lookupIdx, owners, userName, groups, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package rbac
|
||||
package api
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
@@ -14,7 +14,7 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
clusterRoles = map[string]*rbacv1.ClusterRole{
|
||||
ClusterRoles = map[string]*rbacv1.ClusterRole{
|
||||
ProvisionerRoleName: {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ProvisionerRoleName,
|
||||
@@ -41,7 +41,7 @@ var (
|
||||
},
|
||||
}
|
||||
|
||||
provisionerClusterRoleBinding = &rbacv1.ClusterRoleBinding{
|
||||
ProvisionerClusterRoleBinding = &rbacv1.ClusterRoleBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ProvisionerRoleName,
|
||||
},
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
// Selector for resources and their labels or selecting origin namespaces
|
||||
// +kubebuilder:object:generate=true
|
||||
type NamespaceSelector struct {
|
||||
// Select Items based on their labels. If the namespaceSelector is also set, the selector is applied
|
||||
// to items within the selected namespaces. Otherwise for all the items.
|
||||
*metav1.LabelSelector `json:",inline"`
|
||||
}
|
||||
|
||||
// GetMatchingNamespaces retrieves the list of namespaces that match the NamespaceSelector.
|
||||
func (s *NamespaceSelector) GetMatchingNamespaces(ctx context.Context, client client.Client) ([]corev1.Namespace, error) {
|
||||
if s.LabelSelector == nil {
|
||||
return nil, nil // No namespace selector means all namespaces
|
||||
}
|
||||
|
||||
nsSelector, err := metav1.LabelSelectorAsSelector(s.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid namespace selector: %w", err)
|
||||
}
|
||||
|
||||
namespaceList := &corev1.NamespaceList{}
|
||||
if err := client.List(ctx, namespaceList); err != nil {
|
||||
return nil, fmt.Errorf("failed to list namespaces: %w", err)
|
||||
}
|
||||
|
||||
var matchingNamespaces []corev1.Namespace
|
||||
|
||||
for _, ns := range namespaceList.Items {
|
||||
if nsSelector.Matches(labels.Set(ns.Labels)) {
|
||||
matchingNamespaces = append(matchingNamespaces, ns)
|
||||
}
|
||||
}
|
||||
|
||||
return matchingNamespaces, nil
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2020-2025 Project Capsule Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//nolint:dupl
|
||||
package api
|
||||
|
||||
import (
|
||||
|
||||
@@ -161,6 +161,27 @@ func (in *AllowedServices) DeepCopy() *AllowedServices {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CoreOwnerSpec) DeepCopyInto(out *CoreOwnerSpec) {
|
||||
*out = *in
|
||||
out.UserSpec = in.UserSpec
|
||||
if in.ClusterRoles != nil {
|
||||
in, out := &in.ClusterRoles, &out.ClusterRoles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CoreOwnerSpec.
|
||||
func (in *CoreOwnerSpec) DeepCopy() *CoreOwnerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CoreOwnerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *DefaultAllowedListSpec) DeepCopyInto(out *DefaultAllowedListSpec) {
|
||||
*out = *in
|
||||
@@ -239,26 +260,6 @@ func (in *LimitRangesSpec) DeepCopy() *LimitRangesSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NamespaceSelector) DeepCopyInto(out *NamespaceSelector) {
|
||||
*out = *in
|
||||
if in.LabelSelector != nil {
|
||||
in, out := &in.LabelSelector, &out.LabelSelector
|
||||
*out = new(v1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSelector.
|
||||
func (in *NamespaceSelector) DeepCopy() *NamespaceSelector {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NamespaceSelector)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NetworkPolicySpec) DeepCopyInto(out *NetworkPolicySpec) {
|
||||
*out = *in
|
||||
@@ -305,12 +306,7 @@ func (in OwnerListSpec) DeepCopy() OwnerListSpec {
|
||||
// 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)
|
||||
}
|
||||
in.CoreOwnerSpec.DeepCopyInto(&out.CoreOwnerSpec)
|
||||
if in.ProxyOperations != nil {
|
||||
in, out := &in.ProxyOperations, &out.ProxyOperations
|
||||
*out = make([]ProxySettings, len(*in))
|
||||
@@ -344,6 +340,27 @@ func (in *OwnerSpec) DeepCopy() *OwnerSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in OwnerStatusListSpec) DeepCopyInto(out *OwnerStatusListSpec) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(OwnerStatusListSpec, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OwnerStatusListSpec.
|
||||
func (in OwnerStatusListSpec) DeepCopy() OwnerStatusListSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OwnerStatusListSpec)
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user