Skip to content

Commit 431198d

Browse files
authored
Merge pull request openshift#39 from estroz/bugfix/bundle-validation
validation: bundle CRDs are compared by definition key instead of name (openshift#32)
2 parents 759ca0d + 53d53cd commit 431198d

File tree

5 files changed

+123
-26
lines changed

5 files changed

+123
-26
lines changed

pkg/validation/internal/bundle.go

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package internal
22

33
import (
44
"fmt"
5+
"strings"
56

67
"github.com/operator-framework/api/pkg/manifests"
78
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
89
"github.com/operator-framework/api/pkg/validation/errors"
910
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
11+
"k8s.io/apimachinery/pkg/runtime/schema"
1012
)
1113

1214
var BundleValidator interfaces.Validator = interfaces.ValidatorFunc(validateBundles)
@@ -28,39 +30,62 @@ func validateBundle(bundle *manifests.Bundle) (result errors.ManifestResult) {
2830
}
2931

3032
func validateOwnedCRDs(bundle *manifests.Bundle, csv *operatorsv1alpha1.ClusterServiceVersion) (result errors.ManifestResult) {
31-
ownedCrdNames := getOwnedCustomResourceDefintionNames(csv)
32-
crdNames, err := getBundleCRDNames(bundle)
33-
if err != (errors.Error{}) {
34-
result.Add(err)
35-
return result
33+
ownedKeys := getOwnedCustomResourceDefintionKeys(csv)
34+
35+
// Check for duplicate keys in the bundle, which may occur if a v1 and v1beta1 CRD of the same GVK appear.
36+
keySet := make(map[schema.GroupVersionKind]struct{})
37+
for _, key := range getBundleCRDKeys(bundle) {
38+
if _, hasKey := keySet[key]; hasKey {
39+
result.Add(errors.ErrInvalidBundle(fmt.Sprintf("duplicate CRD %q in bundle %q", key, bundle.Name), key))
40+
}
41+
// Always add key to keySet so the below validations run correctly.
42+
keySet[key] = struct{}{}
3643
}
3744

38-
// validating names
39-
for _, crdName := range ownedCrdNames {
40-
if _, ok := crdNames[crdName]; !ok {
41-
result.Add(errors.ErrInvalidBundle(fmt.Sprintf("owned CRD %q not found in bundle %q", crdName, bundle.Name), crdName))
45+
// All owned keys must match a CRD in bundle.
46+
for _, ownedKey := range ownedKeys {
47+
if _, ok := keySet[ownedKey]; !ok {
48+
result.Add(errors.ErrInvalidBundle(fmt.Sprintf("owned CRD %q not found in bundle %q", ownedKey, bundle.Name), ownedKey))
4249
} else {
43-
delete(crdNames, crdName)
50+
delete(keySet, ownedKey)
4451
}
4552
}
46-
// CRDs not defined in the CSV present in the bundle
47-
for crdName := range crdNames {
48-
result.Add(errors.WarnInvalidBundle(fmt.Sprintf("owned CRD %q is present in bundle %q but not defined in CSV", crdName, bundle.Name), crdName))
53+
// All CRDs present in a CSV must be present in the bundle.
54+
for key := range keySet {
55+
result.Add(errors.WarnInvalidBundle(fmt.Sprintf("CRD %q is present in bundle %q but not defined in CSV", key, bundle.Name), key))
4956
}
57+
5058
return result
5159
}
5260

53-
func getOwnedCustomResourceDefintionNames(csv *operatorsv1alpha1.ClusterServiceVersion) (names []string) {
54-
for _, ownedCrd := range csv.Spec.CustomResourceDefinitions.Owned {
55-
names = append(names, ownedCrd.Name)
61+
// getBundleCRDKeys returns a list of definition keys for all owned CRDs in csv.
62+
func getOwnedCustomResourceDefintionKeys(csv *operatorsv1alpha1.ClusterServiceVersion) (keys []schema.GroupVersionKind) {
63+
for _, owned := range csv.Spec.CustomResourceDefinitions.Owned {
64+
group := owned.Name
65+
if split := strings.SplitN(group, ".", 2); len(split) == 2 {
66+
group = split[1]
67+
}
68+
keys = append(keys, schema.GroupVersionKind{Group: group, Version: owned.Version, Kind: owned.Kind})
5669
}
57-
return names
70+
return keys
5871
}
5972

60-
func getBundleCRDNames(bundle *manifests.Bundle) (map[string]struct{}, errors.Error) {
61-
crdNames := map[string]struct{}{}
73+
// getBundleCRDKeys returns a set of definition keys for all CRDs in bundle.
74+
func getBundleCRDKeys(bundle *manifests.Bundle) (keys []schema.GroupVersionKind) {
75+
// Collect all v1 and v1beta1 CRD keys, skipping group which CSVs do not support.
76+
for _, crd := range bundle.V1CRDs {
77+
for _, version := range crd.Spec.Versions {
78+
keys = append(keys, schema.GroupVersionKind{Group: crd.Spec.Group, Version: version.Name, Kind: crd.Spec.Names.Kind})
79+
}
80+
}
6281
for _, crd := range bundle.V1beta1CRDs {
63-
crdNames[crd.GetName()] = struct{}{}
82+
if len(crd.Spec.Versions) == 0 {
83+
keys = append(keys, schema.GroupVersionKind{Group: crd.Spec.Group, Version: crd.Spec.Version, Kind: crd.Spec.Names.Kind})
84+
} else {
85+
for _, version := range crd.Spec.Versions {
86+
keys = append(keys, schema.GroupVersionKind{Group: crd.Spec.Group, Version: version.Name, Kind: crd.Spec.Names.Kind})
87+
}
88+
}
6489
}
65-
return crdNames, errors.Error{}
90+
return keys
6691
}

pkg/validation/internal/bundle_test.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,27 @@ func TestValidateBundle(t *testing.T) {
1616
errString string
1717
}{
1818
{
19-
description: "registryv1 bundle/valid bundle",
19+
description: "registryv1 valid bundle",
2020
directory: "./testdata/valid_bundle",
2121
hasError: false,
2222
},
2323
{
24-
description: "registryv1 bundle/valid bundle",
24+
description: "registryv1 invalid bundle without CRD etcdclusters v1beta2 in bundle",
2525
directory: "./testdata/invalid_bundle",
2626
hasError: true,
27-
errString: `owned CRD "etcdclusters.etcd.database.coreos.com" not found in bundle`,
27+
errString: `owned CRD "etcd.database.coreos.com/v1beta2, Kind=EtcdCluster" not found in bundle`,
2828
},
2929
{
30-
description: "registryv1 bundle/valid bundle",
30+
description: "registryv1 invalid bundle without CRD etcdclusters v1beta2 in CSV",
3131
directory: "./testdata/invalid_bundle_2",
3232
hasError: true,
33-
errString: `owned CRD "etcdclusters.etcd.database.coreos.com" is present in bundle "etcdoperator.v0.9.4" but not defined in CSV`,
33+
errString: `CRD "etcd.database.coreos.com/v1beta2, Kind=EtcdCluster" is present in bundle "etcdoperator.v0.9.4" but not defined in CSV`,
34+
},
35+
{
36+
description: "registryv1 invalid bundle with duplicate CRD etcdclusters in bundle",
37+
directory: "./testdata/invalid_bundle_3",
38+
hasError: true,
39+
errString: `duplicate CRD "test.example.com/v1alpha1, Kind=Test" in bundle "test-operator.v0.0.1"`,
3440
},
3541
}
3642

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
apiVersion: operators.coreos.com/v1alpha1
2+
kind: ClusterServiceVersion
3+
metadata:
4+
annotations:
5+
capabilities: Basic Install
6+
name: test-operator.v0.0.1
7+
namespace: placeholder
8+
spec:
9+
apiservicedefinitions: {}
10+
customresourcedefinitions:
11+
owned:
12+
- description: Test is the Schema for the tests API
13+
kind: Test
14+
name: tests.test.example.com
15+
version: v1alpha1
16+
installModes:
17+
- supported: true
18+
type: OwnNamespace
19+
- supported: true
20+
type: SingleNamespace
21+
- supported: false
22+
type: MultiNamespace
23+
- supported: true
24+
type: AllNamespaces
25+
keywords:
26+
- test-operator
27+
links:
28+
- name: Test Operator
29+
url: https://test-operator.domain
30+
maintainers:
31+
32+
name: Maintainer Name
33+
maturity: alpha
34+
provider:
35+
name: Provider Name
36+
url: https://your.domain
37+
version: 0.0.1
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: tests.test.example.com
5+
spec:
6+
group: test.example.com
7+
names:
8+
kind: Test
9+
listKind: TestList
10+
plural: tests
11+
singular: test
12+
scope: Namespaced
13+
versions:
14+
- name: v1alpha1
15+
served: true
16+
storage: true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: tests.test.example.com
5+
spec:
6+
group: test.example.com
7+
names:
8+
kind: Test
9+
listKind: TestList
10+
plural: tests
11+
singular: test
12+
scope: Namespaced
13+
version: v1alpha1

0 commit comments

Comments
 (0)