Updated names for schema-location vars, added documentation, updated openapi2jsonschema

This commit is contained in:
Yann Hamon 2020-11-15 16:19:49 +01:00
parent 300b571c33
commit 4ae74305d1
6 changed files with 59 additions and 23 deletions

View file

@ -142,7 +142,7 @@ All 3 following command lines are equivalent:
``` ```
$ ./bin/kubeconform fixtures/valid.yaml $ ./bin/kubeconform fixtures/valid.yaml
$ ./bin/kubeconform -schema-location https://kubernetesjsonschema.dev fixtures/valid.yaml $ ./bin/kubeconform -schema-location https://kubernetesjsonschema.dev fixtures/valid.yaml
$ ./bin/kubeconform -schema-location 'https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml $ ./bin/kubeconform -schema-location 'https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json' fixtures/valid.yaml
``` ```
To support validating CRDs, we need to convert OpenAPI files to JSON schema, storing the JSON schemas To support validating CRDs, we need to convert OpenAPI files to JSON schema, storing the JSON schemas
@ -157,10 +157,17 @@ You can validate Openshift manifests using a custom schema location. Set the Ope
against using -kubernetes-version. against using -kubernetes-version.
``` ```
bin/kubeconform -kubernetes-version 3.8.0 -schema-location 'https://raw.githubusercontent.com/garethr/openshift-json-schema/master/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}.json' -summary fixtures/valid.yaml bin/kubeconform -kubernetes-version 3.8.0 -schema-location 'https://raw.githubusercontent.com/garethr/openshift-json-schema/master/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}.json' -summary fixtures/valid.yaml
Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0 Summary: 1 resource found in 1 file - Valid: 1, Invalid: 0, Errors: 0 Skipped: 0
``` ```
Here are the variables you can use in -schema-location:
* *NormalizedKubernetesVersion* - Kubernetes Version, prefixed by v
* *StrictSuffix* - "-strict" or "" depending on whether validation is running in strict mode or not
* *ResourceKind* - Kind of the Kubernetes Resource
* *ResourceAPIVersion* - Version of API used for the resource - "v1" in "apiVersion: monitoring.coreos.com/v1"
* *KindSuffix* - suffix computed from apiVersion - for compatibility with Kubeval schema registries
### Converting an OpenAPI file to a JSON Schema ### Converting an OpenAPI file to a JSON Schema
Kubeconform uses JSON schemas to validate Kubernetes resources. For Custom Resource, the CustomResourceDefinition Kubeconform uses JSON schemas to validate Kubernetes resources. For Custom Resource, the CustomResourceDefinition
@ -168,7 +175,8 @@ first needs to be converted to JSON Schema. A script is provided to convert thes
to JSON schema. Here is an example how to use it: to JSON schema. Here is an example how to use it:
``` ```
$ ./scripts/openapi2jsonschema.py https://raw.githubusercontent.com/aws/amazon-sagemaker-operator-for-k8s/master/config/crd/bases/sagemaker.aws.amazon.com_trainingjobs.yaml > fixtures/registry/trainingjob-sagemaker-v1.json $ ./scripts/openapi2jsonschema.py https://raw.githubusercontent.com/aws/amazon-sagemaker-operator-for-k8s/master/config/crd/bases/sagemaker.aws.amazon.com_trainingjobs.yaml
JSON schema written to trainingjob_v1.json
``` ```
### Speed comparison with Kubeval ### Speed comparison with Kubeval

View file

