Skip to content

Commit b234fe7

Browse files
Merge pull request #878 from dinhxuanvu/update-crd-one
Enable OLM to update CRD when there is only one owner of that CRD
2 parents 0b23b36 + 930163c commit b234fe7

File tree

3 files changed

+470
-26
lines changed

3 files changed

+470
-26
lines changed

pkg/controller/operators/catalog/operator.go

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"reflect"
89
"sync"
910
"time"
1011

@@ -14,6 +15,7 @@ import (
1415
corev1 "k8s.io/api/core/v1"
1516
rbacv1 "k8s.io/api/rbac/v1"
1617
v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
18+
extinf "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
1719
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1820
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1921
"k8s.io/apimachinery/pkg/labels"
@@ -32,6 +34,7 @@ import (
3234
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription"
3335
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/reconciler"
3436
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
37+
index "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/index"
3538
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
3639
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
3740
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
@@ -56,21 +59,22 @@ const (
5659
type Operator struct {
5760
queueinformer.Operator
5861

59-
logger *logrus.Logger
60-
clock utilclock.Clock
61-
opClient operatorclient.ClientInterface
62-
client versioned.Interface
63-
lister operatorlister.OperatorLister
64-
catsrcQueueSet *queueinformer.ResourceQueueSet
65-
subQueueSet *queueinformer.ResourceQueueSet
66-
ipQueueSet *queueinformer.ResourceQueueSet
67-
nsResolveQueue workqueue.RateLimitingInterface
68-
namespace string
69-
sources map[resolver.CatalogKey]resolver.SourceRef
70-
sourcesLock sync.RWMutex
71-
sourcesLastUpdate metav1.Time
72-
resolver resolver.Resolver
73-
reconciler reconciler.RegistryReconcilerFactory
62+
logger *logrus.Logger
63+
clock utilclock.Clock
64+
opClient operatorclient.ClientInterface
65+
client versioned.Interface
66+
lister operatorlister.OperatorLister
67+
catsrcQueueSet *queueinformer.ResourceQueueSet
68+
subQueueSet *queueinformer.ResourceQueueSet
69+
ipQueueSet *queueinformer.ResourceQueueSet
70+
nsResolveQueue workqueue.RateLimitingInterface
71+
namespace string
72+
sources map[resolver.CatalogKey]resolver.SourceRef
73+
sourcesLock sync.RWMutex
74+
sourcesLastUpdate metav1.Time
75+
resolver resolver.Resolver
76+
reconciler reconciler.RegistryReconcilerFactory
77+
csvProvidedAPIsIndexer map[string]cache.Indexer
7478
}
7579

7680
// NewOperator creates a new Catalog Operator.
@@ -98,17 +102,18 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
98102

99103
// Allocate the new instance of an Operator.
100104
op := &Operator{
101-
Operator: queueOperator,
102-
logger: logger,
103-
clock: clock,
104-
opClient: opClient,
105-
client: crClient,
106-
lister: lister,
107-
namespace: operatorNamespace,
108-
sources: make(map[resolver.CatalogKey]resolver.SourceRef),
109-
resolver: resolver.NewOperatorsV1alpha1Resolver(lister),
110-
catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(),
111-
subQueueSet: queueinformer.NewEmptyResourceQueueSet(),
105+
Operator: queueOperator,
106+
logger: logger,
107+
clock: clock,
108+
opClient: opClient,
109+
client: crClient,
110+
lister: lister,
111+
namespace: operatorNamespace,
112+
sources: make(map[resolver.CatalogKey]resolver.SourceRef),
113+
resolver: resolver.NewOperatorsV1alpha1Resolver(lister),
114+
catsrcQueueSet: queueinformer.NewEmptyResourceQueueSet(),
115+
subQueueSet: queueinformer.NewEmptyResourceQueueSet(),
116+
csvProvidedAPIsIndexer: map[string]cache.Indexer{},
112117
}
113118
op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now)
114119

@@ -122,6 +127,10 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
122127
op.lister.OperatorsV1alpha1().RegisterClusterServiceVersionLister(namespace, csvInformer.Lister())
123128
op.RegisterInformer(csvInformer.Informer())
124129

130+
csvInformer.Informer().AddIndexers(cache.Indexers{index.ProvidedAPIsIndexFuncKey: index.ProvidedAPIsIndexFunc})
131+
csvIndexer := csvInformer.Informer().GetIndexer()
132+
op.csvProvidedAPIsIndexer[namespace] = csvIndexer
133+
125134
// TODO: Add namespace resolve sync
126135

127136
// Wire InstallPlans
@@ -244,6 +253,20 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo
244253

245254
}
246255

256+
// Register CustomResourceDefinition QueueInformer
257+
crdInformer := extinf.NewSharedInformerFactory(op.opClient.ApiextensionsV1beta1Interface(), resyncPeriod).Apiextensions().V1beta1().CustomResourceDefinitions()
258+
op.lister.APIExtensionsV1beta1().RegisterCustomResourceDefinitionLister(crdInformer.Lister())
259+
crdQueueInformer, err := queueinformer.NewQueueInformer(
260+
ctx,
261+
queueinformer.WithLogger(op.logger),
262+
queueinformer.WithInformer(crdInformer.Informer()),
263+
queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncObject).ToSyncerWithDelete(op.handleDeletion)),
264+
)
265+
if err != nil {
266+
return nil, err
267+
}
268+
op.RegisterQueueInformer(crdQueueInformer)
269+
247270
// Namespace sync for resolving subscriptions
248271
namespaceInformer := informers.NewSharedInformerFactory(op.opClient.KubernetesInterface(), resyncPeriod).Core().V1().Namespaces()
249272
op.lister.CoreV1().RegisterNamespaceLister(namespaceInformer.Lister())
@@ -1034,6 +1057,25 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
10341057
// Attempt to create the CRD.
10351058
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Create(&crd)
10361059
if k8serrors.IsAlreadyExists(err) {
1060+
currentCRD, _ := o.lister.APIExtensionsV1beta1().CustomResourceDefinitionLister().Get(crd.GetName())
1061+
// Compare 2 CRDs to see if it needs to be updatetd
1062+
if !reflect.DeepEqual(crd, *currentCRD) {
1063+
// Verify CRD ownership, only attempt to update if
1064+
// CRD has only one owner
1065+
// Example: provided=database.coreos.com/v1alpha1/EtcdCluster
1066+
matchedCSV, err := index.CRDProviderNames(o.csvProvidedAPIsIndexer, crd)
1067+
if err != nil {
1068+
return errorwrap.Wrapf(err, "error find matched CSV: %s", step.Resource.Name)
1069+
}
1070+
if len(matchedCSV) == 1 {
1071+
// Attempt to update CRD
1072+
crd.SetResourceVersion(currentCRD.GetResourceVersion())
1073+
_, err = o.opClient.ApiextensionsV1beta1Interface().ApiextensionsV1beta1().CustomResourceDefinitions().Update(&crd)
1074+
if err != nil {
1075+
return errorwrap.Wrapf(err, "error update CRD: %s", step.Resource.Name)
1076+
}
1077+
}
1078+
}
10371079
// If it already existed, mark the step as Present.
10381080
plan.Status.Plan[i].Status = v1alpha1.StepStatusPresent
10391081
continue

pkg/lib/index/api.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package indexer
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
8+
v1beta1ext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
9+
"k8s.io/client-go/tools/cache"
10+
)
11+
12+
const (
13+
// ProvidedAPIsIndexFuncKey is the recommended key to use for registering the index func with an indexer.
14+
ProvidedAPIsIndexFuncKey string = "providedAPIs"
15+
)
16+
17+
// ProvidedAPIsIndexFunc returns indicies from the owned CRDs and APIs of the given object (CSV)
18+
func ProvidedAPIsIndexFunc(obj interface{}) ([]string, error) {
19+
indicies := []string{}
20+
21+
csv, ok := obj.(*v1alpha1.ClusterServiceVersion)
22+
if !ok {
23+
return indicies, fmt.Errorf("invalid object of type: %T", obj)
24+
}
25+
26+
for _, crd := range csv.Spec.CustomResourceDefinitions.Owned {
27+
parts := strings.SplitN(crd.Name, ".", 2)
28+
if len(parts) < 2 {
29+
return indicies, fmt.Errorf("couldn't parse plural.group from crd name: %s", crd.Name)
30+
}
31+
indicies = append(indicies, fmt.Sprintf("%s/%s/%s", parts[1], crd.Version, crd.Kind))
32+
}
33+
for _, api := range csv.Spec.APIServiceDefinitions.Owned {
34+
indicies = append(indicies, fmt.Sprintf("%s/%s/%s", api.Group, api.Version, api.Kind))
35+
}
36+
37+
return indicies, nil
38+
}
39+
40+
// CRDProviderNames returns the names of CSVs that own the given CRD
41+
func CRDProviderNames(indexers map[string]cache.Indexer, crd v1beta1ext.CustomResourceDefinition) (map[string]struct{}, error) {
42+
csvSet := map[string]struct{}{}
43+
crdSpec := map[string]struct{}{}
44+
for _, v := range crd.Spec.Versions {
45+
crdSpec[fmt.Sprintf("%s/%s/%s", crd.Spec.Group, v.Name, crd.Spec.Names.Kind)] = struct{}{}
46+
}
47+
if crd.Spec.Version != "" {
48+
crdSpec[fmt.Sprintf("%s/%s/%s", crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Kind)] = struct{}{}
49+
}
50+
for _, indexer := range indexers {
51+
for key := range crdSpec {
52+
csvs, err := indexer.ByIndex(ProvidedAPIsIndexFuncKey, key)
53+
if err != nil {
54+
return nil, err
55+
}
56+
for _, item := range csvs {
57+
csv, ok := item.(*v1alpha1.ClusterServiceVersion)
58+
if !ok {
59+
continue
60+
}
61+
// Add to set
62+
csvSet[csv.GetName()] = struct{}{}
63+
}
64+
}
65+
}
66+
return csvSet, nil
67+
}

0 commit comments

Comments
 (0)