Skip to content

Commit

Permalink
✨ improve builder UX
Browse files Browse the repository at this point in the history
  • Loading branch information
pwittrock committed Dec 20, 2018
1 parent b497fd5 commit ad518da
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 92 deletions.
48 changes: 48 additions & 0 deletions alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright 2018 The Kubernetes Authors.
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.
*/

/**
* This file alias' common functions and types to reduce the number of imports for simple Controllers and to
* improve discoverability.
*/
package controller_runtime

import (
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/runtime/signals"
)

type Builder = builder.Builder
type Request = reconcile.Request
type Result = reconcile.Result
type Manager = manager.Manager
type Options = manager.Options

var (
GetConfigOrDie = config.GetConfigOrDie
GetConfig = config.GetConfig

NewControllerManagedBy = builder.ControllerManagedBy
NewManager = manager.New

CreateOrUpdate = controllerutil.CreateOrUpdate
SetControllerReference = controllerutil.SetControllerReference
SetupSignalHandler = signals.SetupSignalHandler
)
98 changes: 98 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
Copyright 2018 The Kubernetes Authors.
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 controller_runtime_test

import (
"context"
"fmt"
"os"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
controllers "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
)

// This example creates a simple application Controller that is configured for ReplicaSets and Pods.
//
// * Create a new application for ReplicaSets that manages Pods owned by the ReplicaSet and calls into
// ReplicaSetReconciler.
//
// * Start the application.
// TODO(pwittrock): Update this example when we have better dependency injection support
func ExampleBuilder() {
var log = logf.Log.WithName("builder-examples")

manager, err := controllers.NewManager(controllers.GetConfigOrDie(), controllers.Options{})
if err != nil {
log.Error(err, "could not create manager")
os.Exit(1)
}

_, err = controllers.
NewControllerManagedBy(manager). // Create the Controller
For(&appsv1.ReplicaSet{}). // ReplicaSet is the Application API
Owns(&corev1.Pod{}). // ReplicaSet owns Pods created by it
Build(&ReplicaSetReconciler{Client: manager.GetClient()})
if err != nil {
log.Error(err, "could not create controller")
os.Exit(1)
}

if err := manager.Start(controllers.SetupSignalHandler()); err != nil {
log.Error(err, "could not start manager")
os.Exit(1)
}
}

// ReplicaSetReconciler is a simple Controller example implementation.
type ReplicaSetReconciler struct {
client.Client
}

// Implement the business logic:
// This function will be called when there is a change to a ReplicaSet or a Pod with an OwnerReference
// to a ReplicaSet.
//
// * Read the ReplicaSet
// * Read the Pods
// * Set a Label on the ReplicaSet with the Pod count
func (a *ReplicaSetReconciler) Reconcile(req controllers.Request) (controllers.Result, error) {
// Read the ReplicaSet
rs := &appsv1.ReplicaSet{}
err := a.Get(context.TODO(), req.NamespacedName, rs)
if err != nil {
return controllers.Result{}, err
}

// List the Pods matching the PodTemplate Labels
pods := &corev1.PodList{}
err = a.List(context.TODO(), client.InNamespace(req.Namespace).MatchingLabels(rs.Spec.Template.Labels), pods)
if err != nil {
return controllers.Result{}, err
}

// Update the ReplicaSet
rs.Labels["pod-count"] = fmt.Sprintf("%v", len(pods.Items))
err = a.Update(context.TODO(), rs)
if err != nil {
return controllers.Result{}, err
}

return controllers.Result{}, nil
}
147 changes: 90 additions & 57 deletions pkg/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,145 +32,178 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"
)

// application is a simple Controller for a single API type. It will create a Manager for itself
// if one is not provided.
type application struct {
mgr manager.Manager
ctrl controller.Controller
}

// Supporting mocking out functions for testing
var getConfig = config.GetConfig
var newController = controller.New
var newManager = manager.New
var getGvk = apiutil.GVKForObject

// Builder builds an Application Controller (e.g. Operator) and returns a manager.Manager to start it.
// Builder builds an Application ControllerManagedBy (e.g. Operator) and returns a manager.Manager to start it.
type Builder struct {
apiType runtime.Object
mgr manager.Manager
predicates []predicate.Predicate
managedObjects []runtime.Object
watchRequest []watchRequest
config *rest.Config
ctrl controller.Controller
}

// SimpleController returns a new Builder
// SimpleController returns a new Builder.
// Deprecated: Use ControllerManagedBy(Manager) instead.
func SimpleController() *Builder {
return &Builder{}
}

