Deckhand schemas as YAML files

Use YAML formatting for built-in Deckhand schemas
used for validations to align with other UCP services.

The second most important intention behind this PS
is to allow pre_validate flag to cascade correctly
between the layering and document_validation modules.

If pre_validate is true, then:
  * the base_schema validates ALL documents
  * ALL built-in schemas validate the appropriate
    document given a schema match
  * NO externally registered DataSchema documents
    are used for validation

Else (if pre_validate is false):
  * the base_schema validates ALL documents
  * ALL built-in schemas validate the appropriate
    document given a schema match
  * ALL externally registered DataSchema documents
    are used for validation given a schema match

A more minor change is setting pre_validate flags in
all modules to True for consistency. The idea is to
facilitate the way other projects that import Deckhand
in directly interface with Deckhand.

Change-Id: I859f61989ec15bede1c104b86625d116064f056d
This commit is contained in:
Felipe Monteiro 2018-01-20 01:24:14 +00:00 committed by Tin Lam
parent 218beff695
commit e0fc59e89b
46 changed files with 1376 additions and 1291 deletions

View File

@ -186,7 +186,7 @@ class RenderedDocumentsResource(api_base.BaseResource):
data_schemas = db_api.revision_documents_get(
schema=types.DATA_SCHEMA_SCHEMA, deleted=False)
doc_validator = document_validation.DocumentValidation(
rendered_documents, data_schemas)
rendered_documents, data_schemas, pre_validate=False)
try:
validations = doc_validator.validate_all()
except errors.InvalidDocumentFormat as e:

View File

