Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

⚠️ Allow reconcilers to add watches dynamically #542

Merged
merged 1 commit into from
Aug 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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