-
Notifications
You must be signed in to change notification settings - Fork 78
validation library #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
validation library #3
Conversation
- Checking CSV yaml for data type mismatch against OLM's CSV type
- Adding Command Line Tool for the library
- To avoid "usage" and "flags" getting printed every time there's an error
- For reporting warnings and errors in case of optional and mandatory fields missing, respectively. - To enable users to extract more information from an error/warning.
validation logic, refactoring around interface cmd/verify.go: Verify() name change
- To implement a generic custom error type which is also compatible with upstream.
- To have all the csv checks in one file (csv_validator.go).
- To avoid redundancy while using the cli (operator-verify manifest) instead of (operator-verify verify).
- To verify the operator manifest.
- To compare example annotations to the provided APIs list and report appropriate errors.
- To check for the manifest format and validate the manifest and individual operator files.
- To support validating the CRDs present in the manifest.
- To support validating package yaml present in the manifest.
- To perform validation across manifest files.
- To improve the design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of nits - but the logic seems good.
"os" | ||
|
||
manifests "github.com/operator-framework/api/cmd/operator-verify/manifests" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this newline necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, but I prefer to separate intra-repo from extra-repo imports. I can merge them all if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In OLM we use goimports
which actually does something similar, just with the position of the intra/extra imports flipped from what you have here. Do we want to make it the org wide formatter?
"os" | ||
|
||
"github.com/operator-framework/api/pkg/manifests" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this newline necessary?
seen := map[string]struct{}{} | ||
for i, c := range pkg.Channels { | ||
if c.Name == "" { | ||
errs = append(errs, errors.ErrInvalidPackageManifest(fmt.Sprintf("channel %d name is empty", i), pkg.PackageName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fmt.Sprintf("channel %d name is empty", i), pkg.PackageName)
isn't i
just a integer for each channel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, here I assume there's no other identifying information for a channel so channel number is the only piece of information available to identify which channel is invalid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding this! It's looking good so far. I was only able to get half way through, but I figured it was a good place to stop and get some questions out.
go.mod
Outdated
github.com/ghodss/yaml v1.0.0 | ||
github.com/hashicorp/golang-lru v0.5.3 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190725173916-b56e63a643cc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid the OLM dependency? Eventually all the types could live in this repo anyway, and it would help to avoid circular dependency issues when OLM inevitably imports validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have to duplicate the ClusterServiceVersion and related types here to remove this dependency. Are you ok with this?
We may have to do the same thing for operator-registry (registry.Bundle
and some parsing types).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was the initial idea. Ideally, as soon as we moved something to this repo, we'd switch over in OLM. That whole plan has been put on hold since it makes releasing API changes more difficult; e.g. we would need to release API changes ahead of the controller logic that handles them.
@ecordell, I think we need to make a decision ASAP to either move validation to its own repo or move the OLM APIs here.
func (l *manifestsLoad) populate() error { | ||
dl := sqlite.NewSQLLoaderForDirectory(l, l.dir) | ||
if err := dl.Populate(); err != nil { | ||
return errors.Wrapf(err, "error getting bundles from manifests dir %q", l.dir) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In OLM we actually do the opposite -- but we could totally review that convention if you want @awgreene
} | ||
|
||
// AddOperatorBundle adds the package manifest to l. | ||
func (l *manifestsLoad) AddPackageChannels(pkg registry.PackageManifest) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type needs to implement this exact method signature in order to implement the registry.Load
interface, which includes the error in case an implementation needs to report that the operation failed in some way.
@kevinrizza @ecordell, did we decide if we're abandoning this repo? If so, we should make plans to move this PR into its own validation repo or OLM proper. (hopefully this question doesn't rekindle the entire conversation...) |
There is an active effort to create an API version for the entire operator-framework. I think once that conversation gets further along we may end up wanting to merge this (either in the current state or otherwise) so for now maybe let's just leave it open and reevaluate later? |
Sure, we can leave this here for now -- my comment was spurred by the decision to place net-new OLM APIs directly in its repository (instead of here). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for your patience. I'm going to be in transit soon, so I decided to post the first half of the review in advance. It's looking solid so far, mostly just nits and questions
go.mod
Outdated
github.com/ghodss/yaml v1.0.0 | ||
github.com/hashicorp/golang-lru v0.5.3 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/operator-framework/operator-lifecycle-manager v0.0.0-20190725173916-b56e63a643cc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was the initial idea. Ideally, as soon as we moved something to this repo, we'd switch over in OLM. That whole plan has been put on hold since it makes releasing API changes more difficult; e.g. we would need to release API changes ahead of the controller logic that handles them.
@ecordell, I think we need to make a decision ASAP to either move validation to its own repo or move the OLM APIs here.
return l.pkg | ||
} | ||
|
||
func (l manifests) GetBundles() (bundles []*registry.Bundle) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was probably for convenience since the method receivers are of type *registry.Bundle
. This lets us avoid needing to get the pointer for each element every time we want to call a method.
pkg/validation/internal/csv.go
Outdated
"k8s.io/apimachinery/pkg/util/yaml" | ||
) | ||
|
||
type CSVValidator struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: If you make a function type that implements Validator
, then you won't need this empty struct. A good example is in the go standard library; see https://golang.org/src/net/http/server.go?s=61509:61556#L1993
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So something like
type csvv1alpha1Validator func(*v1alpha1.ClusterServiceVersion) errors.ManifestResult
func (f csvv1alpha1Validator) Validate(objs ...interface{}) (results []errors.ManifestResult) {
for _, obj := range objs {
switch v := obj.(type) {
case *v1alpha1.ClusterServiceVersion:
results = append(results, f(v))
}
}
return results
}
Then export functions that actually validate the CSV object:
var Validatev1alpha1CSV csvv1alpha1Validator = func(*v1alpha1.ClusterServiceVersion) errors.ManifestResult {
...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost. What I'm proposing just allows functions to implement the Validator
interface:
type ValidatorFunc Validate(...interface{}) []errors.ManifestResult
func (v ValidatorFunc) Validate(objs ...interface{}) (results []errors.ManifestResult) {
return v(objs...)
}
var CSVValidator Validator = ValidatorFunc(validateCSVs)
func validateCSVs(objs ...interface{}) (results []errors.ManifestResult) {
for _, obj := range objs {
switch v := obj.(type) {
case *v1alpha1.ClusterServiceVersion:
results = append(results, validateCSV(v))
case *registry.ClusterServiceVersion:
results = append(results, validateCSVRegistry(v))
}
}
return results
}
pkg/validation/internal/csv.go
Outdated
|
||
// Omitted field tags will contain ",omitempty", and ignored tags will | ||
// match "-" exactly, respectively. | ||
isOptionalField := strings.Contains(tag, ",omitempty") || tag == "-" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
follow-up: If we could somehow get the OpenAPI spec for CRDs not included in the bundle, could we use a pre-existing schema validator to handle checking for missing required fields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on what you mean by "OpenAPI spec for CRDs not included in the bundle" a bit? What manifest kind are we checking for missing fields in the context of your question?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One example would be the OpenAPI spec for a Deployment. We would validate the manifest against the OpenAPI schema, which would tell us when fields are missing (due to required directives, etc...). Does that make more sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that does. I will implement this in a follow-up.
f23e367
to
689a491
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!!
Just a few questions (could be a follow up PR). Also we still need to resolve where the OLM APIs live
|
||
## `pkg/validation`: Operator Manifest Validation | ||
|
||
`pkg/validation` exposes a convenient set of interfaces to validate Kubernetes object manifests, primarily for use in an Operator project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of adding some examples? This could be in a separate PR/folder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking examples of implementing validators / using the apis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might make a nice follow-up.
Short: "Operator manifest validation tool", | ||
Long: `operator-verify is a CLI tool that calls functions in pkg/validation.`, | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what do you think of adding an option to output the resulting errors/warnings to a log file - I think this was a use case that Ralph or Scott had with operator courier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think this can be a follow-up.
} | ||
|
||
// AddBundlePackageChannels is a no-op to implement the registry.Load interface. | ||
func (l *manifestsLoad) AddBundlePackageChannels(manifest registry.PackageManifest, bundle registry.Bundle) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: As we change operator-registry we'll need to be aware of the dependency here on the implementation of its Load interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general we're going to have to do something about operator-registry types. If we want this to be a dependency of operator-registry, we're either going to have to copy over types or copy over parse code and use internal types. WDYT? We can probably clean this up in a follow-up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the rest of my review. Mostly follow-ups and nits:
pkg/validation/internal/manifests.go
Outdated
"github.com/operator-framework/operator-registry/pkg/registry" | ||
) | ||
|
||
type ManifestsValidator struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PackageUpdateGraphValidator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose this is a fairer name given this validator's current logic. However I'd rather keep the name general in case the constituent manifest types of a manifests directory are changed, or other, non-update graph validation logic is added in the future. How about VersionedManifestSetValidator
or ManifestsDirValidator
?
Alternatively we could add future logic as described above in another validator and reserve this one for validating the update graph.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively we could add future logic as described above in another validator and reserve this one for validating the update graph.
This is more where my head is at, since I view validators as composable units that form larger validators (if Validators
implemented Validator
). I can also see where it can be preferable to make larger units to avoid duplication of work or handle interdependencies. If that's the case, then either of the names you've suggested are fine with me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ehh because this code is only validating the update graph right now, we can name this PackageUpdateGraphValidator
and add another validator later. These names are subject to change since they're internal types.
4a3cc94
to
02fc1ea
Compare
errors.ManifestResult. pkg/validation/internal: default validator implementations pkg/validation/errors: result and error types for validators pkg/internal: bundle parser pkg/manifests: GetManifestsDir() parses and validates a directory of Operator manifests *: update import paths to operator-framework/api go.*: revendor README.md: concise description of pkg/validation
02fc1ea
to
439c769
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's super close. I just don't want to merge it now only to move it to another repo immediately after. A big chunk of our team is out, so I'll confer with @shawn-hurley and @dmesser soon to settle things.
pkg/validation/internal/csv.go
Outdated
"k8s.io/apimachinery/pkg/util/yaml" | ||
) | ||
|
||
type CSVValidator struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost. What I'm proposing just allows functions to implement the Validator
interface:
type ValidatorFunc Validate(...interface{}) []errors.ManifestResult
func (v ValidatorFunc) Validate(objs ...interface{}) (results []errors.ManifestResult) {
return v(objs...)
}
var CSVValidator Validator = ValidatorFunc(validateCSVs)
func validateCSVs(objs ...interface{}) (results []errors.ManifestResult) {
for _, obj := range objs {
switch v := obj.(type) {
case *v1alpha1.ClusterServiceVersion:
results = append(results, validateCSV(v))
case *registry.ClusterServiceVersion:
results = append(results, validateCSVRegistry(v))
}
}
return results
}
pkg/validation/internal/manifests.go
Outdated
"github.com/operator-framework/operator-registry/pkg/registry" | ||
) | ||
|
||
type ManifestsValidator struct{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively we could add future logic as described above in another validator and reserve this one for validating the update graph.
This is more where my head is at, since I view validators as composable units that form larger validators (if Validators
implemented Validator
). I can also see where it can be preferable to make larger units to avoid duplication of work or handle interdependencies. If that's the case, then either of the names you've suggested are fine with me.
…pend non-default Validators to the Validator; ValidatorFunc implements Validator
/hold cancel |
Moved from dweepgogia/new-manifest-verification.
Implementation of the library described in #2.
Notes:
api
repo structure, which is not followed here but could/should be./cc @gallettilance @shawn-hurley @ecordell