Skip to content

Commit 25cdc88

Browse files
authored
resolve operators on catalog availability change (#216)
* resolve operators on catalog availability change Signed-off-by: Ankita Thomas <[email protected]> * removing catalog state check for operator reconcile Signed-off-by: Ankita Thomas <[email protected]> * e2e tests for catalog watch Signed-off-by: Ankita Thomas <[email protected]> * use smaller test index image, cleanup after tests Signed-off-by: Ankita Thomas <[email protected]> --------- Signed-off-by: Ankita Thomas <[email protected]>
1 parent 35f381d commit 25cdc88

File tree

4 files changed

+138
-14
lines changed

4 files changed

+138
-14
lines changed

config/rbac/role.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ rules:
1212
verbs:
1313
- list
1414
- watch
15+
- apiGroups:
16+
- catalogd.operatorframework.io
17+
resources:
18+
- catalogs
19+
verbs:
20+
- list
21+
- watch
1522
- apiGroups:
1623
- catalogd.operatorframework.io
1724
resources:

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.19
44

55
require (
66
github.com/blang/semver/v4 v4.0.0
7+
github.com/go-logr/logr v1.2.3
78
github.com/onsi/ginkgo/v2 v2.8.3
89
github.com/onsi/gomega v1.27.1
910
github.com/operator-framework/catalogd v0.2.0
@@ -26,7 +27,6 @@ require (
2627
github.com/evanphx/json-patch/v5 v5.6.0 // indirect
2728
github.com/fsnotify/fsnotify v1.6.0 // indirect
2829
github.com/go-air/gini v1.0.4 // indirect
29-
github.com/go-logr/logr v1.2.3 // indirect
3030
github.com/go-logr/zapr v1.2.3 // indirect
3131
github.com/go-openapi/jsonpointer v0.19.5 // indirect
3232
github.com/go-openapi/jsonreference v0.20.0 // indirect

internal/controllers/operator_controller.go

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"context"
2121
"fmt"
2222

23+
"github.com/go-logr/logr"
24+
catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1"
2325
"github.com/operator-framework/deppy/pkg/deppy/solver"
2426
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
2527
"k8s.io/apimachinery/pkg/api/equality"
@@ -32,11 +34,13 @@ import (
3234
"k8s.io/utils/pointer"
3335
ctrl "sigs.k8s.io/controller-runtime"
3436
"sigs.k8s.io/controller-runtime/pkg/client"
37+
"sigs.k8s.io/controller-runtime/pkg/handler"
3538
"sigs.k8s.io/controller-runtime/pkg/log"
36-
37-
"github.com/operator-framework/operator-controller/internal/controllers/validators"
39+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
40+
"sigs.k8s.io/controller-runtime/pkg/source"
3841

3942
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
43+
"github.com/operator-framework/operator-controller/internal/controllers/validators"
4044
"github.com/operator-framework/operator-controller/internal/resolution"
4145
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies"
4246
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
@@ -57,6 +61,7 @@ type OperatorReconciler struct {
5761

5862
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=bundlemetadata,verbs=list;watch
5963
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=packages,verbs=list;watch
64+
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=catalogs,verbs=list;watch
6065

6166
func (r *OperatorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
6267
l := log.FromContext(ctx).WithName("operator-controller")
@@ -287,6 +292,8 @@ func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha
287292
func (r *OperatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
288293
err := ctrl.NewControllerManagedBy(mgr).
289294
For(&operatorsv1alpha1.Operator{}).
295+
Watches(source.NewKindWithCache(&catalogd.Catalog{}, mgr.GetCache()),
296+
handler.EnqueueRequestsFromMapFunc(operatorRequestsForCatalog(context.TODO(), mgr.GetClient(), mgr.GetLogger()))).
290297
Owns(&rukpakv1alpha1.BundleDeployment{}).
291298
Complete(r)
292299

@@ -422,3 +429,26 @@ func setInstalledStatusConditionUnknown(conditions *[]metav1.Condition, message
422429
ObservedGeneration: generation,
423430
})
424431
}
432+
433+
// Generate reconcile requests for all operators affected by a catalog change
434+
func operatorRequestsForCatalog(ctx context.Context, c client.Reader, logger logr.Logger) handler.MapFunc {
435+
return func(object client.Object) []reconcile.Request {
436+
// no way of associating an operator to a catalog so create reconcile requests for everything
437+
operators := operatorsv1alpha1.OperatorList{}
438+
err := c.List(ctx, &operators)
439+
if err != nil {
440+
logger.Error(err, "unable to enqueue operators for catalog reconcile")
441+
return nil
442+
}
443+
var requests []reconcile.Request
444+
for _, op := range operators.Items {
445+
requests = append(requests, reconcile.Request{
446+
NamespacedName: types.NamespacedName{
447+
Namespace: op.GetNamespace(),
448+
Name: op.GetName(),
449+
},
450+
})
451+
}
452+
return requests
453+
}
454+
}

test/e2e/install_test.go

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
catalogd "github.com/operator-framework/catalogd/pkg/apis/core/v1beta1"
1111
operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
1212
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
13+
"k8s.io/apimachinery/pkg/api/errors"
1314
apimeta "k8s.io/apimachinery/pkg/api/meta"
1415
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1516
"k8s.io/apimachinery/pkg/types"
@@ -52,30 +53,35 @@ var _ = Describe("Operator Install", func() {
5253
Image: &catalogd.ImageSource{
5354
// (TODO): Set up a local image registry, and build and store a test catalog in it
5455
// to use in the test suite
55-
Ref: "quay.io/operatorhubio/catalog:latest",
56+
Ref: "quay.io/olmtest/e2e-index:single-package-fbc", //generated from: "quay.io/operatorhubio/catalog:latest",
5657
},
5758
},
5859
},
5960
}
61+
})
62+
It("resolves the specified package with correct bundle path", func() {
6063
err := c.Create(ctx, operatorCatalog)
6164
Expect(err).ToNot(HaveOccurred())
6265
Eventually(func(g Gomega) {
6366
err = c.Get(ctx, types.NamespacedName{Name: "test-catalog"}, operatorCatalog)
6467
g.Expect(err).ToNot(HaveOccurred())
6568
g.Expect(len(operatorCatalog.Status.Conditions)).To(Equal(1))
66-
g.Expect(operatorCatalog.Status.Conditions[0].Message).To(ContainSubstring("successfully unpacked the catalog image"))
69+
cond := apimeta.FindStatusCondition(operatorCatalog.Status.Conditions, catalogd.TypeUnpacked)
70+
g.Expect(cond).ToNot(BeNil())
71+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
72+
g.Expect(cond.Reason).To(Equal(catalogd.ReasonUnpackSuccessful))
73+
g.Expect(cond.Message).To(ContainSubstring("successfully unpacked the catalog image"))
6774
}).WithTimeout(5 * time.Minute).WithPolling(defaultPoll).Should(Succeed())
68-
})
69-
It("resolves the specified package with correct bundle path", func() {
75+
7076
By("creating the Operator resource")
71-
err := c.Create(ctx, operator)
77+
err = c.Create(ctx, operator)
7278
Expect(err).ToNot(HaveOccurred())
7379

7480
By("eventually reporting a successful resolution and bundle path")
7581
Eventually(func(g Gomega) {
7682
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
7783
g.Expect(err).ToNot(HaveOccurred())
78-
g.Expect(len(operator.Status.Conditions)).To(Equal(2))
84+
7985
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
8086
g.Expect(cond).ToNot(BeNil())
8187
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
@@ -97,17 +103,98 @@ var _ = Describe("Operator Install", func() {
97103
bd := rukpakv1alpha1.BundleDeployment{}
98104
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &bd)
99105
g.Expect(err).ToNot(HaveOccurred())
100-
g.Expect(len(bd.Status.Conditions)).To(Equal(2))
101-
g.Expect(bd.Status.Conditions[0].Reason).To(Equal("UnpackSuccessful"))
102-
g.Expect(bd.Status.Conditions[1].Reason).To(Equal("InstallationSucceeded"))
106+
107+
cond = apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha1.TypeHasValidBundle)
108+
g.Expect(cond).ToNot(BeNil())
109+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
110+
g.Expect(cond.Reason).To(Equal(rukpakv1alpha1.ReasonUnpackSuccessful))
111+
112+
cond = apimeta.FindStatusCondition(bd.Status.Conditions, rukpakv1alpha1.TypeInstalled)
113+
g.Expect(cond).ToNot(BeNil())
114+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
115+
g.Expect(cond.Reason).To(Equal(rukpakv1alpha1.ReasonInstallationSucceeded))
116+
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
117+
})
118+
It("resolves again when a new catalog is available", func() {
119+
Eventually(func(g Gomega) {
120+
// target package should not be present on cluster
121+
err := c.Get(ctx, types.NamespacedName{Name: pkgName}, &catalogd.Package{})
122+
Expect(errors.IsNotFound(err)).To(BeTrue())
123+
}).WithTimeout(5 * time.Minute).WithPolling(defaultPoll).Should(Succeed())
124+
125+
By("creating the Operator resource")
126+
err := c.Create(ctx, operator)
127+
Expect(err).ToNot(HaveOccurred())
128+
129+
By("failing to find Operator during resolution")
130+
Eventually(func(g Gomega) {
131+
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
132+
g.Expect(err).ToNot(HaveOccurred())
133+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
134+
g.Expect(cond).ToNot(BeNil())
135+
g.Expect(cond.Status).To(Equal(metav1.ConditionFalse))
136+
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonResolutionFailed))
137+
g.Expect(cond.Message).To(Equal(fmt.Sprintf("package '%s' not found", pkgName)))
103138
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
104139

140+
By("creating an Operator catalog with the desired package")
141+
err = c.Create(ctx, operatorCatalog)
142+
Expect(err).ToNot(HaveOccurred())
143+
Eventually(func(g Gomega) {
144+
err = c.Get(ctx, types.NamespacedName{Name: operatorCatalog.Name}, operatorCatalog)
145+
g.Expect(err).ToNot(HaveOccurred())
146+
cond := apimeta.FindStatusCondition(operatorCatalog.Status.Conditions, catalogd.TypeUnpacked)
147+
g.Expect(cond).ToNot(BeNil())
148+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
149+
g.Expect(cond.Reason).To(Equal(catalogd.ReasonUnpackSuccessful))
150+
}).WithTimeout(5 * time.Minute).WithPolling(defaultPoll).Should(Succeed())
151+
152+
By("eventually resolving the package successfully")
153+
Eventually(func(g Gomega) {
154+
err = c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)
155+
g.Expect(err).ToNot(HaveOccurred())
156+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
157+
g.Expect(cond).ToNot(BeNil())
158+
g.Expect(cond.Status).To(Equal(metav1.ConditionTrue))
159+
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonSuccess))
160+
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
105161
})
106162
AfterEach(func() {
107-
err := c.Delete(ctx, operatorCatalog)
163+
err := c.Delete(ctx, operator)
164+
Expect(err).ToNot(HaveOccurred())
165+
Eventually(func(g Gomega) {
166+
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &operatorv1alpha1.Operator{})
167+
Expect(errors.IsNotFound(err)).To(BeTrue())
168+
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
169+
170+
err = c.Delete(ctx, operatorCatalog)
171+
Expect(err).ToNot(HaveOccurred())
172+
Eventually(func(g Gomega) {
173+
err = c.Get(ctx, types.NamespacedName{Name: operatorCatalog.Name}, &catalogd.Catalog{})
174+
Expect(errors.IsNotFound(err)).To(BeTrue())
175+
}).WithTimeout(defaultTimeout).WithPolling(defaultPoll).Should(Succeed())
176+
177+
// speed up delete without waiting for gc
178+
err = c.DeleteAllOf(ctx, &catalogd.BundleMetadata{})
108179
Expect(err).ToNot(HaveOccurred())
109-
err = c.Delete(ctx, operator)
180+
err = c.DeleteAllOf(ctx, &catalogd.Package{})
110181
Expect(err).ToNot(HaveOccurred())
182+
183+
Eventually(func(g Gomega) {
184+
// ensure resource cleanup
185+
packages := &catalogd.PackageList{}
186+
err = c.List(ctx, packages)
187+
Expect(err).To(BeNil())
188+
Expect(packages.Items).To(BeEmpty())
189+
190+
bmd := &catalogd.BundleMetadataList{}
191+
err = c.List(ctx, bmd)
192+
Expect(err).To(BeNil())
193+
Expect(bmd.Items).To(BeEmpty())
194+
195+
err = c.Get(ctx, types.NamespacedName{Name: operatorName}, &rukpakv1alpha1.BundleDeployment{})
196+
Expect(errors.IsNotFound(err)).To(BeTrue())
197+
}).WithTimeout(5 * time.Minute).WithPolling(defaultPoll).Should(Succeed())
111198
})
112199
})
113200
})

0 commit comments

Comments
 (0)