@ -13,15 +13,17 @@
# limitations under the License.
import abc
import copy
import os
import pkg_resources
import re
import yaml
import jsonschema
from oslo_log import log as logging
import six
from deckhand.engine import document_wrapper
from deckhand.engine.schema import base_schema
from deckhand.engine.schema import v1_0
from deckhand.engine.secrets_manager import SecretsSubstitution
from deckhand import errors
from deckhand import types
@ -29,6 +31,48 @@ from deckhand import utils
LOG = logging.getLogger(__name__)
_DEFAULT_SCHEMAS = {}
_SUPPORTED_SCHEMA_VERSIONS = ('v1',)
def _get_schema_parts(document, schema_key='schema'):
# TODO(fmontei): Remove this function once documents have been standardized
# around macroversions or microversions.
schema_parts = utils.jsonpath_parse(document, schema_key).split('/')
schema_prefix = '/'.join(schema_parts[:2])
schema_version = schema_parts[2]
if schema_version.endswith('.0'):
schema_version = schema_version[:-2]
return schema_prefix, schema_version
def _get_schema_dir():
return pkg_resources.resource_filename('deckhand.engine', 'schemas')
def _build_schema_map():
"""Populates ``_DEFAULT_SCHEMAS`` with built-in Deckhand schemas."""
global _DEFAULT_SCHEMAS
_DEFAULT_SCHEMAS = {k: {} for k in _SUPPORTED_SCHEMA_VERSIONS}
schema_dir = _get_schema_dir()
for schema_file in os.listdir(schema_dir):
if not schema_file.endswith('.yaml'):
continue
with open(os.path.join(schema_dir, schema_file)) as f:
for schema in yaml.safe_load_all(f):
schema_name = schema['metadata']['name']
version = schema_name.split('/')[-1]
_DEFAULT_SCHEMAS.setdefault(version, {})
if schema_file in _DEFAULT_SCHEMAS[version]:
raise RuntimeError("Duplicate DataSchema document [%s] %s "
"detected." % (schema['schema'],
schema_name))
_DEFAULT_SCHEMAS[version].setdefault(
'/'.join(schema_name.split('/')[:2]), schema['data'])
_build_schema_map()
@six.add_metaclass(abc.ABCMeta)
class BaseValidator(object):
@ -41,6 +85,10 @@ class BaseValidator(object):
_supported_versions = ('v1',)
_schema_re = re.compile(r'^[a-zA-Z]+\/[a-zA-Z]+\/v\d+(.0)?$')
def __init__(self):
global _DEFAULT_SCHEMAS
self._schema_map = _DEFAULT_SCHEMAS
@abc.abstractmethod
def matches(self, document):
"""Whether this Validator should be used to validate ``document``.
@ -59,11 +107,15 @@ class GenericValidator(BaseValidator):
or abstract, or what version its schema is.
"""
def __init__(self):
super(GenericValidator, self).__init__()
self.base_schema = self._schema_map['v1']['deckhand/Base']
def matches(self, document):
# Applies to all schemas, so unconditionally returns True.
return True
def validate(self, document):
def validate(self, document, **kwargs):
"""Validate ``document``against basic schema validation.
Sanity-checks each document for mandatory keys like "metadata" and
@ -82,8 +134,8 @@ class GenericValidator(BaseValidator):
"""
try:
jsonschema.Draft4Validator.check_schema(base_schema.schema)
schema_validator = jsonschema.Draft4Validator(base_schema.schema)
jsonschema.Draft4Validator.check_schema(self.base_schema)
schema_validator = jsonschema.Draft4Validator(self.base_schema)
error_messages = [
e.message for e in schema_validator.iter_errors(document)]
except Exception as e:
@ -96,34 +148,110 @@ class GenericValidator(BaseValidator):
'Failed sanity-check validation for document [%s] %s. '
'Details: %s', document.get('schema', 'N/A'),
document.metadata.get('name'), error_messages)
raise errors.InvalidDocumentFormat(details=error_messages)
raise errors.InvalidDocumentFormat(
document_schema=document.schema,
document_name=document.name,
errors=', '.join(error_messages))
class SchemaValidator(BaseValidator):
"""Validator for validating built-in document kinds."""
class DataSchemaValidator(GenericValidator):
"""Validator for validating ``DataSchema`` documents."""
_schema_map = {
'v1': {
'deckhand/CertificateAuthorityKey':
v1_0.certificate_authority_key_schema,
'deckhand/CertificateAuthority': v1_0.certificate_authority_schema,
'deckhand/CertificateKey': v1_0.certificate_key_schema,
'deckhand/Certificate': v1_0.certificate_schema,
'deckhand/DataSchema': v1_0.data_schema_schema,
'deckhand/LayeringPolicy': v1_0.layering_policy_schema,
'deckhand/Passphrase': v1_0.passphrase_schema,
'deckhand/PrivateKey': v1_0.private_key_schema,
'deckhand/PublicKey': v1_0.public_key_schema,
'deckhand/ValidationPolicy': v1_0.validation_policy_schema,
def __init__(self, data_schemas):
super(DataSchemaValidator, self).__init__()
global _DEFAULT_SCHEMAS
self._default_schema_map = _DEFAULT_SCHEMAS
self._external_data_schemas = [d.data for d in data_schemas]
self._schema_map = self._build_schema_map(data_schemas)
def _build_schema_map(self, data_schemas):
schema_map = copy.deepcopy(self._default_schema_map)
for data_schema in data_schemas:
# Ensure that each `DataSchema` document has required properties
# before they themselves can be used to validate other documents.
if 'name' not in data_schema.metadata:
continue
if self._schema_re.match(data_schema.name) is None:
continue
if 'data' not in data_schema:
continue
schema_prefix, schema_version = _get_schema_parts(data_schema,
'metadata.name')
schema_map[schema_version].setdefault(schema_prefix,
data_schema.data)
return schema_map
def matches(self, document):
if document.is_abstract:
LOG.info('Skipping schema validation for abstract document [%s]: '
'%s.', document.schema, document.name)
return False
schema_prefix, schema_version = _get_schema_parts(document)
return schema_prefix in self._schema_map.get(schema_version, {})
def _generate_validation_error_output(self, schema, document, error,
root_path):
"""Returns a formatted output with necessary details for debugging why
a validation failed.
The response is a dictionary with the following keys:
* validation_schema: The schema body that was used to validate the
document.
* schema_path: The JSON path in the schema where the failure
originated.
* name: The document name.
* schema: The document schema.
* path: The JSON path in the document where the failure originated.
* error_section: The "section" in the document above which the error
originated (i.e. the dict in which ``path`` is found).
* message: The error message returned by the ``jsonschema`` validator.
:returns: Dictionary in the above format.
"""
error_path = '.'.join([str(x) for x in error.path])
if error_path:
path_to_error_in_document = '.'.join([root_path, error_path])
else:
path_to_error_in_document = root_path
path_to_error_in_schema = '.' + '.'.join(
[str(x) for x in error.schema_path])
parent_path_to_error_in_document = '.'.join(
path_to_error_in_document.split('.')[:-1]) or '.'
try:
# NOTE(fmontei): Because validation is performed on fully rendered
# documents, it is necessary to omit the parts of the data section
# where substitution may have occurred to avoid exposing any
# secrets. While this may make debugging a few validation failures
# more difficult, it is a necessary evil.
sanitized_document = (
SecretsSubstitution.sanitize_potential_secrets(document))
parent_error_section = utils.jsonpath_parse(
sanitized_document, parent_path_to_error_in_document)
except Exception:
parent_error_section = (
'Failed to find parent section above where error occurred.')
error_output = {
'validation_schema': schema,
'schema_path': path_to_error_in_schema,
'name': document.name,
'schema': document.schema,
'path': path_to_error_in_document,
'error_section': parent_error_section,
# TODO(fmontei): Also sanitize any secrets contained in the message
# as well.
'message': error.message
}
}
# Represents a generic document schema.
_fallback_schema = v1_0.document_schema
return error_output
def _get_schemas(self, document):
"""Retrieve the relevant schemas based on the document's
``schema``.
"""Retrieve the relevant schemas based on the document's ``schema``.
:param dict doc: The document used for finding the correct schema
to validate it based on its ``schema``.
@ -134,32 +262,25 @@ class SchemaValidator(BaseValidator):
"""
schema_prefix, schema_version = _get_schema_parts(document)
matching_schemas = []
relevant_schemas = self._schema_map.get(schema_version, {})
for candidae_schema_prefix, schema in relevant_schemas.items():
if candidae_schema_prefix == schema_prefix:
for candidate_schema_prefix, schema in relevant_schemas.items():
if candidate_schema_prefix == schema_prefix:
if schema not in matching_schemas:
matching_schemas.append(schema)
return matching_schemas
def matches(self, document):
if document.is_abstract:
LOG.info('Skipping schema validation for abstract document [%s]: '
'%s.', document.schema, document.name)
return False
return True
def validate(self, document, validate_section='',
use_fallback_schema=True):
def validate(self, document, pre_validate=True):
"""Validate ``document`` against built-in ``schema``-specific schemas.
Does not apply to abstract documents.
:param dict document: Document to validate.
:param str validate_section: Document section to validate. If empty
string, validates entire ``document``.
:param bool use_fallback_schema: Whether to use the "fallback" schema
if no matching schemas are found by :method:``matches``.
:param document: Document to validate.
:type document: DocumentDict
:param pre_validate: Whether to pre-validate documents using built-in
schema validation. Skips over externally registered ``DataSchema``
documents to avoid false positives. Default is True.
:type pre_validate: bool
:raises RuntimeError: If the Deckhand schema itself is invalid.
:returns: Tuple of (error message, parent path for failing property)
following schema validation failure.
@ -167,19 +288,30 @@ class SchemaValidator(BaseValidator):
"""
schemas_to_use = self._get_schemas(document)
if not schemas_to_use and use_fallback_schema:
LOG.debug('Document schema %s not recognized. Using "fallback" '
'schema.', document.schema)
schemas_to_use = [SchemaValidator._fallback_schema]
if not schemas_to_use:
LOG.debug('Document schema %s not recognized by %s. No further '
'validation required.', document.schema,
self.__class__.__name__)
for schema_to_use in schemas_to_use:
schema = schema_to_use.schema
if validate_section:
to_validate = document.get(validate_section, None)
root_path = '.' + validate_section + '.'
else:
to_validate = document
for schema in schemas_to_use:
is_builtin_schema = schema not in self._external_data_schemas
# NOTE(fmontei): The purpose of this `continue` is to not
# PRE-validate documents against externally registered
# `DataSchema` documents, in order to avoid raising spurious
# errors. These spurious errors arise from `DataSchema` documents
# really only applying post-rendering, when documents have all
# the substitutions they need to pass externally registered
# `DataSchema` validations.
if not is_builtin_schema and pre_validate:
continue
if is_builtin_schema:
root_path = '.'
to_validate = document
else:
root_path = '.data'
to_validate = document.get('data', {})
try:
jsonschema.Draft4Validator.check_schema(schema)
schema_validator = jsonschema.Draft4Validator(schema)
@ -195,80 +327,52 @@ class SchemaValidator(BaseValidator):
'Failed schema validation for document [%s] %s. '
'Details: %s.', document.schema, document.name,
error.message)
yield _generate_validation_error_output(
schema_to_use, document, error, root_path)
class DataSchemaValidator(SchemaValidator):
"""Validator for validating ``DataSchema`` documents."""
def __init__(self, data_schemas):
super(DataSchemaValidator, self).__init__()
self._schema_map = self._build_schema_map(data_schemas)
def _build_schema_map(self, data_schemas):
schema_map = {k: {} for k in self._supported_versions}
for data_schema in data_schemas:
# Ensure that each `DataSchema` document has required properties
# before they themselves can be used to validate other documents.
if 'name' not in data_schema.metadata:
continue
if self._schema_re.match(data_schema.name) is None:
continue
if 'data' not in data_schema:
continue
schema_prefix, schema_version = _get_schema_parts(data_schema,
'metadata.name')
class Schema(object):
schema = data_schema.data
schema_map[schema_version].setdefault(schema_prefix, Schema())
return schema_map
def matches(self, document):
if document.is_abstract:
LOG.info('Skipping schema validation for abstract document [%s]: '
'%s.', document.schema, document.name)
return False
schema_prefix, schema_version = _get_schema_parts(document)
return schema_prefix in self._schema_map.get(schema_version, {})
def validate(self, document):
return super(DataSchemaValidator, self).validate(
document, validate_section='data', use_fallback_schema=False)
yield self._generate_validation_error_output(
schema, document, error, root_path)
class DocumentValidation(object):
def __init__(self, documents, existing_data_schemas=None,
pre_validate=False):
pre_validate=True):
"""Class for document validation logic for documents.
This class is responsible for validating documents according to their
schema.
If ``pre_validate`` is true, then:
* the base_schema validates ALL documents
* ALL built-in schemas validate the appropriate
document given a schema match
* NO externally registered DataSchema documents
are used for validation
Else:
* the base_schema validates ALL documents
* ALL built-in schemas validate the appropriate
document given a schema match
* ALL externally registered DataSchema documents
are used for validation given a schema match
:param documents: Documents to be validated.
:type documents: List[dict]
:param existing_data_schemas: ``DataSchema`` documents created in prior
revisions to be used the "data" section of each document in
``documents``. Additional ``DataSchema`` documents in ``documents``
are combined with these.
revisions to be used to validate the "data" section of each
document in ``documents``. Additional ``DataSchema`` documents in
``documents`` are combined with these.
:type existing_data_schemas: dict or List[dict]
:param pre_validate: Only runs validations from ``GenericValidator``
and ``SchemaValidator`` against the documents if True. Otherwise
runs them all. This is useful to avoid spurious errors arising
from missing properties that may only exist post-substitution.
Default is False.
:param pre_validate: Whether to pre-validate documents using built-in
schema validation. Skips over externally registered ``DataSchema``
documents to avoid false positives. Default is True.
:type pre_validate: bool
"""
self.documents = []
existing_data_schemas = existing_data_schemas or []
data_schemas = [document_wrapper.DocumentDict(d)
for d in existing_data_schemas]
_data_schema_map = {d.name: d for d in data_schemas}
self._documents = []
self._external_data_schemas = [document_wrapper.DocumentDict(d)
for d in existing_data_schemas or []]
data_schema_map = {d.name: d for d in self._external_data_schemas}
raw_properties = ('data', 'metadata', 'schema')
@ -283,30 +387,30 @@ class DocumentValidation(object):
document = document_wrapper.DocumentDict(raw_document)
if document.schema.startswith(types.DATA_SCHEMA_SCHEMA):
data_schemas.append(document)
self._external_data_schemas.append(document)
# If a newer version of the same DataSchema was passed in,
# only use the new one and discard the old one.
if document.name in _data_schema_map:
data_schemas.remove(_data_schema_map.pop(document.name))
if document.name in data_schema_map:
self._external_data_schemas.remove(
data_schema_map.pop(document.name))
self.documents.append(document)
self._documents.append(document)
# NOTE(fmontei): The order of the validators is important. The
# ``GenericValidator`` must come first.
self._validators = [
GenericValidator(),
SchemaValidator(),
DataSchemaValidator(data_schemas)
DataSchemaValidator(self._external_data_schemas)
]
self._pre_validate = pre_validate
def _get_supported_schema_list(self):
schema_list = []
for validator in self._validators[1:]: # Skip over `GenericValidator`.
for schema_version, schema_map in validator._schema_map.items():
for schema_prefix in schema_map:
schema_list.append(schema_prefix + '/' + schema_version)
validator = self._validators[-1]
for schema_version, schema_map in validator._schema_map.items():
for schema_name in schema_map:
schema_list.append(schema_name + '/' + schema_version)
return schema_list
def _format_validation_results(self, results):
@ -349,13 +453,10 @@ class DocumentValidation(object):
supported_schema_list))
LOG.info(message)
validators = self._validators
if self._pre_validate is True:
validators = self._validators[:-1]
for validator in validators:
for validator in self._validators:
if validator.matches(document):
error_outputs = validator.validate(document)
error_outputs = validator.validate(
document, pre_validate=self._pre_validate)
if error_outputs:
for error_output in error_outputs:
result['errors'].append(error_output)
@ -368,7 +469,7 @@ class DocumentValidation(object):
return result
def validate_all(self):
"""Pre-validate that all documents are correctly formatted.
"""Validate that all documents are correctly formatted.
All concrete documents in the revision must successfully pass their
JSON schema validations. The result of the validation is stored under
@ -377,28 +478,15 @@ class DocumentValidation(object):
All abstract documents must themselves be sanity-checked.
Validation is broken up into 3 stages:
Validation is broken up into 2 "main" stages:
1) Validate that each document contains the basic bulding blocks
needed: i.e. ``schema`` and ``metadata`` using a "base" schema.
Failing this validation is deemed a critical failure, resulting
in an exception.
.. note::
The ``data`` section, while mandatory, will not result in
critical failure. This is because a document can rely
on yet another document for ``data`` substitution. But
the validation for the document will be tagged as
``failure``.
2) Validate each specific document type (e.g. validation policy)
using a more detailed schema. Failing this validation is deemed
non-critical, resulting in the error being recorded along with
any other non-critical exceptions, which are returned together
later.
3) Execute ``DataSchema`` validations if applicable.
2) Execute ``DataSchema`` validations if applicable. Includes all
built-in ``DataSchema`` documents by default.
:returns: A list of validations (one for each document validated).
:rtype: List[dict]
@ -410,72 +498,9 @@ class DocumentValidation(object):
validation_results = []
for document in self.documents:
for document in self._documents:
result = self._validate_one(document)
validation_results.append(result)
validations = self._format_validation_results(validation_results)
return validations
def _get_schema_parts(document, schema_key='schema'):
schema_parts = utils.jsonpath_parse(document, schema_key).split('/')
schema_prefix = '/'.join(schema_parts[:2])
schema_version = schema_parts[2]
if schema_version.endswith('.0'):
schema_version = schema_version[:-2]
return schema_prefix, schema_version
def _generate_validation_error_output(schema, document, error, root_path):
"""Returns a formatted output with necessary details for debugging why
a validation failed.
The response is a dictionary with the following keys:
* validation_schema: The schema body that was used to validate the
document.
* schema_path: The JSON path in the schema where the failure originated.
* name: The document name.
* schema: The document schema.
* path: The JSON path in the document where the failure originated.
* error_section: The "section" in the document above which the error
originated (i.e. the dict in which ``path`` is found).
* message: The error message returned by the ``jsonschema`` validator.
:returns: Dictionary in the above format.
"""
path_to_error_in_document = root_path + '.'.join(
[str(x) for x in error.path])
path_to_error_in_schema = '.' + '.'.join(
[str(x) for x in error.schema_path])
parent_path_to_error_in_document = '.'.join(
path_to_error_in_document.split('.')[:-1]) or '.'
try:
# NOTE(fmontei): Because validation is performed on fully rendered
# documents, it is necessary to omit the parts of the data section
# where substitution may have occurred to avoid exposing any
# secrets. While this may make debugging a few validation failures
# more difficult, it is a necessary evil.
sanitized_document = SecretsSubstitution.sanitize_potential_secrets(
document)
parent_error_section = utils.jsonpath_parse(
sanitized_document, parent_path_to_error_in_document)
except Exception:
parent_error_section = (
'Failed to find parent section above where error occurred.')
error_output = {
'validation_schema': schema.schema,
'schema_path': path_to_error_in_schema,
'name': document.name,
'schema': document.schema,
'path': path_to_error_in_document,
'error_section': parent_error_section,
# TODO(fmontei): Also sanitize any secrets contained in the message
# as well.
'message': error.message
}
return error_output

View File

@ -245,7 +245,7 @@ class DocumentLayering(object):
return result
def _validate_documents(self, documents):
def _pre_validate_documents(self, documents):
LOG.debug('%s performing document pre-validation.',
self.__class__.__name__)
validator = document_validation.DocumentValidation(
@ -262,8 +262,9 @@ class DocumentLayering(object):
'Document [%s] %s failed with pre-validation error: %s.',
*error)
raise errors.InvalidDocumentFormat(
details='The following pre-validation errors occurred '
'(schema, name, error): %s.' % val_errors)
document_schema=', '.join(v[0] for v in val_errors),
document_name=', '.join(v[1] for v in val_errors),
errors=', '.join(v[2] for v in val_errors))
def __init__(self, documents, substitution_sources=None, validate=True,
fail_on_missing_sub_src=True):
@ -279,7 +280,8 @@ class DocumentLayering(object):
sources for substitution. Should only include concrete documents.
:type substitution_sources: List[dict]
:param validate: Whether to pre-validate documents using built-in
schema validation. Default is True.
schema validation. Skips over externally registered ``DataSchema``
documents to avoid false positives. Default is True.
:type validate: bool
:param fail_on_missing_sub_src: Whether to fail on a missing
substitution source. Default is True.
@ -299,8 +301,9 @@ class DocumentLayering(object):
self._documents_by_labels = {}
self._layering_policy = None
# TODO(fmontei): Add a hook for post-validation too.
if validate:
self._validate_documents(documents)
self._pre_validate_documents(documents)
layering_policies = list(
filter(lambda x: x.get('schema').startswith(

View File

@ -1,47 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
# Currently supported versions include v1/v1.0 only.
'pattern': '^[A-Za-z]+\/[A-Za-z]+\/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {'type': 'string'},
'name': {'type': 'string'}
},
'additionalProperties': True,
'required': ['schema', 'name']
},
'data': {'type': ['null', 'string', 'integer', 'array', 'object']}
},
'additionalProperties': False,
'required': ['schema', 'metadata']
}
"""Base JSON schema against which all Deckhand documents are validated.
.. literalinclude:: ../../deckhand/engine/schema/base_schema.py
:language: python
:lines: 15-36
This schema is used to sanity-check all documents that are passed to Deckhand.
Failure to pass this schema results in a critical error.
"""
__all__ = ['schema']

View File

@ -1,31 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
from deckhand.engine.schema.v1_0 import certificate_authority_key_schema
from deckhand.engine.schema.v1_0 import certificate_authority_schema
from deckhand.engine.schema.v1_0 import certificate_key_schema
from deckhand.engine.schema.v1_0 import certificate_schema
from deckhand.engine.schema.v1_0 import data_schema_schema
from deckhand.engine.schema.v1_0 import document_schema
from deckhand.engine.schema.v1_0 import layering_policy_schema
from deckhand.engine.schema.v1_0 import passphrase_schema
from deckhand.engine.schema.v1_0 import private_key_schema
from deckhand.engine.schema.v1_0 import public_key_schema
from deckhand.engine.schema.v1_0 import validation_policy_schema
__all__ = ['certificate_key_schema', 'certificate_schema',
'certificate_authority_key_schema', 'certificate_authority_schema',
'private_key_schema', 'public_key_schema',
'data_schema_schema', 'document_schema', 'layering_policy_schema',
'passphrase_schema', 'validation_policy_schema']

View File

@ -1,66 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': (
'^(deckhand/CertificateAuthorityKey/v[1]{1}(\.[0]{1}){0,1})$')
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(metadata/Document/v[1]{1}(\.[0]{1}){0,1})$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with
``deckhand/CertificateAuthorityKey/v1`` ``schema`` are validated.
.. literalinclude::
../../deckhand/engine/schema/v1_0/certificate_authority_key_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all CertificateAuthorityKey documents that
are passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass this
schema will result in an error entry being created for the validation with name
``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,66 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': (
'^(deckhand/CertificateAuthority/v[1]{1}(\.[0]{1}){0,1})$')
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(metadata/Document/v[1]{1}(\.[0]{1}){0,1})$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with
``deckhand/CertificateAuthority/v1`` ``schema`` are validated.
.. literalinclude::
../../deckhand/engine/schema/v1_0/certificate_authority_schema.py
:language: python
:lines: 15-50
This schema is used to sanity-check all CertificateAuthority documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,65 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/CertificateKey/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Document/v\d+(.0)?$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'},
'abstract': {'type': 'boolean'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/CertificateKey/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/certificate_key_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all CertificateKey documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,65 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/Certificate/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Document/v\d+(.0)?$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'},
'abstract': {'type': 'boolean'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/Certificate/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/certificate_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all Certificate documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,76 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
# This specifies the official JSON schema meta-schema. DataSchema documents
# are used by various services to register new schemas that Deckhand can use
# for validation.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/DataSchema/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Control/v\d+(.0)?$'
},
'name': {
'type': 'string',
'pattern': '^[A-Za-z]+\/[A-Za-z]+\/v\d+(.0)?$'
},
# Labels are optional.
'labels': {
'type': 'object'
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': True, # Can include layeringDefinition.
'required': ['schema', 'name']
},
'data': {
'type': 'object',
'properties': {
'$schema': {
'type': ['string', 'integer', 'array', 'object']
}
},
'additionalProperties': True,
'required': ['$schema']
}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/DataSchema/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/data_schema_schema.py
:language: python
:lines: 15-61
This schema is used to sanity-check all DataSchema documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,123 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
substitution_schema = {
'type': 'object',
'properties': {
'dest': {
'type': 'object',
'properties': {
'path': {'type': 'string'},
'pattern': {'type': 'string'}
},
'additionalProperties': False,
'required': ['path']
},
'src': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^[A-Za-z]+/[A-Za-z]+/v\d+(.0)?$'
},
'name': {'type': 'string'},
'path': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'name', 'path']
}
},
'additionalProperties': False,
'required': ['dest', 'src']
}
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^[A-Za-z]+/[A-Za-z]+/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Document/v\d+(.0)?$'
},
'name': {'type': 'string'},
'labels': {'type': 'object'},
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'},
'abstract': {'type': 'boolean'},
# "parentSelector" is optional.
'parentSelector': {'type': 'object'},
# "actions" is optional.
'actions': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'method': {'enum': ['replace', 'delete',
'merge']},
'path': {'type': 'string'}
},
'additionalProperties': False,
'required': ['method', 'path']
}
}
},
'additionalProperties': False,
'required': ['layer']
},
# "substitutions" is optional.
'substitutions': {
'type': 'array',
'items': substitution_schema
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'layeringDefinition']
},
'data': {
'type': ['string', 'integer', 'array', 'object']
}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``metadata/Document/v1``
``metadata.schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/document_schema.py
:language: python
:lines: 15-102
This schema is used to sanity-check all "metadata/Document" documents that are
passed to Deckhand. This validation comes into play when a new schema is
registered under the ``data`` section of a ``deckhand/DataSchema/v1`` document.
This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,74 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/LayeringPolicy/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Control/v\d+(.0)?$'
},
'name': {'type': 'string'},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
},
'layeringDefinition': {
'type': 'object',
'properties': {
'abstract': {'type': 'boolean'}
},
'additionalProperties': False
}
},
'additionalProperties': False,
'required': ['schema', 'name']
},
'data': {
'type': 'object',
'properties': {
'layerOrder': {
'type': 'array',
'items': {'type': 'string'}
}
},
'additionalProperties': True,
'required': ['layerOrder']
}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/LayeringPolicy/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/layering_policy_schema.py
:language: python
:lines: 15-52
This schema is used to sanity-check all LayeringPolicy documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,65 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/Passphrase/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Document/v\d+(.0)?$',
},
'name': {'type': 'string'},
# Not strictly needed.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'},
'abstract': {'type': 'boolean'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/Passphrase/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/passphrase_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all Passphrase documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,64 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(deckhand/PrivateKey/v[1]{1}(\.[0]{1}){0,1})$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(metadata/Document/v[1]{1}(\.[0]{1}){0,1})$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/PrivateKey/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/private_key_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all PrivateKey documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,64 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(deckhand/PublicKey/v[1]{1}(\.[0]{1}){0,1})$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^(metadata/Document/v[1]{1}(\.[0]{1}){0,1})$',
},
'name': {'type': 'string'},
# Not strictly needed for secrets.
'layeringDefinition': {
'type': 'object',
'properties': {
'layer': {'type': 'string'}
}
},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name', 'storagePolicy']
},
'data': {'type': 'string'}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with ``deckhand/PublicKey/v1``
``schema`` are validated.
.. literalinclude:: ../../deckhand/engine/schema/v1_0/public_key_schema.py
:language: python
:lines: 15-49
This schema is used to sanity-check all PublicKey documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -1,80 +0,0 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
schema = {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^deckhand/ValidationPolicy/v\d+(.0)?$'
},
'metadata': {
'type': 'object',
'properties': {
'schema': {
'type': 'string',
'pattern': '^metadata/Control/v\d+(.0)?$'
},
'name': {'type': 'string'},
'storagePolicy': {
'type': 'string',
'enum': ['encrypted', 'cleartext']
}
},
'additionalProperties': False,
'required': ['schema', 'name']
},
'data': {
'type': 'object',
'properties': {
'validations': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {
'type': 'string',
'pattern': '^.*-(validation|verification)$'
},
# 'expiresAfter' is optional.
'expiresAfter': {'type': 'string'}
},
'additionalProperties': False,
'required': ['name']
}
}
},
'additionalProperties': True,
'required': ['validations']
}
},
'additionalProperties': False,
'required': ['schema', 'metadata', 'data']
}
"""JSON schema against which all documents with
``deckhand/ValidationPolicy/v1`` ``schema`` are validated.
.. literalinclude::
../../deckhand/engine/schema/v1_0/validation_policy_schema.py
:language: python
:lines: 15-64
This schema is used to sanity-check all ValidationPolicy documents that are
passed to Deckhand. This schema is only enforced after validation for
:py:data:`~deckhand.engine.schema.base_schema` has passed. Failure to pass
this schema will result in an error entry being created for the validation
with name ``deckhand-schema-validation`` corresponding to the created revision.
"""
__all__ = ['schema']

View File

@ -0,0 +1,119 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/Base/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
schema:
type: string
pattern: ^[A-Za-z]+/[A-Za-z]+/v\d+(.0)?$
metadata:
type: object
properties:
schema:
anyOf:
- type: string
pattern: ^metadata/Document/v\d+(.0)?$
- type: string
pattern: ^metadata/Control/v\d+(.0)?$
name:
type: string
labels:
type: object
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
additionalProperties: false
substitutions:
type: array
items:
type: object
properties:
dest:
type: object
properties:
path:
type: string
pattern:
type: string
additionalProperties: false
required:
- path
src:
type: object
properties:
schema:
type: string
pattern: ^[A-Za-z]+/[A-Za-z]+/v\d+(.0)?$
name:
type: string
path:
type: string
additionalProperties: false
required:
- schema
- name
- path
additionalProperties: false
required:
- dest
- src
storagePolicy:
type: string
enum:
- encrypted
- cleartext
additionalProperties: false
required:
- schema
- name
data:
type:
- "null"
- string
- integer
- array
- object
additionalProperties: false
required:
- schema
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/CertificateAuthorityKey/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/CertificateAuthority/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/CertificateKey/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/Certificate/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,33 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/DataSchema/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
type: object
properties:
data:
type: object
properties:
$schema:
type: string
additionalProperties: true
required:
- $schema
required:
- data

View File

@ -0,0 +1,35 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/LayeringPolicy/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
type: object
properties:
data:
type: object
properties:
layerOrder:
type: array
items:
type: string
additionalProperties: false
required:
- layerOrder
required:
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/Passphrase/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/PrivateKey/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,65 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/PublicKey/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
storagePolicy:
type: string
enum:
- encrypted
- cleartext
required:
- layeringDefinition
- storagePolicy
data:
type: string
required:
- metadata
- data

View File

@ -0,0 +1,76 @@
# Copyright 2017 AT&T Intellectual Property. All other 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.
---
schema: deckhand/DataSchema/v1
metadata:
name: deckhand/ValidationPolicy/v1
schema: metadata/Control/v1
data:
$schema: http://json-schema.org/schema#
type: object
properties:
metadata:
type: object
properties:
layeringDefinition:
type: object
properties:
layer:
type: string
abstract:
type: boolean
parentSelector:
type: object
actions:
type: array
items:
type: object
properties:
method:
enum:
- replace
- delete
- merge
path:
type: string
additionalProperties: false
required:
- method
- path
required:
- layer
required:
- layeringDefinition
data:
properties:
validations:
type: array
items:
type: object
properties:
name:
type: string
pattern: ^.*-(validation|verification)$
expiresAfter:
type: string
additionalProperties: false
required:
- name
required:
- validations
additionalProperties: false
required:
- metadata
- data

View File

@ -175,8 +175,8 @@ class InvalidDocumentFormat(DeckhandException):
**Troubleshoot:**
"""
msg_fmt = ("The provided document(s) failed schema validation. Details: "
"%(details)s")
msg_fmt = ("The provided document [%(document_schema)s] %(document_name)s "
"failed schema validation. Errors: %(errors)s")
code = 400
@ -206,7 +206,6 @@ class InvalidDocumentParent(DeckhandException):
msg_fmt = ("The document parent [%(parent_schema)s] %(parent_name)s is "
"invalid for document [%(document_schema)s] %(document_name)s. "
"Reason: %(reason)s")
code = 400
class IndeterminateDocumentParent(DeckhandException):

View File

@ -27,14 +27,9 @@ DOCUMENT_TEST_SCHEMA = 'example/Kind/v1'
@six.add_metaclass(abc.ABCMeta)
class DeckhandFactory(object):
# TODO(fmontei): Allow this to be overriden in ``__init__``.
# TODO(fmontei): Allow this to be overridden in ``__init__``.
API_VERSION = '1.0'
@abc.abstractmethod
def gen(self, *args):
"""Generate an object for usage by the Deckhand `engine` module."""
pass
@abc.abstractmethod
def gen_test(self, *args, **kwargs):
"""Generate an object with randomized values for a test."""
@ -51,7 +46,11 @@ class DataSchemaFactory(DeckhandFactory):
"metadata": {
"schema": "metadata/Control/v1",
"name": "",
"labels": {}
"labels": {},
"layeringDefinition": {
"abstract": True,
"layer": "site"
}
},
"schema": "deckhand/DataSchema/v1"
}
@ -73,9 +72,6 @@ class DataSchemaFactory(DeckhandFactory):
...
"""
def gen(self):
raise NotImplementedError()
def gen_test(self, metadata_name, data, **metadata_labels):
data_schema_template = copy.deepcopy(self.DATA_SCHEMA_TEMPLATE)
@ -90,18 +86,22 @@ class DataSchemaFactory(DeckhandFactory):
class DocumentFactory(DeckhandFactory):
"""Class for auto-generating document templates for testing."""
LAYERING_DEFINITION = {
LAYERING_POLICY_TEMPLATE = {
"data": {
"layerOrder": []
},
"metadata": {
"name": "placeholder",
"schema": "metadata/Control/v%s" % DeckhandFactory.API_VERSION
"schema": "metadata/Control/v%s" % DeckhandFactory.API_VERSION,
"layeringDefinition": {
"abstract": False,
"layer": ""
}
},
"schema": "deckhand/LayeringPolicy/v%s" % DeckhandFactory.API_VERSION
}
LAYER_TEMPLATE = {
DOCUMENT_TEMPLATE = {
"data": {},
"metadata": {
"labels": {"": ""},
@ -159,10 +159,12 @@ class DocumentFactory(DeckhandFactory):
layer_order = ["global", "region", "site"]
else:
raise ValueError("'num_layers' must be a value between 1 - 3.")
self.layering_definition = copy.deepcopy(self.LAYERING_DEFINITION)
self.layering_definition['metadata']['name'] = test_utils.rand_name(
self.layering_policy = copy.deepcopy(self.LAYERING_POLICY_TEMPLATE)
self.layering_policy['metadata']['name'] = test_utils.rand_name(
'layering-policy')
self.layering_definition['data']['layerOrder'] = layer_order
self.layering_policy['data']['layerOrder'] = layer_order
self.layering_policy['metadata']['layeringDefinition'][
'layer'] = layer_order[0]
if not isinstance(docs_per_layer, (list, tuple)):
raise TypeError("'docs_per_layer' must be a list or tuple "
@ -179,9 +181,6 @@ class DocumentFactory(DeckhandFactory):
self.num_layers = num_layers
self.docs_per_layer = docs_per_layer
def gen(self):
raise NotImplementedError()
def gen_test(self, mapping, site_abstract=True, region_abstract=True,
global_abstract=True, site_parent_selectors=None):
"""Generate the document template.
@ -228,12 +227,12 @@ class DocumentFactory(DeckhandFactory):
:type site_parent_selectors: list
:returns: Rendered template of the form specified above.
"""
rendered_template = [self.layering_definition]
rendered_template = [self.layering_policy]
layer_order = rendered_template[0]['data']['layerOrder']
for layer_idx in range(self.num_layers):
for count in range(self.docs_per_layer[layer_idx]):
layer_template = copy.deepcopy(self.LAYER_TEMPLATE)
layer_template = copy.deepcopy(self.DOCUMENT_TEMPLATE)
layer_name = layer_order[layer_idx]
layer_template = copy.deepcopy(layer_template)
@ -332,7 +331,11 @@ class DocumentSecretFactory(DeckhandFactory):
"metadata": {
"schema": "metadata/Document/v1",
"name": "",
"storagePolicy": ""
"layeringDefinition": {
"abstract": False,
"layer": "site"
},
"storagePolicy": "",
},
"schema": "deckhand/%s/v1"
}
@ -358,9 +361,6 @@ class DocumentSecretFactory(DeckhandFactory):
...
"""
def gen(self):
raise NotImplementedError()
def gen_test(self, schema, storage_policy, data=None, name=None):
if data is None:
data = test_utils.rand_password()

