Skip to content

Commit d791f41

Browse files
committed
FIX: Bug 1982737 - Make malformed CSV fail nicely
- Modified bundle unpacking to fail installPlan with a nice message if after unbundling csv manifests APIVersion or Kind are blank. - Adds retryablError type to return to checked when syncing install plans to see if the error causing issues is retryingable or should cause the IP to be transitioned to failed state. - Makes installPlan fail with a nicer more user friendly message than the current difficult to understand message regarding serviceAccounts. - Adds tests using internal magic-catalog library to confirm that unpacking fails when APIVersion is blank. Signed-off-by: Noah Sapse <[email protected]>
1 parent d505efc commit d791f41

File tree

5 files changed

+136
-1
lines changed

5 files changed

+136
-1
lines changed

Diff for: pkg/controller/errors/errors.go

+15
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ func IsMultipleExistingCRDOwnersError(err error) bool {
4747
return false
4848
}
4949

50+
type RetryableError struct {
51+
error
52+
}
53+
54+
func NewRetryableError(err error) RetryableError {
55+
return RetryableError{err}
56+
}
57+
func IsRetryable(err error) bool {
58+
switch err.(type) {
59+
case RetryableError:
60+
return true
61+
}
62+
return false
63+
}
64+
5065
// GroupVersionKindNotFoundError occurs when we can't find an API via discovery
5166
type GroupVersionKindNotFoundError struct {
5267
Group string

Diff for: pkg/controller/operators/catalog/operator.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,13 @@ type UnpackedBundleReference struct {
13881388
Properties string `json:"properties"`
13891389
}
13901390

1391-
// unpackBundles makes one walk through the bundlelookups and attempts to progress them
1391+
/* unpackBundles makes one walk through the bundlelookups and attempts to progress them
1392+
Returns:
1393+
unpacked: bool - If the bundle was successfully unpacked
1394+
out: *v1alpha1.InstallPlan - the resulting installPlan
1395+
error: error
1396+
*/
1397+
13921398
func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.InstallPlan, error) {
13931399
out := plan.DeepCopy()
13941400
unpacked := true
@@ -1439,6 +1445,10 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In
14391445
// Ensure that bundle can be applied by the current version of OLM by converting to steps
14401446
steps, err := resolver.NewStepsFromBundle(res.Bundle(), out.GetNamespace(), res.Replaces, res.CatalogSourceRef.Name, res.CatalogSourceRef.Namespace)
14411447
if err != nil {
1448+
if retryable := olmerrors.IsRetryable(err); !retryable {
1449+
return false, nil, err
1450+
}
1451+
14421452
errs = append(errs, fmt.Errorf("failed to turn bundle into steps: %v", err))
14431453
unpacked = false
14441454
continue
@@ -1627,6 +1637,14 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {
16271637
if len(plan.Status.BundleLookups) > 0 {
16281638
unpacked, out, err := o.unpackBundles(plan)
16291639
if err != nil {
1640+
// If the error was fatal capture and fail
1641+
if retryable := olmerrors.IsRetryable(err); !retryable {
1642+
if err := o.transitionInstallPlanToFailed(plan, logger, v1alpha1.InstallPlanReasonInstallCheckFailed, err.Error()); err != nil {
1643+
// retry for failure to update status
1644+
syncError = err
1645+
return
1646+
}
1647+
}
16301648
// Retry sync if non-fatal error
16311649
syncError = fmt.Errorf("bundle unpacking failed: %v", err)
16321650
return

Diff for: pkg/controller/registry/resolver/steps.go

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"strings"
88

9+
olmerrors "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/errors"
910
"github.com/operator-framework/operator-registry/pkg/api"
1011
extScheme "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme"
1112
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -118,6 +119,15 @@ func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogS
118119
return nil, err
119120
}
120121

122+
// Check unpacked bundled for for missing APIVersion or Kind
123+
if csv.APIVersion == "" {
124+
return nil, olmerrors.NewRetryableError(fmt.Errorf("bundle CSV %s missing APIVersion", csv.Name))
125+
}
126+
127+
if csv.Kind == "" {
128+
return nil, olmerrors.NewRetryableError(fmt.Errorf("bundle CSV %s missing Kind", csv.Name))
129+
}
130+
121131
csv.SetNamespace(namespace)
122132
csv.Spec.Replaces = replaces
123133
anno, err := projection.PropertiesAnnotationFromPropertyList(bundle.Properties)

Diff for: test/e2e/catalog_e2e_test.go

+67
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ package e2e
66
import (
77
"context"
88
"fmt"
9+
"log"
910
"net"
11+
"path/filepath"
1012
"strconv"
1113
"strings"
1214
"time"
1315

1416
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
17+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1518
k8serror "k8s.io/apimachinery/pkg/api/errors"
1619
"sigs.k8s.io/controller-runtime/pkg/client"
1720

@@ -40,6 +43,7 @@ import (
4043
const (
4144
openshiftregistryFQDN = "image-registry.openshift-image-registry.svc:5000"
4245
catsrcImage = "docker://quay.io/olmtest/catsrc-update-test:"
46+
badCSVDir = "bad-csv"
4347
)
4448

4549
var _ = Describe("Starting CatalogSource e2e tests", func() {
@@ -1340,6 +1344,69 @@ var _ = Describe("Starting CatalogSource e2e tests", func() {
13401344
}
13411345
}
13421346
})
1347+
1348+
When("A CatalogSource is created with an operator that has a CSV with missing metadata.ApiVersion", func() {
1349+
1350+
var (
1351+
magicCatalog MagicCatalog
1352+
catalogSourceName string
1353+
subscription *operatorsv1alpha1.Subscription
1354+
c client.Client
1355+
)
1356+
1357+
BeforeEach(func() {
1358+
c = ctx.Ctx().Client()
1359+
1360+
provider, err := NewFileBasedFiledBasedCatalogProvider(filepath.Join(testdataDir, badCSVDir, "bad-csv.yaml"))
1361+
Expect(err).To(BeNil())
1362+
1363+
catalogSourceName = genName("cat-bad-csv")
1364+
magicCatalog = NewMagicCatalog(c, ns.GetName(), catalogSourceName, provider)
1365+
Expect(magicCatalog.DeployCatalog(context.Background())).To(BeNil())
1366+
1367+
})
1368+
1369+
AfterEach(func() {
1370+
TeardownNamespace(ns.GetName())
1371+
})
1372+
1373+
When("A Subscription is created catalogSource built with the malformed CSV", func() {
1374+
BeforeEach(func () {
1375+
subscription = &operatorsv1alpha1.Subscription{
1376+
ObjectMeta: metav1.ObjectMeta{
1377+
Name: fmt.Sprintf("%s-sub", catalogSourceName),
1378+
Namespace: ns.GetName(),
1379+
},
1380+
Spec: &operatorsv1alpha1.SubscriptionSpec{
1381+
CatalogSource: catalogSourceName,
1382+
CatalogSourceNamespace: ns.GetName(),
1383+
Channel: "stable",
1384+
Package: "packageA",
1385+
},
1386+
}
1387+
Expect(c.Create(context.Background(), subscription)).To(BeNil())
1388+
})
1389+
1390+
It("fails with a ResolutionFailed error condition, and a message that highlights the missing field in the CSV", func() {
1391+
1392+
subscription, err := fetchSubscription(crc, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker)
1393+
Expect(err).Should(BeNil())
1394+
installPlanName := subscription.Status.Install.Name
1395+
1396+
// ensure we wait for the installPlan to fail before moving forward then fetch the subscription again
1397+
installPlan, err := fetchInstallPlan(GinkgoT(), crc, installPlanName, subscription.GetNamespace(), buildInstallPlanPhaseCheckFunc(operatorsv1alpha1.InstallPlanPhaseFailed))
1398+
Expect(err).To(BeNil())
1399+
log.Print(fmt.Printf("installPlan %s failed: re-fetching subscription", installPlan.Name))
1400+
subscription, err = fetchSubscription(crc, subscription.GetNamespace(), subscription.GetName(), subscriptionHasInstallPlanChecker)
1401+
Expect(err).To(BeNil())
1402+
1403+
// expect the message that API missing
1404+
failingCondition := subscription.Status.GetCondition(operatorsv1alpha1.SubscriptionInstallPlanFailed)
1405+
Expect(failingCondition.Message).To(ContainSubstring("missing APIVersion"))
1406+
})
1407+
})
1408+
})
1409+
13431410
})
13441411

13451412
func getOperatorDeployment(c operatorclient.ClientInterface, namespace string, operatorLabels labels.Set) (*appsv1.Deployment, error) {

Diff for: test/e2e/testdata/bad-csv/bad-csv.yaml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
schema: olm.package
3+
name: packageA
4+
defaultChannel: stable
5+
---
6+
schema: olm.channel
7+
package: packageA
8+
name: stable
9+
entries:
10+
- name: bad-csv
11+
---
12+
schema: olm.bundle
13+
name: bad-csv
14+
package: packageA
15+
image: quay.io/nsapse/bad-etcd:latest
16+
properties:
17+
- type: olm.gvk
18+
value:
19+
group: example.com
20+
kind: TestA
21+
version: v1alpha1
22+
- type: olm.package
23+
value:
24+
packageName: packageA
25+
version: 1.0.0

0 commit comments

Comments
 (0)