Skip to content

Commit

Permalink
Create commonclient to abstract controller-runtime versions
Browse files Browse the repository at this point in the history
commonclient contains wrappers around functions we use from
controller-runtime which have changed between 0.11 and 0.15 (the version
range we are supporting currently).

This allows for buildtags to be used to select a previous version of
controller-runtime, without needing to carry patches.
  • Loading branch information
justinsb committed Jun 26, 2023
1 parent 48db027 commit d33e962
Show file tree
Hide file tree
Showing 17 changed files with 210 additions and 42 deletions.
3 changes: 3 additions & 0 deletions commonclient/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package commonclient provides version-independent wrappers over controller-runtime and client-go.
// This enables one codebase to work with multiple versions of controller-runtime.
package commonclient
15 changes: 15 additions & 0 deletions commonclient/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package commonclient

import "sigs.k8s.io/controller-runtime/pkg/manager"

// Factory is a helper type used for version-independent operations.
type Factory struct {
mgr manager.Manager
}

// NewFactory constructs a new Factory.
func NewFactory(mgr manager.Manager) *Factory {
return &Factory{
mgr: mgr,
}
}
56 changes: 56 additions & 0 deletions commonclient/factory_cr11.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//go:build controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14

package commonclient

import (
"context"

"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
)

// SourceKind is a version-indendenent abstraction over calling source.Kind
func (f *Factory) SourceKind(obj client.Object) *source.Kind {
return &source.Kind{Type: obj}
}

// EventHandler is a version-indendenent abstraction over handler.EventHandler
func (f *Factory) EventHandler(h EventHandler) handler.EventHandler {
return &eventHandlerWithoutContext{h: h}
}

type eventHandlerWithoutContext struct {
h EventHandler
}

func (h *eventHandlerWithoutContext) Create(ev event.CreateEvent, q workqueue.RateLimitingInterface) {
h.h.Create(context.TODO(), ev, q)
}
func (h *eventHandlerWithoutContext) Update(ev event.UpdateEvent, q workqueue.RateLimitingInterface) {
h.h.Update(context.TODO(), ev, q)
}
func (h *eventHandlerWithoutContext) Delete(ev event.DeleteEvent, q workqueue.RateLimitingInterface) {
h.h.Delete(context.TODO(), ev, q)
}
func (h *eventHandlerWithoutContext) Generic(ev event.GenericEvent, q workqueue.RateLimitingInterface) {
h.h.Generic(context.TODO(), ev, q)
}

// EventHandler is the controller-runtime 0.15 version of EventHandler (with a context argument)
type EventHandler interface {
// Create is called in response to an create event - e.g. Pod Creation.
Create(context.Context, event.CreateEvent, workqueue.RateLimitingInterface)

// Update is called in response to an update event - e.g. Pod Updated.
Update(context.Context, event.UpdateEvent, workqueue.RateLimitingInterface)

// Delete is called in response to a delete event - e.g. Pod Deleted.
Delete(context.Context, event.DeleteEvent, workqueue.RateLimitingInterface)

// Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or
// external trigger request - e.g. reconcile Autoscaling, or a Webhook.
Generic(context.Context, event.GenericEvent, workqueue.RateLimitingInterface)
}
21 changes: 21 additions & 0 deletions commonclient/factory_cr15.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !(controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14)

package commonclient

import (
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"
)

// SourceKind is a version-indendenent abstraction over calling source.Kind
func (f *Factory) SourceKind(obj client.Object) source.Source {
return source.Kind(f.mgr.GetCache(), obj)
}

// EventHandler is a version-indendenent abstraction over handler.EventHandler
func (f *Factory) EventHandler(h handler.EventHandler) handler.EventHandler {
return h
}

type EventHandler = handler.EventHandler
16 changes: 16 additions & 0 deletions commonclient/restmapper_cr11.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14

package commonclient

import (
"net/http"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

// NewDiscoveryRESTMapper is a version-independent wrapper around apiutil.NewDiscoveryRESTMapper
func NewDiscoveryRESTMapper(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
return apiutil.NewDiscoveryRESTMapper(c)
}
16 changes: 16 additions & 0 deletions commonclient/restmapper_cr15.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !(controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14)

package commonclient

import (
"net/http"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
)

// NewDiscoveryRESTMapper is a version-independent wrapper around apiutil.NewDiscoveryRESTMapper
func NewDiscoveryRESTMapper(c *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
return apiutil.NewDiscoveryRESTMapper(c)
}
9 changes: 6 additions & 3 deletions pkg/patterns/declarative/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/kubebuilder-declarative-pattern/commonclient"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/manifest"
)

