-
Notifications
You must be signed in to change notification settings - Fork 73
🌱 Add registry+v1 bundle config unmarshal and validation layer #2278
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| package bundle | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "strings" | ||
|
|
||
| "k8s.io/apimachinery/pkg/util/sets" | ||
| "k8s.io/apimachinery/pkg/util/validation" | ||
| "sigs.k8s.io/yaml" | ||
|
|
||
| "github.com/operator-framework/api/pkg/operators/v1alpha1" | ||
| ) | ||
|
|
||
| type Config struct { | ||
| WatchNamespace string `json:"watchNamespace"` | ||
| } | ||
|
|
||
| // UnmarshallConfig returns a deserialized and validated *bundle.Config based on bytes and validated | ||
| // against rv1 and the desired install namespaces. It will error if: | ||
| // - rv is nil | ||
| // - bytes is not a valid YAML/JSON object | ||
| // - bytes is a valid YAML/JSON object but does not follow the registry+v1 schema | ||
| // if bytes is nil a nil bundle.Config is returned | ||
| func UnmarshallConfig(bytes []byte, rv1 *RegistryV1, installNamespace string) (*Config, error) { | ||
| if bytes == nil { | ||
| return nil, nil | ||
| } | ||
| if rv1 == nil { | ||
| return nil, errors.New("bundle is nil") | ||
|
perdasilva marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| bundleConfig := &Config{} | ||
| if err := yaml.UnmarshalStrict(bytes, bundleConfig); err != nil { | ||
| return nil, fmt.Errorf("error unmarshalling registry+v1 configuration: %w", formatUnmarshallError(err)) | ||
| } | ||
|
|
||
| if err := validateConfig(bundleConfig, rv1, installNamespace); err != nil { | ||
| return nil, fmt.Errorf("error unmarshalling registry+v1 configuration: %w", err) | ||
| } | ||
|
|
||
| return bundleConfig, nil | ||
| } | ||
|
|
||
| func validateConfig(config *Config, rv1 *RegistryV1, installNamespace string) error { | ||
| // no config, no problem | ||
| if config == nil { | ||
| return nil | ||
| } | ||
|
|
||
| // collect bundle install modes | ||
| installModeSet := sets.New(rv1.CSV.Spec.InstallModes...) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
func validateConfig(config *Config, rv1 *RegistryV1, installNamespace string, installModes sets.Set[v1alpha1.InstallMode]) errornit: also, perhaps would be more readable if we implement function (c *Config) ValidateFor(installNamespaces string, installModes sets.Set[v1alpha1.InstallMode]) error
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the case now. But, not necessarily in the future. We want to validate based on what's in the bundle. At the moment the only pertinent thing is the install mode. I think as an API, taking in the bundle makes more sense.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a private function, so API can change whenever we see a need.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh sorry - I misread I thought this comment was under the Unmarshal function. I'll fix that up!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But then I wouldn't make it package public though...for the reasons I sited in the first response. I think as an API taking the bundle as a parameter is the right thing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
|
||
| // only accept a non-empty value for watchNamespace if the bundle configuration accepts the watchNamespace config | ||
| if config.WatchNamespace != "" && !hasWatchNamespaceAsConfig(installModeSet) { | ||
| return errors.New(`unknown field "watchNamespace"`) | ||
|
pedjak marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // validate input format | ||
| if errs := validation.IsDNS1123Subdomain(config.WatchNamespace); len(errs) > 0 { | ||
| return fmt.Errorf("invalid 'watchNamespace' %q: namespace must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character", config.WatchNamespace) | ||
| } | ||
|
|
||
| // only accept install namespace if OwnNamespace install mode is supported | ||
| if config.WatchNamespace == installNamespace && | ||
| !installModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}) { | ||
| return fmt.Errorf("invalid 'watchNamespace' %q: must not be install namespace (%s)", config.WatchNamespace, installNamespace) | ||
| } | ||
|
|
||
| if config.WatchNamespace != installNamespace && | ||
| !installModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}) { | ||
| return fmt.Errorf("invalid 'watchNamespace' %q: must be install namespace (%s)", config.WatchNamespace, installNamespace) | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func hasWatchNamespaceAsConfig(bundleInstallModeSet sets.Set[v1alpha1.InstallMode]) bool { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the function is used once only - thus for the readability it would be ok to put it inline in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The question the function answers is whether the bundle supports the watchNamespace configuration - this is driven off the install mode support. If I don't need it again for the "required" case I'll inline it and add the appropriate comments
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've refactored to |
||
| return bundleInstallModeSet.Has(v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true}) || | ||
| bundleInstallModeSet.HasAll( | ||
| v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true}, | ||
| v1alpha1.InstallMode{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true}) | ||
| } | ||
|
|
||
| func formatUnmarshallError(err error) error { | ||
| var unmarshalErr *json.UnmarshalTypeError | ||
| if errors.As(err, &unmarshalErr) { | ||
| if unmarshalErr.Field == "" { | ||
| return errors.New("input is not a valid JSON object") | ||
| } else { | ||
| return fmt.Errorf("invalid value type for field %q: expected %q but got %q", unmarshalErr.Field, unmarshalErr.Type.String(), unmarshalErr.Value) | ||
| } | ||
| } | ||
|
|
||
| // unwrap error until the core and process it | ||
| for { | ||
| unwrapped := errors.Unwrap(err) | ||
| if unwrapped == nil { | ||
| // usually the errors present in the form json: <message> or yaml: <message> | ||
| // we want to extract <message> if we can | ||
| errMessageComponents := strings.Split(err.Error(), ":") | ||
| coreErrMessage := strings.TrimSpace(errMessageComponents[len(errMessageComponents)-1]) | ||
| return errors.New(coreErrMessage) | ||
| } | ||
| err = unwrapped | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.