Skip to content

Commit 881b8be

Browse files
committed
Add olm operator plug-in framework
Signed-off-by: perdasilva <[email protected]>
1 parent 665c25b commit 881b8be

File tree

5 files changed

+109
-54
lines changed

5 files changed

+109
-54
lines changed

pkg/controller/operators/olm/config.go

+20
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ type operatorConfig struct {
3737
configClient configv1client.Interface
3838
}
3939

40+
func (o *operatorConfig) OperatorClient() operatorclient.ClientInterface {
41+
return o.operatorClient
42+
}
43+
44+
func (o *operatorConfig) ExternalClient() versioned.Interface {
45+
return o.externalClient
46+
}
47+
48+
func (o *operatorConfig) ResyncPeriod() func() time.Duration {
49+
return o.resyncPeriod
50+
}
51+
52+
func (o *operatorConfig) WatchedNamespaces() []string {
53+
return o.watchedNamespaces
54+
}
55+
56+
func (o *operatorConfig) Logger() *logrus.Logger {
57+
return o.logger
58+
}
59+
4060
func (o *operatorConfig) apply(options []OperatorOption) {
4161
for _, option := range options {
4262
option(o)

pkg/controller/operators/olm/operator.go

+31-41
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/olm/plugins"
1011
"github.com/sirupsen/logrus"
1112
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
1213
corev1 "k8s.io/api/core/v1"
1314
rbacv1 "k8s.io/api/rbac/v1"
14-
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1515
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1616
apierrors "k8s.io/apimachinery/pkg/api/errors"
1717
"k8s.io/apimachinery/pkg/api/meta"
@@ -61,6 +61,10 @@ var (
6161
ErrAPIServiceOwnerConflict = errors.New("unable to adopt APIService")
6262
)
6363

64+
// this unexported operator plugin slice provides an entrypoint for
65+
// downstream to inject its own plugins to augment the controller behavior
66+
var operatorPlugInFactoryFuncs []plugins.OperatorPlugInFactoryFunc
67+
6468
type Operator struct {
6569
queueinformer.Operator
6670

@@ -91,6 +95,7 @@ type Operator struct {
9195
clientAttenuator *scoped.ClientAttenuator
9296
serviceAccountQuerier *scoped.UserDefinedServiceAccountQuerier
9397
clientFactory clients.Factory
98+
plugins []plugins.OperatorPlugin
9499
}
95100

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

596+
// initialize plugins
597+
for _, makePlugIn := range operatorPlugInFactoryFuncs {
598+
plugin, err := makePlugIn(ctx, config, op)
599+
if err != nil {
600+
return nil, fmt.Errorf("error creating plugin: %s", err)
601+
}
602+
op.plugins = append(op.plugins, plugin)
603+
}
604+
605+
if len(operatorPlugInFactoryFuncs) > 0 {
606+
go func() {
607+
// block until operator is done
608+
<-op.Done()
609+
610+
// shutdown plug-ins
611+
for _, plugin := range op.plugins {
612+
if err := plugin.Shutdown(); err != nil {
613+
if op.logger != nil {
614+
op.logger.Warnf("error shutting down plug-in: %s", err)
615+
}
616+
}
617+
}
618+
}()
619+
}
620+
591621
return op, nil
592622
}
593623

@@ -1122,46 +1152,6 @@ func (a *Operator) handleClusterServiceVersionDeletion(obj interface{}) {
11221152
logger.WithError(err).Warnf("failed to requeue gc event: %v", webhook)
11231153
}
11241154
}
1125-
1126-
// Conversion webhooks are defined within a CRD.
1127-
// In an effort to prevent customer dataloss, OLM does not delete CRDs associated with a CSV when it is deleted.
1128-
// Deleting a CSV that introduced a conversion webhook removes the deployment that serviced the conversion webhook calls.
1129-
// If a conversion webhook is defined and the service isn't available, all requests against the CR associated with the CRD will fail.
1130-
// This ultimately breaks kubernetes garbage collection and prevents OLM from reinstalling the CSV as CR validation against the new CRD's
1131-
// openapiv3 schema fails.
1132-
// As such, when a CSV is deleted OLM will check if it is being replaced. If the CSV is not being replaced, OLM will remove the conversion
1133-
// webhook from the CRD definition.
1134-
csvs, err := a.lister.OperatorsV1alpha1().ClusterServiceVersionLister().ClusterServiceVersions(clusterServiceVersion.GetNamespace()).List(labels.Everything())
1135-
if err != nil {
1136-
logger.Errorf("error listing csvs: %v\n", err)
1137-
}
1138-
for _, csv := range csvs {
1139-
if csv.Spec.Replaces == clusterServiceVersion.GetName() {
1140-
return
1141-
}
1142-
}
1143-
1144-
for _, desc := range clusterServiceVersion.Spec.WebhookDefinitions {
1145-
if desc.Type != v1alpha1.ConversionWebhook || len(desc.ConversionCRDs) == 0 {
1146-
continue
1147-
}
1148-
1149-
for i, crdName := range desc.ConversionCRDs {
1150-
crd, err := a.lister.APIExtensionsV1().CustomResourceDefinitionLister().Get(crdName)
1151-
if err != nil {
1152-
logger.Errorf("error getting CRD %v which was defined in CSVs spec.WebhookDefinition[%d]: %v\n", crdName, i, err)
1153-
continue
1154-
}
1155-
1156-
copy := crd.DeepCopy()
1157-
copy.Spec.Conversion.Strategy = apiextensionsv1.NoneConverter
1158-
copy.Spec.Conversion.Webhook = nil
1159-
1160-
if _, err = a.opClient.ApiextensionsInterface().ApiextensionsV1().CustomResourceDefinitions().Update(context.TODO(), copy, metav1.UpdateOptions{}); err != nil {
1161-
logger.Errorf("error updating conversion strategy for CRD %v: %v\n", crdName, err)
1162-
}
1163-
}
1164-
}
11651155
}
11661156

11671157
func (a *Operator) removeDanglingChildCSVs(csv *v1alpha1.ClusterServiceVersion) error {

pkg/controller/operators/olm/operator_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func apiResourcesForObjects(objs []runtime.Object) []*metav1.APIResourceList {
158158

159159
// fakeOperatorConfig is the configuration for a fake operator.
160160
type fakeOperatorConfig struct {
161-
*operatorConfig
161+
*OperatorConfig
162162

163163
recorder record.EventRecorder
164164
namespaces []string
@@ -247,7 +247,7 @@ func withLogger(logger *logrus.Logger) fakeOperatorOption {
247247
func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Operator, error) {
248248
// Apply options to default config
249249
config := &fakeOperatorConfig{
250-
operatorConfig: &operatorConfig{
250+
OperatorConfig: &OperatorConfig{
251251
resyncPeriod: queueinformer.ResyncWithJitter(5*time.Minute, 0.1),
252252
operatorNamespace: "default",
253253
watchedNamespaces: []string{metav1.NamespaceAll},
@@ -288,7 +288,7 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
288288
}
289289
}
290290

291-
op, err := newOperatorWithConfig(ctx, config.operatorConfig)
291+
op, err := newOperatorWithConfig(ctx, config.OperatorConfig)
292292
if err != nil {
293293
return nil, err
294294
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package plugins
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
8+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
9+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer"
10+
"github.com/sirupsen/logrus"
11+
)
12+
13+
// HostOperator is an extensible and observable operator that hosts the plug-in, i.e. which the plug-in is extending
14+
type HostOperator interface {
15+
queueinformer.ObservableOperator
16+
queueinformer.ExtensibleOperator
17+
}
18+
19+
// OperatorConfig gives access to required configuration from the host operator
20+
type OperatorConfig interface {
21+
OperatorClient() operatorclient.ClientInterface
22+
ExternalClient() versioned.Interface
23+
ResyncPeriod() func() time.Duration
24+
WatchedNamespaces() []string
25+
Logger() *logrus.Logger
26+
}
27+
28+
// OperatorPlugin provides a simple interface
29+
// that can be used to extend the olm operator's functionality
30+
type OperatorPlugin interface {
31+
// Shutdown is called once the host operator is done
32+
// to give the plug-in a change to clean up resources if necessary
33+
Shutdown() error
34+
}
35+
36+
// OperatorPlugInFactoryFunc factory function that returns a new instance of a plug-in
37+
type OperatorPlugInFactoryFunc func(ctx context.Context, config OperatorConfig, hostOperator HostOperator) (OperatorPlugin, error)

pkg/lib/queueinformer/queueinformer_operator.go

+18-10
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,19 @@ import (
1313
"k8s.io/client-go/tools/cache"
1414
)
1515

16-
// Operator describes a Reconciler that manages a set of QueueInformers.
17-
type Operator interface {
16+
// ExtensibleOperator describes a Reconciler that can be extended with additional informers and queue informers
17+
type ExtensibleOperator interface {
18+
// RegisterQueueInformer registers the given QueueInformer with the Operator.
19+
// This method returns an error if the Operator has already been started.
20+
RegisterQueueInformer(queueInformer *QueueInformer) error
21+
22+
// RegisterInformer registers an informer with the Operator.
23+
// This method returns an error if the Operator has already been started.
24+
RegisterInformer(cache.SharedIndexInformer) error
25+
}
26+
27+
// ObservableOperator describes a Reconciler whose state can be queried
28+
type ObservableOperator interface {
1829
// Ready returns a channel that is closed when the Operator is ready to run.
1930
Ready() <-chan struct{}
2031

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

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

33-
// RegisterQueueInformer registers the given QueueInformer with the Operator.
34-
// This method returns an error if the Operator has already been started.
35-
RegisterQueueInformer(queueInformer *QueueInformer) error
36-
37-
// RegisterInformer registers an informer with the Operator.
38-
// This method returns an error if the Operator has already been started.
39-
RegisterInformer(cache.SharedIndexInformer) error
40-
45+
// Operator describes a Reconciler that manages a set of QueueInformers.
46+
type Operator interface {
47+
ObservableOperator
48+
ExtensibleOperator
4149
// RunInformers starts the Operator's underlying Informers.
4250
RunInformers(ctx context.Context)
4351

0 commit comments

Comments
 (0)