View File

@ -13,6 +13,7 @@
# limitations under the License.
import copy
import os
import yaml
import mock
@ -20,7 +21,6 @@ from oslo_config import cfg
from deckhand.control import buckets
from deckhand.engine import document_validation
from deckhand.engine.schema.v1_0 import document_schema
from deckhand import factories
from deckhand.tests import test_utils
from deckhand.tests.unit.control import base as test_base
@ -93,6 +93,11 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
def setUp(self):
super(TestValidationsControllerPostValidate, self).setUp()
dataschema_schema = os.path.join(
os.getcwd(), 'deckhand', 'engine', 'schemas',
'dataschema_schema.yaml')
with open(dataschema_schema, 'r') as f:
self.dataschema_schema = yaml.safe_load(f.read())
self._monkey_patch_document_validation()
def _monkey_patch_document_validation(self):
@ -556,25 +561,24 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
'properties': {
'a': {
'type': 'integer' # Test doc will fail b/c of wrong type.
},
'b': {
'type': 'string'
}
},
'required': ['a']
'required': ['a', 'b']
}
data_schema = data_schema_factory.gen_test(
metadata_name, data=schema_to_use)
# Failure #1.
# Create the test document that fails the validation due to the
# schema defined by the `DataSchema` document.
# Failure #1: Provide wrong type for property "a".
# Failure #2: Don't include required property "b".
doc_factory = factories.DocumentFactory(1, [1])
doc_to_test = doc_factory.gen_test(
{'_GLOBAL_DATA_1_': {'data': {'a': 'fail'}}},
global_abstract=False)[-1]
doc_to_test['schema'] = 'example/foo/v1'
doc_to_test['metadata']['name'] = 'test_doc'
# Failure #2.
# Remove required metadata property, causing error to be generated.
del doc_to_test['metadata']['layeringDefinition']
revision_id = self._create_revision(payload=[doc_to_test, data_schema])
@ -597,16 +601,19 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
'error_section': {
'data': {'a': 'fail'},
'metadata': {'labels': {'global': 'global1'},
'name': 'test_doc',
'schema': 'metadata/Document/v1.0'},
'schema': 'example/foo/v1'
'layeringDefinition': {'abstract': False,
'actions': [],
'layer': 'global'},
'name': doc_to_test['metadata']['name'],
'schema': doc_to_test['metadata']['schema']},
'schema': doc_to_test['schema']
},
'name': 'test_doc',
'path': '.metadata',
'path': '.data',
'schema': 'example/foo/v1',
'message': "'layeringDefinition' is a required property",
'validation_schema': document_schema.schema,
'schema_path': '.properties.metadata.required'
'message': "'b' is a required property",
'validation_schema': schema_to_use,
'schema_path': '.required'
}, {
'error_section': {'a': 'fail'},
'name': 'test_doc',
@ -624,7 +631,8 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
body = yaml.safe_load(resp.text)
self.assertEqual('failure', body['status'])
self.assertEqual(expected_errors, body['errors'])
self.assertEqual(sorted(expected_errors, key=lambda x: x['path']),
sorted(body['errors'], key=lambda x: x['path']))
def test_validation_with_registered_data_schema_expect_mixed(self):
rules = {'deckhand:create_cleartext_documents': '@',
@ -721,15 +729,12 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
self.assertIn('errors', body)
self.assertEqual(expected_errors, body['errors'])
def test_document_without_data_section_saves_but_fails_validation(self):
"""Validate that a document without the data section is saved to the
database, but fails validation. This is a valid use case because a
document in a bucket can be created without a data section, which
depends on substitution from another document.
def test_document_without_data_section_ingested(self):
"""Validate that a document without the data section is ingested
successfully.
"""
rules = {'deckhand:create_cleartext_documents': '@',
'deckhand:list_validations': '@',
'deckhand:show_validation': '@'}
'deckhand:list_validations': '@'}
self.policy.set_rules(rules)
documents_factory = factories.DocumentFactory(1, [1])
@ -751,41 +756,11 @@ class TestValidationsControllerPostValidate(ValidationsControllerBaseTest):
body = yaml.safe_load(resp.text)
expected_body = {
'count': 2,
'results': [{'id': 0, 'status': 'failure'}, # Document.
'results': [{'id': 0, 'status': 'success'}, # Document.
{'id': 1, 'status': 'success'}] # DataSchema.
}
self.assertEqual(expected_body, body)
# Validate that the created document failed validation for the expected
# reason.
resp = self.app.simulate_get(
'/api/v1.0/revisions/%s/validations/%s/entries/0' % (
revision_id, types.DECKHAND_SCHEMA_VALIDATION),
headers={'Content-Type': 'application/x-yaml'})
self.assertEqual(200, resp.status_code)
body = yaml.safe_load(resp.text)
expected_errors = [{
'error_section': {
'data': None,
'metadata': {'labels': {'global': 'global1'},
'layeringDefinition': {'abstract': False,
'actions': [],
'layer': 'global'},
'name': document['metadata']['name'],
'schema': 'metadata/Document/v1.0'},
'schema': document['schema']
},
'name': document['metadata']['name'],
'path': '.data',
'schema': document['schema'],
'message': (
"None is not of type 'string', 'integer', 'array', 'object'"),
'validation_schema': document_schema.schema,
'schema_path': '.properties.data.type'
}]
self.assertIn('errors', body)
self.assertEqual(expected_errors, body['errors'])
def test_validation_only_new_data_schema_registered(self):
"""Validate whether newly created DataSchemas replace old DataSchemas
when it comes to validation.
@ -859,7 +834,7 @@ class TestValidationsControllerPreValidate(ValidationsControllerBaseTest):
Validations controller.
"""
def test_pre_validate_flag_skips_over_dataschema_validations(self):
def test_pre_validate_flag_skips_registered_dataschema_validations(self):
rules = {'deckhand:create_cleartext_documents': '@',
'deckhand:list_validations': '@'}
self.policy.set_rules(rules)

View File

@ -71,6 +71,7 @@ class TestDocumentLayeringNegative(
def test_layering_with_empty_layer(self, mock_log):
doc_factory = factories.DocumentFactory(1, [1])
documents = doc_factory.gen_test({}, global_abstract=False)
del documents[0]['metadata']['layeringDefinition']
# Only pass in the LayeringPolicy.
self._test_layering([documents[0]], global_expected=None)
@ -243,3 +244,29 @@ class TestDocumentLayeringValidationNegative(
self.assertRaises(errors.InvalidDocumentFormat,
self._test_layering, [layering_policy, document],
validate=True)
def test_layering_invalid_document_format_generates_error_messages(self):
doc_factory = factories.DocumentFactory(1, [1])
lp_template, document = doc_factory.gen_test({
"_GLOBAL_SUBSTITUTIONS_1_": [{
"dest": {
"path": ".c"
},
"src": {
"schema": "deckhand/Certificate/v1",
"name": "global-cert",
"path": "."
}
}],
}, global_abstract=False)
layering_policy = copy.deepcopy(lp_template)
del layering_policy['data']['layerOrder']
error_re = ("The provided document \[%s\] %s failed schema validation."
" Errors: 'layerOrder' is a required property" % (
layering_policy['schema'],
layering_policy['metadata']['name']))
self.assertRaisesRegexp(
errors.InvalidDocumentFormat, error_re, self._test_layering,
[layering_policy, document], validate=True)

View File

@ -15,9 +15,8 @@
import mock
from deckhand.engine import document_validation
from deckhand.tests.unit.engine import base as engine_test_base
from deckhand import factories
from deckhand.tests.unit.engine import base as engine_test_base
from deckhand import utils
@ -59,7 +58,7 @@ class TestDocumentValidation(engine_test_base.TestDocumentValidationBase):
abstract_document = utils.jsonpath_replace(
test_document, True, '.metadata.layeringDefinition.abstract')
document_validation.DocumentValidation(
abstract_document).validate_all()
abstract_document, pre_validate=False).validate_all()
self.assertTrue(mock_log.info.called)
self.assertIn("Skipping schema validation for abstract document",
mock_log.info.mock_calls[0][1][0])
@ -81,8 +80,12 @@ class TestDocumentValidation(engine_test_base.TestDocumentValidationBase):
'scary-secret', utils.jsonpath_parse(test_document['data'],
sub['dest']['path']))
data_schema_factory = factories.DataSchemaFactory()
data_schema = data_schema_factory.gen_test(test_document['schema'], {})
validations = document_validation.DocumentValidation(
test_document).validate_all()
test_document, existing_data_schemas=[data_schema],
pre_validate=False).validate_all()
self.assertEqual(1, len(validations[0]['errors']))
self.assertIn('Sanitized to avoid exposing secret.',

View File

@ -16,7 +16,6 @@ import mock
from deckhand.engine import document_validation
from deckhand import errors
from deckhand import factories
from deckhand.tests.unit.engine import base as test_base
from deckhand import types
@ -24,20 +23,32 @@ from deckhand import types
class TestDocumentValidationNegative(test_base.TestDocumentValidationBase):
"""Negative testing suite for document validation."""
# The 'data' key is mandatory but not critical if excluded.
exception_map = {
'metadata': errors.InvalidDocumentFormat,
'metadata.schema': errors.InvalidDocumentFormat,
'metadata.name': errors.InvalidDocumentFormat,
'schema': errors.InvalidDocumentFormat,
}
BASIC_PROPERTIES = (
'metadata',
'metadata.schema',
'metadata.name',
'metadata.layeringDefinition',
'metadata.layeringDefinition.layer',
'schema'
)
CRITICAL_PROPERTIES = (
'schema',
'metadata',
'metadata.schema',
'metadata.name',
'metadata.substitutions.0.dest',
'metadata.substitutions.0.dest.path',
'metadata.substitutions.0.src',
'metadata.substitutions.0.src.schema',
'metadata.substitutions.0.src.name',
'metadata.substitutions.0.src.path'
)
def _do_validations(self, document_validator, expected, expected_err):
validations = document_validator.validate_all()
self.assertEqual(2, len(validations))
# The DataSchema document itself should've validated
# successfully.
self.assertEqual('success', validations[0]['status'])
self.assertEqual(1, len(validations))
self.assertEqual('failure', validations[-1]['status'])
self.assertEqual({'version': '1.0', 'name': 'deckhand'},
validations[-1]['validator'])
@ -61,62 +72,65 @@ class TestDocumentValidationNegative(test_base.TestDocumentValidationBase):
missing_prop = property_to_remove.split('.')[-1]
invalid_data = self._corrupt_data(document, property_to_remove)
exception_raised = self.exception_map.get(property_to_remove, None)
exception_raised = property_to_remove in self.CRITICAL_PROPERTIES
expected_err_msg = "'%s' is a required property" % missing_prop
dataschema_factory = factories.DataSchemaFactory()
dataschema = dataschema_factory.gen_test(
invalid_data.get('schema', ''), {})
payload = [dataschema, invalid_data]
doc_validator = document_validation.DocumentValidation(payload)
payload = [invalid_data]
doc_validator = document_validation.DocumentValidation(
payload, pre_validate=False)
if exception_raised:
self.assertRaises(
exception_raised, doc_validator.validate_all)
errors.InvalidDocumentFormat, doc_validator.validate_all)
else:
self._do_validations(doc_validator, invalid_data,
expected_err_msg)
def test_certificate_authority_key_missing_required_sections(self):
document = self._read_data('sample_certificate_authority_key')
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_certificate_authority_missing_required_sections(self):
document = self._read_data('sample_certificate_authority')
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_certificate_key_missing_required_sections(self):
document = self._read_data('sample_certificate_key')
properties_to_remove = tuple(self.exception_map.keys()) + (
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_certificate_missing_required_sections(self):
document = self._read_data('sample_certificate')
properties_to_remove = tuple(self.exception_map.keys()) + (
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_data_schema_missing_required_sections(self):
properties_to_remove = (
'metadata',
'metadata.schema',
'metadata.name',
'schema',
'data.$schema'
)
document = self._read_data('sample_data_schema')
properties_to_remove = tuple(self.exception_map.keys()) + (
'data.$schema',)
self._test_missing_required_sections(document, properties_to_remove)
def test_document_missing_required_sections(self):
def test_generic_document_missing_required_sections(self):
document = self._read_data('sample_document')
properties_to_remove = tuple(self.exception_map.keys()) + (
'metadata.layeringDefinition',
'metadata.layeringDefinition.layer',
'metadata.layeringDefinition.actions.0.method',
'metadata.layeringDefinition.actions.0.path',
'metadata.substitutions.0.dest',
'metadata.substitutions.0.dest.path',
'metadata.substitutions.0.src',
'metadata.substitutions.0.src.schema',
'metadata.substitutions.0.src.name',
'metadata.substitutions.0.src.path')
properties_to_remove = self.CRITICAL_PROPERTIES
self._test_missing_required_sections(document, properties_to_remove)
def test_document_missing_multiple_required_sections(self):
def test_generic_document_missing_multiple_required_sections(self):
"""Validates that multiple errors are reported for a document with
multiple validation errors.
"""
document = self._read_data('sample_document')
properties_to_remove = (
'metadata.layeringDefinition.layer',
'metadata.layeringDefinition.actions.0.method',
'metadata.layeringDefinition.actions.0.path',
'metadata.substitutions.0.dest.path',
@ -128,55 +142,61 @@ class TestDocumentValidationNegative(test_base.TestDocumentValidationBase):
document = self._corrupt_data(document, property_to_remove)
doc_validator = document_validation.DocumentValidation(document)
validations = doc_validator.validate_all()
e = self.assertRaises(errors.InvalidDocumentFormat,
doc_validator.validate_all)
errors = validations[0]['errors']
self.assertEqual(len(properties_to_remove), len(errors))
# Sort the errors to match the order in ``properties_to_remove``.
errors = sorted(errors, key=lambda x: (x['path'], x['message']))
# Validate that an error was generated for each missing property in
# ``properties_to_remove`` that was removed from ``document``.
for idx, property_to_remove in enumerate(properties_to_remove):
parts = property_to_remove.split('.')
parent_path = '.' + '.'.join(parts[:-1])
missing_property = parts[-1]
expected_err = "'%s' is a required property" % missing_property
self.assertEqual(expected_err, errors[idx]['message'])
self.assertEqual(parent_path, errors[idx]['path'])
self.assertIn(expected_err, e.message)
def test_document_invalid_layering_definition_action(self):
document = self._read_data('sample_document')
missing_data = self._corrupt_data(
document, 'metadata.layeringDefinition.actions.0.method',
'invalid', op='replace')
expected_err = "'invalid' is not one of ['replace', 'delete', 'merge']"
expected_err = (
r".+ 'invalid' is not one of \['replace', 'delete', 'merge'\]")
# Ensure that a dataschema document exists for the random document
# schema via mocking.
dataschema_factory = factories.DataSchemaFactory()
dataschema = dataschema_factory.gen_test(document['schema'], {})
payload = [dataschema, missing_data]
payload = [missing_data]
doc_validator = document_validation.DocumentValidation(payload)
self._do_validations(doc_validator, document, expected_err)
self.assertRaisesRegexp(errors.InvalidDocumentFormat, expected_err,
doc_validator.validate_all)
def test_layering_policy_missing_required_sections(self):
properties_to_remove = (
'metadata',
'metadata.schema',
'metadata.name',
'schema',
'data.layerOrder'
)
document = self._read_data('sample_layering_policy')
properties_to_remove = tuple(self.exception_map.keys()) + (
'data.layerOrder',)
self._test_missing_required_sections(document, properties_to_remove)
def test_passphrase_missing_required_sections(self):
document = self._read_data('sample_passphrase')
properties_to_remove = tuple(self.exception_map.keys()) + (
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_privatekey_missing_required_sections(self):
document = self._read_data('sample_private_key')
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_publickey_missing_required_sections(self):
document = self._read_data('sample_public_key')
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'metadata.storagePolicy',)
self._test_missing_required_sections(document, properties_to_remove)
def test_validation_policy_missing_required_sections(self):
document = self._read_data('sample_validation_policy')
properties_to_remove = tuple(self.exception_map.keys()) + (
properties_to_remove = tuple(self.BASIC_PROPERTIES) + (
'data.validations', 'data.validations.0.name')
self._test_missing_required_sections(document, properties_to_remove)
@ -206,25 +226,12 @@ class TestDocumentValidationNegative(test_base.TestDocumentValidationBase):
def test_invalid_validation_schema_raises_runtime_error(self):
document = self._read_data('sample_passphrase')
fake_schema = mock.MagicMock(schema='fake')
fake_schema_map = {'v1': {'deckhand/Passphrase': fake_schema}}
# Validate that broken built-in base schema raises RuntimeError.
with mock.patch.object(document_validation, 'base_schema',
new_callable=mock.PropertyMock(
return_value=fake_schema)):
doc_validator = document_validation.DocumentValidation(document)
with self.assertRaisesRegexp(RuntimeError, 'Unknown error'):
doc_validator.validate_all()
# Validate that broken built-in schema for ``SchemaValidator`` raises
# RuntimeError.
with mock.patch.object(document_validation.SchemaValidator,
'_schema_map', new_callable=mock.PropertyMock(
return_value=fake_schema_map)):
doc_validator = document_validation.DocumentValidation(document)
with self.assertRaisesRegexp(RuntimeError, 'Unknown error'):
doc_validator.validate_all()
doc_validator = document_validation.DocumentValidation(document)
doc_validator._validators[0].base_schema = 'fake'
with self.assertRaisesRegexp(RuntimeError, 'Unknown error'):
doc_validator.validate_all()
# Validate that broken data schema for ``DataSchemaValidator`` raises
# RuntimeError.
@ -233,6 +240,6 @@ class TestDocumentValidationNegative(test_base.TestDocumentValidationBase):
data_schema['metadata']['name'] = document['schema']
data_schema['data'] = 'fake'
doc_validator = document_validation.DocumentValidation(
[document, data_schema])
[document, data_schema], pre_validate=False)
with self.assertRaisesRegexp(RuntimeError, 'Unknown error'):
doc_validator.validate_all()

View File

@ -4,6 +4,9 @@ metadata:
schema: metadata/Document/v1.0
name: application-api
storagePolicy: cleartext
layeringDefinition:
abstract: False
layer: site
data: |-
-----BEGIN CERTIFICATE-----
MIIDYDCCAkigAwIBAgIUKG41PW4VtiphzASAMY4/3hL8OtAwDQYJKoZIhvcNAQEL

View File

@ -0,0 +1,16 @@
---
schema: deckhand/CertificateAuthority/v1.0
metadata:
schema: metadata/Document/v1.0
name: application-api
storagePolicy: cleartext
layeringDefinition:
abstract: False
layer: site
data: |-
-----BEGIN CERTIFICATE-----
MIIDYDCCAkigAwIBAgIUKG41PW4VtiphzASAMY4/3hL8OtAwDQYJKoZIhvcNAQEL
...snip...
P3WT9CfFARnsw2nKjnglQcwKkKLYip0WY2wh3FE7nrQZP6xKNaSRlh6p2pCGwwwH
HkvVwA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,16 @@
---
schema: deckhand/CertificateAuthorityKey/v1.0
metadata:
schema: metadata/Document/v1.0
name: application-api
storagePolicy: cleartext
layeringDefinition:
abstract: False
layer: site
data: |-
-----BEGIN CERTIFICATE-----
MIIDYDCCAkigAwIBAgIUKG41PW4VtiphzASAMY4/3hL8OtAwDQYJKoZIhvcNAQEL
...snip...
P3WT9CfFARnsw2nKjnglQcwKkKLYip0WY2wh3FE7nrQZP6xKNaSRlh6p2pCGwwwH
HkvVwA==
-----END CERTIFICATE-----

View File

@ -4,6 +4,9 @@ metadata:
schema: metadata/Document/v1.0
name: application-api
storagePolicy: encrypted
layeringDefinition:
abstract: False
layer: site
data: |-
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAx+m1+ao7uTVEs+I/Sie9YsXL0B9mOXFlzEdHX8P8x4nx78/T

View File

@ -5,5 +5,8 @@ metadata:
name: promenade/Node/v1.0 # Specifies the documents to be used for validation.
labels:
application: promenade
layeringDefinition:
abstract: False
layer: site
data: # Valid JSON Schema is expected here.
$schema: http://blah

View File

@ -4,6 +4,9 @@ schema: deckhand/LayeringPolicy/v1.0
metadata:
schema: metadata/Control/v1.0
name: a-unique-config-name-12345
layeringDefinition:
abstract: False
layer: site
data:
layerOrder:
- global

View File

@ -6,4 +6,5 @@ metadata:
storagePolicy: encrypted
layeringDefinition:
abstract: False
layer: site
data: some-password

View File

@ -0,0 +1,10 @@
---
schema: deckhand/PrivateKey/v1.0
metadata:
schema: metadata/Document/v1.0
name: application-private-password
storagePolicy: encrypted
layeringDefinition:
abstract: False
layer: site
data: some-private-key

View File

@ -0,0 +1,10 @@
---
schema: deckhand/PublicKey/v1.0
metadata:
schema: metadata/Document/v1.0
name: application-public-password
storagePolicy: encrypted
layeringDefinition:
abstract: False
layer: site
data: some-public-key

View File

@ -4,6 +4,9 @@ schema: deckhand/ValidationPolicy/v1.0
metadata:
schema: metadata/Control/v1.0
name: later-validation
layeringDefinition:
abstract: False
layer: site
data:
validations:
- name: deckhand-schema-validation

View File

@ -33,9 +33,9 @@ DataSchema
that Deckhand can use for validation. No ``DataSchema`` documents with names
beginning with ``deckhand/`` or ``metadata/`` are allowed. The ``metadata.name``
field of each ``DataSchema`` document specifies the top level ``schema`` that it
is used to validate.
is used to validate against.
The contents of its ``data`` key are expected to be the json schema definition
The contents of its ``data`` key are expected to be the JSON schema definition
for the target document type from the target's top level ``data`` key down.
.. TODO: give valid, tiny schema as example

View File

@ -106,6 +106,25 @@ a specific action.
and succeed, then services can check whether the documents in a bucket are
stable, in accordance with the ``ValidationPolicy``.
Validation Stages
-----------------
Deckhand performs pre- and post-rendering validation on documents. For
pre-rendering validation 3 types of behavior are possible:
#. Successfully validated docuemnts are stored in Deckhand's database.
#. Failure to validate a document's basic properties will result in a 400
Bad Request error getting raised.
#. Failure to validate a document's schema-specific properties will result
in a validation error created in the database, which can be later
returned via the Validations API.
For post-rendering validation, 2 types of behavior are possible:
#. Successfully valdiated post-rendered documents are returned to the user.
#. Failure to validate post-rendered documents results in a 500 Internal Server
Error getting raised.
Validation Module
-----------------
@ -120,35 +139,162 @@ Validation Schemas
Below are the schemas Deckhand uses to validate documents.
.. automodule:: deckhand.engine.schema.base_schema
:members: schema
Base Schema
-----------
.. automodule:: deckhand.engine.schema.v1_0.certificate_authority_key_schema
:members: schema
* Base schema.
.. automodule:: deckhand.engine.schema.v1_0.certificate_authority_schema
:members: schema
Base JSON schema against which all Deckhand documents are validated.
.. automodule:: deckhand.engine.schema.v1_0.certificate_key_schema
:members: schema
.. literalinclude:: ../../deckhand/engine/schemas/base_schema.yaml
:language: yaml
:lines: 15-
:caption: Base schema that applies to all documents.
.. automodule:: deckhand.engine.schema.v1_0.certificate_schema
:members: schema
This schema is used to sanity-check all documents that are passed to
Deckhand. Failure to pass this schema results in a critical error.
.. automodule:: deckhand.engine.schema.v1_0.data_schema_schema
:members: schema
DataSchema Schemas
------------------
.. automodule:: deckhand.engine.schema.v1_0.layering_policy_schema
:members: schema
All schemas below are ``DataSchema`` documents. They define additional
properties not included in the base schema or override default properties in
the base schema.
.. automodule:: deckhand.engine.schema.v1_0.passphrase_schema
:members: schema
These schemas are only enforced after validation for the base schema has
passed. Failure to pass these schemas will result in an error entry being
created for the validation with name ``deckhand-schema-validation``
corresponding to the created revision.
.. automodule:: deckhand.engine.schema.v1_0.private_key_schema
:members: schema
* ``CertificateAuthorityKey`` schema.
.. automodule:: deckhand.engine.schema.v1_0.public_key_schema
:members: schema
JSON schema against which all documents with
``deckhand/CertificateAuthorityKey/v1`` schema are validated.
.. automodule:: deckhand.engine.schema.v1_0.validation_policy_schema
:members: schema
.. literalinclude::
../../deckhand/engine/schemas/certificate_authority_key_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``CertificateAuthorityKey`` documents.
This schema is used to sanity-check all CertificateAuthorityKey documents
that are passed to Deckhand.
* ``CertificateAuthority`` schema.
JSON schema against which all documents with
``deckhand/CertificateAuthority/v1`` schema are validated.
.. literalinclude::
../../deckhand/engine/schemas/certificate_authority_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``CertificateAuthority`` documents.
This schema is used to sanity-check all ``CertificateAuthority`` documents
that are passed to Deckhand.
* ``CertificateKey`` schema.
JSON schema against which all documents with ``deckhand/CertificateKey/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/certificate_key_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``CertificateKey`` documents.
This schema is used to sanity-check all ``CertificateKey`` documents that are
passed to Deckhand.
* ``Certificate`` schema.
JSON schema against which all documents with ``deckhand/Certificate/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/certificate_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``Certificate`` documents.
This schema is used to sanity-check all ``Certificate`` documents that are
passed to Deckhand.
* ``DataSchema`` schema.
JSON schema against which all documents with ``deckhand/DataSchema/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/dataschema_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``DataSchema`` documents.
This schema is used to sanity-check all ``DataSchema`` documents that are
passed to Deckhand.
* ``LayeringPolicy`` schema.
JSON schema against which all documents with ``deckhand/LayeringPolicy/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/layering_policy_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``LayeringPolicy`` documents.
This schema is used to sanity-check all ``LayeringPolicy`` documents that are
passed to Deckhand.
* ``PrivateKey`` schema.
JSON schema against which all documents with ``deckhand/PrivateKey/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/passphrase_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``PrivateKey`` documents.
This schema is used to sanity-check all ``PrivateKey`` documents that are
passed to Deckhand.
* ``PublicKey`` schema.
JSON schema against which all documents with ``deckhand/PublicKey/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/public_key_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``PublicKey`` documents.
This schema is used to sanity-check all ``PublicKey`` documents that are
passed to Deckhand.
* ``Passphrase`` schema.
JSON schema against which all documents with ``deckhand/Passphrase/v1``
schema are validated.
.. literalinclude:: ../../deckhand/engine/schemas/private_key_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``Passphrase`` documents.
This schema is used to sanity-check all ``Passphrase`` documents that are
passed to Deckhand.
* ``ValidationPolicy`` schema.
JSON schema against which all documents with ``deckhand/ValidationPolicy/v1``
schema are validated.
.. literalinclude::
../../deckhand/engine/schemas/validation_policy_schema.yaml
:language: yaml
:lines: 15-
:caption: Schema for ``ValidationPolicy`` documents.
This schema is used to sanity-check all ``ValidationPolicy`` documents that
are passed to Deckhand.