Skip to content

Commit

Permalink
Generate configmaps for application-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
zoetrope committed Mar 8, 2024
1 parent d8be22d commit ac3ac76
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 8 deletions.
1 change: 0 additions & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ watch_file('./config/')
k8s_yaml(kustomize('./config/dev'))
k8s_resource(new_name='Cattage Resources', objects=[
'cattage:namespace',
'tenants.cattage.cybozu.io:customresourcedefinition',
'cattage-mutating-webhook-configuration:mutatingwebhookconfiguration',
'cattage-controller-manager:serviceaccount',
'cattage-leader-election-role:role',
Expand Down
5 changes: 5 additions & 0 deletions api/v1beta1/tenant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ type TenantSpec struct {
// Delegates is a list of other tenants that are delegated access to this tenant.
// +optional
Delegates []DelegateSpec `json:"delegates,omitempty"`

// ControllerName is the name of the application-controller that manages this tenant's applications.
// If not specified, the default controller is used.
// +optional
ControllerName string `json:"controllerName,omitempty"`
}

// RootNamespaceSpec defines the desired state of Namespace.
Expand Down
5 changes: 5 additions & 0 deletions charts/cattage/crds/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ spec:
type: string
type: array
type: object
controllerName:
description: |-
ControllerName is the name of the application-controller that manages this tenant's applications.
If not specified, the default controller is used.
type: string
delegates:
description: Delegates is a list of other tenants that are delegated access to this tenant.
items:
Expand Down
12 changes: 12 additions & 0 deletions charts/cattage/templates/generated.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ metadata:
helm.sh/chart: '{{ include "cattage.chart" . }}'
name: '{{ template "cattage.fullname" . }}-manager-role'
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
Expand Down
5 changes: 5 additions & 0 deletions config/crd/bases/cattage.cybozu.io_tenants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ spec:
type: string
type: array
type: object
controllerName:
description: |-
ControllerName is the name of the application-controller that manages this tenant's applications.
If not specified, the default controller is used.
type: string
delegates:
description: Delegates is a list of other tenants that are delegated
access to this tenant.
Expand Down
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ kind: ClusterRole
metadata:
name: manager-role
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
Expand Down
1 change: 1 addition & 0 deletions config/samples/tenant.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ metadata:
spec:
rootNamespaces:
- name: app-a
controllerName: alternative
---
apiVersion: cattage.cybozu.io/v1beta1
kind: Tenant
Expand Down
121 changes: 116 additions & 5 deletions controllers/tenant_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"fmt"
"strings"
"text/template"

cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1"
Expand Down Expand Up @@ -57,6 +58,7 @@ type TenantReconciler struct {
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;escalate;bind
//+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups="",resources=events,verbs=create;update;patch
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -124,6 +126,18 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res
return ctrl.Result{}, err
}

err = r.reconcileConfigMapForApplicationController(ctx, tenant)
if err != nil {
tenant.Status.Health = cattagev1beta1.TenantUnhealthy
meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{
Type: cattagev1beta1.ConditionReady,
Status: metav1.ConditionFalse,
Reason: "Failed",
Message: err.Error(),
})
return ctrl.Result{}, err
}

tenant.Status.Health = cattagev1beta1.TenantHealthy
meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{
Type: cattagev1beta1.ConditionReady,
Expand Down Expand Up @@ -252,7 +266,7 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *cattagev1beta1.
}
logger.Info("starting finalization")
nss := &corev1.NamespaceList{}
if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaces: tenant.Name}); err != nil {
if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaceIndex: tenant.Name}); err != nil {
return fmt.Errorf("failed to list namespaces: %w", err)
}
for _, ns := range nss.Items {
Expand Down Expand Up @@ -411,7 +425,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt
}
}
nss := &corev1.NamespaceList{}
if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaces: tenant.Name}); err != nil {
if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaceIndex: tenant.Name}); err != nil {
return fmt.Errorf("failed to list namespaces: %w", err)
}
for _, ns := range nss.Items {
Expand Down Expand Up @@ -442,7 +456,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev
}

nss := &corev1.NamespaceList{}
if err := r.client.List(ctx, nss, client.MatchingFields{constants.TenantNamespaces: tenant.Name}); err != nil {
if err := r.client.List(ctx, nss, client.MatchingFields{constants.TenantNamespaceIndex: tenant.Name}); err != nil {
return fmt.Errorf("failed to list namespaces: %w", err)
}
namespaces := make([]string, len(nss.Items))
Expand Down Expand Up @@ -505,6 +519,90 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev
return nil
}

func (r *TenantReconciler) reconcileConfigMapForApplicationController(ctx context.Context, tenant *cattagev1beta1.Tenant) error {
cmList := &corev1.ConfigMapList{}
err := r.client.List(ctx, cmList, client.MatchingLabels{"generated-by": "cattage"})
if err != nil {
return err
}
controllerNames := map[string]struct{}{}
for _, cm := range cmList.Items {
controllerNames[cm.Labels["controller-name"]] = struct{}{}
}

controllerName := tenant.Spec.ControllerName
if controllerName == "" {
controllerName = constants.DefaultApplicationControllerName
}
controllerNames[controllerName] = struct{}{}

for name := range controllerNames {
err := r.updateConfigMap(ctx, name)
if err != nil {
return err
}
}

return nil
}

func (r *TenantReconciler) updateConfigMap(ctx context.Context, controllerName string) error {
logger := log.FromContext(ctx)

configMapName := controllerName + "-application-controller-cm"
cm := &corev1.ConfigMap{}
cm.Name = configMapName
cm.Namespace = r.config.ArgoCD.Namespace

tenants := &cattagev1beta1.TenantList{}
if err := r.client.List(ctx, tenants, client.MatchingFields{constants.ControllerNameIndex: controllerName}); err != nil {
return fmt.Errorf("failed to list tenants: %w", err)
}

if len(tenants.Items) == 0 {
err := r.client.Delete(ctx, cm)
return err
}

namespaces := make([]string, 0)
for _, t := range tenants.Items {
nss := &corev1.NamespaceList{}
if err := r.client.List(ctx, nss, client.MatchingFields{constants.TenantNamespaceIndex: t.Name}); err != nil {
return fmt.Errorf("failed to list namespaces: %w", err)
}
for _, ns := range nss.Items {
namespaces = append(namespaces, ns.Name)
}
}

op, err := ctrl.CreateOrUpdate(ctx, r.client, cm, func() error {
cm.Labels = map[string]string{
"generated-by": "cattage",
"controller-name": controllerName,
}
cm.Data = map[string]string{
"application.namespaces": strings.Join(namespaces, ","),
}
cm.OwnerReferences = nil
for _, tenant := range tenants.Items {
err := controllerutil.SetOwnerReference(&tenant, cm, r.client.Scheme())
if err != nil {
return err
}
}
return nil
})
if err != nil {
logger.Error(err, "failed to update ConfigMap")
return err
}
if op != controllerutil.OperationResultNone {
logger.Info("ConfigMap successfully reconciled")
}

return nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error {
tenantHandler := func(ctx context.Context, o client.Object) []reconcile.Request {
Expand All @@ -525,7 +623,7 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error {

func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager) error {
ns := &corev1.Namespace{}
err := mgr.GetFieldIndexer().IndexField(ctx, ns, constants.RootNamespaces, func(rawObj client.Object) []string {
err := mgr.GetFieldIndexer().IndexField(ctx, ns, constants.RootNamespaceIndex, func(rawObj client.Object) []string {
nsType := rawObj.GetLabels()[accurate.LabelType]
if nsType != accurate.NSTypeRoot {
return nil
Expand All @@ -540,11 +638,24 @@ func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager) error {
return err
}

return mgr.GetFieldIndexer().IndexField(ctx, ns, constants.TenantNamespaces, func(rawObj client.Object) []string {
err = mgr.GetFieldIndexer().IndexField(ctx, ns, constants.TenantNamespaceIndex, func(rawObj client.Object) []string {
tenantName := rawObj.GetLabels()[constants.OwnerTenant]
if tenantName == "" {
return nil
}
return []string{tenantName}
})
if err != nil {
return err
}

tenant := &cattagev1beta1.Tenant{}
return mgr.GetFieldIndexer().IndexField(ctx, tenant, constants.ControllerNameIndex, func(rawObj client.Object) []string {
tenant := rawObj.(*cattagev1beta1.Tenant)
controllerName := tenant.Spec.ControllerName
if controllerName == "" {
return []string{constants.DefaultApplicationControllerName}
}
return []string{controllerName}
})
}
5 changes: 3 additions & 2 deletions pkg/constants/indexer.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package constants

const RootNamespaces = "cattage.namespaces.root"
const TenantNamespaces = "cattage.namespaces.tenant"
const RootNamespaceIndex = "cattage.namespaces.root"
const TenantNamespaceIndex = "cattage.namespaces.tenant"
const ControllerNameIndex = "cattage.tenants.controller"
2 changes: 2 additions & 0 deletions pkg/constants/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ const OwnerTenant = MetaPrefix + "tenant"
const OwnerAppNamespace = MetaPrefix + "owner-namespace"

const TenantFieldManager = MetaPrefix + "tenant-controller"

const DefaultApplicationControllerName = "default"

0 comments on commit ac3ac76

Please sign in to comment.