Files
capsule/controllers/rbac/manager.go
Dario Tranchitella a7f7c00558 Supporting ingresses.networking.k8s.io/v1 (#110)
* Updating to controller-runtime v0.7.0-alpha.4 and k8s 0.19.3

* Implementing ingresses.networking.k8s.io/v1

* Aligning to latest zap signatures
2020-10-23 21:19:14 +02:00

184 lines
6.0 KiB
Go

/*
Copyright 2020 Clastix Labs.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbac
import (
"context"
"fmt"
"github.com/go-logr/logr"
"github.com/hashicorp/go-multierror"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type Manager struct {
CapsuleGroup string
Log logr.Logger
Client client.Client
}
// Using the Client interface, required by the Runnable interface
func (r *Manager) InjectClient(c client.Client) error {
r.Client = c
return nil
}
func (r *Manager) filterByClusterRolesNames(name string) bool {
return name == ProvisionerRoleName || name == DeleterRoleName
}
func (r *Manager) SetupWithManager(mgr ctrl.Manager) (err error) {
crErr := ctrl.NewControllerManagedBy(mgr).
For(&rbacv1.ClusterRole{}, builder.WithPredicates(predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return r.filterByClusterRolesNames(event.Object.GetName())
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return r.filterByClusterRolesNames(deleteEvent.Object.GetName())
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return r.filterByClusterRolesNames(updateEvent.ObjectNew.GetName())
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return r.filterByClusterRolesNames(genericEvent.Object.GetName())
},
})).
Complete(r)
if crErr != nil {
err = multierror.Append(err, crErr)
}
crbErr := ctrl.NewControllerManagedBy(mgr).
For(&rbacv1.ClusterRoleBinding{}, builder.WithPredicates(predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
return event.Object.GetName() == ProvisionerRoleName
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return deleteEvent.Object.GetName() == ProvisionerRoleName
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
return updateEvent.ObjectNew.GetName() == ProvisionerRoleName
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return genericEvent.Object.GetName() == ProvisionerRoleName
},
})).
Complete(r)
if crbErr != nil {
err = multierror.Append(err, crbErr)
}
return
}
// This reconcile function is serving both ClusterRole and ClusterRoleBinding: that's ok, we're watching for multiple
// 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(ProvisionerRoleName); err != nil {
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", ProvisionerRoleName)
break
}
if err = r.EnsureClusterRoleBinding(); err != nil {
r.Log.Error(err, "Reconciliation for ClusterRoleBinding failed", "ClusterRoleBinding", ProvisionerRoleName)
break
}
case DeleterRoleName:
if err = r.EnsureClusterRole(DeleterRoleName); err != nil {
r.Log.Error(err, "Reconciliation for ClusterRole failed", "ClusterRole", DeleterRoleName)
break
}
}
return reconcile.Result{}, err
}
func (r *Manager) EnsureClusterRoleBinding() (err error) {
crb := &rbacv1.ClusterRoleBinding{
ObjectMeta: v1.ObjectMeta{
Name: ProvisionerRoleName,
},
}
_, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, crb, func() error {
// RoleRef is immutable, so we need to delete and recreate ClusterRoleBinding if it changed
if crb.ResourceVersion != "" && !equality.Semantic.DeepDerivative(provisionerClusterRoleBinding.RoleRef, crb.RoleRef) {
return ImmutableClusterRoleBindingError{}
}
crb.RoleRef = provisionerClusterRoleBinding.RoleRef
crb.Subjects = []rbacv1.Subject{
{
Kind: "Group",
Name: r.CapsuleGroup,
},
}
return nil
})
if err != nil {
if _, ok := err.(ImmutableClusterRoleBindingError); ok {
if err = r.Client.Delete(context.TODO(), crb); err != nil {
r.Log.Error(err, "Cannot delete CRB during reset due to RoleRef change")
return
}
return r.Client.Create(context.TODO(), provisionerClusterRoleBinding, &client.CreateOptions{})
}
r.Log.Error(err, "Cannot CreateOrUpdate CRB")
return
}
return
}
func (r *Manager) EnsureClusterRole(roleName string) (err error) {
role, ok := clusterRoles[roleName]
if !ok {
return fmt.Errorf("ClusterRole %s is not mapped", roleName)
}
clusterRole := &rbacv1.ClusterRole{
ObjectMeta: v1.ObjectMeta{
Name: role.GetName(),
},
}
_, err = controllerutil.CreateOrUpdate(context.TODO(), r.Client, clusterRole, func() error {
clusterRole.Rules = role.Rules
return nil
})
return
}
// This is the Runnable function that is triggered upon Manager start-up to perform the first RBAC reconciliation
// 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) (err error) {
for roleName := range clusterRoles {
r.Log.Info("setting up ClusterRoles", "ClusterRole", roleName)
if err = r.EnsureClusterRole(roleName); err != nil {
return
}
}
r.Log.Info("setting up ClusterRoleBindings", "ClusterRoleBinding", ProvisionerRoleName)
err = r.EnsureClusterRoleBinding()
return
}