Skip to content

Instantly share code, notes, and snippets.

@grantr
Last active March 25, 2019 21:23

Revisions

  1. grantr revised this gist Mar 25, 2019. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions reconciler.go
    Original file line number Diff line number Diff line change
    @@ -50,6 +50,8 @@ 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
  2. grantr revised this gist Mar 25, 2019. 1 changed file with 24 additions and 1 deletion.
    25 changes: 24 additions & 1 deletion reconciler.go
    Original file line number Diff line number Diff line change
    @@ -25,12 +25,20 @@ type ObjectFinalizer interface {
    // 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
    }
    @@ -54,6 +62,19 @@ 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 {
    @@ -92,8 +113,10 @@ var (
    // 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)
    }
  3. grantr revised this gist Mar 25, 2019. No changes.
  4. grantr created this gist Mar 25, 2019.
    118 changes: 118 additions & 0 deletions reconciler.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    // 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.

    // BrokerReconciler is a Reconciler that embeds the Controller Runtime reconciler
    // struct to get its behavior.
    type BrokerReconciler struct {
    *ControllerRuntimeReconciler
    }

    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()
    }

    // 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) {
    }

    // 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() {
    br := &BrokerReconciler{
    // ...
    }
    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
    }