diff --git a/go.mod b/go.mod index 8a02b55ae5..8e8333023e 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/onsi/gomega v1.13.0 github.com/openshift/api v0.0.0-20200331152225-585af27e34fd github.com/openshift/client-go v0.0.0-20200326155132-2a6cd50aedd0 - github.com/operator-framework/api v0.10.1 + github.com/operator-framework/api v0.10.2 github.com/operator-framework/operator-registry v1.17.5 github.com/otiai10/copy v1.2.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 6a43a12331..b37c304d6e 100644 --- a/go.sum +++ b/go.sum @@ -874,8 +874,8 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/operator-framework/api v0.7.1/go.mod h1:L7IvLd/ckxJEJg/t4oTTlnHKAJIP/p51AvEslW3wYdY= -github.com/operator-framework/api v0.10.1 h1:2tBjIr4hRZ0iaJ4UuenVjocbo9xrJi1O409K/tlEddo= -github.com/operator-framework/api v0.10.1/go.mod h1:tV0BUNvly7szq28ZPBXhjp1Sqg5yHCOeX19ui9K4vjI= +github.com/operator-framework/api v0.10.2 h1:fo8Bhyx1v46NdJIz2rUzfzNUpe1KDNPtVpHVpuxZnk0= +github.com/operator-framework/api v0.10.2/go.mod h1:tV0BUNvly7szq28ZPBXhjp1Sqg5yHCOeX19ui9K4vjI= github.com/operator-framework/operator-registry v1.17.5 h1:LR8m1rFz5Gcyje8WK6iYt+gIhtzqo52zMRALdmTYHT0= github.com/operator-framework/operator-registry v1.17.5/go.mod h1:sRQIgDMZZdUcmHltzyCnM6RUoDF+WS8Arj1BQIARDS8= github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 6aec1d1ffe..051a276de9 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -909,9 +909,22 @@ func (o *Operator) syncResolvingNamespace(obj interface{}) error { // not-satisfiable error if _, ok := err.(solver.NotSatisfiable); ok { logger.WithError(err).Debug("resolution failed") + updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ConstraintsNotSatisfiable", err.Error(), true) + if updateErr != nil { + logger.WithError(updateErr).Debug("failed to update subs conditions") + } return nil } + updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "ErrorPreventedResolution", err.Error(), true) + if updateErr != nil { + logger.WithError(updateErr).Debug("failed to update subs conditions") + } return err + } else { + updateErr := o.setSubsCond(subs, v1alpha1.SubscriptionResolutionFailed, "", "", false) + if updateErr != nil { + logger.WithError(updateErr).Debug("failed to update subs conditions") + } } // create installplan if anything updated @@ -1189,6 +1202,55 @@ func (o *Operator) createInstallPlan(namespace string, gen int, subs []*v1alpha1 return reference.GetReference(res) } +func (o *Operator) setSubsCond(subs []*v1alpha1.Subscription, condType v1alpha1.SubscriptionConditionType, reason, message string, setTrue bool) error { + var ( + errs []error + mu sync.Mutex + wg sync.WaitGroup + getOpts = metav1.GetOptions{} + updateOpts = metav1.UpdateOptions{} + lastUpdated = o.now() + ) + for _, sub := range subs { + sub.Status.LastUpdated = lastUpdated + cond := sub.Status.GetCondition(condType) + cond.Reason = reason + cond.Message = message + if setTrue { + cond.Status = corev1.ConditionTrue + } else { + cond.Status = corev1.ConditionFalse + } + sub.Status.SetCondition(cond) + + wg.Add(1) + go func(s v1alpha1.Subscription) { + defer wg.Done() + + update := func() error { + // Update the status of the latest revision + latest, err := o.client.OperatorsV1alpha1().Subscriptions(s.GetNamespace()).Get(context.TODO(), s.GetName(), getOpts) + if err != nil { + return err + } + + latest.Status = s.Status + _, err = o.client.OperatorsV1alpha1().Subscriptions(sub.Namespace).UpdateStatus(context.TODO(), latest, updateOpts) + + return err + } + if err := retry.RetryOnConflict(retry.DefaultRetry, update); err != nil { + mu.Lock() + defer mu.Unlock() + errs = append(errs, err) + } + }(*sub) + } + wg.Wait() + + return utilerrors.NewAggregate(errs) +} + type UnpackedBundleReference struct { Kind string `json:"kind"` Name string `json:"name"` diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go index 13ea785f9d..c1a0750ae8 100644 --- a/test/e2e/subscription_e2e_test.go +++ b/test/e2e/subscription_e2e_test.go @@ -2105,6 +2105,60 @@ var _ = Describe("Subscription", func() { require.NoError(GinkgoT(), err) }) + When("A subscription is created for an operator that requires an API that is not available", func() { + + var ( + c operatorclient.ClientInterface + crc versioned.Interface + teardown func() + cleanup func() + packages []registry.PackageManifest + subName = genName("test-subscription") + catSrcName = genName("test-catalog") + ) + + BeforeEach(func() { + c = newKubeClient() + crc = newCRClient() + + packages = []registry.PackageManifest{ + { + PackageName: "packageA", + Channels: []registry.PackageChannel{ + {Name: "alpha", CurrentCSVName: "csvA"}, + }, + DefaultChannelName: "alpha", + }, + } + crd := newCRD(genName("foo")) + csv := newCSV("csvA", testNamespace, "", semver.MustParse("1.0.0"), nil, []apiextensions.CustomResourceDefinition{crd}, nil) + + _, teardown = createInternalCatalogSource(c, ctx.Ctx().OperatorClient(), catSrcName, testNamespace, packages, nil, []operatorsv1alpha1.ClusterServiceVersion{csv}) + + // Ensure that the catalog source is resolved before we create a subscription. + _, err := fetchCatalogSourceOnStatus(crc, catSrcName, testNamespace, catalogSourceRegistryPodSynced) + require.NoError(GinkgoT(), err) + + cleanup = createSubscriptionForCatalog(crc, testNamespace, subName, catSrcName, "packageA", "alpha", "", operatorsv1alpha1.ApprovalAutomatic) + }) + + AfterEach(func() { + cleanup() + teardown() + }) + + It("the subscription has a condition in it's status that indicates the resolution error", func() { + Eventually(func() (corev1.ConditionStatus, error) { + sub, err := crc.OperatorsV1alpha1().Subscriptions(testNamespace).Get(context.Background(), subName, metav1.GetOptions{}) + if err != nil { + return corev1.ConditionUnknown, err + } + return sub.Status.GetCondition(operatorsv1alpha1.SubscriptionResolutionFailed).Status, nil + }).Should(Equal(corev1.ConditionTrue)) + }) + + }) + When("an unannotated ClusterServiceVersion exists with an associated Subscription", func() { var ( teardown func() diff --git a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go index 7eaae1e68c..e048d4988c 100644 --- a/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go +++ b/vendor/github.com/operator-framework/api/pkg/operators/v1alpha1/subscription_types.go @@ -102,6 +102,9 @@ const ( // SubscriptionInstallPlanFailed indicates that the installation of a Subscription's InstallPlan has failed. SubscriptionInstallPlanFailed SubscriptionConditionType = "InstallPlanFailed" + + // SubscriptionResolutionFailed indicates that the dependency resolution in the namespace in which the subscription is created has failed + SubscriptionResolutionFailed SubscriptionConditionType = "ResolutionFailed" ) const ( diff --git a/vendor/modules.txt b/vendor/modules.txt index aa0d1d11c9..651914f38a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -489,7 +489,7 @@ github.com/openshift/client-go/config/informers/externalversions/config github.com/openshift/client-go/config/informers/externalversions/config/v1 github.com/openshift/client-go/config/informers/externalversions/internalinterfaces github.com/openshift/client-go/config/listers/config/v1 -# github.com/operator-framework/api v0.10.1 +# github.com/operator-framework/api v0.10.2 ## explicit github.com/operator-framework/api/crds github.com/operator-framework/api/pkg/lib/version