@ -36,7 +36,6 @@ func (r LocalRegistry) DownloadSchema(resourceKind, resourceAPIVersion, k8sVersi
if err != nil { if err != nil {
return []byte{}, nil return []byte{}, nil
} }
f, err := os.Open(schemaFile) f, err := os.Open(schemaFile)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {

View file

@ -45,14 +45,16 @@ func schemaPath(tpl, resourceKind, resourceAPIVersion, k8sVersion string, strict
} }
tplData := struct { tplData := struct {
NormalizedVersion string NormalizedKubernetesVersion string
StrictSuffix string StrictSuffix string
ResourceKind string ResourceKind string
KindSuffix string ResourceAPIVersion string
KindSuffix string
}{ }{
normalisedVersion, normalisedVersion,
strictSuffix, strictSuffix,
strings.ToLower(resourceKind), strings.ToLower(resourceKind),
groupParts[len(groupParts)-1],
kindSuffix, kindSuffix,
} }

View file

@ -11,7 +11,7 @@ func TestSchemaPath(t *testing.T) {
errExpected error errExpected error
}{ }{
{ {
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json", "https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Deployment", "Deployment",
"apps/v1", "apps/v1",
"1.16.0", "1.16.0",
@ -20,7 +20,7 @@ func TestSchemaPath(t *testing.T) {
nil, nil,
}, },
{ {
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json", "https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Deployment", "Deployment",
"apps/v1", "apps/v1",
"1.16.0", "1.16.0",
@ -29,7 +29,7 @@ func TestSchemaPath(t *testing.T) {
nil, nil,
}, },
{ {
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json", "https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Service", "Service",
"v1", "v1",
"1.18.0", "1.18.0",
@ -38,7 +38,7 @@ func TestSchemaPath(t *testing.T) {
nil, nil,
}, },
{ {
"https://kubernetesjsonschema.dev/{{ .NormalizedVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json", "https://kubernetesjsonschema.dev/{{ .NormalizedKubernetesVersion }}-standalone{{ .StrictSuffix }}/{{ .ResourceKind }}{{ .KindSuffix }}.json",
"Service", "Service",
"v1", "v1",
"master", "master",

View file

@ -45,6 +45,9 @@ func FromStream(ctx context.Context, path string, r io.Reader) (<-chan Resource,
go func() { go func() {
scanner := bufio.NewScanner(r) scanner := bufio.NewScanner(r)
const maxResourceSize = 1024 * 1024
buf := make([]byte, maxResourceSize)
scanner.Buffer(buf, maxResourceSize)
scanner.Split(SplitYAMLDocument) scanner.Split(SplitYAMLDocument)
SCAN: SCAN:

View file

@ -4,6 +4,7 @@
import yaml import yaml
import json import json
import sys import sys
import os
import urllib.request import urllib.request
def iteritems(d): def iteritems(d):
@ -91,14 +92,37 @@ if len(sys.argv) == 0:
print("missing file") print("missing file")
exit(1) exit(1)
if sys.argv[1].startswith("http"): for crdFile in sys.argv[1:]:
f = urllib.request.urlopen(sys.argv[1]) if crdFile.startswith("http"):
else: f = urllib.request.urlopen(crdFile)
f = open(sys.argv[1]) else:
with f: f = open(crdFile)
y = yaml.load(f, Loader=yaml.SafeLoader) with f:
schema = y["spec"]["validation"]["openAPIV3Schema"] y = yaml.load(f, Loader=yaml.SafeLoader)
schema = additional_properties(schema) filename = ""
schema = replace_int_or_string(schema) schemaJSON = ""
print(json.dumps(schema)) if "spec" in y and "validation" in y["spec"] and "openAPIV3Schema" in y["spec"]["validation"]:
exit(0) filename = y["spec"]["names"]["kind"].lower()+"_"+y["spec"]["version"].lower()+".json"
schema = y["spec"]["validation"]["openAPIV3Schema"]
schema = additional_properties(schema)
schema = replace_int_or_string(schema)
schemaJSON = json.dumps(schema)
elif "spec" in y and "versions" in y["spec"]:
for version in y["spec"]["versions"]:
if "schema" in version and "openAPIV3Schema" in version["schema"]:
filename = y["spec"]["names"]["kind"].lower()+"_"+version["name"].lower()+".json"
schema = version["schema"]["openAPIV3Schema"]
schema = additional_properties(schema)
schema = replace_int_or_string(schema)
schemaJSON = json.dumps(schema)
# Dealing with user input here..
filename = os.path.basename(filename)
f = open(filename, "w")
f.write(schemaJSON)
f.close()
print("JSON schema written to {filename}".format(filename=filename))
exit(0)