Compare commits

...

10 Commits

Author SHA1 Message Date
Dario Tranchitella
c0d4aab582 build(helm): CRD update for PriorityClass enum 2021-07-21 16:48:13 +02:00
Dario Tranchitella
6761fb93dc build(kustomize): CRD update for PriorityClass enum 2021-07-21 16:48:13 +02:00
Dario Tranchitella
bf9e0f6b10 test: PriorityClass proxy operations conversion 2021-07-21 16:48:13 +02:00
Dario Tranchitella
f937942c49 feat: capsule-proxy operations for PriorityClass resources 2021-07-21 16:48:13 +02:00
Dario Tranchitella
89d7f301c6 build(helm): CRD update for v1beta1 service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
2a6ff09340 build(kustomize): CRD update for v1beta1 service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
35f48107fc test(e2e): aligning tests to new v1beta1 structure and ExternalName case 2021-07-21 14:34:56 +02:00
Dario Tranchitella
7aa62b6f1d test: conversion for new Service options 2021-07-21 14:34:56 +02:00
Dario Tranchitella
58645f39bb chore(samples): example for ServiceOptions 2021-07-21 14:34:56 +02:00
Dario Tranchitella
0e55823a0c feat: toggling ExternalName service 2021-07-21 14:34:56 +02:00
17 changed files with 412 additions and 113 deletions

View File

