Skip to content

Commit 6ad5c2b

Browse files
committed
reduce complexity of reconcile method
- reduce cyclomatic complexity from 8 to 5 - make it more obvious when and how conditions are set when various errors occur - when errors occur: always set ready condition, always return the error
1 parent 52bbccb commit 6ad5c2b

File tree

3 files changed

+89
-46
lines changed

3 files changed

+89
-46
lines changed

api/v1alpha1/operator_types.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ limitations under the License.
1717
package v1alpha1
1818

1919
import (
20-
operatorutil "github.com/operator-framework/operator-controller/internal/util"
2120
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
22+
operatorutil "github.com/operator-framework/operator-controller/internal/util"
2223
)
2324

2425
// OperatorSpec defines the desired state of Operator
@@ -32,9 +33,10 @@ const (
3233
// TODO(user): add more Types, here and into init()
3334
TypeReady = "Ready"
3435

35-
ReasonNotImplemented = "NotImplemented"
36-
ReasonResolutionFailed = "ResolutionFailed"
37-
ReasonResolutionSucceeded = "ResolutionSucceeded"
36+
ReasonResolutionSucceeded = "ResolutionSucceeded"
37+
ReasonResolutionFailed = "ResolutionFailed"
38+
ReasonBundleLookupFailed = "BundleLookupFailed"
39+
ReasonBundleDeploymentFailed = "BundleDeploymentFailed"
3840
)
3941

4042
func init() {
@@ -44,7 +46,10 @@ func init() {
4446
)
4547
// TODO(user): add Reasons from above
4648
operatorutil.ConditionReasons = append(operatorutil.ConditionReasons,
47-
ReasonNotImplemented, ReasonResolutionSucceeded, ReasonResolutionFailed,
49+
ReasonResolutionSucceeded,
50+
ReasonResolutionFailed,
51+
ReasonBundleLookupFailed,
52+
ReasonBundleDeploymentFailed,
4853
)
4954
}
5055

controllers/operator_controller.go

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122

23+
"github.com/operator-framework/deppy/pkg/deppy/solver"
2224
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
2325
"k8s.io/apimachinery/pkg/api/equality"
2426
apimeta "k8s.io/apimachinery/pkg/api/meta"
@@ -35,6 +37,7 @@ import (
3537
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
3638
"github.com/operator-framework/operator-controller/internal/resolution"
3739
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/bundles_and_dependencies"
40+
"github.com/operator-framework/operator-controller/internal/resolution/variable_sources/entity"
3841
)
3942

4043
// OperatorReconciler reconciles a Operator object
@@ -105,54 +108,87 @@ func checkForUnexpectedFieldChange(a, b operatorsv1alpha1.Operator) bool {
105108

106109
// Helper function to do the actual reconcile
107110
func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha1.Operator) (ctrl.Result, error) {
108-
// define condition parameters
109-
var status = metav1.ConditionTrue
110-
var reason = operatorsv1alpha1.ReasonResolutionSucceeded
111-
var message = "resolution was successful"
112-
113111
// run resolution
114112
solution, err := r.Resolver.Resolve(ctx)
115113
if err != nil {
116-
status = metav1.ConditionFalse
117-
reason = operatorsv1alpha1.ReasonResolutionFailed
118-
message = err.Error()
119-
} else {
120-
// extract package bundle path from resolved variable
121-
for _, variable := range solution.SelectedVariables() {
122-
switch v := variable.(type) {
123-
case *bundles_and_dependencies.BundleVariable:
124-
packageName, err := v.BundleEntity().PackageName()
125-
if err != nil {
126-
return ctrl.Result{}, err
127-
}
128-
if packageName == op.Spec.PackageName {
129-
bundlePath, err := v.BundleEntity().BundlePath()
130-
if err != nil {
131-
return ctrl.Result{}, err
132-
}
133-
// Create bundleDeployment if not found or Update if needed
134-
dep := r.generateExpectedBundleDeployment(*op, bundlePath)
135-
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
136-
return ctrl.Result{}, err
137-
}
138-
break
139-
}
140-
}
141-
}
114+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
115+
Type: operatorsv1alpha1.TypeReady,
116+
Status: metav1.ConditionFalse,
117+
Reason: operatorsv1alpha1.ReasonResolutionFailed,
118+
Message: err.Error(),
119+
ObservedGeneration: op.GetGeneration(),
120+
})
121+
return ctrl.Result{}, err
122+
}
123+
124+
// lookup the bundle entity in the solution that corresponds to the
125+
// Operator's desired package name.
126+
bundleEntity, err := r.getBundleEntityFromSolution(solution, op.Spec.PackageName)
127+
if err != nil {
128+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
129+
Type: operatorsv1alpha1.TypeReady,
130+
Status: metav1.ConditionFalse,
131+
Reason: operatorsv1alpha1.ReasonBundleLookupFailed,
132+
Message: err.Error(),
133+
ObservedGeneration: op.GetGeneration(),
134+
})
135+
return ctrl.Result{}, err
136+
}
137+
138+
// Get the bundle image reference for the bundle
139+
bundleImage, err := bundleEntity.BundlePath()
140+
if err != nil {
141+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
142+
Type: operatorsv1alpha1.TypeReady,
143+
Status: metav1.ConditionFalse,
144+
Reason: operatorsv1alpha1.ReasonBundleLookupFailed,
145+
Message: err.Error(),
146+
ObservedGeneration: op.GetGeneration(),
147+
})
148+
return ctrl.Result{}, err
149+
}
150+
151+
// Ensure a BundleDeployment exists with its bundle source from the bundle
152+
// image we just looked up in the solution.
153+
dep := r.generateExpectedBundleDeployment(*op, bundleImage)
154+
if err := r.ensureBundleDeployment(ctx, dep); err != nil {
155+
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
156+
Type: operatorsv1alpha1.TypeReady,
157+
Status: metav1.ConditionFalse,
158+
Reason: operatorsv1alpha1.ReasonBundleDeploymentFailed,
159+
Message: err.Error(),
160+
ObservedGeneration: op.GetGeneration(),
161+
})
162+
return ctrl.Result{}, err
142163
}
143164

