Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
H
helm3
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
go
helm3
Commits
6e15ece7
Commit
6e15ece7
authored
Nov 24, 2015
by
Graham Welch
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use new schema syntax and recursively set default values. Includes unit tests.
parent
9854ebd0
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
950 additions
and
98 deletions
+950
-98
schema_validation.py
expandybird/expansion/schema_validation.py
+56
-43
schema_validation_test.py
expandybird/expansion/schema_validation_test.py
+616
-0
schema_validation_utils.py
expandybird/expansion/schema_validation_utils.py
+26
-55
bad.jinja.schema
expandybird/test/schemas/bad.jinja.schema
+9
-0
default_ref.jinja.schema
expandybird/test/schemas/default_ref.jinja.schema
+14
-0
defaults.jinja.schema
expandybird/test/schemas/defaults.jinja.schema
+12
-0
defaults.py.schema
expandybird/test/schemas/defaults.py.schema
+12
-0
invalid_default.jinja.schema
expandybird/test/schemas/invalid_default.jinja.schema
+11
-0
invalid_reference.py.schema
expandybird/test/schemas/invalid_reference.py.schema
+10
-0
invalid_reference_schema.py.schema
expandybird/test/schemas/invalid_reference_schema.py.schema
+8
-0
metadata.py.schema
expandybird/test/schemas/metadata.py.schema
+20
-0
missing_quote.py.schema
expandybird/test/schemas/missing_quote.py.schema
+11
-0
nested_defaults.py.schema
expandybird/test/schemas/nested_defaults.py.schema
+33
-0
numbers.py.schema
expandybird/test/schemas/numbers.py.schema
+27
-0
ref_nested_defaults.py.schema
expandybird/test/schemas/ref_nested_defaults.py.schema
+36
-0
reference.jinja.schema
expandybird/test/schemas/reference.jinja.schema
+14
-0
req_default_ref.py.schema
expandybird/test/schemas/req_default_ref.py.schema
+14
-0
required.jinja.schema
expandybird/test/schemas/required.jinja.schema
+10
-0
required_default.jinja.schema
expandybird/test/schemas/required_default.jinja.schema
+11
-0
No files found.
expandybird/expansion/schema_validation.py
View file @
6e15ece7
...
...
@@ -26,13 +26,12 @@ PROPERTIES = "properties"
# This validator will set default values in properties.
# This does not return a complete set of errors; use only for setting defaults.
# Pass this object a schema to get a validator for that schema.
DEFAULT_
VALIDATOR
=
schema_validation_utils
.
OnlyValidateProperties
(
schema_validation_utils
.
ExtendWithDefault
(
jsonschema
.
Draft4Validator
)
)
DEFAULT_
SETTER
=
schema_validation_utils
.
ExtendWithDefault
(
jsonschema
.
Draft4Validator
)
# This is a regular validator, use after using the DEFAULT_
VALIDATO
R
# This is a regular validator, use after using the DEFAULT_
SETTE
R
# Pass this object a schema to get a validator for that schema.
VALIDATOR
=
schema_validation_utils
.
OnlyValidateProperties
(
jsonschema
.
Draft4Validator
)
VALIDATOR
=
jsonschema
.
Draft4Validator
# This is a validator using the default Draft4 metaschema,
# use it to validate user schemas.
...
...
@@ -61,29 +60,61 @@ IMPORT_SCHEMA_VALIDATOR = jsonschema.Draft4Validator(
yaml
.
safe_load
(
IMPORT_SCHEMA
))
def
_ValidateSchema
(
schema
,
validating_imports
,
schema_name
,
template_name
):
"""Validate that the passed in schema file is correctly formatted.
Args:
schema: contents of the schema file
validating_imports: boolean, if we should validate the 'imports'
section of the schema
schema_name: name of the schema file to validate
template_name: name of the template whose properties are being validated
Raises:
ValidationErrors: A list of ValidationError errors that occured when
validating the schema file
"""
schema_errors
=
[]
# Validate the syntax of the optional "imports:" section of the schema
if
validating_imports
:
schema_errors
.
extend
(
IMPORT_SCHEMA_VALIDATOR
.
iter_errors
(
schema
))
# Validate the syntax of the jsonSchema section of the schema
try
:
schema_errors
.
extend
(
SCHEMA_VALIDATOR
.
iter_errors
(
schema
))
except
jsonschema
.
RefResolutionError
as
e
:
# Calls to iter_errors could throw a RefResolution exception
raise
ValidationErrors
(
schema_name
,
template_name
,
[
e
],
is_schema_error
=
True
)
if
schema_errors
:
raise
ValidationErrors
(
schema_name
,
template_name
,
schema_errors
,
is_schema_error
=
True
)
def
Validate
(
properties
,
schema_name
,
template_name
,
imports
):
"""Given a set of properties, validates it against the given schema.
Args:
properties: dict, the properties to be validated
schema_name: name of the schema file to validate
template_name: name of the template whose's properties are being validated
imports: map from string to string, the map of imported files names
and contents
template_name: name of the template whose properties are being validated
imports: the map of imported files names to file contents
Returns:
Dict containing the validated properties, with defaults filled in
Raises:
ValidationErrors: A list of ValidationError errors that occurred when
validating the properties and schema
validating the properties and schema,
or if the schema file was not found
"""
if
schema_name
not
in
imports
:
raise
ValidationErrors
(
schema_name
,
template_name
,
[
"Could not find schema file '"
+
schema_name
+
"'."
])
else
:
raw_schema
=
imports
[
schema_name
]
[
"Could not find schema file '
%
s'."
%
schema_name
])
raw_schema
=
imports
[
schema_name
]
if
properties
is
None
:
properties
=
{}
...
...
@@ -91,33 +122,14 @@ def Validate(properties, schema_name, template_name, imports):
schema
=
yaml
.
safe_load
(
raw_schema
)
# If the schema is empty, do nothing.
if
schema
is
None
:
if
not
schema
:
return
properties
schema_errors
=
[]
validating_imports
=
IMPORTS
in
schema
and
schema
[
IMPORTS
]
# Validate the syntax of the optional "imports:" section of the schema
if
validating_imports
:
schema_errors
.
extend
(
list
(
IMPORT_SCHEMA_VALIDATOR
.
iter_errors
(
schema
)))
# Validate the syntax of the optional "properties:" section of the schema
if
PROPERTIES
in
schema
and
schema
[
PROPERTIES
]:
try
:
schema_errors
.
extend
(
list
(
SCHEMA_VALIDATOR
.
iter_errors
(
schema
[
PROPERTIES
])))
except
jsonschema
.
RefResolutionError
as
e
:
# Calls to iter_errors could throw a RefResolution exception
raise
ValidationErrors
(
schema_name
,
template_name
,
list
(
e
),
is_schema_error
=
True
)
if
schema_errors
:
raise
ValidationErrors
(
schema_name
,
template_name
,
schema_errors
,
is_schema_error
=
True
)
# If this doesn't raise any exceptions, we can assume we have a valid schema
_ValidateSchema
(
schema
,
validating_imports
,
schema_name
,
template_name
)
######
# Assume we have a valid schema
######
errors
=
[]
# Validate that all files specified as "imports:" were included
...
...
@@ -131,24 +143,25 @@ def Validate(properties, schema_name, template_name, imports):
import_name
=
import_object
[
"path"
]
if
import_name
not
in
imports
:
errors
.
append
((
"File '"
+
import_name
+
"' requested in schema '"
+
schema_name
+
"' but not included with imports."
))
errors
.
append
((
"File '
%
s' requested in schema '
%
s' "
"but not included with imports."
%
(
import_name
,
schema_name
)))
try
:
# This code block uses DEFAULT_
VALIDATO
R and VALIDATOR for two very
# This code block uses DEFAULT_
SETTE
R and VALIDATOR for two very
# different purposes.
# DEFAULT_
VALIDATO
R is based on JSONSchema 4, but uses modified validators:
# DEFAULT_
SETTE
R is based on JSONSchema 4, but uses modified validators:
# - The 'required' validator does nothing
# - The 'properties' validator sets default values on user properties
# With these changes, the validator does not report errors correctly.
#
# So, we do error reporting in two steps:
# 1) Use DEFAULT_
VALIDATO
R to set default values in the user's properties
# 1) Use DEFAULT_
SETTE
R to set default values in the user's properties
# 2) Use the unmodified VALIDATOR to report all of the errors
# Calling iter_errors mutates properties in place, adding default values.
# You must call list()! This is a generator, not a function!
list
(
DEFAULT_
VALIDATO
R
(
schema
)
.
iter_errors
(
properties
))
list
(
DEFAULT_
SETTE
R
(
schema
)
.
iter_errors
(
properties
))
# Now that we have default values, validate the properties
errors
.
extend
(
list
(
VALIDATOR
(
schema
)
.
iter_errors
(
properties
)))
...
...
@@ -158,7 +171,7 @@ def Validate(properties, schema_name, template_name, imports):
except
jsonschema
.
RefResolutionError
as
e
:
# Calls to iter_errors could throw a RefResolution exception
raise
ValidationErrors
(
schema_name
,
template_name
,
list
(
e
)
,
is_schema_error
=
True
)
[
e
]
,
is_schema_error
=
True
)
except
TypeError
as
e
:
raise
ValidationErrors
(
schema_name
,
template_name
,
...
...
@@ -191,7 +204,7 @@ class ValidationErrors(Exception):
message
=
"Invalid properties for '
%
s':
\n
"
%
self
.
template_name
for
error
in
self
.
errors
:
if
type
(
error
)
is
jsonschema
.
exceptions
.
ValidationError
:
if
isinstance
(
error
,
jsonschema
.
exceptions
.
ValidationError
)
:
error_message
=
error
.
message
location
=
list
(
error
.
path
)
if
location
and
len
(
location
):
...
...
expandybird/expansion/schema_validation_test.py
0 → 100644
View file @
6e15ece7
######################################################################
# Copyright 2015 The Kubernetes Authors All rights reserved.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
######################################################################
import
os
import
unittest
import
schema_validation
import
yaml
INVALID_PROPERTIES
=
"Invalid properties for 'template.py'"
def
GetFilePath
():
"""Find our source and data files."""
return
os
.
path
.
dirname
(
os
.
path
.
abspath
(
__file__
))
def
ReadTestFile
(
filename
):
"""Returns contents of a file from the testdata/ directory."""
full_path
=
os
.
path
.
join
(
GetFilePath
(),
'..'
,
'test'
,
'schemas'
,
filename
)
return
open
(
full_path
,
'r'
)
.
read
()
def
RawValidate
(
raw_properties
,
schema_name
,
raw_schema
):
return
ImportsRawValidate
(
raw_properties
,
schema_name
,
{
schema_name
:
raw_schema
})
def
ImportsRawValidate
(
raw_properties
,
schema_name
,
import_map
):
"""Takes raw properties, calls validate and returns yaml properties."""
properties
=
yaml
.
safe_load
(
raw_properties
)
return
schema_validation
.
Validate
(
properties
,
schema_name
,
'template.py'
,
import_map
)
class
SchemaValidationTest
(
unittest
.
TestCase
):
"""Tests of the schema portion of the template expansion library."""
def
testDefaults
(
self
):
schema_name
=
'defaults.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
expected_properties
=
"""
alpha: alpha
one: 1
"""
self
.
assertEqual
(
yaml
.
safe_load
(
expected_properties
),
RawValidate
(
empty_properties
,
schema_name
,
schema
))
def
testNestedDefaults
(
self
):
schema_name
=
'nested_defaults.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
zone: us-central1-a
disks:
- name: backup # diskType and sizeGb set by default
- name: cache # sizeGb set by default
diskType: pd-ssd
- name: data # Nothing set by default
diskType: pd-ssd
sizeGb: 150
- name: swap # diskType set by default
sizeGb: 200
"""
expected_properties
=
"""
zone: us-central1-a
disks:
- sizeGb: 100
diskType: pd-standard
name: backup
- sizeGb: 100
diskType: pd-ssd
name: cache
- sizeGb: 150
diskType: pd-ssd
name: data
- sizeGb: 200
diskType: pd-standard
name: swap
"""
self
.
assertEqual
(
yaml
.
safe_load
(
expected_properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testNestedRefDefaults
(
self
):
schema_name
=
'ref_nested_defaults.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
zone: us-central1-a
disks:
- name: backup # diskType and sizeGb set by default
- name: cache # sizeGb set by default
diskType: pd-ssd
- name: data # Nothing set by default
diskType: pd-ssd
sizeGb: 150
- name: swap # diskType set by default
sizeGb: 200
"""
expected_properties
=
"""
zone: us-central1-a
disks:
- sizeGb: 100
diskType: pd-standard
name: backup
- sizeGb: 100
diskType: pd-ssd
name: cache
- sizeGb: 150
diskType: pd-ssd
name: data
- sizeGb: 200
diskType: pd-standard
name: swap
"""
self
.
assertEqual
(
yaml
.
safe_load
(
expected_properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testInvalidDefault
(
self
):
schema_name
=
'invalid_default.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"'string' is not of type 'integer' at ['number']"
,
e
.
message
)
def
testRequiredDefault
(
self
):
schema_name
=
'required_default.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
expected_properties
=
"""
name: my_name
"""
self
.
assertEqual
(
yaml
.
safe_load
(
expected_properties
),
RawValidate
(
empty_properties
,
schema_name
,
schema
))
def
testRequiredDefaultReference
(
self
):
schema_name
=
'req_default_ref.py.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"'my_name' is not of type 'integer' at ['number']"
,
e
.
message
)
def
testDefaultReference
(
self
):
schema_name
=
'default_ref.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
expected_properties
=
'number: 1'
self
.
assertEqual
(
yaml
.
safe_load
(
expected_properties
),
RawValidate
(
empty_properties
,
schema_name
,
schema
))
def
testMissingQuoteInReference
(
self
):
schema_name
=
'missing_quote.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
'number: 1'
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
2
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"type 'NoneType' is not iterable"
,
e
.
message
)
self
.
assertIn
(
'around your reference'
,
e
.
message
)
def
testRequiredPropertyMissing
(
self
):
schema_name
=
'required.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"'name' is a required property"
,
e
.
errors
[
0
]
.
message
)
def
testRequiredPropertyValid
(
self
):
schema_name
=
'required.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
name: my-name
"""
self
.
assertEqual
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testMultipleErrors
(
self
):
schema_name
=
'defaults.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
one: not a number
alpha: 12345
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
2
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"'not a number' is not of type 'integer' at ['one']"
,
e
.
message
)
self
.
assertIn
(
"12345 is not of type 'string' at ['alpha']"
,
e
.
message
)
def
testNumbersValid
(
self
):
schema_name
=
'numbers.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
minimum0: 0
exclusiveMin0: 1
maximum10: 10
exclusiveMax10: 9
even: 20
odd: 21
"""
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testNumbersInvalid
(
self
):
schema_name
=
'numbers.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
minimum0: -1
exclusiveMin0: 0
maximum10: 11
exclusiveMax10: 10
even: 21
odd: 20
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
6
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"-1 is less than the minimum of 0 at ['minimum0']"
,
e
.
message
)
self
.
assertIn
((
'0 is less than or equal to the minimum of 0'
" at ['exclusiveMin0']"
),
e
.
message
)
self
.
assertIn
(
"11 is greater than the maximum of 10 at ['maximum10']"
,
e
.
message
)
self
.
assertIn
((
'10 is greater than or equal to the maximum of 10'
" at ['exclusiveMax10']"
),
e
.
message
)
self
.
assertIn
(
"21 is not a multiple of 2 at ['even']"
,
e
.
message
)
self
.
assertIn
(
"{'multipleOf': 2} is not allowed for 20 at ['odd']"
,
e
.
message
)
def
testReference
(
self
):
schema_name
=
'reference.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
odd: 6
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
'even'
,
e
.
message
)
self
.
assertIn
(
'is not allowed for 6'
,
e
.
message
)
def
testBadSchema
(
self
):
schema_name
=
'bad.jinja.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
2
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"u'minimum' is a dependency of u'exclusiveMinimum'"
,
e
.
message
)
self
.
assertIn
(
"0 is not of type u'boolean'"
,
e
.
message
)
def
testInvalidReference
(
self
):
schema_name
=
'invalid_reference.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
'odd: 1'
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
'Unresolvable JSON pointer'
,
e
.
message
)
def
testInvalidReferenceInSchema
(
self
):
schema_name
=
'invalid_reference_schema.py.schema'
schema
=
ReadTestFile
(
schema_name
)
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
'Unresolvable JSON pointer'
,
e
.
message
)
def
testMetadata
(
self
):
schema_name
=
'metadata.py.schema'
schema
=
ReadTestFile
(
schema_name
)
properties
=
"""
one: 2
alpha: beta
"""
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testInvalidInput
(
self
):
schema_name
=
'schema'
schema
=
"""
info:
title: Invalid Input
properties: invalid
"""
properties
=
"""
one: 2
alpha: beta
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"'invalid' is not of type u'object'"
,
e
.
message
)
def
testPattern
(
self
):
schema_name
=
'schema'
schema
=
r"""
properties:
bad-zone:
pattern: \w+-\w+-\w+
zone:
pattern: \w+-\w+-\w+
"""
properties
=
"""
bad-zone: abc
zone: us-central1-a
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
'Invalid properties'
,
e
.
message
)
self
.
assertIn
(
"'abc' does not match"
,
e
.
message
)
self
.
assertIn
(
'bad-zone'
,
e
.
message
)
def
testUniqueItems
(
self
):
schema_name
=
'schema'
schema
=
"""
properties:
bad-list:
type: array
uniqueItems: true
list:
type: array
uniqueItems: true
"""
properties
=
"""
bad-list:
- a
- b
- a
list:
- a
- b
- c
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
'Invalid properties'
,
e
.
message
)
self
.
assertIn
(
'has non-unique elements'
,
e
.
message
)
self
.
assertIn
(
'bad-list'
,
e
.
message
)
def
testUniqueItemsOnString
(
self
):
schema_name
=
'schema'
schema
=
"""
properties:
ok-string:
type: string
uniqueItems: true
string:
type: string
uniqueItems: true
"""
properties
=
"""
ok-string: aaa
string: abc
"""
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
schema_name
,
schema
))
def
testRequiredTopLevel
(
self
):
schema_name
=
'schema'
schema
=
"""
info:
title: Invalid Input
required:
- name
"""
properties
=
"""
one: 2
alpha: beta
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
INVALID_PROPERTIES
,
e
.
message
)
self
.
assertIn
(
"'name' is a required property"
,
e
.
message
)
def
testEmptySchemaProperties
(
self
):
schema_name
=
'schema'
schema
=
"""
info:
title: Empty Input
properties:
"""
properties
=
"""
one: 2
alpha: beta
"""
try
:
RawValidate
(
properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"None is not of type u'object' at [u'properties']"
,
e
.
message
)
def
testNoInput
(
self
):
schema
=
"""
info:
title: No other sections
"""
properties
=
"""
one: 2
alpha: beta
"""
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
'schema'
,
schema
))
def
testEmptySchema
(
self
):
schema
=
''
properties
=
"""
one: 2
alpha: beta
"""
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
RawValidate
(
properties
,
'schema'
,
schema
))
def
testImportPathSchema
(
self
):
schema
=
"""
imports:
- path: a
- path: path/to/b
name: b
"""
properties
=
"""
one: 2
alpha: beta
"""
import_map
=
{
'schema'
:
schema
,
'a'
:
''
,
'b'
:
''
}
self
.
assertEquals
(
yaml
.
safe_load
(
properties
),
ImportsRawValidate
(
properties
,
'schema'
,
import_map
))
def
testImportSchemaMissing
(
self
):
schema
=
''
empty_properties
=
''
try
:
properties
=
yaml
.
safe_load
(
empty_properties
)
schema_validation
.
Validate
(
properties
,
'schema'
,
'template'
,
{
'wrong_name'
:
schema
})
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Could not find schema file 'schema'"
,
e
.
message
)
def
testImportsMalformedNotAList
(
self
):
schema_name
=
'schema'
schema
=
"""
imports: not-a-list
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"is not of type 'array' at ['imports']"
,
e
.
message
)
def
testImportsMalformedMissingPath
(
self
):
schema_name
=
'schema'
schema
=
"""
imports:
- name: no_path.yaml
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"'path' is a required property"
,
e
.
message
)
def
testImportsMalformedNonunique
(
self
):
schema_name
=
'schema'
schema
=
"""
imports:
- path: a.yaml
name: a
- path: a.yaml
name: a
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
'non-unique elements'
,
e
.
message
)
def
testImportsMalformedAdditionalProperties
(
self
):
schema_name
=
'schema'
schema
=
"""
imports:
- path: a.yaml
gnome: a
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
1
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
'Additional properties are not allowed'
" ('gnome' was unexpected)"
,
e
.
message
)
def
testImportAndInputErrors
(
self
):
schema
=
"""
imports:
- path: file
required:
- name
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
'schema'
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
2
,
len
(
e
.
errors
))
self
.
assertIn
(
"'file' requested in schema 'schema'"
,
e
.
message
)
self
.
assertIn
(
"'name' is a required property"
,
e
.
message
)
def
testImportAndInputSchemaErrors
(
self
):
schema_name
=
'schema'
schema
=
"""
imports: not-a-list
required: not-a-list
"""
empty_properties
=
''
try
:
RawValidate
(
empty_properties
,
schema_name
,
schema
)
self
.
fail
(
'Validation should fail'
)
except
schema_validation
.
ValidationErrors
as
e
:
self
.
assertEqual
(
2
,
len
(
e
.
errors
))
self
.
assertIn
(
"Invalid schema '
%
s'"
%
schema_name
,
e
.
message
)
self
.
assertIn
(
"is not of type 'array' at ['imports']"
,
e
.
message
)
self
.
assertIn
(
"is not of type u'array' at [u'required']"
,
e
.
message
)
if
__name__
==
'__main__'
:
unittest
.
main
()
expandybird/expansion/schema_validation_utils.py
View file @
6e15ece7
...
...
@@ -15,35 +15,10 @@
import
jsonschema
DEFAULT
=
"default"
PROPERTIES
=
"properties"
REF
=
"$ref"
REQUIRED
=
"required"
def
OnlyValidateProperties
(
validator_class
):
"""Takes a validator and makes it process only the 'properties' top level.
Args:
validator_class: A class to add a new validator to
Returns:
A validator_class that will validate properties against things
under the top level "properties" field
"""
def
PropertiesValidator
(
unused_validator
,
inputs
,
instance
,
schema
):
if
inputs
is
None
:
inputs
=
{}
for
error
in
validator_class
(
schema
)
.
iter_errors
(
instance
,
inputs
):
yield
error
# This makes sure the only keyword jsonschema will validate is 'properties'
new_validators
=
ClearValidatorMap
(
validator_class
.
VALIDATORS
)
new_validators
.
update
({
PROPERTIES
:
PropertiesValidator
})
return
jsonschema
.
validators
.
extend
(
validator_class
,
new_validators
)
DEFAULT
=
'default'
PROPERTIES
=
'properties'
REF
=
'$ref'
REQUIRED
=
'required'
def
ExtendWithDefault
(
validator_class
):
...
...
@@ -55,33 +30,33 @@ def ExtendWithDefault(validator_class):
Returns:
A validator_class that will set default values and ignore required fields
"""
validate_properties
=
validator_class
.
VALIDATORS
[
'properties'
]
def
SetDefaultsInProperties
(
validator
,
properties
,
instance
,
unused_schema
):
if
properties
is
None
:
properties
=
{}
SetDefaults
(
validator
,
properties
,
instance
)
def
SetDefaultsInProperties
(
validator
,
user_schema
,
user_properties
,
parent_schema
)
:
SetDefaults
(
validator
,
user_schema
or
{},
user_properties
,
parent_schema
,
validate_properties
)
return
jsonschema
.
validators
.
extend
(
validator_class
,
{
PROPERTIES
:
SetDefaultsInProperties
,
REQUIRED
:
IgnoreKeyword
})
def
SetDefaults
(
validator
,
properties
,
instance
):
def
SetDefaults
(
validator
,
user_schema
,
user_properties
,
parent_schema
,
validate_properties
):
"""Populate the default values of properties.
Args:
validator: A generator that validates the "properties" keyword
properties: User properties on which to set defaults
instance: Piece of user schema containing "properties"
validator: A generator that validates the "properties" keyword of the schema
user_schema: Schema which might define defaults, might be a nested part of
the entire schema file.
user_properties: User provided values which we are setting defaults on
parent_schema: Schema object that contains the schema being evaluated on
this pass, user_schema.
validate_properties: Validator function, called recursively.
"""
if
not
properties
:
return
for
dm_property
,
subschema
in
properties
.
iteritems
():
# If the property already has a value, we don't need it's default
if
dm_property
in
instance
:
return
for
schema_property
,
subschema
in
user_schema
.
iteritems
():
# The ordering of these conditions assumes that '$ref' blocks override
# all other schema info, which is what the jsonschema library assumes.
...
...
@@ -89,17 +64,21 @@ def SetDefaults(validator, properties, instance):
# see if that reference defines a 'default' value
if
REF
in
subschema
:
out
=
ResolveReferencedDefault
(
validator
,
subschema
[
REF
])
instance
.
setdefault
(
dm
_property
,
out
)
user_properties
.
setdefault
(
schema
_property
,
out
)
# Otherwise, see if the subschema has a 'default' value
elif
DEFAULT
in
subschema
:
instance
.
setdefault
(
dm_property
,
subschema
[
DEFAULT
])
user_properties
.
setdefault
(
schema_property
,
subschema
[
DEFAULT
])
# Recursively apply defaults. This is a generator, so we must wrap with list()
list
(
validate_properties
(
validator
,
user_schema
,
user_properties
,
parent_schema
))
def
ResolveReferencedDefault
(
validator
,
ref
):
"""Resolves a reference, and returns any default value it defines.
Args:
validator: A generator th
e
validates the "$ref" keyword
validator: A generator th
at
validates the "$ref" keyword
ref: The target of the "$ref" keyword
Returns:
...
...
@@ -110,14 +89,6 @@ def ResolveReferencedDefault(validator, ref):
return
resolved
[
DEFAULT
]
def
ClearValidatorMap
(
validators
):
"""Remaps all JsonSchema validators to make them do nothing."""
ignore_validators
=
{}
for
keyword
in
validators
:
ignore_validators
.
update
({
keyword
:
IgnoreKeyword
})
return
ignore_validators
def
IgnoreKeyword
(
unused_validator
,
unused_required
,
unused_instance
,
unused_schema
):
"""Validator for JsonSchema that does nothing."""
...
...
expandybird/test/schemas/bad.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a lots of errors in it
imports:
properties:
exclusiveMin:
type: integer
exclusiveMinimum: 0
expandybird/test/schemas/default_ref.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a property that has a referenced default value
imports:
properties:
number:
$ref: '#/level/mult'
level:
mult:
type: integer
multipleOf: 1
default: 1
expandybird/test/schemas/defaults.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with properties that have default values
imports:
properties:
one:
type: integer
default: 1
alpha:
type: string
default: alpha
expandybird/test/schemas/defaults.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with properties that have default values
imports:
properties:
one:
type: integer
default: 1
alpha:
type: string
default: alpha
expandybird/test/schemas/invalid_default.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a required integer property that has a default string value
imports:
required:
- number
properties:
number:
type: integer
default: string
expandybird/test/schemas/invalid_reference.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with references to something that doesnt exist
imports:
properties:
odd:
type: integer
not:
$ref: '#/wheeeeeee'
expandybird/test/schemas/invalid_reference_schema.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with references to something that doesnt exist
imports:
properties:
odd:
$ref: '#/wheeeeeee'
expandybird/test/schemas/metadata.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with properties that have extra metadata
imports:
properties:
one:
type: integer
default: 1
metadata:
gcloud: is great!
compute: is awesome
alpha:
type: string
default: alpha
metadata:
- you
- can
- do
- anything
expandybird/test/schemas/missing_quote.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with references
imports:
properties:
number:
$ref: #/number
number:
type: integer
expandybird/test/schemas/nested_defaults.py.schema
0 → 100644
View file @
6e15ece7
info:
title: VM with Disks
author: Kubernetes
description: Creates a single vm, then attaches disks to it.
required:
- zone
properties:
zone:
type: string
description: GCP zone
default: us-central1-a
disks:
type: array
items:
type: object
required:
- name
properties:
name:
type: string
description: Suffix for this disk
sizeGb:
type: integer
default: 100
diskType:
type: string
enum:
- pd-standard
- pd-ssd
default: pd-standard
additionalProperties: false
expandybird/test/schemas/numbers.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a lots of number properties and restrictions
imports:
properties:
minimum0:
type: integer
minimum: 0
exclusiveMin0:
type: integer
minimum: 0
exclusiveMinimum: true
maximum10:
type: integer
maximum: 10
exclusiveMax10:
type: integer
maximum: 10
exclusiveMaximum: true
even:
type: integer
multipleOf: 2
odd:
type: integer
not:
multipleOf: 2
expandybird/test/schemas/ref_nested_defaults.py.schema
0 → 100644
View file @
6e15ece7
info:
title: VM with Disks
author: Kubernetes
description: Creates a single vm, then attaches disks to it.
required:
- zone
properties:
zone:
type: string
description: GCP zone
default: us-central1-a
disks:
type: array
items:
$ref: '#/disk'
disk:
type: object
required:
- name
properties:
name:
type: string
description: Suffix for this disk
sizeGb:
type: integer
default: 100
diskType:
type: string
enum:
- pd-standard
- pd-ssd
default: pd-standard
additionalProperties: false
expandybird/test/schemas/reference.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with references
imports:
properties:
odd:
type: integer
not:
$ref: '#/even'
even:
multipleOf: 2
expandybird/test/schemas/req_default_ref.py.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a required property that has a referenced default value
imports:
required:
- number
properties:
number:
$ref: '#/default_val'
default_val:
type: integer
default: my_name
expandybird/test/schemas/required.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a required property
imports:
required:
- name
properties:
name:
type: string
expandybird/test/schemas/required_default.jinja.schema
0 → 100644
View file @
6e15ece7
info:
title: Schema with a required property that has a default value
imports:
required:
- name
properties:
name:
type: string
default: my_name
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment