Skip to content

Commit b99cc20

Browse files
authored
fix: check for unsat resolutions in Reconcile (#352)
Signed-off-by: Joe Lanford <[email protected]>
1 parent 8047dfc commit b99cc20

File tree

2 files changed

+45
-8
lines changed

2 files changed

+45
-8
lines changed

internal/controllers/operator_controller.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ package controllers
1818

1919
import (
2020
"context"
21+
"errors"
2122
"fmt"
23+
"sort"
24+
"strings"
2225

2326
"github.com/go-logr/logr"
2427
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
28+
"github.com/operator-framework/deppy/pkg/deppy"
2529
"github.com/operator-framework/deppy/pkg/deppy/solver"
2630
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
2731
"k8s.io/apimachinery/pkg/api/equality"
@@ -136,6 +140,21 @@ func (r *OperatorReconciler) reconcile(ctx context.Context, op *operatorsv1alpha
136140
return ctrl.Result{}, err
137141
}
138142

143+
// TODO: Checking for unsat is awkward using the current version of deppy.
144+
// This awkwardness has been fixed in an unreleased version of deppy.
145+
// When there is a new minor release of deppy, we can revisit this and
146+
// simplify this to a normal error check.
147+
// See https://github.com/operator-framework/deppy/issues/139.
148+
unsat := deppy.NotSatisfiable{}
149+
if ok := errors.As(solution.Error(), &unsat); ok && len(unsat) > 0 {
150+
op.Status.InstalledBundleResource = ""
151+
setInstalledStatusConditionUnknown(&op.Status.Conditions, "installation has not been attempted as resolution is unsatisfiable", op.GetGeneration())
152+
op.Status.ResolvedBundleResource = ""
153+
msg := prettyUnsatMessage(unsat)
154+
setResolvedStatusConditionFailed(&op.Status.Conditions, msg, op.GetGeneration())
155+
return ctrl.Result{}, unsat
156+
}
157+
139158
// lookup the bundle entity in the solution that corresponds to the
140159
// Operator's desired package name.
141160
bundleEntity, err := r.getBundleEntityFromSolution(solution, op.Spec.PackageName)
@@ -459,3 +478,23 @@ func operatorRequestsForCatalog(ctx context.Context, c client.Reader, logger log
459478
return requests
460479
}
461480
}
481+
482+
// TODO: This can be removed when operator controller bumps to a
483+
// version of deppy that contains a fix for this issue:
484+
// https://github.com/operator-framework/deppy/issues/142
485+
486+
// prettyUnsatMessage ensures that the unsat message is deterministic and
487+
// human-readable. It sorts the individual constraint strings lexicographically
488+
// and joins them with a semicolon (rather than a comma, which the unsat.Error()
489+
// function does). This function also has the side effect of sorting the items
490+
// in the unsat slice.
491+
func prettyUnsatMessage(unsat deppy.NotSatisfiable) string {
492+
sort.Slice(unsat, func(i, j int) bool {
493+
return unsat[i].String() < unsat[j].String()
494+
})
495+
msgs := make([]string, 0, len(unsat))
496+
for _, c := range unsat {
497+
msgs = append(msgs, c.String())
498+
}
499+
return fmt.Sprintf("constraints not satisfiable: %s", strings.Join(msgs, "; "))
500+
}

test/e2e/install_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ import (
66

77
. "github.com/onsi/ginkgo/v2"
88
. "github.com/onsi/gomega"
9-
9+
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
10+
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
1011
"k8s.io/apimachinery/pkg/api/errors"
1112
apimeta "k8s.io/apimachinery/pkg/api/meta"
1213
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1314
"k8s.io/apimachinery/pkg/types"
1415
"k8s.io/apimachinery/pkg/util/rand"
1516

16-
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
17-
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
18-
1917
operatorv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
2018
)
2119

@@ -185,16 +183,16 @@ var _ = Describe("Operator Install", func() {
185183
g.Expect(operator.Status.ResolvedBundleResource).ToNot(BeEmpty())
186184
}).Should(Succeed())
187185

188-
By("updating the Operator resource to an invalid version")
189-
operator.Spec.Version = "0.65.1" // the correct one should be 0.47.0
186+
By("updating the Operator resource to a non-successor version")
187+
operator.Spec.Version = "0.65.1" // current (0.37.0) and successor (0.47.0) are the only values that would be SAT.
190188
Expect(c.Update(ctx, operator)).To(Succeed())
191-
By("eventually reporting a failed resolution")
189+
By("eventually reporting an unsatisfiable resolution")
192190
Eventually(func(g Gomega) {
193191
g.Expect(c.Get(ctx, types.NamespacedName{Name: operator.Name}, operator)).To(Succeed())
194192
cond := apimeta.FindStatusCondition(operator.Status.Conditions, operatorv1alpha1.TypeResolved)
195193
g.Expect(cond).ToNot(BeNil())
196194
g.Expect(cond.Reason).To(Equal(operatorv1alpha1.ReasonResolutionFailed))
197-
g.Expect(cond.Message).To(ContainSubstring("entity for package \"prometheus\" not found in solutio"))
195+
g.Expect(cond.Message).To(MatchRegexp(`^constraints not satisfiable:.*; installed package prometheus requires at least one of.*0.47.0[^,]*,[^,]*0.37.0[^;]*;.*`))
198196
g.Expect(operator.Status.ResolvedBundleResource).To(BeEmpty())
199197
}).Should(Succeed())
200198

0 commit comments

Comments
 (0)