diff --git a/deckhand/engine/document_validation.py b/deckhand/engine/document_validation.py index d97e38bd..c9f24961 100644 --- a/deckhand/engine/document_validation.py +++ b/deckhand/engine/document_validation.py @@ -13,17 +13,20 @@ # limitations under the License. import jsonschema +from oslo_log import log as logging +import six from deckhand.engine.schema.v1_0 import default_policy_validation from deckhand.engine.schema.v1_0 import default_schema_validation from deckhand import errors +LOG = logging.getLogger(__name__) + class DocumentValidation(object): """Class for document validation logic for YAML files. - This class is responsible for parsing, validating and retrieving secret - values for values stored in the YAML file. + This class is responsible for performing built-in validations on Documents. :param data: YAML data that requires secrets to be validated, merged and consolidated. @@ -45,6 +48,8 @@ class DocumentValidation(object): - `deckhand-document-schema-validation` - `deckhand-policy-validation` """ + + # TODO: Use the correct validation based on the Document's schema. internal_validations = [ {'version': 'v1', 'fqn': 'deckhand-document-schema-validation', 'schema': default_schema_validation}, @@ -56,7 +61,7 @@ class DocumentValidation(object): @property def schema(self): - # TODO: return schema based on version and kind. + # TODO: return schema based on Document's schema. return [v['schema'] for v in self.internal_validations if v['version'] == self.schema_version][0].schema @@ -64,11 +69,22 @@ class DocumentValidation(object): """Pre-validate that the YAML file is correctly formatted.""" self._validate_with_schema() - # TODO(fm577c): Query Deckhand API to validate "src" values. - def _validate_with_schema(self): - # Validate the document using the schema defined by the document's - # `schemaVersion` and `kind`. + # Validate the document using the document's ``schema``. Only validate + # concrete documents. + try: + abstract = self.data['metadata']['layeringDefinition'][ + 'abstract'] + is_abstract = six.text_type(abstract).lower() == 'true' + except KeyError as e: + raise errors.InvalidFormat( + "Could not find 'abstract' property from document.") + + if is_abstract: + LOG.info( + "Skipping validation for the document because it is abstract") + return + try: schema_version = self.data['schema'].split('/')[-1] doc_schema_version = self.SchemaVersion(schema_version) diff --git a/deckhand/errors.py b/deckhand/errors.py index 9ef701f8..f79341d3 100644 --- a/deckhand/errors.py +++ b/deckhand/errors.py @@ -14,7 +14,7 @@ class DeckhandException(Exception): - """Base Nova Exception + """Base Deckhand Exception To correctly use this class, inherit from it and define a 'msg_fmt' property. That msg_fmt will get printf'd with the keyword arguments provided to the constructor. diff --git a/deckhand/tests/unit/engine/test_document_validation.py b/deckhand/tests/unit/engine/test_document_validation.py index 835b531e..eeaee958 100644 --- a/deckhand/tests/unit/engine/test_document_validation.py +++ b/deckhand/tests/unit/engine/test_document_validation.py @@ -17,6 +17,7 @@ import os import testtools import yaml +import mock import six from deckhand.engine import document_validation @@ -80,7 +81,6 @@ class TestDocumentValidation(testtools.TestCase): "is a required property.") invalid_data = [ (self._corrupt_data('data'), 'data'), - (self._corrupt_data('metadata'), 'metadata'), (self._corrupt_data('metadata.schema'), 'schema'), (self._corrupt_data('metadata.name'), 'name'), (self._corrupt_data('metadata.substitutions'), 'substitutions'), @@ -92,3 +92,29 @@ class TestDocumentValidation(testtools.TestCase): with six.assertRaisesRegex(self, errors.InvalidFormat, expected_err % missing_key): document_validation.DocumentValidation(invalid_entry) + + def test_initialization_missing_abstract_section(self): + expected_err = ("Could not find 'abstract' property from document.") + + invalid_data = [ + self._corrupt_data('metadata'), + self._corrupt_data('metadata.layeringDefinition'), + self._corrupt_data('metadata.layeringDefinition.abstract'), + ] + + for invalid_entry in invalid_data: + with six.assertRaisesRegex(self, errors.InvalidFormat, + expected_err): + document_validation.DocumentValidation(invalid_entry) + + @mock.patch.object(document_validation, 'LOG', autospec=True) + def test_initialization_with_abstract_document(self, mock_log): + abstract_data = copy.deepcopy(self.data) + + for true_val in (True, 'true', 'True'): + abstract_data['metadata']['layeringDefinition']['abstract'] = True + + document_validation.DocumentValidation(abstract_data) + mock_log.info.assert_called_once_with( + "Skipping validation for the document because it is abstract") + mock_log.info.reset_mock() diff --git a/deckhand/tests/unit/resources/sample.yaml b/deckhand/tests/unit/resources/sample.yaml index d1e83e0d..a5ebaa6a 100644 --- a/deckhand/tests/unit/resources/sample.yaml +++ b/deckhand/tests/unit/resources/sample.yaml @@ -8,7 +8,7 @@ metadata: genesis: enabled master: enabled layeringDefinition: - abstract: true + abstract: false layer: region parentSelector: required_key_a: required_label_a