Skip to content

Commit

Permalink
events accesscode controller (#219)
Browse files Browse the repository at this point in the history
* refactor: reordered role list, added label

* feat: added helper for ReconcilerFunc

* feat: added predicates for filtering events to controllers

* fix: accesscodes are namespaced

* fix: explicitly get rolebinding informer

* feat: changed accesscode controller to controller-runtime
  • Loading branch information
ebauman authored Feb 6, 2025
1 parent 0c5346a commit 4e9ad95
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 86 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ require (
k8s.io/code-generator v0.32.1
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
sigs.k8s.io/controller-runtime v0.16.3
sigs.k8s.io/controller-runtime v0.20.1
sigs.k8s.io/controller-tools v0.12.0
)

Expand Down
12 changes: 12 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZ
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
Expand Down Expand Up @@ -816,6 +818,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e h1:KhcknUwkWHKZPbFy2P7jH5LKJ3La+0ZeknkkmrSgqb0=
github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/cadvisor v0.48.1/go.mod h1:ZkYbiiVdyoqBmI2ahZI8GlmirT78OAOER0z4EQugkxQ=
github.com/google/cel-go v0.16.1 h1:3hZfSNiAU3KOiNtxuFXVp5WFy4hf/Ly3Sa4/7F8SXNo=
github.com/google/cel-go v0.16.1/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY=
Expand Down Expand Up @@ -1569,18 +1573,22 @@ k8s.io/api v0.25.4/go.mod h1:IG2+RzyPQLllQxnhzD8KQNEu4c4YvyDTpSMztf4A0OQ=
k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM=
k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc=
k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE=
k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0=
k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA=
k8s.io/apiextensions-apiserver v0.25.4/go.mod h1:bkSGki5YBoZWdn5pWtNIdGvDrrsRWlmnvl9a+tAw5vQ=
k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw=
k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.25.4/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A=
k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8=
k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc=
k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI=
k8s.io/apiserver v0.28.2 h1:rBeYkLvF94Nku9XfXyUIirsVzCzJBs6jMn3NWeHieyI=
k8s.io/apiserver v0.28.2/go.mod h1:f7D5e8wH8MWcKD7azq6Csw9UN+CjdtXIVQUyUhrtb+E=
k8s.io/apiserver v0.28.3 h1:8Ov47O1cMyeDzTXz0rwcfIIGAP/dP7L8rWbEljRcg5w=
k8s.io/apiserver v0.28.3/go.mod h1:YIpM+9wngNAv8Ctt0rHG4vQuX/I5rvkEMtZtsxW2rNM=
k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag=
k8s.io/cli-runtime v0.22.2 h1:fsd9rFk9FSaVq4SUq1fM27c8CFGsYZUJ/3BkgmjYWuY=
k8s.io/cli-runtime v0.22.2/go.mod h1:tkm2YeORFpbgQHEK/igqttvPTRIHFRz5kATlw53zlMI=
k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
Expand All @@ -1593,6 +1601,7 @@ k8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=
k8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=
k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI=
k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8=
k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM=
k8s.io/component-helpers v0.22.2 h1:guQ9oYclE5LMydWFfAFA+u7SQgQzz2g+YgpJ5QooSyY=
k8s.io/component-helpers v0.22.2/go.mod h1:+N61JAR9aKYSWbnLA88YcFr9K/6ISYvRNybX7QW7Rs8=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
Expand Down Expand Up @@ -1651,6 +1660,9 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2/go.mod h1:+qG7ISX
sigs.k8s.io/cli-utils v0.27.0 h1:BxI7lPNn0fBZa5g4UwR+ShJyL4CCxELA6tLHbr2YrpU=
sigs.k8s.io/cli-utils v0.27.0/go.mod h1:8ll2fyx+bzjbwmwUnKBQU+2LDbMDsxy44DiDZ+drALg=
sigs.k8s.io/controller-runtime v0.10.1/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY=
sigs.k8s.io/controller-runtime v0.20.0/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5ZtjZnHaE=
sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.8.11 h1:LzQzlq6Z023b+mBtc6v72N2mSHYmN8x7ssgbf/hv0H8=
sigs.k8s.io/kustomize/api v0.8.11/go.mod h1:a77Ls36JdfCWojpUqR6m60pdGY1AYFix4AH83nJtY1g=
Expand Down
56 changes: 26 additions & 30 deletions v4/pkg/controllers/accesscode/controller.go
Original file line number Diff line number Diff line change
@@ -1,47 +1,43 @@
package accesscode

import (
"context"
"errors"
"github.com/hobbyfarm/gargantua/v4/pkg/apis/hobbyfarm.io/v4alpha1"
"github.com/hobbyfarm/gargantua/v4/pkg/factoryhelpers"
"github.com/rancher/lasso/pkg/client"
"github.com/rancher/lasso/pkg/controller"
"github.com/hobbyfarm/gargantua/v4/pkg/controllers/helpers"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
client2 "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
)

type accessCodeController struct {
roleClient *client.Client

accessCodeController controller.SharedController
otacController controller.SharedController
kclient client2.Client
scheme *runtime.Scheme
}

func RegisterHandlers(factory controller.SharedControllerFactory) error {
roleClient, err := factoryhelpers.ClientForObject(&v4alpha1.Role{}, factory)
if err != nil {
return err
func New(mgr manager.Manager) error {
acc := &accessCodeController{
kclient: mgr.GetClient(),
scheme: mgr.GetScheme(),
}

acController, err := factory.ForObject(&v4alpha1.AccessCode{})
if err != nil {
return err
}
errs := make([]error, 0)

otacController, err := factory.ForObject(&v4alpha1.OneTimeAccessCode{})
if err != nil {
return err
if err := builder.
ControllerManagedBy(mgr).
Owns(&v4alpha1.Role{}, builder.MatchEveryOwner).
Named("accesscode-role").
For(&v4alpha1.AccessCode{}).Complete(helpers.ReconcileFunc(acc.ReconcileRole)); err != nil {
errs = append(errs, err)
}

acc := &accessCodeController{
roleClient: roleClient,
accessCodeController: acController,
otacController: otacController,
if err := builder.
ControllerManagedBy(mgr).
Owns(&v4alpha1.RoleBinding{}, builder.MatchEveryOwner).
Named("accesscode-rolebinding").
For(&v4alpha1.AccessCode{}).Complete(helpers.ReconcileFunc(acc.ReconcileRoleBinding)); err != nil {
errs = append(errs, err)
}

acController.RegisterHandler(context.TODO(), "access-code-ensure-role",
controller.SharedControllerHandlerFunc(acc.ensureRole))

otacController.RegisterHandler(context.TODO(), "otac-ensure-role",
controller.SharedControllerHandlerFunc(acc.ensureRole))

return nil
return errors.Join(errs...)
}
120 changes: 68 additions & 52 deletions v4/pkg/controllers/accesscode/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,92 @@ import (
"fmt"
"github.com/hobbyfarm/gargantua/v4/pkg/apis/hobbyfarm.io/v4alpha1"
labels2 "github.com/hobbyfarm/gargantua/v4/pkg/labels"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/hobbyfarm/gargantua/v4/pkg/uid"
"log/slog"
client2 "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func (acc *accessCodeController) ensureRole(key string, obj runtime.Object) (runtime.Object, error) {
var courses, scenarios, scheduledEvents, machineSets []string
var labelSelector, objName string

switch a := obj.(type) {
case *v4alpha1.AccessCode:
courses = a.Spec.Courses
scenarios = a.Spec.Scenarios
scheduledEvents = a.Spec.ScheduledEvents
machineSets = a.Spec.MachineSets
objName = a.GetName()
labelSelector = labels2.AccessCodeLabel
case *v4alpha1.OneTimeAccessCode:
courses = a.Spec.Courses
scenarios = a.Spec.Scenarios
scheduledEvents = a.Spec.ScheduledEvents
machineSets = a.Spec.MachineSets
objName = a.GetName()
labelSelector = labels2.OneTimeAccessCodeLabel
}

// retrieve role corresponding with (ot)ac
func (acc *accessCodeController) ReconcileRole(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
roleList := &v4alpha1.RoleList{}
if err := acc.roleClient.List(context.TODO(), "", roleList, metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", labelSelector, objName),
if err := acc.kclient.List(ctx, roleList, client2.MatchingLabels{
labels2.CodeRoleLabel: request.Name,
}); err != nil {
return nil, err
return reconcile.Result{}, err
}

ac := &v4alpha1.AccessCode{}
if err := acc.kclient.Get(ctx, request.NamespacedName, ac); err != nil {
return reconcile.Result{}, client2.IgnoreNotFound(err)
}

var role *v4alpha1.Role
// if there isn't a role, create it
// Because mink adds "-p" to end of UIDs for some reason
// We need to remove it because it breaks references to other objects
// Especially when using owner refs
ac.UID = uid.RemoveUIDPublic(ac.UID)

var requeue bool

if len(roleList.Items) == 0 {
slog.Debug("role does not exist for (ot)ac, creating it", "kind", obj.GetObjectKind().GroupVersionKind().Kind,
"objectName", objName)
role = &v4alpha1.Role{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "coderole-",
Labels: map[string]string{
labelSelector: objName,
},
},
err := acc.createRole(ctx, request, ac)
if err != nil {
return reconcile.Result{}, err
}

if err := acc.roleClient.Create(context.TODO(), "", role, role, metav1.CreateOptions{}); err != nil {
return nil, err
requeue = true
}

if len(roleList.Items) == 1 {
var role = &roleList.Items[0]

acc.setRules(ac, role)

if err := controllerutil.SetOwnerReference(ac, role, acc.scheme); err != nil {
slog.Error("error setting owner reference for role", "error", err.Error())
return reconcile.Result{}, err
}
} else if len(roleList.Items) == 1 {
role = &roleList.Items[0]
} else {

if err := acc.kclient.Update(ctx, role); err != nil {
return reconcile.Result{}, err
}
}

if len(roleList.Items) > 1 {
// there should not be more than one role for a code
return nil, fmt.Errorf("more than one role exists for %s %s", obj.GetObjectKind().GroupVersionKind().Kind, objName)
slog.Error("> 1 role exists for accesscode", "accesscode", request.Name)
return reconcile.Result{}, fmt.Errorf("> 1 role exists for accesscode")
}

role.Rules = []v4alpha1.Rule{
makeRule("scenarios", scenarios),
makeRule("courses", courses),
makeRule("scheduledEvents", scheduledEvents),
makeRule("machineSets", machineSets),
return reconcile.Result{
Requeue: requeue,
}, nil
}

func (acc *accessCodeController) createRole(ctx context.Context, request reconcile.Request, accessCode *v4alpha1.AccessCode) error {
var role = &v4alpha1.Role{}
slog.Debug("role does not exist for accesscode, creating it", "accesscode", request.Name)
role.GenerateName = "coderole-"
role.Labels = map[string]string{
labels2.CodeRoleLabel: request.Name,
}

if err := acc.roleClient.Update(context.TODO(), "", role, role, metav1.UpdateOptions{}); err != nil {
return nil, err
if err := acc.kclient.Create(ctx, role); err != nil {
slog.Error("error creating role for accesscode", "error", err.Error(), "accesscode", request.Name)
return err
}

return role, nil
slog.Debug("role created for accesscode", "accesscode", request.Name, "role", role.Name)
return nil
}

func (acc *accessCodeController) setRules(accessCode *v4alpha1.AccessCode, role *v4alpha1.Role) {
role.Rules = []v4alpha1.Rule{
makeRule("scenarios", accessCode.Spec.Scenarios),
makeRule("courses", accessCode.Spec.Courses),
makeRule("scheduledEvents", accessCode.Spec.ScheduledEvents),
makeRule("machineSets", accessCode.Spec.MachineSets),
}
}

func makeRule(resources string, resourceNames []string) v4alpha1.Rule {
Expand Down
81 changes: 81 additions & 0 deletions v4/pkg/controllers/accesscode/rolebinding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package accesscode

import (
"context"
"github.com/hobbyfarm/gargantua/v4/pkg/apis/hobbyfarm.io/v4alpha1"
labels2 "github.com/hobbyfarm/gargantua/v4/pkg/labels"
"github.com/hobbyfarm/gargantua/v4/pkg/uid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"log/slog"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

func (acc *accessCodeController) ReconcileRoleBinding(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
roleBindingList := &v4alpha1.RoleBindingList{}
if err := acc.kclient.List(ctx, roleBindingList, client.MatchingLabels{
labels2.CodeRoleBindingLabel: request.Name,
}); err != nil {
return reconcile.Result{}, err
}

ac := &v4alpha1.AccessCode{}
if err := acc.kclient.Get(ctx, request.NamespacedName, ac); err != nil {
return reconcile.Result{}, client.IgnoreNotFound(err)
}

// Remove to avoid issues with ownership
ac.UID = uid.RemoveUIDPublic(ac.UID)

var requeue = false
if len(roleBindingList.Items) == 0 {
if err := acc.createRoleBinding(ctx, ac); err != nil {
return reconcile.Result{}, err
}

requeue = true
}

if len(roleBindingList.Items) == 1 {
var rolebinding = &roleBindingList.Items[0]

// set ownership, everything else (membership) is handled elsewhere
if err := controllerutil.SetControllerReference(ac, rolebinding, acc.scheme); err != nil {
return reconcile.Result{}, err
}

if err := acc.kclient.Update(ctx, rolebinding); err != nil {
return reconcile.Result{}, err
}
}

if len(roleBindingList.Items) > 1 {
slog.Error("more than one rolebinding exists for accesscode", "accesscode", ac.Name)
}

return reconcile.Result{Requeue: requeue}, nil
}

func (acc *accessCodeController) createRoleBinding(ctx context.Context, ac *v4alpha1.AccessCode) error {
slog.Debug("rolebinding does not exist for accesscode, creating it", "accesscode", ac.Name)
rb := &v4alpha1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
GenerateName: "code-",
Labels: map[string]string{
labels2.CodeRoleBindingLabel: ac.Name,
},
},
}

if err := acc.kclient.Create(ctx, rb); err != nil {
slog.Error("error creating rolebinding for accesscode", "error", err.Error(),
"accesscode", ac.Name)
return err
}

slog.Debug("created rolebinding for accesscode", "rolebinding", rb.Name,
"accesscode", ac.Name)

return nil
}
9 changes: 9 additions & 0 deletions v4/pkg/controllers/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"log/slog"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

type Obj interface {
Expand All @@ -20,3 +21,11 @@ func UpdateStatus(ctx context.Context, client *client.Client, obj Obj) {
"objName", obj.GetName(), "error", err.Error())
}
}

func ReconcileFunc(f reconcile.Func) reconcile.Reconciler {
return struct {
reconcile.Func
}{
Func: f,
}
}
Loading

0 comments on commit 4e9ad95

Please sign in to comment.