mirror of
https://github.com/yannh/kubeconform.git
synced 2026-02-19 09:57:02 +00:00
Merge 77c022cfa1 into 1bd44986dd
This commit is contained in:
commit
17b2fd074f
19 changed files with 430 additions and 22 deletions
|
|
@ -81,6 +81,56 @@ resetCacheFolder() {
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "Fail when annotation key is invalid" {
|
||||||
|
run bin/kubeconform fixtures/annotation_key_invalid.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when annotation value is missing" {
|
||||||
|
run bin/kubeconform fixtures/annotation_missing_value.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when annotation value is null" {
|
||||||
|
run bin/kubeconform fixtures/annotation_null_value.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when label name is too long" {
|
||||||
|
run bin/kubeconform fixtures/label_name_length.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when label namespace is invalid domain" {
|
||||||
|
run bin/kubeconform fixtures/label_namespace.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when label value is too long" {
|
||||||
|
run bin/kubeconform fixtures/label_value_length.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail when metadata name is missing" {
|
||||||
|
run bin/kubeconform fixtures/metadata_name_missing.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Pass if skip-metadata added" {
|
||||||
|
run bin/kubeconform -skip-metadata fixtures/metadata_name_missing.yaml
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Pass with extra metadata fields" {
|
||||||
|
run bin/kubeconform fixtures/metadata_extra.yaml
|
||||||
|
[ "$status" -eq 0 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "Fail extra metadata fields when strict" {
|
||||||
|
run bin/kubeconform -strict fixtures/metadata_extra.yaml
|
||||||
|
[ "$status" -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
@test "Return relevant error for non-existent file" {
|
@test "Return relevant error for non-existent file" {
|
||||||
run bin/kubeconform fixtures/not-here
|
run bin/kubeconform fixtures/not-here
|
||||||
[ "$status" -eq 1 ]
|
[ "$status" -eq 1 ]
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ func kubeconform(cfg config.Config) int {
|
||||||
SkipKinds: cfg.SkipKinds,
|
SkipKinds: cfg.SkipKinds,
|
||||||
RejectKinds: cfg.RejectKinds,
|
RejectKinds: cfg.RejectKinds,
|
||||||
KubernetesVersion: cfg.KubernetesVersion.String(),
|
KubernetesVersion: cfg.KubernetesVersion.String(),
|
||||||
|
SkipMetadata: cfg.SkipMetadata,
|
||||||
Strict: cfg.Strict,
|
Strict: cfg.Strict,
|
||||||
IgnoreMissingSchemas: cfg.IgnoreMissingSchemas,
|
IgnoreMissingSchemas: cfg.IgnoreMissingSchemas,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
8
fixtures/annotation_key_invalid.yaml
Normal file
8
fixtures/annotation_key_invalid.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
annotations:
|
||||||
|
cert(manager.io/cluster-issuer": issue #275
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
8
fixtures/annotation_missing_value.yaml
Normal file
8
fixtures/annotation_missing_value.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
annotations:
|
||||||
|
some.domain/some-key:
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
8
fixtures/annotation_null_value.yaml
Normal file
8
fixtures/annotation_null_value.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
annotations:
|
||||||
|
some.domain/some-key: null
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
8
fixtures/label_name_length.yaml
Normal file
8
fixtures/label_name_length.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
labels:
|
||||||
|
abcdefghijklmnopqrstuvwxyz-01234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZ: "123456789_123456789_123456789_123456789_123456789_123456789_123"
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
8
fixtures/label_namespace.yaml
Normal file
8
fixtures/label_namespace.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
labels:
|
||||||
|
abcdefghijklmnopqrstuvwxyz-01234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZ.example.com/ABCDEFGHIJKLMNOPQRSTUVWXYZ: "123456789_123456789_123456789_123456789_123456789_123456789_123"
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
8
fixtures/label_value_length.yaml
Normal file
8
fixtures/label_value_length.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
labels:
|
||||||
|
some.domain/some-key: "123456789_123456789_123456789_123456789_123456789_123456789_1234"
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
9
fixtures/metadata_extra.yaml
Normal file
9
fixtures/metadata_extra.yaml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: some-values
|
||||||
|
namespace: my-namespace
|
||||||
|
annotation:
|
||||||
|
flub: annotation should be annotations
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
4
fixtures/metadata_missing.yaml
Normal file
4
fixtures/metadata_missing.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
6
fixtures/metadata_name_missing.yaml
Normal file
6
fixtures/metadata_name_missing.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
6
fixtures/object_name-max_length.yaml
Normal file
6
fixtures/object_name-max_length.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: abcdefghijklmnopqrstuvwxyz-01234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||||
|
data:
|
||||||
|
file.name: "a value"
|
||||||
|
|
@ -22,6 +22,7 @@ type Config struct {
|
||||||
RejectKinds map[string]struct{} `yaml:"reject" json:"reject"`
|
RejectKinds map[string]struct{} `yaml:"reject" json:"reject"`
|
||||||
SchemaLocations []string `yaml:"schemaLocations" json:"schemaLocations"`
|
SchemaLocations []string `yaml:"schemaLocations" json:"schemaLocations"`
|
||||||
SkipKinds map[string]struct{} `yaml:"skip" json:"skip"`
|
SkipKinds map[string]struct{} `yaml:"skip" json:"skip"`
|
||||||
|
SkipMetadata bool `yaml:"skipMetadata" json:"skipMetadata"`
|
||||||
SkipTLS bool `yaml:"insecureSkipTLSVerify" json:"insecureSkipTLSVerify"`
|
SkipTLS bool `yaml:"insecureSkipTLSVerify" json:"insecureSkipTLSVerify"`
|
||||||
Strict bool `yaml:"strict" json:"strict"`
|
Strict bool `yaml:"strict" json:"strict"`
|
||||||
Summary bool `yaml:"summary" json:"summary"`
|
Summary bool `yaml:"summary" json:"summary"`
|
||||||
|
|
@ -97,6 +98,7 @@ func FromFlags(progName string, args []string) (Config, string, error) {
|
||||||
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, pretty, tap, text")
|
flags.StringVar(&c.OutputFormat, "output", "text", "output format - json, junit, pretty, tap, text")
|
||||||
flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)")
|
flags.BoolVar(&c.Verbose, "verbose", false, "print results for all resources (ignored for tap and junit output)")
|
||||||
flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure")
|
flags.BoolVar(&c.SkipTLS, "insecure-skip-tls-verify", false, "disable verification of the server's SSL certificate. This will make your HTTPS connections insecure")
|
||||||
|
flags.BoolVar(&c.SkipMetadata, "skip-metadata", false, "skip extra validations of metadata section")
|
||||||
flags.StringVar(&c.Cache, "cache", "", "cache schemas downloaded via HTTP to this folder")
|
flags.StringVar(&c.Cache, "cache", "", "cache schemas downloaded via HTTP to this folder")
|
||||||
flags.BoolVar(&c.Help, "h", false, "show help information")
|
flags.BoolVar(&c.Help, "h", false, "show help information")
|
||||||
flags.BoolVar(&c.Version, "v", false, "show version information")
|
flags.BoolVar(&c.Version, "v", false, "show version information")
|
||||||
|
|
|
||||||
35
pkg/registry/embeded.go
Normal file
35
pkg/registry/embeded.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.json
|
||||||
|
var content embed.FS
|
||||||
|
|
||||||
|
type EmbeddedRegistry struct {
|
||||||
|
debug bool
|
||||||
|
strict bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEmbeddedRegistry creates a new "registry", that will serve schemas from embedded resource
|
||||||
|
func NewEmbeddedRegistry(debug bool, strict bool) *EmbeddedRegistry {
|
||||||
|
return &EmbeddedRegistry{
|
||||||
|
debug, strict,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadSchema retrieves the schema from a file for the resource
|
||||||
|
func (r EmbeddedRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersion string) (string, []byte, error) {
|
||||||
|
var fileName string
|
||||||
|
if r.strict {
|
||||||
|
fileName = resourceKind + "-strict.json"
|
||||||
|
} else {
|
||||||
|
fileName = resourceKind + ".json"
|
||||||
|
}
|
||||||
|
bytes, err := content.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return resourceKind, nil, nil
|
||||||
|
}
|
||||||
|
return "embedded:" + resourceKind, bytes, nil
|
||||||
|
}
|
||||||
110
pkg/registry/metadata-strict.json
Normal file
110
pkg/registry/metadata-strict.json
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiVersion": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"$ref": "#/$defs/NAME"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"patternProperties": {
|
||||||
|
"^.+$": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"finalizers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generateName": {
|
||||||
|
"$ref": "#/$defs/RFC-1123-prefix"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"patternProperties": {
|
||||||
|
"^.+$": {
|
||||||
|
"$ref": "#/$defs/NAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"managedFields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/$defs/RFC-1123"
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/$defs/RFC-1123"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": [
|
||||||
|
"generateName"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"apiVersion",
|
||||||
|
"kind",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"$defs": {
|
||||||
|
"PREFIXED": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"pattern": "^(.{0,253}/)?.{1,63}$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^([a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})*/)?[a-z0-9A-Z]+([_.-][a-z0-9A-Z]+)*$"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NAME": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 63,
|
||||||
|
"pattern": "^[a-z0-9A-Z]+([_.-][a-z0-9A-Z]+)*$"
|
||||||
|
},
|
||||||
|
"RFC-1123": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 63,
|
||||||
|
"pattern": "^[a-z0-9]+(-+[a-z0-9]+)*$"
|
||||||
|
},
|
||||||
|
"RFC-1123-prefix": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 58,
|
||||||
|
"pattern": "^[a-z0-9]+[a-z0-9-]*$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
109
pkg/registry/metadata.json
Normal file
109
pkg/registry/metadata.json
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"apiVersion": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"kind": {
|
||||||
|
"$ref": "#/$defs/NAME"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"annotations": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"patternProperties": {
|
||||||
|
"^.+$": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"finalizers": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"generateName": {
|
||||||
|
"$ref": "#/$defs/RFC-1123-prefix"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "object",
|
||||||
|
"propertyNames": {
|
||||||
|
"$ref": "#/$defs/PREFIXED"
|
||||||
|
},
|
||||||
|
"patternProperties": {
|
||||||
|
"^.+$": {
|
||||||
|
"$ref": "#/$defs/NAME"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"managedFields": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"$ref": "#/$defs/RFC-1123"
|
||||||
|
},
|
||||||
|
"namespace": {
|
||||||
|
"$ref": "#/$defs/RFC-1123"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"required": [
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"required": [
|
||||||
|
"generateName"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"apiVersion",
|
||||||
|
"kind",
|
||||||
|
"metadata"
|
||||||
|
],
|
||||||
|
"$defs": {
|
||||||
|
"PREFIXED": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"pattern": "^(.{0,253}/)?.{1,63}$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pattern": "^([a-z0-9-]{1,63}(\\.[a-z0-9-]{1,63})*/)?[a-z0-9A-Z]+([_.-][a-z0-9A-Z]+)*$"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"NAME": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 63,
|
||||||
|
"pattern": "^[a-z0-9A-Z]+([_.-][a-z0-9A-Z]+)*$"
|
||||||
|
},
|
||||||
|
"RFC-1123": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 63,
|
||||||
|
"pattern": "^[a-z0-9]+(-+[a-z0-9]+)*$"
|
||||||
|
},
|
||||||
|
"RFC-1123-prefix": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1,
|
||||||
|
"maxLength": 58,
|
||||||
|
"pattern": "^[a-z0-9]+[a-z0-9-]*$"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,6 +58,7 @@ type Opts struct {
|
||||||
Debug bool // Debug infos will be print here
|
Debug bool // Debug infos will be print here
|
||||||
SkipTLS bool // skip TLS validation when downloading from an HTTP Schema Registry
|
SkipTLS bool // skip TLS validation when downloading from an HTTP Schema Registry
|
||||||
SkipKinds map[string]struct{} // List of resource Kinds to ignore
|
SkipKinds map[string]struct{} // List of resource Kinds to ignore
|
||||||
|
SkipMetadata bool // skip extra validation of metadata
|
||||||
RejectKinds map[string]struct{} // List of resource Kinds to reject
|
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
|
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
|
Strict bool // thros an error if resources contain undocumented fields
|
||||||
|
|
@ -92,11 +93,17 @@ func New(schemaLocations []string, opts Opts) (Validator, error) {
|
||||||
opts.RejectKinds = map[string]struct{}{}
|
opts.RejectKinds = map[string]struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var metadataSchema *jsonschema.Schema = nil
|
||||||
|
if !opts.SkipMetadata {
|
||||||
|
metadataSchema, _ = downloadSchema([]registry.Registry{registry.NewEmbeddedRegistry(opts.Debug, opts.Strict)}, "metadata", "", "")
|
||||||
|
}
|
||||||
|
|
||||||
return &v{
|
return &v{
|
||||||
opts: opts,
|
opts: opts,
|
||||||
schemaDownload: downloadSchema,
|
schemaDownload: downloadSchema,
|
||||||
schemaCache: cache.NewInMemoryCache(),
|
schemaCache: cache.NewInMemoryCache(),
|
||||||
regs: registries,
|
regs: registries,
|
||||||
|
metadataSchema: metadataSchema,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +112,23 @@ type v struct {
|
||||||
schemaCache cache.Cache
|
schemaCache cache.Cache
|
||||||
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error)
|
schemaDownload func(registries []registry.Registry, kind, version, k8sVersion string) (*jsonschema.Schema, error)
|
||||||
regs []registry.Registry
|
regs []registry.Registry
|
||||||
|
metadataSchema *jsonschema.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(schema *jsonschema.Schema, r map[string]interface{}, validationErrors []ValidationError) ([]ValidationError, error) {
|
||||||
|
err := schema.Validate(r)
|
||||||
|
if err != nil {
|
||||||
|
var e *jsonschema.ValidationError
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
for _, ve := range e.Causes {
|
||||||
|
validationErrors = append(validationErrors, ValidationError{
|
||||||
|
Path: ve.InstanceLocation,
|
||||||
|
Msg: ve.Message,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validationErrors, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateResource validates a single resource. This allows to validate
|
// ValidateResource validates a single resource. This allows to validate
|
||||||
|
|
@ -162,6 +186,12 @@ func (val *v) ValidateResource(res resource.Resource) Result {
|
||||||
return Result{Resource: res, Err: fmt.Errorf("prohibited resource kind %s", sig.Kind), Status: Error}
|
return Result{Resource: res, Err: fmt.Errorf("prohibited resource kind %s", sig.Kind), Status: Error}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validationErrors := []ValidationError{}
|
||||||
|
var metaDataError error = nil
|
||||||
|
if val.metadataSchema != nil {
|
||||||
|
validationErrors, metaDataError = validate(val.metadataSchema, r, validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
cached := false
|
cached := false
|
||||||
var schema *jsonschema.Schema
|
var schema *jsonschema.Schema
|
||||||
|
|
||||||
|
|
@ -183,27 +213,21 @@ func (val *v) ValidateResource(res resource.Resource) Result {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status := Valid
|
||||||
if schema == nil {
|
if schema == nil {
|
||||||
if val.opts.IgnoreMissingSchemas {
|
if val.opts.IgnoreMissingSchemas {
|
||||||
return Result{Resource: res, Err: nil, Status: Skipped}
|
status = Skipped
|
||||||
|
} else {
|
||||||
|
return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
validationErrors, err = validate(schema, r, validationErrors)
|
||||||
|
if err == nil {
|
||||||
|
err = metaDataError
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result{Resource: res, Err: fmt.Errorf("could not find schema for %s", sig.Kind), Status: Error}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = schema.Validate(r)
|
if len(validationErrors) > 0 {
|
||||||
if err != nil {
|
|
||||||
validationErrors := []ValidationError{}
|
|
||||||
var e *jsonschema.ValidationError
|
|
||||||
if errors.As(err, &e) {
|
|
||||||
for _, ve := range e.Causes {
|
|
||||||
validationErrors = append(validationErrors, ValidationError{
|
|
||||||
Path: ve.InstanceLocation,
|
|
||||||
Msg: ve.Message,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return Result{
|
return Result{
|
||||||
Resource: res,
|
Resource: res,
|
||||||
Status: Invalid,
|
Status: Invalid,
|
||||||
|
|
@ -211,8 +235,7 @@ func (val *v) ValidateResource(res resource.Resource) Result {
|
||||||
ValidationErrors: validationErrors,
|
ValidationErrors: validationErrors,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Result{Resource: res, Status: status}
|
||||||
return Result{Resource: res, Status: Valid}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateWithContext validates resources found in r
|
// ValidateWithContext validates resources found in r
|
||||||
|
|
|
||||||
|
|
@ -381,6 +381,7 @@ lastName: bar
|
||||||
val := v{
|
val := v{
|
||||||
opts: Opts{
|
opts: Opts{
|
||||||
SkipKinds: map[string]struct{}{},
|
SkipKinds: map[string]struct{}{},
|
||||||
|
SkipMetadata: true,
|
||||||
RejectKinds: map[string]struct{}{},
|
RejectKinds: map[string]struct{}{},
|
||||||
IgnoreMissingSchemas: testCase.ignoreMissingSchema,
|
IgnoreMissingSchemas: testCase.ignoreMissingSchema,
|
||||||
Strict: testCase.strict,
|
Strict: testCase.strict,
|
||||||
|
|
@ -453,8 +454,9 @@ age: not a number
|
||||||
|
|
||||||
val := v{
|
val := v{
|
||||||
opts: Opts{
|
opts: Opts{
|
||||||
SkipKinds: map[string]struct{}{},
|
SkipKinds: map[string]struct{}{},
|
||||||
RejectKinds: map[string]struct{}{},
|
SkipMetadata: true,
|
||||||
|
RejectKinds: map[string]struct{}{},
|
||||||
},
|
},
|
||||||
schemaCache: nil,
|
schemaCache: nil,
|
||||||
schemaDownload: downloadSchema,
|
schemaDownload: downloadSchema,
|
||||||
|
|
@ -502,8 +504,9 @@ firstName: foo
|
||||||
|
|
||||||
val := v{
|
val := v{
|
||||||
opts: Opts{
|
opts: Opts{
|
||||||
SkipKinds: map[string]struct{}{},
|
SkipKinds: map[string]struct{}{},
|
||||||
RejectKinds: map[string]struct{}{},
|
SkipMetadata: true,
|
||||||
|
RejectKinds: map[string]struct{}{},
|
||||||
},
|
},
|
||||||
schemaCache: nil,
|
schemaCache: nil,
|
||||||
schemaDownload: downloadSchema,
|
schemaDownload: downloadSchema,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ Usage: ./bin/kubeconform [OPTION]... [FILE OR FOLDER]...
|
||||||
override schemas location search path (can be specified multiple times)
|
override schemas location search path (can be specified multiple times)
|
||||||
-skip string
|
-skip string
|
||||||
comma-separated list of kinds to ignore
|
comma-separated list of kinds to ignore
|
||||||
|
-skip-metadata
|
||||||
|
skip extra validations of metadata section
|
||||||
-strict
|
-strict
|
||||||
disallow additional properties not in schema or duplicated keys
|
disallow additional properties not in schema or duplicated keys
|
||||||
-summary
|
-summary
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue