Skip to content

Commit 0d31556

Browse files
committedJan 21, 2020
Address feedbacks from comments/reviews
1. Improve errors handling 2. Flattering some nested conditional loops 3. Refactoring supported types 4. Handle plain type validation Signed-off-by: Vu Dinh <[email protected]>
1 parent 1f920b9 commit 0d31556

File tree

15 files changed

+161
-98
lines changed

15 files changed

+161
-98
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@ bin
449449

450450
# Ignore sqlite
451451
*.db
452+
*.db-journal
452453

453454
# Ignore vscode
454455
.vscode

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func validateFunc(cmd *cobra.Command, args []string) error {
6262
return err
6363
}
6464

65-
err = imageValidator.ValidateBundleFormat(filepath.Join(dir, bundle.ManifestsDir))
65+
err = imageValidator.ValidateBundleContent(filepath.Join(dir, bundle.ManifestsDir))
6666
if err != nil {
6767
return err
6868
}

‎docs/design/operator-bundle.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ $ ./opm alpha bundle build --tag quay.io/coreos/test-operator.v0.1.0:latest --im
224224
225225
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.
226226
227-
After the bundle image format is confirmed, the command will validate the bundle contents such as manifests and metadata files only if the bundle format is `RegistryV1` type meaning it contains `ClusterResourceVersion` and its associated Kubernetes objects that makes up an Operator. The content validation process will ensure the individual file in the bundle image is valid and can be applied to an OLM-enabled cluster provided all necessary permissions and configurations are met.
227+
After the bundle image format is confirmed, the command will validate the bundle contents such as manifests and metadata files if the bundle format is `RegistryV1` or "Plain" type. "RegistryV1" format means it contains `ClusterResourceVersion` and its associated Kubernetes objects while `PlainType` means it contains all Kubernetes objects. The content validation process will ensure the individual file in the bundle image is valid and can be applied to an OLM-enabled cluster provided all necessary permissions and configurations are met.
228228
229229
*Notes:*
230230
* The bundle content validation is best effort which means it will not guarantee 100% accuracy due to nature of Kubernetes objects may need certain permissions and configurations, which users may not have, in order to be applied successfully in a cluster.

‎go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ require (
1414
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2
1515
github.com/onsi/ginkgo v1.10.1
1616
github.com/onsi/gomega v1.7.0
17-
github.com/operator-framework/api v0.0.0-20191127212340-9066a6e95573
17+
github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9
1818
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5
1919
github.com/otiai10/copy v1.0.1
2020
github.com/pkg/errors v0.8.1

‎go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt
516516
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
517517
github.com/operator-framework/api v0.0.0-20191127212340-9066a6e95573 h1:bMO43IWWPM3HCGIiuM/GyXjtSJWsrhOlzUpZMesVUw0=
518518
github.com/operator-framework/api v0.0.0-20191127212340-9066a6e95573/go.mod h1:S5IdlJvmKkF84K2tBvsrqJbI2FVy03P88R75snpRxJo=
519+
github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9 h1:HfxMEPJ0djo/RNfrmli3kI2oKS6IeuIZWu1Q5Rewt/o=
520+
github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9/go.mod h1:S5IdlJvmKkF84K2tBvsrqJbI2FVy03P88R75snpRxJo=
519521
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5 h1:rjaihxY50c5C+kbQIK4s36R8zxByATYrgRbua4eiG6o=
520522
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5/go.mod h1:zL34MNy92LPutBH5gQK+gGhtgTUlZZX03I2G12vWHF4=
521523
github.com/operator-framework/operator-registry v1.5.1/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY=

‎pkg/lib/bundle/supported_resources.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package bundle
2+
3+
const (
4+
CSVKind = "ClusterServiceVersion"
5+
CRDKind = "CustomResourceDefinition"
6+
SecretKind = "Secret"
7+
ClusterRoleKind = "ClusterRole"
8+
ClusterRoleBindingKind = "ClusterRoleBinding"
9+
ServiceAccountKind = "ServiceAccount"
10+
ServiceKind = "Service"
11+
RoleKind = "Role"
12+
RoleBindingKind = "RoleBinding"
13+
PrometheusRuleKind = "PrometheusRule"
14+
ServiceMonitorKind = "ServiceMonitor"
15+
)
16+
17+
var supportedResources map[string]bool
18+
19+
// Add a list of supported resources to the map
20+
// Key: Kind name
21+
// Value: If namaspaced kind, true. Otherwise, false
22+
func init() {
23+
supportedResources = make(map[string]bool, 11)
24+
25+
supportedResources[CSVKind] = true
26+
supportedResources[CRDKind] = false
27+
supportedResources[ClusterRoleKind] = false
28+
supportedResources[ClusterRoleBindingKind] = false
29+
supportedResources[ServiceKind] = true
30+
supportedResources[ServiceAccountKind] = true
31+
supportedResources[RoleKind] = true
32+
supportedResources[RoleBindingKind] = true
33+
supportedResources[PrometheusRuleKind] = true
34+
supportedResources[ServiceMonitorKind] = true
35+
}
36+
37+
// IsSupported checks if the object kind is OLM-supported and if it is namespaced
38+
func IsSupported(kind string) (bool, bool) {
39+
namespaced, ok := supportedResources[kind]
40+
return ok, namespaced
41+
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ spec:
1616
storage: true
1717
- name: v1beta2
1818
served: true
19-
storage: true
19+
storage: false

‎pkg/lib/bundle/validate.go

+61-71
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,6 @@ import (
2525
"gopkg.in/yaml.v2"
2626
)
2727

28-
const (
29-
csvKind = "ClusterServiceVersion"
30-
crdKind = "CustomResourceDefinition"
31-
secretKind = "Secret"
32-
clusterRoleKind = "ClusterRole"
33-
clusterRoleBindingKind = "ClusterRoleBinding"
34-
serviceAccountKind = "ServiceAccount"
35-
serviceKind = "Service"
36-
roleKind = "Role"
37-
roleBindingKind = "RoleBinding"
38-
)
39-
4028
type Meta struct {
4129
metav1.TypeMeta `json:",inline"`
4230
metav1.ObjectMeta `json:"metadata"`
@@ -73,7 +61,11 @@ func (i imageValidator) ValidateBundleFormat(directory string) error {
7361
var annotationsDir, manifestsDir string
7462
var validationErrors []error
7563

76-
items, _ := ioutil.ReadDir(directory)
64+
items, err := ioutil.ReadDir(directory)
65+
if err != nil {
66+
validationErrors = append(validationErrors, err)
67+
}
68+
7769
for _, item := range items {
7870
if item.IsDir() {
7971
switch s := item.Name(); s {
@@ -200,29 +192,21 @@ func (i imageValidator) ValidateBundleContent(manifestDir string) error {
200192
}
201193

202194
switch mediaType {
203-
case HelmType, PlainType:
195+
case HelmType:
204196
return nil
205197
}
206198

207-
supportedTypes := map[string]string{
208-
csvKind: "",
209-
crdKind: "",
210-
secretKind: "",
211-
clusterRoleKind: "",
212-
clusterRoleBindingKind: "",
213-
serviceKind: "",
214-
serviceAccountKind: "",
215-
roleKind: "",
216-
roleBindingKind: "",
217-
}
218-
219199
var csvName string
220200
unstObjs := []*unstructured.Unstructured{}
221201
csvValidator := v.ClusterServiceVersionValidator
222202
crdValidator := v.CustomResourceDefinitionValidator
223203

224204
// Read all files in manifests directory
225-
items, _ := ioutil.ReadDir(manifestDir)
205+
items, err := ioutil.ReadDir(manifestDir)
206+
if err != nil {
207+
validationErrors = append(validationErrors, err)
208+
}
209+
226210
for _, item := range items {
227211
fileWithPath := filepath.Join(manifestDir, item.Name())
228212
data, err := ioutil.ReadFile(fileWithPath)
@@ -233,50 +217,56 @@ func (i imageValidator) ValidateBundleContent(manifestDir string) error {
233217

234218
dec := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 30)
235219
k8sFile := &unstructured.Unstructured{}
236-
if err := dec.Decode(k8sFile); err == nil {
237-
unstObjs = append(unstObjs, k8sFile)
238-
kind := k8sFile.GetObjectKind().GroupVersionKind().Kind
239-
i.logger.Debugf(`Validating file "%s" with type "%s"`, item.Name(), kind)
240-
// Verify if the object kind is supported for registryV1 format
241-
if _, ok := supportedTypes[kind]; !ok {
242-
validationErrors = append(validationErrors, fmt.Errorf("%s is not supported type for registryV1 bundle: %s", kind, fileWithPath))
243-
} else {
244-
if kind == csvKind {
245-
csv := &v1.ClusterServiceVersion{}
246-
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, csv)
247-
if err != nil {
248-
validationErrors = append(validationErrors, err)
249-
}
250-
251-
csvName = csv.GetName()
252-
results := csvValidator.Validate(csv)
253-
if len(results) > 0 {
254-
for _, err := range results[0].Errors {
255-
validationErrors = append(validationErrors, err)
256-
}
257-
}
258-
} else if kind == crdKind {
259-
crd := &v1beta1.CustomResourceDefinition{}
260-
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, crd)
261-
if err != nil {
262-
validationErrors = append(validationErrors, err)
263-
}
264-
265-
results := crdValidator.Validate(crd)
266-
if len(results) > 0 {
267-
for _, err := range results[0].Errors {
268-
validationErrors = append(validationErrors, err)
269-
}
270-
}
271-
} else {
272-
err := validateKubectlable(data)
273-
if err != nil {
274-
validationErrors = append(validationErrors, err)
275-
}
220+
err = dec.Decode(k8sFile)
221+
if err != nil {
222+
validationErrors = append(validationErrors, err)
223+
continue
224+
}
225+
226+
unstObjs = append(unstObjs, k8sFile)
227+
gvk := k8sFile.GetObjectKind().GroupVersionKind()
228+
i.logger.Debugf(`Validating "%s" from file "%s"`, gvk.String(), item.Name())
229+
// Verify if the object kind is supported for RegistryV1 format
230+
ok, _ := IsSupported(gvk.Kind)
231+
if mediaType == RegistryV1Type && !ok {
232+
validationErrors = append(validationErrors, fmt.Errorf("%s is not supported type for registryV1 bundle: %s", gvk.Kind, fileWithPath))
233+
continue
234+
}
235+
236+
if gvk.Kind == CSVKind {
237+
csv := &v1.ClusterServiceVersion{}
238+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, csv)
239+
if err != nil {
240+
validationErrors = append(validationErrors, err)
241+
continue
242+
}
243+
244+
csvName = csv.GetName()
245+
results := csvValidator.Validate(csv)
246+
if len(results) > 0 {
247+
for _, err := range results[0].Errors {
248+
validationErrors = append(validationErrors, err)
249+
}
250+
}
251+
} else if gvk.Kind == CRDKind {
252+
crd := &v1beta1.CustomResourceDefinition{}
253+
err := runtime.DefaultUnstructuredConverter.FromUnstructured(k8sFile.Object, crd)
254+
if err != nil {
255+
validationErrors = append(validationErrors, err)
256+
continue
257+
}
258+
259+
results := crdValidator.Validate(crd)
260+
if len(results) > 0 {
261+
for _, err := range results[0].Errors {
262+
validationErrors = append(validationErrors, err)
276263
}
277264
}
278265
} else {
279-
validationErrors = append(validationErrors, err)
266+
err := validateKubectlable(data)
267+
if err != nil {
268+
validationErrors = append(validationErrors, err)
269+
}
280270
}
281271
}
282272

@@ -301,13 +291,13 @@ func (i imageValidator) ValidateBundleContent(manifestDir string) error {
301291

302292
// Validate if the file is kubecle-able
303293
func validateKubectlable(fileBytes []byte) error {
304-
exampleFileBytesJson, err := y.YAMLToJSON(fileBytes)
294+
exampleFileBytesJSON, err := y.YAMLToJSON(fileBytes)
305295
if err != nil {
306296
return err
307297
}
308298

309299
parsedMeta := &Meta{}
310-
err = json.Unmarshal(exampleFileBytesJson, parsedMeta)
300+
err = json.Unmarshal(exampleFileBytesJSON, parsedMeta)
311301
if err != nil {
312302
return err
313303
}
@@ -322,7 +312,7 @@ func validateKubectlable(fileBytes []byte) error {
322312
)
323313

324314
if len(errs) > 0 {
325-
return fmt.Errorf("error validating object metadata: %s. %v", errs, parsedMeta)
315+
return fmt.Errorf("error validating object metadata: %s. %v", errs.ToAggregate(), parsedMeta)
326316
}
327317

328318
return nil

‎pkg/lib/bundle/validate_test.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func TestValidateBundleContent(t *testing.T) {
107107
mediaType: RegistryV1Type,
108108
directory: "./testdata/validate/invalid_manifests_bundle/invalid_crd/",
109109
numErrors: 1,
110-
errString: "cannot restore slice from string",
110+
errString: "must contain unique version name",
111111
},
112112
{
113113
description: "registryv1 bundle/invalid sa",
@@ -135,8 +135,10 @@ func TestValidateBundleContent(t *testing.T) {
135135
fmt.Println(tt.directory)
136136
err := validator.ValidateBundleContent(tt.directory)
137137
var validationError ValidationError
138-
isValidationErr := errors.As(err, &validationError)
139-
require.True(t, isValidationErr)
138+
if err != nil {
139+
isValidationErr := errors.As(err, &validationError)
140+
require.True(t, isValidationErr)
141+
}
140142
require.Len(t, validationError.Errors, tt.numErrors, table[i].description)
141143
if len(validationError.Errors) > 0 {
142144
e := validationError.Errors[0]

‎vendor/github.com/operator-framework/api/pkg/validation/internal/crd.go

+34-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/k8s.io/api/discovery/v1alpha1/doc.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1/doc.go

+5-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/k8s.io/client-go/kubernetes/typed/auditregistration/v1alpha1/doc.go

+5-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/k8s.io/client-go/kubernetes/typed/discovery/v1alpha1/generated_expansion.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/modules.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ github.com/onsi/gomega/matchers/support/goraph/util
160160
github.com/onsi/gomega/types
161161
# github.com/opencontainers/go-digest v1.0.0-rc1
162162
github.com/opencontainers/go-digest
163-
# github.com/operator-framework/api v0.0.0-20191127212340-9066a6e95573
163+
# github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9
164164
github.com/operator-framework/api/pkg/validation
165165
github.com/operator-framework/api/pkg/validation/errors
166166
github.com/operator-framework/api/pkg/validation/interfaces

0 commit comments

Comments
 (0)
Please sign in to comment.