This commit is contained in:
Lukas Lüdke 2024-08-06 02:22:09 +05:30 committed by GitHub
commit 3625be2d75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 9 deletions

View file

@ -61,6 +61,7 @@ type Opts struct {
RejectKinds map[string]struct{} // List of resource Kinds to reject
KubernetesVersion string // Kubernetes Version - has to match one in https://github.com/instrumenta/kubernetes-json-schema
Strict bool // thros an error if resources contain undocumented fields
StrictExceptions map[string]struct{} // list of field paths (e.g. spec.template.metadata) to ignore when strict is enabled
IgnoreMissingSchemas bool // skip a resource if no schema for that resource can be found
}
@ -68,8 +69,10 @@ type Opts struct {
func New(schemaLocations []string, opts Opts) (Validator, error) {
// Default to our kubernetes-json-schema fork
// raw.githubusercontent.com is frontend by Fastly and very fast
defaultLocationsUsed := false
if len(schemaLocations) == 0 {
schemaLocations = []string{"https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json"}
defaultLocationsUsed = true
}
registries := []registry.Registry{}
@ -91,20 +94,62 @@ func New(schemaLocations []string, opts Opts) (Validator, error) {
if opts.RejectKinds == nil {
opts.RejectKinds = map[string]struct{}{}
}
if opts.StrictExceptions == nil {
opts.StrictExceptions = map[string]struct{}{}
}
if len(opts.StrictExceptions) == 0 {
// If no strict exceptions are specified, we add the metadata field
// as it is mostly not autogenerated in the OpenAPI schemas generated from CRDs
opts.StrictExceptions["metadata"] = struct{}{}
}
return &v{
opts: opts,
schemaDownload: downloadSchema,
schemaCache: cache.NewInMemoryCache(),
regs: registries,
opts: opts,
schemaDownload: downloadSchema,
schemaCache: cache.NewInMemoryCache(),
regs: registries,
defaultLocationsUsed: defaultLocationsUsed,
}, nil
}
func (val *v) setStrictSchema(schema *jsonschema.Schema, prefixPath string) {
if _, ok := val.opts.StrictExceptions[prefixPath]; ok {
return
}
if schema.AdditionalProperties == nil {
schema.AdditionalProperties = false
}
if schema.Items != nil {
casted, ok := schema.Items.(*jsonschema.Schema)
if ok {
fullPath := prefixPath + ".0"
val.setStrictSchema(casted, fullPath)
} else {
casted, ok := schema.Items.([]*jsonschema.Schema)
if ok {
for i, s := range casted {
fullPath := prefixPath + "." + fmt.Sprintf("%d", i)
val.setStrictSchema(s, fullPath)
}
}
}
}
for name, s := range schema.Properties {
fullPath := prefixPath + "." + name
if fullPath[0] == '.' {
fullPath = fullPath[1:]
}
val.setStrictSchema(s, fullPath)
}
}
type v struct {
opts Opts
schemaCache cache.Cache
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error)
regs []registry.Registry
opts Opts
schemaCache cache.Cache
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error)
regs []registry.Registry
defaultLocationsUsed bool
}
// ValidateResource validates a single resource. This allows to validate
@ -191,6 +236,11 @@ func (val *v) ValidateResource(res resource.Resource) Result {
return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error}
}
// If strict mode is enabled, we set additionalProperties to false
// if not explicitly set in the schema
if val.opts.Strict && !val.defaultLocationsUsed {
val.setStrictSchema(schema, "")
}
err = schema.Validate(r)
if err != nil {
validationErrors := []ValidationError{}