Add Revision resource.
This commit is contained in:
parent
adca9575b6
commit
b44392bcb4
|
@ -21,6 +21,7 @@ from oslo_log import log as logging
|
|||
from deckhand.conf import config
|
||||
from deckhand.control import base as api_base
|
||||
from deckhand.control import documents
|
||||
from deckhand.control import revisions
|
||||
from deckhand.control import secrets
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
|
||||
|
@ -68,6 +69,7 @@ def start_api(state_manager=None):
|
|||
|
||||
v1_0_routes = [
|
||||
('documents', documents.DocumentsResource()),
|
||||
('revisions/{revision_id}/documents', revisions.RevisionsResource()),
|
||||
('secrets', secrets.SecretsResource())
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# 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.
|
||||
|
||||
import copy
|
||||
import yaml
|
||||
|
||||
import falcon
|
||||
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils as json
|
||||
|
||||
from deckhand.control import base as api_base
|
||||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.engine import document_validation
|
||||
from deckhand import errors as deckhand_errors
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RevisionsResource(api_base.BaseResource):
|
||||
"""API resource for realizing CRUD endpoints for Document Revisions."""
|
||||
|
||||
def on_get(self, req, resp, revision_id):
|
||||
"""Returns all documents for a `revision_id`.
|
||||
|
||||
Returns a multi-document YAML response containing all the documents
|
||||
matching the filters specified via query string parameters. Returned
|
||||
documents will be as originally posted with no substitutions or
|
||||
layering applied.
|
||||
"""
|
||||
revision = db_api.revision_get(revision_id)
|
||||
resp.status = falcon.HTTP_201
|
||||
resp.body = revision['documents']
|
|
@ -123,8 +123,9 @@ def document_create(values, session=None):
|
|||
|
||||
filters = models.Document.UNIQUE_CONSTRAINTS
|
||||
try:
|
||||
existing_document = document_get(**{c: values[c] for c in filters
|
||||
if c != 'revision_id'})
|
||||
existing_document = document_get(
|
||||
raw_dict=True,
|
||||
**{c: values[c] for c in filters if c != 'revision_id'})
|
||||
except db_exception.DBError:
|
||||
# Ignore bad data at this point. Allow creation to bubble up the error
|
||||
# related to bad data.
|
||||
|
@ -162,10 +163,10 @@ def document_create(values, session=None):
|
|||
return created_document
|
||||
|
||||
|
||||
def document_get(session=None, **filters):
|
||||
def document_get(session=None, raw_dict=False, **filters):
|
||||
session = session or get_session()
|
||||
document = session.query(models.Document).filter_by(**filters).first()
|
||||
return document.to_dict() if document else {}
|
||||
return document.to_dict(raw_dict=raw_dict) if document else {}
|
||||
|
||||
|
||||
####################
|
||||
|
|
|
@ -134,6 +134,13 @@ class Revision(BASE, DeckhandBase):
|
|||
parent_id = Column(Integer, ForeignKey('revisions.id'), nullable=True)
|
||||
child_id = Column(Integer, ForeignKey('revisions.id'), nullable=True)
|
||||
|
||||
documents = relationship("Document")
|
||||
|
||||
def to_dict(self):
|
||||
d = super(Revision, self).to_dict()
|
||||
d['documents'] = [doc.to_dict() for doc in self.documents]
|
||||
return d
|
||||
|
||||
|
||||
class Document(BASE, DeckhandBase):
|
||||
UNIQUE_CONSTRAINTS = ('schema', 'name', 'revision_id')
|
||||
|
@ -151,10 +158,18 @@ class Document(BASE, DeckhandBase):
|
|||
data = Column(JSONEncodedDict(), nullable=False)
|
||||
revision_id = Column(Integer, ForeignKey('revisions.id'), nullable=False)
|
||||
|
||||
revision = relationship("Revision",
|
||||
foreign_keys=[revision_id],
|
||||
cascade="all, delete")
|
||||
def to_dict(self, raw_dict=False):
|
||||
"""Convert the ``Document`` object into a dictionary format.
|
||||
|
||||
:param raw_dict: if True, returns unmodified data; else returns data
|
||||
expected by users.
|
||||
:returns: dictionary format of ``Document`` object.
|
||||
"""
|
||||
d = super(Document, self).to_dict()
|
||||
# ``_metadata`` is used in the DB schema as ``metadata`` is reserved.
|
||||
if not raw_dict:
|
||||
d['metadata'] = d.pop('_metadata')
|
||||
return d
|
||||
|
||||
def register_models(engine):
|
||||
"""Create database tables for all models with the given engine."""
|
||||
|
|
|
@ -21,13 +21,15 @@ from testtools import matchers
|
|||
from deckhand.db.sqlalchemy import api as db_api
|
||||
from deckhand.tests.unit import base
|
||||
|
||||
BASE_EXPECTED_FIELDS = ("created_at", "updated_at", "deleted_at", "deleted")
|
||||
DOCUMENT_EXPECTED_FIELDS = BASE_EXPECTED_FIELDS + (
|
||||
"id", "schema", "name", "metadata", "data", "revision_id")
|
||||
REVISION_EXPECTED_FIELDS = BASE_EXPECTED_FIELDS + (
|
||||
"id", "child_id", "parent_id", "documents")
|
||||
|
||||
|
||||
class DocumentFixture(object):
|
||||
|
||||
EXPECTED_FIELDS = ("created_at", "updated_at", "deleted_at", "deleted",
|
||||
"id", "schema", "name", "_metadata", "data",
|
||||
"revision_id")
|
||||
|
||||
@staticmethod
|
||||
def get_minimal_fixture(**kwargs):
|
||||
fixture = {'data': 'fake document data',
|
||||
|
@ -54,9 +56,19 @@ class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
|||
self._validate_revision(revision)
|
||||
return revision
|
||||
|
||||
def _validate_object(self, obj):
|
||||
for attr in BASE_EXPECTED_FIELDS:
|
||||
if attr.endswith('_at'):
|
||||
self.assertThat(obj[attr], matchers.MatchesAny(
|
||||
matchers.Is(None), matchers.IsInstance(str)))
|
||||
else:
|
||||
self.assertIsInstance(obj[attr], bool)
|
||||
|
||||
def _validate_document(self, actual, expected=None, is_deleted=False):
|
||||
self._validate_object(actual)
|
||||
|
||||
# Validate that the document has all expected fields and is a dict.
|
||||
expected_fields = list(DocumentFixture.EXPECTED_FIELDS)
|
||||
expected_fields = list(DOCUMENT_EXPECTED_FIELDS)
|
||||
if not is_deleted:
|
||||
expected_fields.remove('deleted_at')
|
||||
|
||||
|
@ -64,9 +76,6 @@ class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
|||
for field in expected_fields:
|
||||
self.assertIn(field, actual)
|
||||
|
||||
# ``_metadata`` is used in the DB schema as ``metadata`` is reserved.
|
||||
actual['metadata'] = actual.pop('_metadata')
|
||||
|
||||
if expected:
|
||||
# Validate that the expected values are equivalent to actual
|
||||
# values.
|
||||
|
@ -74,11 +83,10 @@ class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
|||
self.assertEqual(val, actual[key])
|
||||
|
||||
def _validate_revision(self, revision):
|
||||
expected_attrs = ('id', 'child_id', 'parent_id')
|
||||
for attr in expected_attrs:
|
||||
self._validate_object(revision)
|
||||
|
||||
for attr in REVISION_EXPECTED_FIELDS:
|
||||
self.assertIn(attr, revision)
|
||||
self.assertThat(revision[attr], matchers.MatchesAny(
|
||||
matchers.Is(None), matchers.IsInstance(unicode)))
|
||||
|
||||
def _validate_revision_connections(self, parent_document, parent_revision,
|
||||
child_document, child_revision,
|
||||
|
@ -200,3 +208,14 @@ class TestDocumentsApi(base.DeckhandWithDBTestCase):
|
|||
self._validate_revision_connections(
|
||||
parent_document, parent_revision, child_document, child_revision,
|
||||
False)
|
||||
|
||||
def test_get_documents_by_revision_id(self):
|
||||
payload = DocumentFixture.get_minimal_fixture()
|
||||
document = self._create_document(payload)
|
||||
|
||||
revision = self._get_revision(document['revision_id'])
|
||||
self.assertEqual(1, len(revision['documents']))
|
||||
self.assertEqual(document, revision['documents'][0])
|
||||
|
||||
def test_get_multiple_documents_by_revision_id(self):
|
||||
# TODO
|
||||
|
|
Loading…
Reference in New Issue