Skip to content

OLM operator support for downstream plug-ins #2843

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

Merged
Merged
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
20 changes: 20 additions & 0 deletions pkg/controller/operators/olm/config.go
Original file line number Diff line number Diff line change
@@ -37,6 +37,26 @@ type operatorConfig struct {
configClient configv1client.Interface
}

func (o *operatorConfig) OperatorClient() operatorclient.ClientInterface {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These chances go in conjunction with the OperatorConfig client interface on the plug-in package. This way the plug-in can have access to some of the configuration information

return o.operatorClient
}

func (o *operatorConfig) ExternalClient() versioned.Interface {
return o.externalClient
}

func (o *operatorConfig) ResyncPeriod() func() time.Duration {
return o.resyncPeriod
}

func (o *operatorConfig) WatchedNamespaces() []string {
return o.watchedNamespaces
}

func (o *operatorConfig) Logger() *logrus.Logger {
return o.logger
}

func (o *operatorConfig) apply(options []OperatorOption) {
for _, option := range options {
option(o)
31 changes: 31 additions & 0 deletions pkg/controller/operators/olm/operator.go
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ import (
"strings"
"time"

"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/plugins"
"github.com/sirupsen/logrus"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
corev1 "k8s.io/api/core/v1"
@@ -61,6 +62,10 @@ var (
ErrAPIServiceOwnerConflict = errors.New("unable to adopt APIService")
)

// this unexported operator plugin slice provides an entrypoint for
// downstream to inject its own plugins to augment the controller behavior
var operatorPlugInFactoryFuncs []plugins.OperatorPlugInFactoryFunc
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using factory functions instead of the instantiated plug-in objects themselves. This guarantees that each instance of the operator gets their own instance of the plug-ins


type Operator struct {
queueinformer.Operator

@@ -91,6 +96,7 @@ type Operator struct {
clientAttenuator *scoped.ClientAttenuator
serviceAccountQuerier *scoped.UserDefinedServiceAccountQuerier
clientFactory clients.Factory
plugins []plugins.OperatorPlugin
}

func NewOperator(ctx context.Context, options ...OperatorOption) (*Operator, error) {
@@ -588,6 +594,31 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
OverridesBuilderFunc: overridesBuilderFunc.GetDeploymentInitializer,
}

// initialize plugins
for _, makePlugIn := range operatorPlugInFactoryFuncs {
plugin, err := makePlugIn(ctx, config, op)
if err != nil {
return nil, fmt.Errorf("error creating plugin: %s", err)
}
op.plugins = append(op.plugins, plugin)
}

if len(operatorPlugInFactoryFuncs) > 0 {
go func() {
// block until operator is done
<-op.Done()

// shutdown plug-ins
for _, plugin := range op.plugins {
if err := plugin.Shutdown(); err != nil {
if op.logger != nil {
op.logger.Warnf("error shutting down plug-in: %s", err)
}
}
}
}()
}

return op, nil
}

37 changes: 37 additions & 0 deletions pkg/controller/operators/olm/plugins/operator_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package plugins

import (
"context"
"time"

"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
"github.com/sirupsen/logrus"
)

// HostOperator is an extensible and observable operator that hosts the plug-in, i.e. which the plug-in is extending
type HostOperator interface {
queueinformer.ObservableOperator
queueinformer.ExtensibleOperator
}

// OperatorConfig gives access to required configuration from the host operator
type OperatorConfig interface {
OperatorClient() operatorclient.ClientInterface
ExternalClient() versioned.Interface
ResyncPeriod() func() time.Duration
WatchedNamespaces() []string
Logger() *logrus.Logger
}

// OperatorPlugin provides a simple interface
// that can be used to extend the olm operator's functionality
type OperatorPlugin interface {
// Shutdown is called once the host operator is done
// to give the plug-in a change to clean up resources if necessary
Shutdown() error
}

// OperatorPlugInFactoryFunc factory function that returns a new instance of a plug-in
type OperatorPlugInFactoryFunc func(ctx context.Context, config OperatorConfig, hostOperator HostOperator) (OperatorPlugin, error)
28 changes: 18 additions & 10 deletions pkg/lib/queueinformer/queueinformer_operator.go
Original file line number Diff line number Diff line change
@@ -13,8 +13,19 @@ import (
"k8s.io/client-go/tools/cache"
)

// Operator describes a Reconciler that manages a set of QueueInformers.
type Operator interface {
// ExtensibleOperator describes a Reconciler that can be extended with additional informers and queue informers
type ExtensibleOperator interface {
// RegisterQueueInformer registers the given QueueInformer with the Operator.
// This method returns an error if the Operator has already been started.
RegisterQueueInformer(queueInformer *QueueInformer) error

// RegisterInformer registers an informer with the Operator.
// This method returns an error if the Operator has already been started.
RegisterInformer(cache.SharedIndexInformer) error
}

// ObservableOperator describes a Reconciler whose state can be queried
type ObservableOperator interface {
// Ready returns a channel that is closed when the Operator is ready to run.
Ready() <-chan struct{}

@@ -29,15 +40,12 @@ type Operator interface {

// HasSynced returns true if the Operator's Informers have synced, false otherwise.
HasSynced() bool
}

// RegisterQueueInformer registers the given QueueInformer with the Operator.
// This method returns an error if the Operator has already been started.
RegisterQueueInformer(queueInformer *QueueInformer) error

// RegisterInformer registers an informer with the Operator.
// This method returns an error if the Operator has already been started.
RegisterInformer(cache.SharedIndexInformer) error

// Operator describes a Reconciler that manages a set of QueueInformers.
type Operator interface {
ObservableOperator
ExtensibleOperator
// RunInformers starts the Operator's underlying Informers.
RunInformers(ctx context.Context)