@@ -22,39 +22,47 @@ const (
podPriorityAllowedAnnotation = "priorityclass.capsule.clastix.io/allowed"
podPriorityAllowedRegexAnnotation = "priorityclass.capsule.clastix.io/allowed-regex"
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
enableNodePortsAnnotation = "capsule.clastix.io/enable-node-ports"
enableExternalNameAnnotation = "capsule.clastix.io/enable-external-name"
ownerGroupsAnnotation = "owners.capsule.clastix.io/group"
ownerUsersAnnotation = "owners.capsule.clastix.io/user"
ownerServiceAccountAnnotation = "owners.capsule.clastix.io/serviceaccount"
enableNodeListingAnnotation = "capsule.clastix.io/enable-node-listing"
enableNodeUpdateAnnotation = "capsule.clastix.io/enable-node-update"
enableNodeDeletionAnnotation = "capsule.clastix.io/enable-node-deletion"
enableStorageClassListingAnnotation = "capsule.clastix.io/enable-storageclass-listing"
enableStorageClassUpdateAnnotation = "capsule.clastix.io/enable-storageclass-update"
enableStorageClassDeletionAnnotation = "capsule.clastix.io/enable-storageclass-deletion"
enableIngressClassListingAnnotation = "capsule.clastix.io/enable-ingressclass-listing"
enableIngressClassUpdateAnnotation = "capsule.clastix.io/enable-ingressclass-update"
enableIngressClassDeletionAnnotation = "capsule.clastix.io/enable-ingressclass-deletion"
enableNodeListingAnnotation = "capsule.clastix.io/enable-node-listing"
enableNodeUpdateAnnotation = "capsule.clastix.io/enable-node-update"
enableNodeDeletionAnnotation = "capsule.clastix.io/enable-node-deletion"
enableStorageClassListingAnnotation = "capsule.clastix.io/enable-storageclass-listing"
enableStorageClassUpdateAnnotation = "capsule.clastix.io/enable-storageclass-update"
enableStorageClassDeletionAnnotation = "capsule.clastix.io/enable-storageclass-deletion"
enableIngressClassListingAnnotation = "capsule.clastix.io/enable-ingressclass-listing"
enableIngressClassUpdateAnnotation = "capsule.clastix.io/enable-ingressclass-update"
enableIngressClassDeletionAnnotation = "capsule.clastix.io/enable-ingressclass-deletion"
enablePriorityClassListingAnnotation = "capsule.clastix.io/enable-priorityclass-listing"
enablePriorityClassUpdateAnnotation = "capsule.clastix.io/enable-priorityclass-update"
enablePriorityClassDeletionAnnotation = "capsule.clastix.io/enable-priorityclass-deletion"
)
func (t *Tenant) convertV1Alpha1OwnerToV1Beta1() capsulev1beta1.OwnerListSpec {
var serviceKindToAnnotationMap = map[capsulev1beta1.ProxyServiceKind][]string{
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
capsulev1beta1.IngressClassesProxy: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
capsulev1beta1.NodesProxy: {enableNodeListingAnnotation, enableNodeUpdateAnnotation, enableNodeDeletionAnnotation},
capsulev1beta1.StorageClassesProxy: {enableStorageClassListingAnnotation, enableStorageClassUpdateAnnotation, enableStorageClassDeletionAnnotation},
capsulev1beta1.IngressClassesProxy: {enableIngressClassListingAnnotation, enableIngressClassUpdateAnnotation, enableIngressClassDeletionAnnotation},
capsulev1beta1.PriorityClassesProxy: {enablePriorityClassListingAnnotation, enablePriorityClassUpdateAnnotation, enablePriorityClassDeletionAnnotation},
}
var annotationToOperationMap = map[string]capsulev1beta1.ProxyOperation{
enableNodeListingAnnotation: capsulev1beta1.ListOperation,
enableNodeUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableNodeDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableStorageClassListingAnnotation: capsulev1beta1.ListOperation,
enableStorageClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableStorageClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableIngressClassListingAnnotation: capsulev1beta1.ListOperation,
enableIngressClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableIngressClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableNodeListingAnnotation: capsulev1beta1.ListOperation,
enableNodeUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableNodeDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableStorageClassListingAnnotation: capsulev1beta1.ListOperation,
enableStorageClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableStorageClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enableIngressClassListingAnnotation: capsulev1beta1.ListOperation,
enableIngressClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enableIngressClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
enablePriorityClassListingAnnotation: capsulev1beta1.ListOperation,
enablePriorityClassUpdateAnnotation: capsulev1beta1.UpdateOperation,
enablePriorityClassDeletionAnnotation: capsulev1beta1.DeleteOperation,
}
var annotationToOwnerKindMap = map[string]capsulev1beta1.OwnerKind{
ownerUsersAnnotation: capsulev1beta1.UserOwner,
@@ -136,9 +144,13 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
}
}
if t.Spec.ServicesMetadata != nil {
dst.Spec.ServicesMetadata = &capsulev1beta1.AdditionalMetadataSpec{
AdditionalLabels: t.Spec.ServicesMetadata.AdditionalLabels,
AdditionalAnnotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{
AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
AdditionalLabels: t.Spec.ServicesMetadata.AdditionalLabels,
AdditionalAnnotations: t.Spec.ServicesMetadata.AdditionalAnnotations,
},
}
}
}
if t.Spec.StorageClasses != nil {
@@ -226,7 +238,28 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableNodePortsAnnotation, t.GetName()))
}
dst.Spec.EnableNodePorts = pointer.BoolPtr(val)
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.NodePort = pointer.BoolPtr(val)
}
enableExternalName, ok := annotations[enableExternalNameAnnotation]
if ok {
val, err := strconv.ParseBool(enableExternalName)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("unable to parse %s annotation on tenant %s", enableExternalNameAnnotation, t.GetName()))
}
if dst.Spec.ServiceOptions == nil {
dst.Spec.ServiceOptions = &capsulev1beta1.ServiceOptions{}
}
if dst.Spec.ServiceOptions.AllowedServices == nil {
dst.Spec.ServiceOptions.AllowedServices = &capsulev1beta1.AllowedServices{}
}
dst.Spec.ServiceOptions.AllowedServices.ExternalName = pointer.BoolPtr(val)
}
// Status
@@ -240,6 +273,7 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
delete(dst.ObjectMeta.Annotations, podPriorityAllowedAnnotation)
delete(dst.ObjectMeta.Annotations, podPriorityAllowedRegexAnnotation)
delete(dst.ObjectMeta.Annotations, enableNodePortsAnnotation)
delete(dst.ObjectMeta.Annotations, enableExternalNameAnnotation)
delete(dst.ObjectMeta.Annotations, ownerGroupsAnnotation)
delete(dst.ObjectMeta.Annotations, ownerUsersAnnotation)
delete(dst.ObjectMeta.Annotations, ownerServiceAccountAnnotation)
@@ -252,6 +286,9 @@ func (t *Tenant) ConvertTo(dstRaw conversion.Hub) error {
delete(dst.ObjectMeta.Annotations, enableIngressClassListingAnnotation)
delete(dst.ObjectMeta.Annotations, enableIngressClassUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enableIngressClassDeletionAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassListingAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassUpdateAnnotation)
delete(dst.ObjectMeta.Annotations, enablePriorityClassDeletionAnnotation)
return nil
}
@@ -304,6 +341,17 @@ func (t *Tenant) convertV1Beta1OwnerToV1Alpha1(src *capsulev1beta1.Tenant) {
proxyAnnotations[enableNodeDeletionAnnotation] = append(proxyAnnotations[enableNodeDeletionAnnotation], owner.Name)
}
}
case capsulev1beta1.PriorityClassesProxy:
for _, operation := range setting.Operations {
switch operation {
case capsulev1beta1.ListOperation:
proxyAnnotations[enablePriorityClassListingAnnotation] = append(proxyAnnotations[enablePriorityClassListingAnnotation], owner.Name)
case capsulev1beta1.UpdateOperation:
proxyAnnotations[enablePriorityClassUpdateAnnotation] = append(proxyAnnotations[enablePriorityClassUpdateAnnotation], owner.Name)
case capsulev1beta1.DeleteOperation:
proxyAnnotations[enablePriorityClassDeletionAnnotation] = append(proxyAnnotations[enablePriorityClassDeletionAnnotation], owner.Name)
}
}
case capsulev1beta1.StorageClassesProxy:
for _, operation := range setting.Operations {
switch operation {
@@ -364,10 +412,10 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
AdditionalAnnotations: src.Spec.NamespacesMetadata.AdditionalAnnotations,
}
}
if src.Spec.ServicesMetadata != nil {
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AdditionalMetadata != nil {
t.Spec.ServicesMetadata = &AdditionalMetadataSpec{
AdditionalLabels: src.Spec.ServicesMetadata.AdditionalLabels,
AdditionalAnnotations: src.Spec.ServicesMetadata.AdditionalAnnotations,
AdditionalLabels: src.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels,
AdditionalAnnotations: src.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations,
}
}
if src.Spec.StorageClasses != nil {
@@ -437,7 +485,10 @@ func (t *Tenant) ConvertFrom(srcRaw conversion.Hub) error {
}
}
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.EnableNodePorts)
if src.Spec.ServiceOptions != nil && src.Spec.ServiceOptions.AllowedServices != nil {
t.Annotations[enableNodePortsAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.NodePort)
t.Annotations[enableExternalNameAnnotation] = strconv.FormatBool(*src.Spec.ServiceOptions.AllowedServices.ExternalName)
}
// Status
t.Status = TenantStatus{

View File

@@ -43,6 +43,13 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
"foo": "bar",
},
}
var v1beta1ServiceOptions = &capsulev1beta1.ServiceOptions{
AdditionalMetadata: v1beta1AdditionalMetadataSpec,
AllowedServices: &capsulev1beta1.AllowedServices{
NodePort: pointer.BoolPtr(false),
ExternalName: pointer.BoolPtr(false),
},
}
var v1beta1AllowedListSpec = &capsulev1beta1.AllowedListSpec{
Exact: []string{"foo", "bar"},
Regex: "^foo*",
@@ -164,6 +171,10 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
Kind: "StorageClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
{
Kind: "PriorityClasses",
Operations: []capsulev1beta1.ProxyOperation{"List"},
},
},
},
{
@@ -213,7 +224,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
},
NamespaceQuota: &namespaceQuota,
NamespacesMetadata: v1beta1AdditionalMetadataSpec,
ServicesMetadata: v1beta1AdditionalMetadataSpec,
ServiceOptions: v1beta1ServiceOptions,
StorageClasses: v1beta1AllowedListSpec,
IngressClasses: v1beta1AllowedListSpec,
IngressHostnames: v1beta1AllowedListSpec,
@@ -248,7 +259,6 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
Exact: []string{"default"},
Regex: "^tier-.*$",
},
EnableNodePorts: pointer.BoolPtr(false),
},
Status: capsulev1beta1.TenantStatus{
Size: 1,
@@ -266,6 +276,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
Annotations: map[string]string{
"foo": "bar",
podAllowedImagePullPolicyAnnotation: "Always,IfNotPresent",
enableExternalNameAnnotation: "false",
enableNodePortsAnnotation: "false",
podPriorityAllowedAnnotation: "default",
podPriorityAllowedRegexAnnotation: "^tier-.*$",
@@ -280,6 +291,7 @@ func generateTenantsSpecs() (Tenant, capsulev1beta1.Tenant) {
enableIngressClassListingAnnotation: "alice,owner-foo,owner-bar",
enableIngressClassUpdateAnnotation: "alice,bob",
enableIngressClassDeletionAnnotation: "alice,jack",
enablePriorityClassListingAnnotation: "jack",
},
},
Spec: TenantSpec{

View File

@@ -31,7 +31,7 @@ func (p ProxyOperation) String() string {
return string(p)
}
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses
// +kubebuilder:validation:Enum=Nodes;StorageClasses;IngressClasses;PriorityClasses
type ProxyServiceKind string
func (p ProxyServiceKind) String() string {
@@ -39,9 +39,10 @@ func (p ProxyServiceKind) String() string {
}
const (
NodesProxy ProxyServiceKind = "Nodes"
StorageClassesProxy ProxyServiceKind = "StorageClasses"
IngressClassesProxy ProxyServiceKind = "IngressClasses"
NodesProxy ProxyServiceKind = "Nodes"
StorageClassesProxy ProxyServiceKind = "StorageClasses"
IngressClassesProxy ProxyServiceKind = "IngressClasses"
PriorityClassesProxy ProxyServiceKind = "PriorityClasses"
ListOperation ProxyOperation = "List"
UpdateOperation ProxyOperation = "Update"

View File

@@ -0,0 +1,20 @@
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package v1beta1
type ServiceOptions struct {
// Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
AdditionalMetadata *AdditionalMetadataSpec `json:"additionalMetadata,omitempty"`
// Block or deny certain type of Services. Optional.
AllowedServices *AllowedServices `json:"allowedServices,omitempty"`
}
type AllowedServices struct {
//+kubebuilder:default=true
// Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
NodePort *bool `json:"nodePort,omitempty"`
//+kubebuilder:default=true
// Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
ExternalName *bool `json:"externalName,omitempty"`
}

View File

@@ -17,8 +17,8 @@ type TenantSpec struct {
NamespaceQuota *int32 `json:"namespaceQuota,omitempty"`
// Specifies additional labels and annotations the Capsule operator places on any Namespace resource in the Tenant. Optional.
NamespacesMetadata *AdditionalMetadataSpec `json:"namespacesMetadata,omitempty"`
// Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
ServicesMetadata *AdditionalMetadataSpec `json:"servicesMetadata,omitempty"`
// Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
ServiceOptions *ServiceOptions `json:"serviceOptions,omitempty"`
// Specifies the allowed StorageClasses assigned to the Tenant. Capsule assures that all PersistentVolumeClaim resources created in the Tenant can use only one of the allowed StorageClasses. Optional.
StorageClasses *AllowedListSpec `json:"storageClasses,omitempty"`
// Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
@@ -43,10 +43,6 @@ type TenantSpec struct {
ImagePullPolicies []ImagePullPolicySpec `json:"imagePullPolicies,omitempty"`
// Specifies the allowed IngressClasses assigned to the Tenant. Capsule assures that all Ingress resources created in the Tenant can use only one of the allowed IngressClasses. Optional.
PriorityClasses *AllowedListSpec `json:"priorityClasses,omitempty"`
//+kubebuilder:default=true
// Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
EnableNodePorts *bool `json:"enableNodePorts,omitempty"`
}
//+kubebuilder:object:root=true

View File

@@ -83,6 +83,31 @@ func (in *AllowedListSpec) DeepCopy() *AllowedListSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AllowedServices) DeepCopyInto(out *AllowedServices) {
*out = *in
if in.NodePort != nil {
in, out := &in.NodePort, &out.NodePort
*out = new(bool)
**out = **in
}
if in.ExternalName != nil {
in, out := &in.ExternalName, &out.ExternalName
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AllowedServices.
func (in *AllowedServices) DeepCopy() *AllowedServices {
if in == nil {
return nil
}
out := new(AllowedServices)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in ByKindAndName) DeepCopyInto(out *ByKindAndName) {
{
@@ -253,6 +278,31 @@ func (in *ResourceQuotaSpec) DeepCopy() *ResourceQuotaSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServiceOptions) DeepCopyInto(out *ServiceOptions) {
*out = *in
if in.AdditionalMetadata != nil {
in, out := &in.AdditionalMetadata, &out.AdditionalMetadata
*out = new(AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
if in.AllowedServices != nil {
in, out := &in.AllowedServices, &out.AllowedServices
*out = new(AllowedServices)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceOptions.
func (in *ServiceOptions) DeepCopy() *ServiceOptions {
if in == nil {
return nil
}
out := new(ServiceOptions)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Tenant) DeepCopyInto(out *Tenant) {
*out = *in
@@ -332,9 +382,9 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
*out = new(AdditionalMetadataSpec)
(*in).DeepCopyInto(*out)
}
if in.ServicesMetadata != nil {
in, out := &in.ServicesMetadata, &out.ServicesMetadata
*out = new(AdditionalMetadataSpec)
if in.ServiceOptions != nil {
in, out := &in.ServiceOptions, &out.ServiceOptions
*out = new(ServiceOptions)
(*in).DeepCopyInto(*out)
}
if in.StorageClasses != nil {
@@ -401,11 +451,6 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) {
*out = new(AllowedListSpec)
(*in).DeepCopyInto(*out)
}
if in.EnableNodePorts != nil {
in, out := &in.EnableNodePorts, &out.EnableNodePorts
*out = new(bool)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec.

View File

@@ -646,10 +646,6 @@ spec:
allowedRegex:
type: string
type: object
enableNodePorts:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
externalServiceIPs:
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
properties:
@@ -1062,6 +1058,7 @@ spec:
- Nodes
- StorageClasses
- IngressClasses
- PriorityClasses
type: string
operations:
items:
@@ -1141,16 +1138,32 @@ spec:
type: object
type: array
type: object
servicesMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
serviceOptions:
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
additionalLabels:
additionalProperties:
type: string
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
externalName:
default: true
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
nodePort:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
type: object
type: object
storageClasses:

View File

@@ -646,10 +646,6 @@ spec:
allowedRegex:
type: string
type: object
enableNodePorts:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
externalServiceIPs:
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
properties:
@@ -1062,6 +1058,7 @@ spec:
- Nodes
- StorageClasses
- IngressClasses
- PriorityClasses
type: string
operations:
items:
@@ -1141,16 +1138,32 @@ spec:
type: object
type: array
type: object
servicesMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
serviceOptions:
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
additionalLabels:
additionalProperties:
type: string
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
externalName:
default: true
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
nodePort:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
type: object
type: object
storageClasses:

View File

@@ -725,10 +725,6 @@ spec:
allowedRegex:
type: string
type: object
enableNodePorts:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
externalServiceIPs:
description: Specifies the external IPs that can be used in Services with type ClusterIP. An empty list means all the IPs are allowed. Optional.
properties:
@@ -1141,6 +1137,7 @@ spec:
- Nodes
- StorageClasses
- IngressClasses
- PriorityClasses
type: string
operations:
items:
@@ -1220,16 +1217,32 @@ spec:
type: object
type: array
type: object
servicesMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
serviceOptions:
description: Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
additionalAnnotations:
additionalProperties:
type: string
type: object
additionalLabels:
additionalProperties:
type: string
type: object
type: object
additionalLabels:
additionalProperties:
type: string
allowedServices:
description: Block or deny certain type of Services. Optional.
properties:
externalName:
default: true
description: Specifies if ExternalName service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
nodePort:
default: true
description: Specifies if NodePort service type resources are allowed for the Tenant. Default is true. Optional.
type: boolean
type: object
type: object
storageClasses:
@@ -1361,7 +1374,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/clastix/capsule:v0.1.0-rc3
image: quay.io/clastix/capsule:v0.1.0-rc4
imagePullPolicy: IfNotPresent
name: manager
ports:

View File

@@ -7,4 +7,4 @@ kind: Kustomization
images:
- name: controller
newName: quay.io/clastix/capsule
newTag: v0.1.0-rc3
newTag: v0.1.0-rc4

View File

@@ -16,7 +16,15 @@ spec:
- docker.io
- quay.io
allowedRegex: ^\w+.gcr.io$
enableNodePorts: false
serviceOptions:
additionalMetadata:
additionalAnnotations:
capsule.clastix.io/bgp: "true"
additionalLabels:
capsule.clastix.io/pool: gas
allowedServices:
nodePort: false
externalName: false
externalServiceIPs:
allowed:
- 10.20.0.0/16
@@ -122,11 +130,6 @@ spec:
-
hard:
requests.storage: 100Gi
servicesMetadata:
additionalAnnotations:
capsule.clastix.io/bgp: "true"
additionalLabels:
capsule.clastix.io/pool: gas
storageClasses:
allowed:
- default

View File

@@ -53,8 +53,8 @@ func (r *abstractServiceLabelsReconciler) Reconcile(ctx context.Context, request
}
_, err = controllerutil.CreateOrUpdate(ctx, r.client, r.obj, func() (err error) {
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServicesMetadata.AdditionalLabels))
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServicesMetadata.AdditionalAnnotations))
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels))
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations))
return nil
})
@@ -78,7 +78,7 @@ func (r *abstractServiceLabelsReconciler) getTenant(ctx context.Context, namespa
return nil, err
}
if tenant.Spec.ServicesMetadata == nil {
if tenant.Spec.ServiceOptions == nil || tenant.Spec.ServiceOptions.AdditionalMetadata == nil {
return nil, NewNoServicesMetadata(namespacedName.Name)
}

View File

@@ -0,0 +1,108 @@
//+build e2e
// Copyright 2020-2021 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/utils/pointer"
capsulev1beta1 "github.com/clastix/capsule/api/v1beta1"
)
var _ = Describe("creating an ExternalName service when it is disabled for Tenant", func() {
tnt := &capsulev1beta1.Tenant{
ObjectMeta: metav1.ObjectMeta{
Name: "disable-external-service",
},
Spec: capsulev1beta1.TenantSpec{
Owners: capsulev1beta1.OwnerListSpec{
{
Name: "google",
Kind: "User",
},
},
ServiceOptions: &capsulev1beta1.ServiceOptions{
AllowedServices: &capsulev1beta1.AllowedServices{
ExternalName: pointer.BoolPtr(false),
},
},
},
}
JustBeforeEach(func() {
EventuallyCreation(func() error {
tnt.ResourceVersion = ""
return k8sClient.Create(context.TODO(), tnt)
}).Should(Succeed())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.TODO(), tnt)).Should(Succeed())
})
It("should fail creating a service with ExternalService type", func() {
ns := NewNamespace("disable-external-service")
NamespaceCreation(ns, tnt.Spec.Owners[0], defaultTimeoutInterval).Should(Succeed())
EventuallyCreation(func() error {
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-ip",
Namespace: ns.GetName(),
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeClusterIP,
Ports: []corev1.ServicePort{
{
Port: 8888,
TargetPort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 8888,
},
Protocol: corev1.ProtocolTCP,
},
},
},
}
cs := ownerClient(tnt.Spec.Owners[0])
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
return err
}).Should(Succeed())
EventuallyCreation(func() error {
svc := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: "disable-external-service",
Namespace: ns.GetName(),
},
Spec: corev1.ServiceSpec{
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Port: 9999,
TargetPort: intstr.IntOrString{
Type: intstr.Int,
IntVal: 9999,
},
Protocol: corev1.ProtocolTCP,
},
},
},
}
cs := ownerClient(tnt.Spec.Owners[0])
_, err := cs.CoreV1().Services(ns.Name).Create(context.Background(), svc, metav1.CreateOptions{})
return err
}).ShouldNot(Succeed())
})
})

