Skip to content

Commit 1f920b9

Browse files
committedJan 16, 2020
Refactor bundle validation code to seperate format and contend validation
1. Seperate bundle format and content validation into 2 seperate functions 2. Remove operator-sdk reference from registry docs. Using `opm` reference instead. 3. Add bundle object validation to improve content validation Signed-off-by: Vu Dinh <[email protected]>
1 parent 7673f8e commit 1f920b9

32 files changed

+1224
-97
lines changed
 

‎cmd/opm/alpha/bundle/validate.go

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bundle
33
import (
44
"io/ioutil"
55
"os"
6+
"path/filepath"
67

78
"github.com/operator-framework/operator-registry/pkg/lib/bundle"
89
log "github.com/sirupsen/logrus"
@@ -54,9 +55,14 @@ func validateFunc(cmd *cobra.Command, args []string) error {
5455
return err
5556
}
5657

57-
logger.Info("Unpacked image layers, validating bundle image contents")
58+
logger.Info("Unpacked image layers, validating bundle image format & contents")
5859

59-
err = imageValidator.ValidateBundle(dir)
60+
err = imageValidator.ValidateBundleFormat(dir)
61+
if err != nil {
62+
return err
63+
}
64+
65+
err = imageValidator.ValidateBundleFormat(filepath.Join(dir, bundle.ManifestsDir))
6066
if err != nil {
6167
return err
6268
}

‎docs/design/operator-bundle.md

+19-25
Original file line numberDiff line numberDiff line change
@@ -91,40 +91,34 @@ $ tree
9191

9292
## Operator Bundle Commands
9393

94-
Operator SDK CLI is available to generate Bundle annotations and Dockerfile based on provided operator manifests.
94+
`opm` (Operator Package Manager) is a CLI tool to generate bundle annotations, build bundle manifests image, validate bundle manifests image and other functionalities. Please note that the `generate`, `build` and `validate` features of `opm` CLI are currently in alpha and only meant for development use.
9595

96-
### Operator SDK CLI
96+
### `opm` (Operator Package Manager)
9797

98-
In order to use Operator SDK CLI, follow the operator-SDK installation instruction:
98+
In order to use `opm` CLI, follow the `opm` build instruction:
9999

100-
1. Install the [Operator SDK CLI](https://github.com/operator-framework/operator-sdk/blob/master/doc/user/install-operator-sdk.md)
100+
1. Clone the operator registry repository:
101101

102-
Now, a binary named `operator-sdk` is available in OLM's directory to use.
103102
```bash
104-
$ ./operator-sdk
105-
An SDK for building operators with ease
106-
107-
Usage:
108-
operator-sdk [command]
103+
$ git clone https://github.com/operator-framework/operator-registry
104+
```
109105

110-
Available Commands:
111-
bundle Operator bundle commands
106+
2. Build `opm` binary using this command:
112107

113-
Flags:
114-
-h, --help help for operator-sdk
115-
--verbose Enable verbose logging
116-
117-
Use "operator-sdk [command] --help" for more information about a command.
108+
```bash
109+
$ go build ./cmd/opm/
118110
```
119111

112+
Now, a binary named `opm` is now built in current directory and ready to be used.
113+
120114
### Generate Bundle Annotations and DockerFile
121115
*Notes:*
122116
* If there are `annotations.yaml` and `Dockerfile` existing in the directory, they will be overwritten.
123117

124-
Using `operator-sdk` CLI, bundle annotations can be generated from provided operator manifests. The overall `bundle generate` command usage is:
118+
Using `opm` CLI, bundle annotations can be generated from provided operator manifests. The overall `bundle generate` command usage is:
125119
```bash
126120
Usage:
127-
operator-sdk bundle generate [flags]
121+
opm alpha bundle generate [flags]
128122

129123
Flags:
130124
-c, --channels string The list of channels that bundle image belongs to
@@ -141,7 +135,7 @@ The `--directory/-d`, `--channels/-c`, `--package/-p` are required flags while `
141135

142136
The command for `generate` task is:
143137
```bash
144-
$ ./operator-sdk bundle generate --directory /test --package test-operator \
138+
$ ./opm alpha bundle generate --directory /test --package test-operator \
145139
--channels stable,beta --default stable
146140
```
147141

@@ -173,7 +167,7 @@ $ docker build -f /path/to/Dockerfile -t quay.io/test/test-operator:latest /path
173167
Operator bundle image can be built from provided operator manifests using `build` command (see *Notes* below). The overall `bundle build` command usage is:
174168
```bash
175169
Usage:
176-
operator-SDK bundle build [flags]
170+
opm alpha bundle build [flags]
177171

178172
Flags:
179173
-c, --channels string The list of channels that bundle image belongs to
@@ -192,15 +186,15 @@ Flags:
192186
193187
The command for `build` task is:
194188
```bash
195-
$ ./operator-sdk bundle build --directory /test --tag quay.io/coreos/test-operator.v0.1.0:latest \
189+
$ ./opm alpha bundle build --directory /test --tag quay.io/coreos/test-operator.v0.1.0:latest \
196190
--package test-operator --channels stable,beta --default stable
197191
```
198192
199193
The `--directory` or `-d` specifies the directory where the operator manifests for a specific version are located. The `--tag` or `-t` specifies the image tag that you want the operator bundle image to have. By using `build` command, the `annotations.yaml` and `Dockerfile` are automatically generated in the background.
200194
201195
The default image builder is `Docker`. However, ` Buildah` and `Podman` are also supported. An image builder can specified via `--image-builder` or `-b` optional tag in `build` command. For example:
202196
```bash
203-
$ ./operator-sdk bundle build --directory /test/0.1.0/ --tag quay.io/coreos/test-operator.v0.1.0:latest \
197+
$ ./opm alpha bundle build --directory /test/0.1.0/ --tag quay.io/coreos/test-operator.v0.1.0:latest \
204198
--image-builder podman --package test-operator --channels stable,beta --default stable
205199
```
206200
@@ -215,7 +209,7 @@ The `--package` or `-p` is the name of package fo the operator such as `etcd` wh
215209
Operator bundle image can validate bundle image that is publicly available in an image registry using `validate` command (see *Notes* below). The overall `bundle validate` command usage is:
216210
```bash
217211
Usage:
218-
operator-SDK bundle validate [flags]
212+
opm alpha bundle validate [flags]
219213
220214
Flags:
221215
-t, --tag string The name of the bundle image will be built
@@ -225,7 +219,7 @@ Flags:
225219
226220
The command for `validate` task is:
227221
```bash
228-
$ ./operator-sdk bundle build --tag quay.io/coreos/test-operator.v0.1.0:latest --image-builder docker
222+
$ ./opm alpha bundle build --tag quay.io/coreos/test-operator.v0.1.0:latest --image-builder docker
229223
```
230224
231225
The `validate` command will first extract the contents of the bundle image into a temporary directory after it pulls the image from its image registry. Then, it will validate the format of bundle image to ensure manifests and metadata are located in their appropriate directories (`/manifests/` for bundle manifests files such as CSV and `/metadata/` for metadata files such as `annotations.yaml`). Also, it will validate the information in `annotations.yaml` to confirm that metadata is matching the provided data. For example, the provided media type in annotations.yaml just matches the actual media type is provided in the bundle image.

‎pkg/lib/bundle/errors.go

+5-10
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,22 @@ import (
66
)
77

88
// ValidationError is an imlementation of the Error type
9-
// that defines a set of errors when validating the bundle
9+
// that defines a list of errors when validating the bundle
1010
type ValidationError struct {
11-
AnnotationErrors []error
12-
FormatErrors []error
11+
Errors []error
1312
}
1413

1514
func (v ValidationError) Error() string {
1615
var errs []string
17-
for _, err := range v.AnnotationErrors {
18-
errs = append(errs, err.Error())
19-
}
20-
for _, err := range v.FormatErrors {
16+
for _, err := range v.Errors {
2117
errs = append(errs, err.Error())
2218
}
2319
return fmt.Sprintf("Bundle validation errors: %s",
2420
strings.Join(errs, ","))
2521
}
2622

27-
func NewValidationError(annotationErrs, formatErrs []error) ValidationError {
23+
func NewValidationError(errs []error) ValidationError {
2824
return ValidationError{
29-
AnnotationErrors: annotationErrs,
30-
FormatErrors: formatErrs,
25+
Errors: errs,
3126
}
3227
}

‎pkg/lib/bundle/interfaces.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ type BundleImageValidator interface {
1414
PullBundleImage(imageTag string, directory string) error
1515
// Validate bundle takes a directory containing the contents of a bundle image
1616
// and validates that the format is correct
17-
ValidateBundle(directory string) error
17+
ValidateBundleFormat(directory string) error
18+
// Validate bundle takes a directory containing the contents of a bundle image
19+
// and validates that the content is correct
20+
ValidateBundleContent(directory string) error
1821
}
1922

2023
// NewImageValidator is a constructor that returns an ImageValidator
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: etcdbackups.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdBackup
9+
listKind: EtcdBackupList
10+
plural: etcdbackups
11+
singular: etcdbackup
12+
scope: Namespaced
13+
version: v1beta2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: etcdclusters.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdCluster
9+
listKind: EtcdClusterList
10+
plural: etcdclusters
11+
shortNames:
12+
- etcdclus
13+
- etcd
14+
singular: etcdcluster
15+
scope: Namespaced
16+
version: v1beta2

‎pkg/lib/bundle/testdata/validate/invalid_manifests_bundle/invalid_crd/etcdoperator.v0.9.4.clusterserviceversion.yaml

+309
Large diffs are not rendered by default.

‎pkg/lib/bundle/testdata/validate/invalid_manifests_bundle/invalid_crd/etcdrestores.etcd.database.coreos.com.crd.yaml

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,10 @@ spec:
1010
plural: etcdrestores
1111
singular: etcdrestore
1212
scope: Namespaced
13-
versions: v1beta2
13+
versions:
14+
- name: v1beta2
15+
served: true
16+
storage: true
17+
- name: v1beta2
18+
served: true
19+
storage: true
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: etcdbackups.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdBackup
9+
listKind: EtcdBackupList
10+
plural: etcdbackups
11+
singular: etcdbackup
12+
scope: Namespaced
13+
version: v1beta2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: etcdclusters.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdCluster
9+
listKind: EtcdClusterList
10+
plural: etcdclusters
11+
shortNames:
12+
- etcdclus
13+
- etcd
14+
singular: etcdcluster
15+
scope: Namespaced
16+
version: v1beta2
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: etcdrestores.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdRestore
9+
listKind: EtcdRestoreList
10+
plural: etcdrestores
11+
singular: etcdrestore
12+
scope: Namespaced
13+
version: v1beta2
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: etcdbackups.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdBackup
9+
listKind: EtcdBackupList
10+
plural: etcdbackups
11+
singular: etcdbackup
12+
scope: Namespaced
13+
version: v1beta2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: etcdclusters.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdCluster
9+
listKind: EtcdClusterList
10+
plural: etcdclusters
11+
shortNames:
12+
- etcdclus
13+
- etcd
14+
singular: etcdcluster
15+
scope: Namespaced
16+
version: v1beta2

‎pkg/lib/bundle/testdata/validate/invalid_manifests_bundle/invalid_sa/etcdoperator.v0.9.4.clusterserviceversion.yaml

+309
Large diffs are not rendered by default.
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: etcdrestores.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdRestore
9+
listKind: EtcdRestoreList
10+
plural: etcdrestores
11+
singular: etcdrestore
12+
scope: Namespaced
13+
version: v1beta2
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: etcdbackups.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdBackup
9+
listKind: EtcdBackupList
10+
plural: etcdbackups
11+
singular: etcdbackup
12+
scope: Namespaced
13+
version: v1beta2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
apiVersion: apiextensions.k8s.io/v1beta1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: etcdclusters.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdCluster
9+
listKind: EtcdClusterList
10+
plural: etcdclusters
11+
shortNames:
12+
- etcdclus
13+
- etcd
14+
singular: etcdcluster
15+
scope: Namespaced
16+
version: v1beta2

‎pkg/lib/bundle/testdata/validate/invalid_manifests_bundle/invalid_type/etcdoperator.v0.9.4.clusterserviceversion.yaml

+309
Large diffs are not rendered by default.
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: etcdrestores.etcd.database.coreos.com
5+
spec:
6+
group: etcd.database.coreos.com
7+
names:
8+
kind: EtcdRestore
9+
listKind: EtcdRestoreList
10+
plural: etcdrestores
11+
singular: etcdrestore
12+
scope: Namespaced
13+
version: v1beta2

‎pkg/lib/bundle/validate.go

+80-48
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010
v "github.com/operator-framework/api/pkg/validation"
1111
v1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
1212
"github.com/operator-framework/operator-registry/pkg/containertools"
13+
"github.com/operator-framework/operator-registry/pkg/registry"
1314

14-
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
15+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1516
apiValidation "k8s.io/apimachinery/pkg/api/validation"
1617
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1718
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -20,7 +21,6 @@ import (
2021
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
2122

2223
y "github.com/ghodss/yaml"
23-
"github.com/sirupsen/logrus"
2424
log "github.com/sirupsen/logrus"
2525
"gopkg.in/yaml.v2"
2626
)
@@ -58,12 +58,20 @@ func (i imageValidator) PullBundleImage(imageTag, directory string) error {
5858
}
5959

6060
// ValidateBundle takes a directory containing the contents of a bundle and validates
61-
// the format and contents of that bundle for correctness
62-
func (i imageValidator) ValidateBundle(directory string) error {
61+
// the format of that bundle for correctness using these criteria
62+
// 1. Validate if the directory has two required directories for /manifests and /metadata
63+
// 2. Expecting bundle manifests files to be in /manifests and metadata files (including
64+
// annotations.yaml) to be in /metadata
65+
// 3. Validate the information in annotations to match the bundle contents such as
66+
// its media type, and channel information.
67+
// Inputs:
68+
// directory: the directory which the /manifests and /metadata exist
69+
// Outputs:
70+
// error: ValidattionError which contains a list of errors
71+
func (i imageValidator) ValidateBundleFormat(directory string) error {
6372
var manifestsFound, metadataFound bool
6473
var annotationsDir, manifestsDir string
65-
var annotationErrors []error
66-
var formatErrors []error
74+
var validationErrors []error
6775

6876
items, _ := ioutil.ReadDir(directory)
6977
for _, item := range items {
@@ -82,38 +90,29 @@ func (i imageValidator) ValidateBundle(directory string) error {
8290
}
8391

8492
if manifestsFound == false {
85-
formatErrors = append(formatErrors, fmt.Errorf("Unable to locate manifests directory"))
93+
validationErrors = append(validationErrors, fmt.Errorf("Unable to locate manifests directory"))
8694
}
8795
if metadataFound == false {
88-
formatErrors = append(formatErrors, fmt.Errorf("Unable to locate metadata directory"))
96+
validationErrors = append(validationErrors, fmt.Errorf("Unable to locate metadata directory"))
8997
}
9098

9199
// Break here if we can't even find the files
92-
if len(formatErrors) > 0 {
93-
return NewValidationError(annotationErrors, formatErrors)
100+
if len(validationErrors) > 0 {
101+
return NewValidationError(validationErrors)
94102
}
95103

96104
i.logger.Debug("Getting mediaType info from manifests directory")
97105
mediaType, err := GetMediaType(manifestsDir)
98106
if err != nil {
99-
formatErrors = append(formatErrors, err)
100-
}
101-
102-
// Validate bundle contents (only for registryv1 and plaintype type bundles)
103-
i.logger.Debug("Validating bundle contents")
104-
validationErrors := validateBundleContents(manifestsDir, mediaType, i.logger)
105-
if len(validationErrors) > 0 {
106-
for _, err := range validationErrors {
107-
formatErrors = append(formatErrors, err)
108-
}
107+
validationErrors = append(validationErrors, err)
109108
}
110109

111110
// Validate annotations.yaml
112111
annotationsFile, err := ioutil.ReadFile(filepath.Join(annotationsDir, AnnotationsFile))
113112
if err != nil {
114113
fmtErr := fmt.Errorf("Unable to read annotations.yaml file: %s", err.Error())
115-
formatErrors = append(formatErrors, fmtErr)
116-
return NewValidationError(annotationErrors, formatErrors)
114+
validationErrors = append(validationErrors, fmtErr)
115+
return NewValidationError(validationErrors)
117116
}
118117

119118
var fileAnnotations AnnotationMetadata
@@ -131,7 +130,7 @@ func (i imageValidator) ValidateBundle(directory string) error {
131130

132131
err = yaml.Unmarshal(annotationsFile, &fileAnnotations)
133132
if err != nil {
134-
formatErrors = append(formatErrors, fmt.Errorf("Unable to parse annotations.yaml file"))
133+
validationErrors = append(validationErrors, fmt.Errorf("Unable to parse annotations.yaml file"))
135134
}
136135

137136
for label, item := range annotations {
@@ -140,29 +139,29 @@ func (i imageValidator) ValidateBundle(directory string) error {
140139
i.logger.Debugf(`Found annotation "%s" with value "%s"`, label, val)
141140
} else {
142141
aErr := fmt.Errorf("Missing annotation %q", label)
143-
annotationErrors = append(annotationErrors, aErr)
142+
validationErrors = append(validationErrors, aErr)
144143
}
145144

146145
switch label {
147146
case MediatypeLabel:
148147
if item != val {
149148
aErr := fmt.Errorf("Expecting annotation %q to have value %q instead of %q", label, item, val)
150-
annotationErrors = append(annotationErrors, aErr)
149+
validationErrors = append(validationErrors, aErr)
151150
}
152151
case ManifestsLabel:
153152
if item != ManifestsDir {
154153
aErr := fmt.Errorf("Expecting annotation %q to have value %q instead of %q", label, ManifestsDir, val)
155-
annotationErrors = append(annotationErrors, aErr)
154+
validationErrors = append(validationErrors, aErr)
156155
}
157156
case MetadataDir:
158157
if item != MetadataLabel {
159158
aErr := fmt.Errorf("Expecting annotation %q to have value %q instead of %q", label, MetadataDir, val)
160-
annotationErrors = append(annotationErrors, aErr)
159+
validationErrors = append(validationErrors, aErr)
161160
}
162161
case ChannelsLabel, ChannelDefaultLabel:
163162
if val == "" {
164163
aErr := fmt.Errorf("Expecting annotation %q to have non-empty value", label)
165-
annotationErrors = append(annotationErrors, aErr)
164+
validationErrors = append(validationErrors, aErr)
166165
} else {
167166
annotations[label] = val
168167
}
@@ -171,25 +170,38 @@ func (i imageValidator) ValidateBundle(directory string) error {
171170

172171
_, err = ValidateChannelDefault(annotations[ChannelsLabel], annotations[ChannelDefaultLabel])
173172
if err != nil {
174-
annotationErrors = append(annotationErrors, err)
173+
validationErrors = append(validationErrors, err)
175174
}
176175

177-
if len(annotationErrors) > 0 || len(formatErrors) > 0 {
178-
return NewValidationError(annotationErrors, formatErrors)
176+
if len(validationErrors) > 0 {
177+
return NewValidationError(validationErrors)
179178
}
180179

181180
return nil
182181
}
183182

184-
// validateBundleContents confirms that the CSV and CRD files inside the bundle directory are valid
185-
// and can be installed in a cluster. Other GVK types are confirmed as valid kube objects but are not
186-
// explicitly validated currently.
187-
func validateBundleContents(manifestDir string, mediaType string, logger *logrus.Entry) (errors []error) {
188-
var contentsErrors []error
183+
// ValidateBundleContent confirms that the CSV and CRD files inside the bundle
184+
// directory are valid and can be installed in a cluster. Other GVK types are
185+
// also validated to confirm if they are "kubectl-able" to a cluster meaning
186+
// if they can be applied to a cluster using `kubectl` provided users have all
187+
// necessary permissions and configurations.
188+
// Inputs:
189+
// manifestDir: the directory which all bundle manifests files are located
190+
// Outputs:
191+
// error: ValidattionError which contains a list of errors
192+
func (i imageValidator) ValidateBundleContent(manifestDir string) error {
193+
var validationErrors []error
194+
195+
i.logger.Debug("Validating bundle contents")
196+
197+
mediaType, err := GetMediaType(manifestDir)
198+
if err != nil {
199+
validationErrors = append(validationErrors, err)
200+
}
189201

190202
switch mediaType {
191203
case HelmType, PlainType:
192-
return contentsErrors
204+
return nil
193205
}
194206

195207
supportedTypes := map[string]string{
@@ -204,6 +216,8 @@ func validateBundleContents(manifestDir string, mediaType string, logger *logrus
204216
roleBindingKind: "",
205217
}
206218

219+
var csvName string
220+
unstObjs := []*unstructured.Unstructured{}
207221
csvValidator := v.ClusterServiceVersionValidator
208222
crdValidator := v.CustomResourceDefinitionValidator
209223

@@ -213,58 +227,76 @@ func validateBundleContents(manifestDir string, mediaType string, logger *logrus
213227
fileWithPath := filepath.Join(manifestDir, item.Name())
214228
data, err := ioutil.ReadFile(fileWithPath)
215229
if err != nil {
216-
contentsErrors = append(contentsErrors, fmt.Errorf("Unable to read file %s in supported types", fileWithPath))
230+
validationErrors = append(validationErrors, fmt.Errorf("Unable to read file %s in supported types", fileWithPath))
217231
continue
218232
}
219233

220234
dec := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 30)
221235
k8sFile := &unstructured.Unstructured{}
222236
if err := dec.Decode(k8sFile); err == nil {
237+
unstObjs = append(unstObjs, k8sFile)
223238
kind := k8sFile.GetObjectKind().GroupVersionKind().Kind
224-
logger.Debugf(`Validating file "%s" with type "%s"`, item.Name(), kind)
239+
i.logger.Debugf(`Validating file "%s" with type "%s"`, item.Name(), kind)
225240
// Verify if the object kind is supported for registryV1 format
226241
if _, ok := supportedTypes[kind]; !ok {
227-
contentsErrors = append(contentsErrors, fmt.Errorf("%s is not supported type for registryV1 bundle: %s", kind, fileWithPath))
242+
validationErrors = append(validationErrors, fmt.Errorf("%s is not supported type for registryV1 bundle: %s", kind, fileWithPath))
228243
} else {
229244
if kind == csvKind {
230245
csv := &v1.ClusterServiceVersion{}
231246
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, csv)
232247
if err != nil {
233-
contentsErrors = append(contentsErrors, err)
248+
validationErrors = append(validationErrors, err)
234249
}
235250

251+
csvName = csv.GetName()
236252
results := csvValidator.Validate(csv)
237253
if len(results) > 0 {
238254
for _, err := range results[0].Errors {
239-
contentsErrors = append(contentsErrors, err)
255+
validationErrors = append(validationErrors, err)
240256
}
241257
}
242258
} else if kind == crdKind {
243-
crd := &apiextensions.CustomResourceDefinition{}
259+
crd := &v1beta1.CustomResourceDefinition{}
244260
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, crd)
245261
if err != nil {
246-
contentsErrors = append(contentsErrors, err)
262+
validationErrors = append(validationErrors, err)
247263
}
248264

249265
results := crdValidator.Validate(crd)
250266
if len(results) > 0 {
251267
for _, err := range results[0].Errors {
252-
contentsErrors = append(contentsErrors, err)
268+
validationErrors = append(validationErrors, err)
253269
}
254270
}
255271
} else {
256272
err := validateKubectlable(data)
257273
if err != nil {
258-
contentsErrors = append(contentsErrors, err)
274+
validationErrors = append(validationErrors, err)
259275
}
260276
}
261277
}
262278
} else {
263-
contentsErrors = append(contentsErrors, err)
279+
validationErrors = append(validationErrors, err)
280+
}
281+
}
282+
283+
// Validate the bundle object
284+
if len(unstObjs) > 0 {
285+
bundle := registry.NewBundle(csvName, "", "", unstObjs...)
286+
bundleValidator := v.BundleValidator
287+
results := bundleValidator.Validate(bundle)
288+
if len(results) > 0 {
289+
for _, err := range results[0].Errors {
290+
validationErrors = append(validationErrors, err)
291+
}
264292
}
265293
}
266294

267-
return contentsErrors
295+
if len(validationErrors) > 0 {
296+
return NewValidationError(validationErrors)
297+
}
298+
299+
return nil
268300
}
269301

270302
// Validate if the file is kubecle-able

‎pkg/lib/bundle/validate_test.go

+19-10
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func TestPullBundle_Error(t *testing.T) {
5151
assert.Equal(t, expectedErr, err)
5252
}
5353

54-
func TestValidateBundle(t *testing.T) {
54+
func TestValidateBundleFormat(t *testing.T) {
5555
dir := "./testdata/validate/valid_bundle/"
5656

5757
logger := logrus.NewEntry(logrus.New())
@@ -60,7 +60,7 @@ func TestValidateBundle(t *testing.T) {
6060
logger: logger,
6161
}
6262

63-
err := validator.ValidateBundle(dir)
63+
err := validator.ValidateBundleFormat(dir)
6464
require.NoError(t, err)
6565
}
6666

@@ -73,15 +73,21 @@ func TestValidateBundle_InvalidRegistryVersion(t *testing.T) {
7373
logger: logger,
7474
}
7575

76-
err := validator.ValidateBundle(dir)
76+
err := validator.ValidateBundleFormat(dir)
7777
require.Error(t, err)
7878
var validationError ValidationError
7979
isValidationErr := errors.As(err, &validationError)
8080
require.True(t, isValidationErr)
81-
require.Equal(t, len(validationError.AnnotationErrors), 1)
81+
require.Equal(t, len(validationError.Errors), 1)
8282
}
8383

84-
func TestValidateBundleContents(t *testing.T) {
84+
func TestValidateBundleContent(t *testing.T) {
85+
logger := logrus.NewEntry(logrus.New())
86+
87+
validator := imageValidator{
88+
logger: logger,
89+
}
90+
8591
var table = []struct {
8692
description string
8793
mediaType string
@@ -126,11 +132,14 @@ func TestValidateBundleContents(t *testing.T) {
126132
}
127133

128134
for i, tt := range table {
129-
logger := logrus.NewEntry(logrus.New())
130-
result := validateBundleContents(tt.directory, tt.mediaType, logger)
131-
require.Len(t, result, tt.numErrors, table[i].description)
132-
if len(result) > 0 {
133-
e := result[0]
135+
fmt.Println(tt.directory)
136+
err := validator.ValidateBundleContent(tt.directory)
137+
var validationError ValidationError
138+
isValidationErr := errors.As(err, &validationError)
139+
require.True(t, isValidationErr)
140+
require.Len(t, validationError.Errors, tt.numErrors, table[i].description)
141+
if len(validationError.Errors) > 0 {
142+
e := validationError.Errors[0]
134143
require.Contains(t, e.Error(), tt.errString)
135144
}
136145
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)
Please sign in to comment.