Updated /GET revisions response body.

This commit is contained in:
Felipe Monteiro 2017-07-31 17:01:40 +01:00
parent 8ff4639c6c
commit 841906a435
10 changed files with 68 additions and 28 deletions

View File

@ -79,15 +79,23 @@ Sample response:
```yaml
---
child_id: null
count: 2
created_at: '2017-07-31T14:36:00.348967'
deleted: false
deleted_at: null
id: d3428d6a-d8c4-4a5b-8006-aba974cc36a2
parent_id: null
results: []
updated_at: '2017-07-31T14:36:00.348973'
count: 7
next: https://deckhand/api/v1.0/revisions?limit=2&offset=2
prev: null
results:
- id: 0
url: https://deckhand/api/v1.0/revisions/0
createdAt: 2017-07-14T21:23Z
validationPolicies:
site-deploy-validation:
status: failed
- id: 1
url: https://deckhand/api/v1.0/revisions/1
createdAt: 2017-07-16T01:15Z
validationPolicies:
site-deploy-validation:
status: succeeded
...
```
GET `/revisions/{revision_id}/documents`

View File

@ -17,11 +17,14 @@ import yaml
import falcon
from falcon import request
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
import six
from deckhand import errors
LOG = logging.getLogger(__name__)
class BaseResource(object):
"""Base resource class for implementing API resources."""
@ -79,12 +82,17 @@ class BaseResource(object):
resp.status = status_code
def to_yaml_body(self, dict_body):
"""Converts dictionary into YAML response body.
"""Converts JSON body into YAML response body.
:dict_body: response body to be converted to YAML.
:returns: YAML encoding of `dict_body`.
"""
return yaml.safe_dump_all(dict_body)
if isinstance(dict_body, dict):
return yaml.safe_dump(dict_body)
elif isinstance(dict_body, list):
return yaml.safe_dump_all(dict_body)
return TypeError('Unrecognized dict_body type when converting response'
' body to YAML format.')
class DeckhandRequestContext(object):

View File

@ -47,10 +47,11 @@ class DocumentsResource(api_base.BaseResource):
LOG.error(error_msg)
return self.return_error(resp, falcon.HTTP_400, message=error_msg)
# Validate the document before doing anything with it.
# All concrete documents in the payload must successfully pass their
# JSON schema validations. Otherwise raise an error.
try:
for doc in documents:
document_validation.DocumentValidation(doc)
document_validation.DocumentValidation(doc).pre_validate()
except deckhand_errors.InvalidFormat as e:
return self.return_error(resp, falcon.HTTP_400, message=e)

View File

@ -19,7 +19,7 @@ from deckhand.db.sqlalchemy import api as db_api
class RevisionsResource(api_base.BaseResource):
"""API resource for realizing CRUD endpoints for Document Revisions."""
"""API resource for realizing CRUD endpoints for Revisions."""
def on_get(self, req, resp):
"""Returns list of existing revisions.

View File

@ -212,11 +212,21 @@ def revision_get_all(session=None):
session = session or get_session()
revisions = session.query(models.Revision).all()
revisions_resp = [r.to_dict() for r in revisions]
resp_body = {
'count': len(revisions_resp),
'next': None,
'prev': None,
'revisions': []
}
for revision in revisions_resp:
revision['count'] = len(revision.pop('documents'))
result = {}
for attr in ('id', 'created_at'):
result[utils.to_camel_case(attr)] = revision[attr]
result['count'] = len(revision.pop('documents'))
resp_body['revisions'].append(result)
return revisions_resp
return resp_body
def revision_get_documents(revision_id, session=None, **filters):

View File

@ -121,7 +121,7 @@ class Document(BASE, DeckhandBase):
name = Column(String(64), nullable=False)
# NOTE: Do not define a maximum length for these JSON data below. However,
# this approach is not compatible with all database types.
# "metadata" is reserved, so use "doc_metadata" instead.
# "metadata" is reserved, so use "_metadata" instead.
_metadata = Column(oslo_types.JsonEncodedDict(), nullable=False)
data = Column(oslo_types.JsonEncodedDict(), nullable=False)
revision_id = Column(Integer, ForeignKey('revisions.id'), nullable=False)

View File

@ -34,7 +34,6 @@ class DocumentValidation(object):
def __init__(self, data):
self.data = data
self.pre_validate_data()
class SchemaVersion(object):
"""Class for retrieving correct schema for pre-validation on YAML.
@ -65,7 +64,7 @@ class DocumentValidation(object):
return [v['schema'] for v in self.internal_validations
if v['version'] == self.schema_version][0].schema
def pre_validate_data(self):
def pre_validate(self):
"""Pre-validate that the YAML file is correctly formatted."""
self._validate_with_schema()

View File

@ -23,6 +23,10 @@ class TestRevisions(base.TestDbBase):
self._create_documents(payload)
revisions = self._list_revisions()
self.assertIsInstance(revisions, list)
self.assertIsInstance(revisions, dict)
self.assertIn('revisions', revisions)
self.assertIsInstance(revisions['revisions'], list)
revisions = revisions['revisions']
self.assertEqual(1, len(revisions))
self.assertEqual(4, revisions[0]["count"])

View File

@ -71,10 +71,8 @@ class TestDocumentValidation(testtools.TestCase):
return corrupted_data
def test_initialization(self):
doc_validation = document_validation.DocumentValidation(
self.data)
self.assertIsInstance(doc_validation,
document_validation.DocumentValidation)
doc_validation = document_validation.DocumentValidation(self.data)
doc_validation.pre_validate() # Should not raise any errors.
def test_initialization_missing_sections(self):
expected_err = ("The provided YAML file is invalid. Exception: '%s' "
@ -91,11 +89,12 @@ class TestDocumentValidation(testtools.TestCase):
for invalid_entry, missing_key in invalid_data:
with six.assertRaisesRegex(self, errors.InvalidFormat,
expected_err % missing_key):
document_validation.DocumentValidation(invalid_entry)
doc_validation = document_validation.DocumentValidation(
invalid_entry)
doc_validation.pre_validate()
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'),
@ -105,7 +104,9 @@ class TestDocumentValidation(testtools.TestCase):
for invalid_entry in invalid_data:
with six.assertRaisesRegex(self, errors.InvalidFormat,
expected_err):
document_validation.DocumentValidation(invalid_entry)
doc_validation = document_validation.DocumentValidation(
invalid_entry)
doc_validation.pre_validate()
@mock.patch.object(document_validation, 'LOG', autospec=True)
def test_initialization_with_abstract_document(self, mock_log):
@ -114,7 +115,9 @@ class TestDocumentValidation(testtools.TestCase):
for true_val in (True, 'true', 'True'):
abstract_data['metadata']['layeringDefinition']['abstract'] = True
document_validation.DocumentValidation(abstract_data)
doc_validation = document_validation.DocumentValidation(
abstract_data)
doc_validation.pre_validate()
mock_log.info.assert_called_once_with(
"Skipping validation for the document because it is abstract")
mock_log.info.reset_mock()

View File

@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import string
def multi_getattr(multi_key, dict_data):
"""Iteratively check for nested attributes in the YAML data.
@ -45,3 +47,8 @@ def multi_getattr(multi_key, dict_data):
data = data.get(attr)
return data
def to_camel_case(s):
return (s[0].lower() + string.capwords(s, sep='_').replace('_', '')[1:]
if s else s)