Skip to content

Instantly share code, notes, and snippets.

@grantr
Last active March 25, 2019 21:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grantr/d149209f65def521e8e95b9d301f591e to your computer and use it in GitHub Desktop.
Save grantr/d149209f65def521e8e95b9d301f591e to your computer and use it in GitHub Desktop.
A controller interface that can live peacefully in knative/pkg
// ObjectReconciler is what the user implements. This is the generic version.
// The controller MUST be aware of whether it's running with CR or CG.
// With this interface, at least the code will be structured similarly
// and the client interaction can converge over time (or not, as desired)
// This is called ObjectReconciler to avoid confusion with existing Reconciler
// interfaces.
type ObjectReconciler interface {
// This could alternately be called ReconcileObject.
Reconcile(context.Context, runtime.Object) error
}
// ObjectFinalizer is optionally implemented by the user when a finalizer is
// required.
type ObjectFinalizer interface {
// AddFinalizer is called when the object has no finalizer and has not
// been deleted. The function can return true or false to signal that
// a finalizer should be added or not. Most of the time it will just
// return true.
AddFinalizer(context.Context, runtime.Object) bool
// FinalizeObject is called when the object has a finalizer and has been deleted.
Finalize(context.Context, runtime.Object) error
}
// Code generation could possibly be used to generate typed versions of these
// interfaces, e.g. BrokerReconciler, BrokerFinalizer that take
// *eventingv1alpha1.Broker instead of runtime.Object.
// Each Reconciler struct embeds an "implementation" struct. This allows multiple
// versions of controller boilerplates to be used concurrently, with as many
// interfaces as possible remaining the same. Ideally we would eventually converge
// on a single implementation, but that's not guaranteed - they may have different
// performance or flexibility tradeoffs.
// BrokerReconciler is a Reconciler that embeds the Controller Runtime reconciler
// struct to get its behavior.
type BrokerReconciler struct {
*ControllerRuntimeReconciler
}
// ControllerRuntimeReconciler is an implementation of all the boilerplate necessary
// to write and run a Reconciler that uses Controller Runtime underneath.
type ControllerRuntimeReconciler struct {
crr controllerRuntimeReconcileImpl
}
// Start implements the Controller Runtime ProvideController boilerplate.
// It adds the reconciler to a package-level Manager which is now an
// implementation detail of this package.
func (crr *ControllerRuntimeReconciler) Start(ctx context.Context) error {
// Manager setup is done by a package-level function containing
// a sync.Once.
mgr, err := GetManager()
// The Reconciler object given to controller.Controller is the internal
// controllerRuntimeReconcileImpl object rather than this outer one.
}
// This is an internal type that implements the
// Reconcile(reconcile.Request) (reconcile.Result, error) interface that
// Controller Runtime expects. Keeping this internal avoids method signature
// conflicts and user visibility of the Controller Runtime interface.
type controllerRuntimeReconcileImpl struct {
}
func (crri *controllerRuntimeReconcileImpl) Reconcile(request reconcile.Request) (reconcile.Result, error) {
}
// RevisionReconciler embeds the client-go Reconciler implementation.
type RevisionReconciler struct {
*ClientGoReconciler
}
// ClientGoReconciler is an alternative to ControllerRuntimeReconciler.
type ClientGoReconciler struct {
}
// Start implements the client-go boilerplate, e.g. starting informers.
func (cgr *ClientGoReconciler) Start(ctx context.Context) {
}
// Starter is used by the main function to start each reconciler, and blocks
// until the controller is stopped. This is implemented by an embedded struct.
type Starter interface {
Start(context.Context) error
}
// StarterList collects starters and starts them with an errgroup.
type StarterList struct{
starters []Starter
}
func (sl *StarterList) Add(s Starter) {
sl.starters = append(sl.starters, s)
}
func (sl *StarterList) Items() []Starter {
return sl.starters
}
func (sl *StarterList) Start(ctx context.Context) error {
g, gCtx := errgroup.WithContext(ctx)
for _, s := range sl.Starters.Items() {
g.Go(func() error {
return s.Start(gCtx)
})
}
g.Wait()
}
// There's a default package-level StartersList in the enclosing package,
// similar to scheme.Scheme.
var (
Starters StarterList
)
// Reconciler packages define init() methods that set up reconcilers as
// package-internal vars, then add those vars to the default starters list.
func init() {
// The content of this function will be specific to the Reconciler behavior
// and the implementation being used.
br := &BrokerReconciler{
// The content of this
}
Starters.Add(br)
}
// The controller manager main function looks like this.
func main() {
// Do logging, config set up tasks as needed
signaledCtx, cancel := SignaledContext(configuredCtx)
log.Fatalf(Starters.Start(signaledCtx))
}
// SignaledContext is a slightly reworking of the existing signals code to use
// context cancellation instead of stop channels.
func SignaledContext(ctx context.Context) (context.Context, context.CancelFunc) {
signaledCtx, cancel := context.WithCancel(ctx)
go func() {
<-globalSignalsCh
cancel()
}
return signaledCtx, cancel
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment