Skip to content

Commit 3002cf7

Browse files
authored
FIX: Bug 1982737 - Make malformed CSV fail nicely (#2673)
- Modified bundle unpacking to fail installPlan with a nice message if after unbundling csv manifests APIVersion or Kind are blank. - Adds FatalError type to return to checked when syncing install plans to see if the error causing issues is fatal 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]> Co-authored-by: Noah Sapse <[email protected]>
1 parent d87319a commit 3002cf7

File tree

5 files changed

+134
-1
lines changed

5 files changed

+134
-1
lines changed

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 FatalError struct {
51+
error
52+
}
53+
54+
func NewFatalError(err error) FatalError {
55+
return FatalError{err}
56+
}
57+
func IsFatal(err error) bool {
58+
switch err.(type) {
59+
case FatalError:
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

pkg/controller/operators/catalog/operator.go

+19-1
Original file line numberDiff line numberDiff line change
@@ -1376,7 +1376,13 @@ type UnpackedBundleReference struct {
13761376
Properties string `json:"properties"`
13771377
}
13781378

1379-
// unpackBundles makes one walk through the bundlelookups and attempts to progress them
1379+
/* unpackBundles makes one walk through the bundlelookups and attempts to progress them
1380+
Returns:
1381+
unpacked: bool - If the bundle was successfully unpacked
1382+
out: *v1alpha1.InstallPlan - the resulting installPlan
1383+
error: error
1384+
*/
1385+
13801386
func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.InstallPlan, error) {
13811387
out := plan.DeepCopy()
13821388
unpacked := true
@@ -1427,6 +1433,10 @@ func (o *Operator) unpackBundles(plan *v1alpha1.InstallPlan) (bool, *v1alpha1.In
14271433
// Ensure that bundle can be applied by the current version of OLM by converting to steps
14281434
steps, err := resolver.NewStepsFromBundle(res.Bundle(), out.GetNamespace(), res.Replaces, res.CatalogSourceRef.Name, res.CatalogSourceRef.Namespace)
14291435
if err != nil {
1436+
if fatal := olmerrors.IsFatal(err); fatal {
1437+
return false, nil, err
1438+
}
1439+
14301440
errs = append(errs, fmt.Errorf("failed to turn bundle into steps: %v", err))
14311441
unpacked = false
14321442
continue
@@ -1615,6 +1625,14 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {
16151625
if len(plan.Status.BundleLookups) > 0 {
16161626
unpacked, out, err := o.unpackBundles(plan)
16171627
if err != nil {
1628+
// If the error was fatal capture and fail
1629+
if fatal := olmerrors.IsFatal(err); fatal {
1630+
if err := o.transitionInstallPlanToFailed(plan, logger, v1alpha1.InstallPlanReasonInstallCheckFailed, err.Error()); err != nil {
1631+
// retry for failure to update status
1632+
syncError = err
1633+
return
1634+
}
1635+
}
16181636
// Retry sync if non-fatal error
16191637
syncError = fmt.Errorf("bundle unpacking failed: %v", err)
16201638
return

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.NewFatalError(fmt.Errorf("bundle CSV %s missing APIVersion", csv.Name))
125+
}
126+
127+
if csv.Kind == "" {
128+
return nil, olmerrors.NewFatalError(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)

test/e2e/catalog_e2e_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import (
77
"context"
88
"fmt"
99
"net"
10+
"path/filepath"
1011
"strconv"
1112
"strings"
1213
"time"
1314

1415
operatorsv1 "github.com/operator-framework/api/pkg/operators/v1"
16+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1517
k8serror "k8s.io/apimachinery/pkg/api/errors"
1618
"sigs.k8s.io/controller-runtime/pkg/client"
1719

@@ -40,6 +42,7 @@ import (
4042
const (
4143
openshiftregistryFQDN = "image-registry.openshift-image-registry.svc:5000"
4244
catsrcImage = "docker://quay.io/olmtest/catsrc-update-test:"
45+
badCSVDir = "bad-csv"
4346
)
4447

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

13451410
func getOperatorDeployment(c operatorclient.ClientInterface, namespace string, operatorLabels labels.Set) (*appsv1.Deployment, error) {
+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/olmtest/missing_api_version: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)