Skip to content

Commit 2973a14

Browse files
ankitathomasperdasilvavarshaprasad96
authored
Add catalogsource entitysource (#129)
* add catalogsource entitysource Signed-off-by: Ankita Thomas <[email protected]> * move catalogsource querier out to new controller Signed-off-by: Ankita Thomas <[email protected]> * Hook up reconciler to main Signed-off-by: perdasilva <[email protected]> * fix unit tests and manifests Signed-off-by: Ankita Thomas <[email protected]> * Switch from hardcoded data source to catalog source entity source Signed-off-by: perdasilva <[email protected]> * Move entity_source pkg outside of variable_source pkg Signed-off-by: perdasilva <[email protected]> * Add vendor to .gitignore Signed-off-by: perdasilva <[email protected]> * Remove registry cache querier Signed-off-by: perdasilva <[email protected]> * Add initial catalog source controller unit tests Signed-off-by: perdasilva <[email protected]> * fixing unit tests, removing unused files Signed-off-by: Ankita Thomas <[email protected]> * Refactor catsrc controller unit tests Signed-off-by: perdasilva <[email protected]> * update go.mod Signed-off-by: perdasilva <[email protected]> * fix format, imports, and lint Signed-off-by: perdasilva <[email protected]> * add rbac for issuing events Signed-off-by: perdasilva <[email protected]> * add catsrc controller e2e tests Signed-off-by: Varsha Prasad Narsing <[email protected]> * add e2e tests Signed-off-by: Varsha Prasad Narsing <[email protected]> * Fix e2e catalogsource image Signed-off-by: Ankita Thomas <[email protected]> --------- Signed-off-by: Ankita Thomas <[email protected]> Signed-off-by: perdasilva <[email protected]> Signed-off-by: Varsha Prasad Narsing <[email protected]> Co-authored-by: perdasilva <[email protected]> Co-authored-by: Varsha Prasad Narsing <[email protected]>
1 parent f4b4bba commit 2973a14

23 files changed

+1851
-38
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
vendor
12

23
# Binaries for programs and plugins
34
*.exe

config/rbac/role.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ metadata:
55
creationTimestamp: null
66
name: manager-role
77
rules:
8+
- apiGroups:
9+
- ""
10+
resources:
11+
- events
12+
verbs:
13+
- create
14+
- patch
815
- apiGroups:
916
- core.rukpak.io
1017
resources:
@@ -16,6 +23,14 @@ rules:
1623
- patch
1724
- update
1825
- watch
26+
- apiGroups:
27+
- operators.coreos.com
28+
resources:
29+
- catalogsources
30+
verbs:
31+
- get
32+
- list
33+
- watch
1934
- apiGroups:
2035
- operators.operatorframework.io
2136
resources:
+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
package controllers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"reflect"
7+
"sync"
8+
"time"
9+
10+
"github.com/operator-framework/api/pkg/operators/v1alpha1"
11+
"github.com/operator-framework/deppy/pkg/deppy"
12+
"github.com/operator-framework/deppy/pkg/deppy/input"
13+
"k8s.io/apimachinery/pkg/api/errors"
14+
"k8s.io/apimachinery/pkg/runtime"
15+
"k8s.io/client-go/tools/record"
16+
ctrl "sigs.k8s.io/controller-runtime"
17+
"sigs.k8s.io/controller-runtime/pkg/client"
18+
"sigs.k8s.io/controller-runtime/pkg/log"
19+
20+
"github.com/operator-framework/operator-controller/internal/resolution/entity_sources/catalogsource"
21+
)
22+
23+
const (
24+
defaultCatalogSourceSyncInterval = 5 * time.Minute
25+
defaultRegistryGRPCConnectionTimeout = 10 * time.Second
26+
27+
eventTypeNormal = "Normal"
28+
eventTypeWarning = "Warning"
29+
30+
eventReasonCacheUpdated = "BundleCacheUpdated"
31+
eventReasonCacheUpdateFailed = "BundleCacheUpdateFailed"
32+
)
33+
34+
type CatalogSourceReconcilerOption func(reconciler *CatalogSourceReconciler)
35+
36+
func WithRegistryClient(registry catalogsource.RegistryClient) CatalogSourceReconcilerOption {
37+
return func(reconciler *CatalogSourceReconciler) {
38+
reconciler.registry = registry
39+
}
40+
}
41+
42+
func WithUnmanagedCatalogSourceSyncInterval(interval time.Duration) CatalogSourceReconcilerOption {
43+
return func(reconciler *CatalogSourceReconciler) {
44+
reconciler.unmanagedCatalogSourceSyncInterval = interval
45+
}
46+
}
47+
48+
// applyDefaults applies default values to empty CatalogSourceReconciler fields _after_ options have been applied
49+
func applyDefaults() CatalogSourceReconcilerOption {
50+
return func(reconciler *CatalogSourceReconciler) {
51+
if reconciler.registry == nil {
52+
reconciler.registry = catalogsource.NewRegistryGRPCClient(defaultRegistryGRPCConnectionTimeout)
53+
}
54+
if reconciler.unmanagedCatalogSourceSyncInterval == 0 {
55+
reconciler.unmanagedCatalogSourceSyncInterval = defaultCatalogSourceSyncInterval
56+
}
57+
}
58+
}
59+
60+
type CatalogSourceReconciler struct {
61+
sync.RWMutex
62+
client.Client
63+
scheme *runtime.Scheme
64+
registry catalogsource.RegistryClient
65+
recorder record.EventRecorder
66+
unmanagedCatalogSourceSyncInterval time.Duration
67+
cache map[string]map[deppy.Identifier]*input.Entity
68+
}
69+
70+
func NewCatalogSourceReconciler(client client.Client, scheme *runtime.Scheme, recorder record.EventRecorder, options ...CatalogSourceReconcilerOption) *CatalogSourceReconciler {
71+
reconciler := &CatalogSourceReconciler{
72+
RWMutex: sync.RWMutex{},
73+
Client: client,
74+
scheme: scheme,
75+
recorder: recorder,
76+
unmanagedCatalogSourceSyncInterval: 0,
77+
cache: map[string]map[deppy.Identifier]*input.Entity{},
78+
}
79+
// apply options
80+
options = append(options, applyDefaults())
81+
for _, option := range options {
82+
option(reconciler)
83+
}
84+
85+
return reconciler
86+
}
87+
88+
// +kubebuilder:rbac:groups=operators.coreos.com,resources=catalogsources,verbs=get;list;watch
89+
// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
90+
91+
func (r *CatalogSourceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
92+
l := log.FromContext(ctx).WithName("catalogsource-controller")
93+
l.V(1).Info("starting")
94+
defer l.V(1).Info("ending")
95+
96+
var catalogSource = &v1alpha1.CatalogSource{}
97+
if err := r.Client.Get(ctx, req.NamespacedName, catalogSource); err != nil {
98+
if errors.IsNotFound(err) {
99+
r.dropSource(req.String())
100+
}
101+
return ctrl.Result{}, client.IgnoreNotFound(err)
102+
}
103+
104+
entities, err := r.registry.ListEntities(ctx, catalogSource)
105+
// TODO: invalidate stale cache for failed updates
106+
if err != nil {
107+
r.recorder.Event(catalogSource, eventTypeWarning, eventReasonCacheUpdateFailed, fmt.Sprintf("Failed to update bundle cache from %s/%s: %v", catalogSource.GetNamespace(), catalogSource.GetName(), err))
108+
return ctrl.Result{Requeue: !isManagedCatalogSource(*catalogSource)}, err
109+
}
110+
if updated := r.updateCache(req.String(), entities); updated {
111+
r.recorder.Event(catalogSource, eventTypeNormal, eventReasonCacheUpdated, fmt.Sprintf("Successfully updated bundle cache from %s/%s", catalogSource.GetNamespace(), catalogSource.GetName()))
112+
}
113+
114+
if isManagedCatalogSource(*catalogSource) {
115+
return ctrl.Result{}, nil
116+
}
117+
return ctrl.Result{RequeueAfter: r.unmanagedCatalogSourceSyncInterval}, nil
118+
}
119+
120+
// SetupWithManager sets up the controller with the Manager.
121+
func (r *CatalogSourceReconciler) SetupWithManager(mgr ctrl.Manager) error {
122+
return ctrl.NewControllerManagedBy(mgr).
123+
For(&v1alpha1.CatalogSource{}).
124+
Complete(r)
125+
}
126+
127+
// TODO: find better way to identify catalogSources unmanaged by olm
128+
func isManagedCatalogSource(catalogSource v1alpha1.CatalogSource) bool {
129+
return len(catalogSource.Spec.Address) == 0
130+
}
131+
132+
func (r *CatalogSourceReconciler) updateCache(sourceID string, entities []*input.Entity) bool {
133+
newSourceCache := make(map[deppy.Identifier]*input.Entity)
134+
for _, entity := range entities {
135+
newSourceCache[entity.Identifier()] = entity
136+
}
137+
if _, ok := r.cache[sourceID]; ok && reflect.DeepEqual(r.cache[sourceID], newSourceCache) {
138+
return false
139+
}
140+
r.RWMutex.Lock()
141+
defer r.RWMutex.Unlock()
142+
r.cache[sourceID] = newSourceCache
143+
// return whether cache had updates
144+
return true
145+
}
146+
147+
func (r *CatalogSourceReconciler) dropSource(sourceID string) {
148+
r.RWMutex.Lock()
149+
defer r.RWMutex.Unlock()
150+
delete(r.cache, sourceID)
151+
}
152+
153+
func (r *CatalogSourceReconciler) Get(ctx context.Context, id deppy.Identifier) *input.Entity {
154+
r.RWMutex.RLock()
155+
defer r.RWMutex.RUnlock()
156+
// don't count on deppy ID to reflect its catalogsource
157+
for _, source := range r.cache {
158+
if entity, ok := source[id]; ok {
159+
return entity
160+
}
161+
}
162+
return nil
163+
}
164+
165+
func (r *CatalogSourceReconciler) Filter(ctx context.Context, filter input.Predicate) (input.EntityList, error) {
166+
resultSet := input.EntityList{}
167+
if err := r.Iterate(ctx, func(entity *input.Entity) error {
168+
if filter(entity) {
169+
resultSet = append(resultSet, *entity)
170+
}
171+
return nil
172+
}); err != nil {
173+
return nil, err
174+
}
175+
return resultSet, nil
176+
}
177+
178+
func (r *CatalogSourceReconciler) GroupBy(ctx context.Context, fn input.GroupByFunction) (input.EntityListMap, error) {
179+
resultSet := input.EntityListMap{}
180+
if err := r.Iterate(ctx, func(entity *input.Entity) error {
181+
keys := fn(entity)
182+
for _, key := range keys {
183+
resultSet[key] = append(resultSet[key], *entity)
184+
}
185+
return nil
186+
}); err != nil {
187+
return nil, err
188+
}
189+
return resultSet, nil
190+
}
191+
192+
func (r *CatalogSourceReconciler) Iterate(ctx context.Context, fn input.IteratorFunction) error {
193+
r.RWMutex.RLock()
194+
defer r.RWMutex.RUnlock()
195+
for _, source := range r.cache {
196+
for _, entity := range source {
197+
if err := fn(entity); err != nil {
198+
return err
199+
}
200+
}
201+
}
202+
return nil
203+
}

0 commit comments

Comments
 (0)