// ForType sets the ForType that generates other types
func (b *Builder) ForType(apiType runtime.Object) *Builder {
b.apiType = apiType
return b
// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
func ControllerManagedBy(m manager.Manager) *Builder {
return SimpleController().WithManager(m)
}

// ForType defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
// update events by *reconciling the object*.
// This is the equivalent of calling
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
// Deprecated: Use For
func (blder *Builder) ForType(apiType runtime.Object) *Builder {
return blder.For(apiType)
}

// For defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
// update events by *reconciling the object*.
// This is the equivalent of calling
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
func (blder *Builder) For(apiType runtime.Object) *Builder {
blder.apiType = apiType
return blder
}

// Owns defines types of Objects being *generated* by the ControllerManagedBy, and configures the ControllerManagedBy to respond to
// create / delete / update events by *reconciling the owner object*. This is the equivalent of calling
// Watches(&handler.EnqueueRequestForOwner{&source.Kind{Type: <ForType-apiType>}, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
func (blder *Builder) Owns(apiType runtime.Object) *Builder {
blder.managedObjects = append(blder.managedObjects, apiType)
return blder
}

// Owns configures the Application Controller to respond to create / delete / update events for objects it managedObjects
// - e.g. creates. apiType is an empty instance of an object matching the managed object type.
func (b *Builder) Owns(apiType runtime.Object) *Builder {
b.managedObjects = append(b.managedObjects, apiType)
return b
type watchRequest struct {
src source.Source
eventhandler handler.EventHandler
}

// Watches exposes the lower-level ControllerManagedBy Watches functions through the builder. Consider using
// Owns or For instead of Watches directly.
func (blder *Builder) Watches(src source.Source, eventhandler handler.EventHandler) *Builder {
blder.watchRequest = append(blder.watchRequest, watchRequest{src: src, eventhandler: eventhandler})
return blder
}

// WithConfig sets the Config to use for configuring clients. Defaults to the in-cluster config or to ~/.kube/config.
func (b *Builder) WithConfig(config *rest.Config) *Builder {
b.config = config
return b
// Deprecated: Use ControllerManagedBy(Manager) and this isn't needed.
func (blder *Builder) WithConfig(config *rest.Config) *Builder {
blder.config = config
return blder
}

// WithManager sets the Manager to use for registering the Controller. Defaults to a new manager.Manager.
func (b *Builder) WithManager(m manager.Manager) *Builder {
b.mgr = m
return b
// WithManager sets the Manager to use for registering the ControllerManagedBy. Defaults to a new manager.Manager.
// Deprecated: Use ControllerManagedBy(Manager) and this isn't needed.
func (blder *Builder) WithManager(m manager.Manager) *Builder {
blder.mgr = m
return blder
}

// WithEventFilter sets the event filters, to filter which create/update/delete/generic events eventually
// trigger reconciliations. For example, filtering on whether the resource version has changed.
// Defaults to the empty list.
func (b *Builder) WithEventFilter(p predicate.Predicate) *Builder {
b.predicates = append(b.predicates, p)
return b
func (blder *Builder) WithEventFilter(p predicate.Predicate) *Builder {
blder.predicates = append(blder.predicates, p)
return blder
}

// Build builds the Application Controller and returns the Manager used to start it.
func (b *Builder) Build(r reconcile.Reconciler) (manager.Manager, error) {
// Build builds the Application ControllerManagedBy and returns the Manager used to start it.
func (blder *Builder) Build(r reconcile.Reconciler) (manager.Manager, error) {
if r == nil {
return nil, fmt.Errorf("must call WithReconciler to set Reconciler")
}

// Set the Config
if err := b.doConfig(); err != nil {
if err := blder.doConfig(); err != nil {
return nil, err
}

// Set the Manager
if err := b.doManager(); err != nil {
if err := blder.doManager(); err != nil {
return nil, err
}

// Set the Controller
if err := b.doController(r); err != nil {
// Set the ControllerManagedBy
if err := blder.doController(r); err != nil {
return nil, err
}

a := &application{mgr: b.mgr, ctrl: b.ctrl}

// Reconcile type
s := &source.Kind{Type: b.apiType}
h := &handler.EnqueueRequestForObject{}
err := a.ctrl.Watch(s, h, b.predicates...)
src := &source.Kind{Type: blder.apiType}
hdler := &handler.EnqueueRequestForObject{}
err := blder.ctrl.Watch(src, hdler, blder.predicates...)
if err != nil {
return nil, err
}

// Watch the managed types
for _, t := range b.managedObjects {
s := &source.Kind{Type: t}
h := &handler.EnqueueRequestForOwner{
OwnerType: b.apiType,
// Watches the managed types
for _, obj := range blder.managedObjects {
src := &source.Kind{Type: obj}
hdler := &handler.EnqueueRequestForOwner{
OwnerType: blder.apiType,
IsController: true,
}
if err := a.ctrl.Watch(s, h, b.predicates...); err != nil {
if err := blder.ctrl.Watch(src, hdler, blder.predicates...); err != nil {
return nil, err
}
}

// Do the watch requests
for _, w := range blder.watchRequest {
if err := blder.ctrl.Watch(w.src, w.eventhandler, blder.predicates...); err != nil {
return nil, err
}

}

return a.mgr, nil
return blder.mgr, nil
}

func (b *Builder) doConfig() error {
if b.config != nil {
func (blder *Builder) doConfig() error {
if blder.config != nil {
return nil
}
var err error
b.config, err = getConfig()
blder.config, err = getConfig()
return err
}

func (b *Builder) doManager() error {
if b.mgr != nil {
func (blder *Builder) doManager() error {
if blder.mgr != nil {
return nil
}
var err error
b.mgr, err = newManager(b.config, manager.Options{})
blder.mgr, err = newManager(blder.config, manager.Options{})
return err
}

func (b *Builder) getControllerName() (string, error) {
gvk, err := getGvk(b.apiType, b.mgr.GetScheme())
func (blder *Builder) getControllerName() (string, error) {
gvk, err := getGvk(blder.apiType, blder.mgr.GetScheme())
if err != nil {
return "", err
}
name := fmt.Sprintf("%s-application", strings.ToLower(gvk.Kind))
return name, nil
}

func (b *Builder) doController(r reconcile.Reconciler) error {
name, err := b.getControllerName()
func (blder *Builder) doController(r reconcile.Reconciler) error {
name, err := blder.getControllerName()
if err != nil {
return err
}
b.ctrl, err = newController(name, b.mgr, controller.Options{Reconciler: r})
blder.ctrl, err = newController(name, blder.mgr, controller.Options{Reconciler: r})
return err
}
Loading

0 comments on commit ad518da

Please sign in to comment.