334 lines
10 KiB
Go
334 lines
10 KiB
Go
/*
|
|
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 builder
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-logr/logr"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/klog/v2"
|
|
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
|
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
|
)
|
|
|
|
// Supporting mocking out functions for testing.
|
|
var newController = controller.New
|
|
var getGvk = apiutil.GVKForObject
|
|
|
|
// project represents other forms that the we can use to
|
|
// send/receive a given resource (metadata-only, unstructured, etc).
|
|
type objectProjection int
|
|
|
|
const (
|
|
// projectAsNormal doesn't change the object from the form given.
|
|
projectAsNormal objectProjection = iota
|
|
// projectAsMetadata turns this into an metadata-only watch.
|
|
projectAsMetadata
|
|
)
|
|
|
|
// Builder builds a Controller.
|
|
type Builder struct {
|
|
forInput ForInput
|
|
ownsInput []OwnsInput
|
|
watchesInput []WatchesInput
|
|
mgr manager.Manager
|
|
globalPredicates []predicate.Predicate
|
|
ctrl controller.Controller
|
|
ctrlOptions controller.Options
|
|
name string
|
|
}
|
|
|
|
// ControllerManagedBy returns a new controller builder that will be started by the provided Manager.
|
|
func ControllerManagedBy(m manager.Manager) *Builder {
|
|
return &Builder{mgr: m}
|
|
}
|
|
|
|
// ForInput represents the information set by For method.
|
|
type ForInput struct {
|
|
object client.Object
|
|
predicates []predicate.Predicate
|
|
objectProjection objectProjection
|
|
err error
|
|
}
|
|
|
|
// 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(object client.Object, opts ...ForOption) *Builder {
|
|
if blder.forInput.object != nil {
|
|
blder.forInput.err = fmt.Errorf("For(...) should only be called once, could not assign multiple objects for reconciliation")
|
|
return blder
|
|
}
|
|
input := ForInput{object: object}
|
|
for _, opt := range opts {
|
|
opt.ApplyToFor(&input)
|
|
}
|
|
|
|
blder.forInput = input
|
|
return blder
|
|
}
|
|
|
|
// OwnsInput represents the information set by Owns method.
|
|
type OwnsInput struct {
|
|
object client.Object
|
|
predicates []predicate.Predicate
|
|
objectProjection objectProjection
|
|
}
|
|
|
|
// 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(&source.Kind{Type: <ForType-forInput>}, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true}).
|
|
func (blder *Builder) Owns(object client.Object, opts ...OwnsOption) *Builder {
|
|
input := OwnsInput{object: object}
|
|
for _, opt := range opts {
|
|
opt.ApplyToOwns(&input)
|
|
}
|
|
|
|
blder.ownsInput = append(blder.ownsInput, input)
|
|
return blder
|
|
}
|
|
|
|
// WatchesInput represents the information set by Watches method.
|
|
type WatchesInput struct {
|
|
src source.Source
|
|
eventhandler handler.EventHandler
|
|
predicates []predicate.Predicate
|
|
objectProjection objectProjection
|
|
}
|
|
|
|
// Watches exposes the lower-level ControllerManagedBy Watches functions through the builder. Consider using
|
|
// Owns or For instead of Watches directly.
|
|
// Specified predicates are registered only for given source.
|
|
func (blder *Builder) Watches(src source.Source, eventhandler handler.EventHandler, opts ...WatchesOption) *Builder {
|
|
input := WatchesInput{src: src, eventhandler: eventhandler}
|
|
for _, opt := range opts {
|
|
opt.ApplyToWatches(&input)
|
|
}
|
|
|
|
blder.watchesInput = append(blder.watchesInput, input)
|
|
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.
|
|
// Given predicate is added for all watched objects.
|
|
// Defaults to the empty list.
|
|
func (blder *Builder) WithEventFilter(p predicate.Predicate) *Builder {
|
|
blder.globalPredicates = append(blder.globalPredicates, p)
|
|
return blder
|
|
}
|
|
|
|
// WithOptions overrides the controller options use in doController. Defaults to empty.
|
|
func (blder *Builder) WithOptions(options controller.Options) *Builder {
|
|
blder.ctrlOptions = options
|
|
return blder
|
|
}
|
|
|
|
// WithLogConstructor overrides the controller options's LogConstructor.
|
|
func (blder *Builder) WithLogConstructor(logConstructor func(*reconcile.Request) logr.Logger) *Builder {
|
|
blder.ctrlOptions.LogConstructor = logConstructor
|
|
return blder
|
|
}
|
|
|
|
// Named sets the name of the controller to the given name. The name shows up
|
|
// in metrics, among other things, and thus should be a prometheus compatible name
|
|
// (underscores and alphanumeric characters only).
|
|
//
|
|
// By default, controllers are named using the lowercase version of their kind.
|
|
func (blder *Builder) Named(name string) *Builder {
|
|
blder.name = name
|
|
return blder
|
|
}
|
|
|
|
// Complete builds the Application Controller.
|
|
func (blder *Builder) Complete(r reconcile.Reconciler) error {
|
|
_, err := blder.Build(r)
|
|
return err
|
|
}
|
|
|
|
// Build builds the Application Controller 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")
|
|
}
|
|
if blder.mgr == nil {
|
|
return nil, fmt.Errorf("must provide a non-nil Manager")
|
|
}
|
|
if blder.forInput.err != nil {
|
|
return nil, blder.forInput.err
|
|
}
|
|
// Checking the reconcile type exist or not
|
|
if blder.forInput.object == nil {
|
|
return nil, fmt.Errorf("must provide an object for reconciliation")
|
|
}
|
|
|
|
// Set the ControllerManagedBy
|
|
if err := blder.doController(r); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Set the Watch
|
|
if err := blder.doWatch(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return blder.ctrl, nil
|
|
}
|
|
|
|
func (blder *Builder) project(obj client.Object, proj objectProjection) (client.Object, error) {
|
|
switch proj {
|
|
case projectAsNormal:
|
|
return obj, nil
|
|
case projectAsMetadata:
|
|
metaObj := &metav1.PartialObjectMetadata{}
|
|
gvk, err := getGvk(obj, blder.mgr.GetScheme())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to determine GVK of %T for a metadata-only watch: %w", obj, err)
|
|
}
|
|
metaObj.SetGroupVersionKind(gvk)
|
|
return metaObj, nil
|
|
default:
|
|
panic(fmt.Sprintf("unexpected projection type %v on type %T, should not be possible since this is an internal field", proj, obj))
|
|
}
|
|
}
|
|
|
|
func (blder *Builder) doWatch() error {
|
|
// Reconcile type
|
|
typeForSrc, err := blder.project(blder.forInput.object, blder.forInput.objectProjection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
src := &source.Kind{Type: typeForSrc}
|
|
hdler := &handler.EnqueueRequestForObject{}
|
|
allPredicates := append(blder.globalPredicates, blder.forInput.predicates...)
|
|
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Watches the managed types
|
|
for _, own := range blder.ownsInput {
|
|
typeForSrc, err := blder.project(own.object, own.objectProjection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
src := &source.Kind{Type: typeForSrc}
|
|
hdler := &handler.EnqueueRequestForOwner{
|
|
OwnerType: blder.forInput.object,
|
|
IsController: true,
|
|
}
|
|
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
|
|
allPredicates = append(allPredicates, own.predicates...)
|
|
if err := blder.ctrl.Watch(src, hdler, allPredicates...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Do the watch requests
|
|
for _, w := range blder.watchesInput {
|
|
allPredicates := append([]predicate.Predicate(nil), blder.globalPredicates...)
|
|
allPredicates = append(allPredicates, w.predicates...)
|
|
|
|
// If the source of this watch is of type *source.Kind, project it.
|
|
if srckind, ok := w.src.(*source.Kind); ok {
|
|
typeForSrc, err := blder.project(srckind.Type, w.objectProjection)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
srckind.Type = typeForSrc
|
|
}
|
|
|
|
if err := blder.ctrl.Watch(w.src, w.eventhandler, allPredicates...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (blder *Builder) getControllerName(gvk schema.GroupVersionKind) string {
|
|
if blder.name != "" {
|
|
return blder.name
|
|
}
|
|
return strings.ToLower(gvk.Kind)
|
|
}
|
|
|
|
func (blder *Builder) doController(r reconcile.Reconciler) error {
|
|
globalOpts := blder.mgr.GetControllerOptions()
|
|
|
|
ctrlOptions := blder.ctrlOptions
|
|
if ctrlOptions.Reconciler == nil {
|
|
ctrlOptions.Reconciler = r
|
|
}
|
|
|
|
// Retrieve the GVK from the object we're reconciling
|
|
// to prepopulate logger information, and to optionally generate a default name.
|
|
gvk, err := getGvk(blder.forInput.object, blder.mgr.GetScheme())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Setup concurrency.
|
|
if ctrlOptions.MaxConcurrentReconciles == 0 {
|
|
groupKind := gvk.GroupKind().String()
|
|
|
|
if concurrency, ok := globalOpts.GroupKindConcurrency[groupKind]; ok && concurrency > 0 {
|
|
ctrlOptions.MaxConcurrentReconciles = concurrency
|
|
}
|
|
}
|
|
|
|
// Setup cache sync timeout.
|
|
if ctrlOptions.CacheSyncTimeout == 0 && globalOpts.CacheSyncTimeout != nil {
|
|
ctrlOptions.CacheSyncTimeout = *globalOpts.CacheSyncTimeout
|
|
}
|
|
|
|
controllerName := blder.getControllerName(gvk)
|
|
|
|
// Setup the logger.
|
|
if ctrlOptions.LogConstructor == nil {
|
|
log := blder.mgr.GetLogger().WithValues(
|
|
"controller", controllerName,
|
|
"controllerGroup", gvk.Group,
|
|
"controllerKind", gvk.Kind,
|
|
)
|
|
|
|
ctrlOptions.LogConstructor = func(req *reconcile.Request) logr.Logger {
|
|
log := log
|
|
if req != nil {
|
|
log = log.WithValues(
|
|
gvk.Kind, klog.KRef(req.Namespace, req.Name),
|
|
"namespace", req.Namespace, "name", req.Name,
|
|
)
|
|
}
|
|
return log
|
|
}
|
|
}
|
|
|
|
// Build the controller and return.
|
|
blder.ctrl, err = newController(controllerName, blder.mgr, ctrlOptions)
|
|
return err
|
|
}
|