View File

@@ -30,7 +30,11 @@ var _ = Describe("creating a nodePort service when it is disabled for Tenant", f
Kind: "User",
},
},
EnableNodePorts: pointer.BoolPtr(false),
ServiceOptions: &capsulev1beta1.ServiceOptions{
AllowedServices: &capsulev1beta1.AllowedServices{
NodePort: pointer.BoolPtr(false),
},
},
},
}

View File

@@ -33,14 +33,16 @@ var _ = Describe("adding metadata to Service objects", func() {
Kind: "User",
},
},
ServicesMetadata: &capsulev1beta1.AdditionalMetadataSpec{
AdditionalLabels: map[string]string{
"k8s.io/custom-label": "foo",
"clastix.io/custom-label": "bar",
},
AdditionalAnnotations: map[string]string{
"k8s.io/custom-annotation": "bizz",
"clastix.io/custom-annotation": "buzz",
ServiceOptions: &capsulev1beta1.ServiceOptions{
AdditionalMetadata: &capsulev1beta1.AdditionalMetadataSpec{
AdditionalLabels: map[string]string{
"k8s.io/custom-label": "foo",
"clastix.io/custom-label": "bar",
},
AdditionalAnnotations: map[string]string{
"k8s.io/custom-annotation": "bizz",
"clastix.io/custom-annotation": "buzz",
},
},
},
AdditionalRoleBindings: []capsulev1beta1.AdditionalRoleBindingsSpec{
@@ -98,7 +100,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional labels", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: svc.GetName(), Namespace: ns.GetName()}, svc)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
ok, _ = HaveKeyWithValue(k, v).Match(svc.Labels)
if !ok {
return false
@@ -110,7 +112,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional annotations", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: svc.GetName(), Namespace: ns.GetName()}, svc)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
ok, _ = HaveKeyWithValue(k, v).Match(svc.Annotations)
if !ok {
return false
@@ -153,7 +155,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional labels", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ep.GetName(), Namespace: ns.GetName()}, ep)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
ok, _ = HaveKeyWithValue(k, v).Match(ep.Labels)
if !ok {
return false
@@ -165,7 +167,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional annotations", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: ep.GetName(), Namespace: ns.GetName()}, ep)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
ok, _ = HaveKeyWithValue(k, v).Match(ep.Annotations)
if !ok {
return false
@@ -212,7 +214,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional annotations EndpointSlice", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalAnnotations {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalAnnotations {
ok, _ = HaveKeyWithValue(k, v).Match(eps.Annotations)
if !ok {
return false
@@ -224,7 +226,7 @@ var _ = Describe("adding metadata to Service objects", func() {
By("checking additional labels on EndpointSlice", func() {
Eventually(func() (ok bool) {
Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: eps.GetName(), Namespace: ns.GetName()}, eps)).Should(Succeed())
for k, v := range tnt.Spec.ServicesMetadata.AdditionalLabels {
for k, v := range tnt.Spec.ServiceOptions.AdditionalMetadata.AdditionalLabels {
ok, _ = HaveKeyWithValue(k, v).Match(eps.Labels)
if !ok {
return false

View File

@@ -41,3 +41,13 @@ func NewNodePortDisabledError() error {
func (nodePortDisabled) Error() string {
return "NodePort service types are forbidden for the tenant: please, reach out to the system administrators"
}
type externalNameDisabled struct{}
func NewExternalNameDisabledError() error {
return &externalNameDisabled{}
}
func (externalNameDisabled) Error() string {
return "ExternalName service types are forbidden for the tenant: please, reach out to the system administrators"
}

View File

@@ -44,7 +44,7 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
tnt := tntList.Items[0]
if svc.Spec.Type == corev1.ServiceTypeNodePort && tnt.Spec.EnableNodePorts != nil && !*tnt.Spec.EnableNodePorts {
if svc.Spec.Type == corev1.ServiceTypeNodePort && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.NodePort {
recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenNodePort", "Service %s/%s cannot be type of NodePort for the current Tenant", req.Namespace, req.Name)
response := admission.Denied(NewNodePortDisabledError().Error())
@@ -52,6 +52,14 @@ func (r *handler) handleService(ctx context.Context, clt client.Client, decoder
return &response
}
if svc.Spec.Type == corev1.ServiceTypeExternalName && tnt.Spec.ServiceOptions != nil && tnt.Spec.ServiceOptions.AllowedServices != nil && !*tnt.Spec.ServiceOptions.AllowedServices.ExternalName {
recorder.Eventf(&tnt, corev1.EventTypeWarning, "ForbiddenExternalName", "Service %s/%s cannot be type of ExternalName for the current Tenant", req.Namespace, req.Name)
response := admission.Denied(NewExternalNameDisabledError().Error())
return &response
}
if svc.Spec.ExternalIPs == nil || tnt.Spec.ExternalServiceIPs == nil {
return nil
}