144165
// update operator status
145166
apimeta.SetStatusCondition(&op.Status.Conditions, metav1.Condition{
146167
Type: operatorsv1alpha1.TypeReady,
147-
Status: status,
148-
Reason: reason,
149-
Message: message,
168+
Status: metav1.ConditionTrue,
169+
Reason: operatorsv1alpha1.ReasonResolutionSucceeded,
170+
Message: "resolution was successful",
150171
ObservedGeneration: op.GetGeneration(),
151172
})
152-
153173
return ctrl.Result{}, nil
154174
}
155175

176+
func (r *OperatorReconciler) getBundleEntityFromSolution(solution *solver.Solution, packageName string) (*entity.BundleEntity, error) {
177+
for _, variable := range solution.SelectedVariables() {
178+
switch v := variable.(type) {
179+
case *bundles_and_dependencies.BundleVariable:
180+
entityPkgName, err := v.BundleEntity().PackageName()
181+
if err != nil {
182+
return nil, err
183+
}
184+
if packageName == entityPkgName {
185+
return v.BundleEntity(), nil
186+
}
187+
}
188+
}
189+
return nil, fmt.Errorf("entity for package %q not found in solution", packageName)
190+
}
191+
156192
func (r *OperatorReconciler) generateExpectedBundleDeployment(o operatorsv1alpha1.Operator, bundlePath string) *unstructured.Unstructured {
157193
// We use unstructured here to avoid problems of serializing default values when sending patches to the apiserver.
158194
// If you use a typed object, any default values from that struct get serialized into the JSON patch, which could

controllers/operator_controller_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ var _ = Describe("Reconcile Test", func() {
6767
By("running reconcile")
6868
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
6969
Expect(res).To(Equal(ctrl.Result{}))
70-
// TODO: should resolution failure return an error?
71-
Expect(err).NotTo(HaveOccurred())
70+
Expect(err).To(MatchError(fmt.Sprintf("package '%s' not found", pkgName)))
7271

7372
By("fetching updated operator after reconcile")
7473
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
@@ -243,7 +242,7 @@ var _ = Describe("Reconcile Test", func() {
243242
err := cl.Create(ctx, operator)
244243
Expect(err).NotTo(HaveOccurred())
245244
})
246-
PIt("sets resolution failure status and returns an error", func() {
245+
It("sets resolution failure status and returns an error", func() {
247246
By("running reconcile")
248247
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
249248
Expect(res).To(Equal(ctrl.Result{}))
@@ -253,7 +252,11 @@ var _ = Describe("Reconcile Test", func() {
253252
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())
254253

255254
By("checking the expected conditions")
256-
// TODO: there should be a condition update that sets Ready false in this scenario
255+
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorsv1alpha1.TypeReady)
256+
Expect(cond).NotTo(BeNil())
257+
Expect(cond.Status).To(Equal(metav1.ConditionFalse))
258+
Expect(cond.Reason).To(Equal(operatorsv1alpha1.ReasonBundleLookupFailed))
259+
Expect(cond.Message).To(ContainSubstring(`error determining bundle path for entity`))
257260
})
258261
})
259262
When("the operator specifies a duplicate package", func() {
@@ -277,8 +280,7 @@ var _ = Describe("Reconcile Test", func() {
277280
By("running reconcile")
278281
res, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: opKey})
279282
Expect(res).To(Equal(ctrl.Result{}))
280-
// TODO: should this scenario return an error?
281-
Expect(err).NotTo(HaveOccurred())
283+
Expect(err).To(MatchError(Equal(`duplicate identifier "required package prometheus" in input`)))
282284

283285
By("fetching updated operator after reconcile")
284286
Expect(cl.Get(ctx, opKey, operator)).NotTo(HaveOccurred())

0 commit comments

Comments
 (0)