Skip to content

Commit

Permalink
feat(manager): add support for pod additionalMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
gchiesa committed Oct 30, 2023
1 parent 60cce9c commit 75f4266
Show file tree
Hide file tree
Showing 14 changed files with 481 additions and 1 deletion.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ dev-setup:
{'op': 'replace', 'path': '/webhooks/5/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/pods\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/6/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/persistentvolumeclaims\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/7/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/services\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
{'op': 'replace', 'path': '/webhooks/8/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}},\
{'op': 'replace', 'path': '/webhooks/9/clientConfig', 'value':{'url':\"$${WEBHOOK_URL}/tenants\",'caBundle':\"$${CA_BUNDLE}\"}}\
]" && \
kubectl patch crd tenants.capsule.clastix.io \
--type='json' -p="[\
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta2/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type TenantSpec struct {
NamespaceOptions *NamespaceOptions `json:"namespaceOptions,omitempty"`
// Specifies options for the Service, such as additional metadata or block of certain type of Services. Optional.
ServiceOptions *api.ServiceOptions `json:"serviceOptions,omitempty"`
// Specifies options for the Pods, such additional metadata to enforce on each pod part of the tenant
PodOptions *api.PodOptions `json:"podOptions,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.
// A default value can be specified, and all the PersistentVolumeClaim resources created will inherit the declared class.
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 48 additions & 0 deletions charts/capsule/crds/tenant-crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pod, such as additional metadata. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
type: boolean
Expand Down Expand Up @@ -1737,6 +1753,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pod, such as additional metadata. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
priorityClasses:
description: Specifies the allowed priorityClasses assigned to the
Tenant. Capsule assures that all Pods resources created in the Tenant
Expand Down Expand Up @@ -2869,6 +2901,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pod, such as additional metadata. Optional.
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled,
the deletion request will be declined.
Expand Down
18 changes: 18 additions & 0 deletions config/crd/bases/capsule.clastix.io_tenants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2859,6 +2859,24 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pods, such additional metadata
to enforce on each pod part of the tenant
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule
operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled,
the deletion request will be declined.
Expand Down
16 changes: 16 additions & 0 deletions config/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2437,6 +2437,22 @@ spec:
- name
type: object
type: array
podOptions:
description: Specifies options for the Pods, such additional metadata to enforce on each pod part of the tenant
properties:
additionalMetadata:
description: Specifies additional labels and annotations the Capsule operator places on any Service resource in the Tenant. Optional.
properties:
annotations:
additionalProperties:
type: string
type: object
labels:
additionalProperties:
type: string
type: object
type: object
type: object
preventDeletion:
description: Prevent accidental deletion of the Tenant. When enabled, the deletion request will be declined.
type: boolean
Expand Down
117 changes: 117 additions & 0 deletions controllers/podlabels/abstract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package podlabels

import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/projectcapsule/capsule/pkg/utils"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
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/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

"github.com/pkg/errors"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
)

type abstractPodLabelsReconciler struct {
obj client.Object
client client.Client
log logr.Logger
}

func (r *abstractPodLabelsReconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
tenant, err := r.getTenant(ctx, request.NamespacedName, r.client)
if err != nil {
r.log.Error(err, fmt.Sprintf("Cannot get tenant for %T %s/%s", r.obj, request.Namespace, request.Name))

noTenantObjError := &NonTenantObjectError{}
noPodMetaError := &NoPodMetadataError{}

if errors.As(err, &noTenantObjError) || errors.As(err, &noPodMetaError) {
return reconcile.Result{}, nil
}

r.log.Error(err, fmt.Sprintf("Cannot sync %T %s/%s labels", r.obj, r.obj.GetNamespace(), r.obj.GetName()))

return reconcile.Result{}, err
}

err = r.client.Get(ctx, request.NamespacedName, r.obj)
if err != nil {
if apierr.IsNotFound(err) {
return reconcile.Result{}, nil
}

return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, r.client, r.obj, func() (err error) {
r.obj.SetLabels(r.sync(r.obj.GetLabels(), tenant.Spec.PodOptions.AdditionalMetadata.Labels))
r.obj.SetAnnotations(r.sync(r.obj.GetAnnotations(), tenant.Spec.PodOptions.AdditionalMetadata.Annotations))
return nil
})
return reconcile.Result{}, err
}

func (r *abstractPodLabelsReconciler) getTenant(ctx context.Context, namespacedName types.NamespacedName, client client.Client) (*capsulev1beta2.Tenant, error) {
ns := &corev1.Namespace{}
tenant := &capsulev1beta2.Tenant{}

if err := client.Get(ctx, types.NamespacedName{Name: namespacedName.Namespace}, ns); err != nil {
return nil, err
}

capsuleLabel, _ := utils.GetTypeLabel(&capsulev1beta2.Tenant{})
if _, ok := ns.GetLabels()[capsuleLabel]; !ok {
return nil, NewNonTenantObject(namespacedName.Name)
}

if err := client.Get(ctx, types.NamespacedName{Name: ns.Labels[capsuleLabel]}, tenant); err != nil {
return nil, err
}

if tenant.Spec.PodOptions == nil || tenant.Spec.PodOptions.AdditionalMetadata == nil {
return nil, NewNoPodMetadata(namespacedName.Name)
}

return tenant, nil
}

func (r *abstractPodLabelsReconciler) sync(available map[string]string, tenantSpec map[string]string) map[string]string {
if tenantSpec != nil {
if available == nil {
available = tenantSpec
} else {
for key, value := range tenantSpec {
if available[key] != value {
available[key] = value
}
}
}
}

return available
}

func (r *abstractPodLabelsReconciler) forOptionPerInstanceName(ctx context.Context) builder.ForOption {
return builder.WithPredicates(predicate.NewPredicateFuncs(func(object client.Object) bool {
return r.IsNamespaceInTenant(ctx, object.GetNamespace())
}))
}

func (r *abstractPodLabelsReconciler) IsNamespaceInTenant(ctx context.Context, namespace string) bool {
tl := &capsulev1beta2.TenantList{}
if err := r.client.List(ctx, tl, client.MatchingFieldsSelector{
Selector: fields.OneTermEqualSelector(".status.namespaces", namespace),
}); err != nil {
return false
}

return len(tl.Items) > 0
}
27 changes: 27 additions & 0 deletions controllers/podlabels/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package podlabels

import "fmt"

type NonTenantObjectError struct {
objectName string
}

func NewNonTenantObject(objectName string) error {
return &NonTenantObjectError{objectName: objectName}
}

func (n NonTenantObjectError) Error() string {
return fmt.Sprintf("Skipping labels sync for %s as it doesn't belong to tenant", n.objectName)
}

type NoPodMetadataError struct {
objectName string
}

func NewNoPodMetadata(objectName string) error {
return &NoPodMetadataError{objectName: objectName}
}

func (n NoPodMetadataError) Error() string {
return fmt.Sprintf("Skipping labels sync for %s because no AdditionalLabels or AdditionalAnnotations presents in Tenant spec", n.objectName)
}
26 changes: 26 additions & 0 deletions controllers/podlabels/pod.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package podlabels

import (
"context"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
ctrl "sigs.k8s.io/controller-runtime"
)

type PodLabelsReconciler struct {
abstractPodLabelsReconciler

Log logr.Logger
}

func (r *PodLabelsReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
r.abstractPodLabelsReconciler = abstractPodLabelsReconciler{
obj: &corev1.Pod{},
client: mgr.GetClient(),
log: r.Log,
}

return ctrl.NewControllerManagedBy(mgr).
For(r.abstractPodLabelsReconciler.obj, r.abstractPodLabelsReconciler.forOptionPerInstanceName(ctx)).
Complete(r)
}
Loading

0 comments on commit 75f4266

Please sign in to comment.