Skip to content

Commit

Permalink
Return controller from builder.Build
Browse files Browse the repository at this point in the history
Return controller from builder.Build to allow developers to use it after
it's been created by the builder; e.g., to add watches dynamically from
a reconciler.

Remove deprecated SimpleController() and Builder.WithManager().

Signed-off-by: Andy Goldstein <[email protected]>
  • Loading branch information
ncdc committed Jul 31, 2019
1 parent 59b131b commit 37c8aa1
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 131 deletions.
60 changes: 10 additions & 50 deletions pkg/builder/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/client/config"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
Expand All @@ -33,9 +32,7 @@ import (
)

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

// Builder builds a Controller.
Expand All @@ -50,16 +47,9 @@ type Builder struct {
name string
}

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

// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
func ControllerManagedBy(m manager.Manager) *Builder {
return SimpleController().WithManager(m)
return &Builder{mgr: m}
}

// ForType defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
Expand Down Expand Up @@ -109,14 +99,6 @@ func (blder *Builder) WithConfig(config *rest.Config) *Builder {
return blder
}

// 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.
Expand All @@ -141,23 +123,17 @@ func (blder *Builder) Complete(r reconcile.Reconciler) error {
return err
}

// Build builds the Application ControllerManagedBy and returns the Manager used to start it.
//
// Deprecated: Use Complete
func (blder *Builder) Build(r reconcile.Reconciler) (manager.Manager, error) {
// Build builds the Application ControllerManagedBy and returns the Controller it created.
func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
if r == nil {
return nil, fmt.Errorf("must provide a non-nil Reconciler")
}

// Set the Config
if err := blder.loadRestConfig(); err != nil {
return nil, err
if blder.mgr == nil {
return nil, fmt.Errorf("must provide a non-nil Manager")
}

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

// Set the ControllerManagedBy
if err := blder.doController(r); err != nil {
Expand All @@ -169,7 +145,7 @@ func (blder *Builder) Build(r reconcile.Reconciler) (manager.Manager, error) {
return nil, err
}

return blder.mgr, nil
return blder.ctrl, nil
}

func (blder *Builder) doWatch() error {
Expand Down Expand Up @@ -203,26 +179,10 @@ func (blder *Builder) doWatch() error {
return nil
}

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

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

func (blder *Builder) getControllerName() (string, error) {
Expand Down
88 changes: 23 additions & 65 deletions pkg/builder/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/manager"
Expand All @@ -44,9 +43,7 @@ var _ = Describe("application", func() {

BeforeEach(func() {
stop = make(chan struct{})
getConfig = func() (*rest.Config, error) { return cfg, nil }
newController = controller.New
newManager = manager.New
})

AfterEach(func() {
Expand All @@ -57,35 +54,32 @@ var _ = Describe("application", func() {

Describe("New", func() {
It("should return success if given valid objects", func() {
instance, err := SimpleController().
For(&appsv1.ReplicaSet{}).
Owns(&appsv1.ReplicaSet{}).
Build(noop)
By("creating a controller manager")
m, err := manager.New(cfg, manager.Options{})
Expect(err).NotTo(HaveOccurred())
Expect(instance).NotTo(BeNil())
})

It("should return an error if the Config is invalid", func() {
getConfig = func() (*rest.Config, error) { return cfg, fmt.Errorf("expected error") }
instance, err := SimpleController().
instance, err := ControllerManagedBy(m).
For(&appsv1.ReplicaSet{}).
Owns(&appsv1.ReplicaSet{}).
Build(noop)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("expected error"))
Expect(instance).To(BeNil())
Expect(err).NotTo(HaveOccurred())
Expect(instance).NotTo(BeNil())
})

It("should return an error if there is no GVK for an object", func() {
instance, err := SimpleController().
By("creating a controller manager")
m, err := manager.New(cfg, manager.Options{})
Expect(err).NotTo(HaveOccurred())

instance, err := ControllerManagedBy(m).
For(&fakeType{}).
Owns(&appsv1.ReplicaSet{}).
Build(noop)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no kind is registered for the type builder.fakeType"))
Expect(instance).To(BeNil())

instance, err = SimpleController().
instance, err = ControllerManagedBy(m).
For(&appsv1.ReplicaSet{}).
Owns(&fakeType{}).
Build(noop)
Expand All @@ -94,25 +88,17 @@ var _ = Describe("application", func() {
Expect(instance).To(BeNil())
})

It("should return an error if it cannot create the manager", func() {
newManager = func(config *rest.Config, options manager.Options) (manager.Manager, error) {
return nil, fmt.Errorf("expected error")
}
instance, err := SimpleController().
For(&appsv1.ReplicaSet{}).
Owns(&appsv1.ReplicaSet{}).
Build(noop)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("expected error"))
Expect(instance).To(BeNil())
})

It("should return an error if it cannot create the controller", func() {
newController = func(name string, mgr manager.Manager, options controller.Options) (
controller.Controller, error) {
return nil, fmt.Errorf("expected error")
}
instance, err := SimpleController().

By("creating a controller manager")
m, err := manager.New(cfg, manager.Options{})
Expect(err).NotTo(HaveOccurred())

instance, err := ControllerManagedBy(m).
For(&appsv1.ReplicaSet{}).
Owns(&appsv1.ReplicaSet{}).
Build(noop)
Expand Down Expand Up @@ -150,30 +136,6 @@ var _ = Describe("application", func() {
})
})

Describe("Start with SimpleController", func() {
It("should Reconcile Owns objects", func(done Done) {
bldr := SimpleController().
ForType(&appsv1.Deployment{}).
WithConfig(cfg).
Owns(&appsv1.ReplicaSet{})
doReconcileTest("1", stop, bldr, nil, false)

close(done)
}, 10)

It("should Reconcile Owns objects with a Manager", func(done Done) {
m, err := manager.New(cfg, manager.Options{})
Expect(err).NotTo(HaveOccurred())

bldr := SimpleController().
WithManager(m).
For(&appsv1.Deployment{}).
Owns(&appsv1.ReplicaSet{})
doReconcileTest("2", stop, bldr, m, false)
close(done)
}, 10)
})

Describe("Start with ControllerManagedBy", func() {
It("should Reconcile Owns objects", func(done Done) {
m, err := manager.New(cfg, manager.Options{})
Expand Down Expand Up @@ -217,25 +179,21 @@ func doReconcileTest(nameSuffix string, stop chan struct{}, blder *Builder, mgr
return reconcile.Result{}, nil
})

instance := mgr
if complete {
err := blder.Complete(fn)
Expect(err).NotTo(HaveOccurred())
} else {
var err error
instance, err = blder.Build(fn)
var c controller.Controller
c, err = blder.Build(fn)
Expect(err).NotTo(HaveOccurred())
}

// Manager should match
if mgr != nil {
Expect(instance).To(Equal(mgr))
Expect(c).NotTo(BeNil())
}

By("Starting the application")
go func() {
defer GinkgoRecover()
Expect(instance.Start(stop)).NotTo(HaveOccurred())
Expect(mgr.Start(stop)).NotTo(HaveOccurred())
By("Stopping the application")
}()

Expand Down Expand Up @@ -263,7 +221,7 @@ func doReconcileTest(nameSuffix string, stop chan struct{}, blder *Builder, mgr
},
},
}
err := instance.GetClient().Create(context.TODO(), dep)
err := mgr.GetClient().Create(context.TODO(), dep)
Expect(err).NotTo(HaveOccurred())

By("Waiting for the Deployment Reconcile")
Expand Down Expand Up @@ -293,7 +251,7 @@ func doReconcileTest(nameSuffix string, stop chan struct{}, blder *Builder, mgr
Template: dep.Spec.Template,
},
}
err = instance.GetClient().Create(context.TODO(), rs)
err = mgr.GetClient().Create(context.TODO(), rs)
Expect(err).NotTo(HaveOccurred())

By("Waiting for the ReplicaSet Reconcile")
Expand Down
15 changes: 3 additions & 12 deletions pkg/builder/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,25 +55,16 @@ func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
// Complete builds the webhook.
func (blder *WebhookBuilder) Complete() error {
// Set the Config
if err := blder.loadRestConfig(); err != nil {
return err
}
blder.loadRestConfig()

// Set the Webhook if needed
return blder.registerWebhooks()
}

func (blder *WebhookBuilder) loadRestConfig() error {
if blder.config != nil {
return nil
}
if blder.mgr != nil {
func (blder *WebhookBuilder) loadRestConfig() {
if blder.config == nil {
blder.config = blder.mgr.GetConfig()
return nil
}
var err error
blder.config, err = getConfig()
return err
}

func (blder *WebhookBuilder) registerWebhooks() error {
Expand Down
3 changes: 0 additions & 3 deletions pkg/builder/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
. "github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/scheme"
Expand All @@ -40,9 +39,7 @@ var _ = Describe("application", func() {

BeforeEach(func() {
stop = make(chan struct{})
getConfig = func() (*rest.Config, error) { return cfg, nil }
newController = controller.New
newManager = manager.New
})

AfterEach(func() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/patterns/application/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ limitations under the License.
// An application is a Controller and Resource that together implement the operational logic for an application.
// They are often used to take off-the-shelf OSS applications, and make them Kubernetes native.
//
// A typical application Controller may use a new builder.SimpleController() to create a Controller
// A typical application Controller may use builder.ControllerManagedBy() to create a Controller
// for a single API type that manages other objects it creates.
//
// Application Controllers are most useful for stateful applications such as Cassandra, Etcd and MySQL
Expand Down

0 comments on commit 37c8aa1

Please sign in to comment.