Expand Down Expand Up @@ -348,12 +349,14 @@ func (gvkt *gvkTracker) start(ctx context.Context) error {
}

func newGVKTracker(mgr manager.Manager, obj *unstructured.Unstructured, namespaced bool) (gvkt *gvkTracker) {
f := commonclient.NewFactory(mgr)

gvkt = &gvkTracker{}
gvkt.list = newItems()
gvkt.recorder = objectRecorderFor(obj.GroupVersionKind())
gvkt.src = source.Kind(mgr.GetCache(), obj)
gvkt.src = f.SourceKind(obj)
gvkt.predicate = predicate.Funcs{}
gvkt.eventHandler = recordTrigger{gvkt.list, namespaced, gvkt.recorder}
gvkt.eventHandler = f.EventHandler(recordTrigger{gvkt.list, namespaced, gvkt.recorder})

return
}
Expand Down Expand Up @@ -531,7 +534,7 @@ func (r record) DeleteIfNeeded(name string, limit int) bool {
return false
}

var _ handler.EventHandler = recordTrigger{}
var _ commonclient.EventHandler = recordTrigger{}

type recordTrigger struct {
list *items
Expand Down
10 changes: 2 additions & 8 deletions pkg/patterns/declarative/pkg/applier/applylib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
"k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/kubebuilder-declarative-pattern/applylib/applyset"
Expand Down Expand Up @@ -91,14 +90,9 @@ func runApplierGoldenTests(t *testing.T, testDir string, interceptHTTPServer boo
t.Errorf("error parsing manifest %q: %v", p, err)
}

c, err := restclient.HTTPClientFor(restConfig)
restMapper, err := restmapper.NewForTest(restConfig)
if err != nil {
t.Fatalf("error from HTTClientFor: %v", err)
}

restMapper, err := restmapper.NewControllerRESTMapper(restConfig, c)
if err != nil {
t.Fatalf("error from controllerrestmapper.NewControllerRESTMapper: %v", err)
t.Fatalf("error from controllerrestmapper.NewForTest: %v", err)
}

parent := fakeParent()
Expand Down
4 changes: 2 additions & 2 deletions pkg/patterns/declarative/pkg/applier/global.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build !without_exec_applier || !without_direct_applier
// +build !without_exec_applier !without_direct_applier
//go:build !without_direct_applier
// +build !without_direct_applier

package applier

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:build without_exec_applier && without_direct_applier
// +build without_exec_applier,without_direct_applier
//go:build without_direct_applier
// +build without_direct_applier

package applier

Expand Down
4 changes: 2 additions & 2 deletions pkg/patterns/declarative/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ import (
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/kubebuilder-declarative-pattern/commonclient"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/watch"
)

Expand Down Expand Up @@ -105,7 +105,7 @@ func WatchChildren(options WatchChildrenOptions) error {
if err != nil {
return err
}
rm, err := apiutil.NewDiscoveryRESTMapper(options.RESTConfig, client)
rm, err := commonclient.NewDiscoveryRESTMapper(options.RESTConfig, client)
if err != nil {
return err
}
Expand Down
15 changes: 0 additions & 15 deletions pkg/restmapper/controllerrestmapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,12 @@ package restmapper
import (
"context"
"fmt"
"net/http"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
)

// NewControllerRESTMapper is the constructor for a ControllerRESTMapper
func NewControllerRESTMapper(cfg *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfigAndClient(cfg, httpClient)
if err != nil {
return nil, err
}

return &ControllerRESTMapper{
uncached: discoveryClient,
cache: newCache(),
}, nil
}

// ControllerRESTMapper is a meta.RESTMapper that is optimized for controllers.
// It caches results in memory, and minimizes discovery because we don't need shortnames etc in controllers.
// Controllers primarily need to map from GVK -> GVR.
Expand Down
27 changes: 27 additions & 0 deletions pkg/restmapper/controllerrestmapper_cr11.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//go:build controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14

package restmapper

import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
)

// NewControllerRESTMapper is the constructor for a ControllerRESTMapper.
func NewControllerRESTMapper(cfg *rest.Config) (meta.RESTMapper, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
return nil, err
}

return &ControllerRESTMapper{
uncached: discoveryClient,
cache: newCache(),
}, nil
}

// NewForTest creates a ControllerRESTMapper, but is intended to be a common interface for use by tests.
func NewForTest(cfg *rest.Config) (meta.RESTMapper, error) {
return NewControllerRESTMapper(cfg)
}
34 changes: 34 additions & 0 deletions pkg/restmapper/controllerrestmapper_cr15.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build !(controllerruntime_11 || controllerruntime_12 || controllerruntime_13 || controllerruntime_14)

package restmapper

import (
"fmt"
"net/http"

"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
)

// NewControllerRESTMapper is the constructor for a ControllerRESTMapper
func NewControllerRESTMapper(cfg *rest.Config, httpClient *http.Client) (meta.RESTMapper, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfigAndClient(cfg, httpClient)
if err != nil {
return nil, err
}

return &ControllerRESTMapper{
uncached: discoveryClient,
cache: newCache(),
}, nil
}

// NewForTest creates a ControllerRESTMapper, but is intended to be a common interface for use by tests.
func NewForTest(restConfig *rest.Config) (meta.RESTMapper, error) {
client, err := restclient.HTTPClientFor(restConfig)
if err != nil {
return nil, fmt.Errorf("error from restclient.HTTPClientFor: %w", err)
}
return NewControllerRESTMapper(restConfig, client)
}
9 changes: 2 additions & 7 deletions pkg/restmapper/controllerrestmapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
"k8s.io/klog/v2"
"sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver"
)
Expand Down Expand Up @@ -39,13 +38,9 @@ func TestRESTMapping(t *testing.T) {
Host: addr.String(),
}

client, err := restclient.HTTPClientFor(restConfig)
restMapper, err := NewForTest(restConfig)
if err != nil {
t.Fatalf("error from HTTClientFor: %v", err)
}
restMapper, err := NewControllerRESTMapper(restConfig, client)
if err != nil {
t.Fatalf("error from NewControllerRESTMapper: %v", err)
t.Fatalf("error from NewForTest: %v", err)
}
gvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"}
restMapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
Expand Down
6 changes: 4 additions & 2 deletions pkg/test/testreconciler/simpletest/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/kubebuilder-declarative-pattern/commonclient"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/addon"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/patterns/declarative/pkg/applier"
Expand Down Expand Up @@ -86,6 +86,8 @@ func (r *SimpleTestReconciler) setupReconciler(mgr ctrl.Manager) error {
}

func (r *SimpleTestReconciler) SetupWithManager(mgr ctrl.Manager) error {
f := commonclient.NewFactory(mgr)

if err := r.setupReconciler(mgr); err != nil {
return err
}
Expand All @@ -96,7 +98,7 @@ func (r *SimpleTestReconciler) SetupWithManager(mgr ctrl.Manager) error {
}

// Watch for changes to SimpleTest objects
err = c.Watch(source.Kind(mgr.GetCache(), &api.SimpleTest{}), &handler.EnqueueRequestForObject{})
err = c.Watch(f.SourceKind(&api.SimpleTest{}), &handler.EnqueueRequestForObject{})
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/test/testreconciler/simpletest/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"k8s.io/klog/v2"
"k8s.io/klog/v2/klogr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/kubebuilder-declarative-pattern/commonclient"
"sigs.k8s.io/kubebuilder-declarative-pattern/pkg/restmapper"

"sigs.k8s.io/kubebuilder-declarative-pattern/mockkubeapiserver"
Expand Down Expand Up @@ -74,7 +75,7 @@ func testSimpleReconciler(h *testharness.Harness, testdir string, applier applie
LeaderElection: false,

// MapperProvider provides the rest mapper used to map go types to Kubernetes APIs
MapperProvider: restmapper.NewControllerRESTMapper,
MapperProvider: commonclient.WrapRESTMapperFunc(restmapper.NewControllerRESTMapper),

Logger: logger,
})
Expand Down

0 comments on commit d33e962

Please sign in to comment.