diff --git a/deckhand/control/revision_documents.py b/deckhand/control/revision_documents.py index c10527f4..e80e965a 100644 --- a/deckhand/control/revision_documents.py +++ b/deckhand/control/revision_documents.py @@ -93,7 +93,7 @@ class RenderedDocumentsResource(api_base.BaseResource): @policy.authorize('deckhand:list_cleartext_documents') @common.sanitize_params([ - 'schema', 'metadata.name', 'metadata.label']) + 'schema', 'metadata.name', 'metadata.label', 'status.bucket']) def on_get(self, req, resp, sanitized_params, revision_id): include_encrypted = policy.conditional_authorize( 'deckhand:list_encrypted_documents', req.context, do_raise=False) diff --git a/deckhand/db/sqlalchemy/api.py b/deckhand/db/sqlalchemy/api.py index 634d0ab2..82e6128d 100644 --- a/deckhand/db/sqlalchemy/api.py +++ b/deckhand/db/sqlalchemy/api.py @@ -19,6 +19,7 @@ import ast import copy import functools import hashlib +import re import threading from oslo_config import cfg @@ -524,6 +525,16 @@ def _update_revision_history(documents): return documents +def _add_microversion(value): + """Hack for coercing all Deckhand schema fields (``schema`` and + ``metadata.schema``) into ending with v1.0 rather than v1, for example. + """ + microversion_re = r'^.*/.*/v[0-9]{1}$' + if re.match(value, microversion_re): + return value + '.0' + return value + + def _apply_filters(dct, **filters): """Apply filters to ``dct``. @@ -571,13 +582,32 @@ def _apply_filters(dct, **filters): filter_val.items()).issubset(set(actual_val.items())) if not is_subset: return False + # Else both filters are string literals. else: + # Filtering by schema must support namespace matching + # (e.g. schema=promenade) such that all kind and schema + # documents with promenade namespace are returned, or + # (e.g. schema=promenade/Node) such that all version + # schemas with namespace=schema and kind=Node are returned. if isinstance(actual_val, bool): filter_val = _transform_filter_bool(filter_val) - # Else both filters are string literals. - if filter_key in ['metadata.schema', 'schema']: - if not actual_val.startswith(filter_val): + if filter_key in ['schema', 'metadata.schema']: + actual_val = _add_microversion(actual_val) + filter_val = _add_microversion(filter_val) + parts = actual_val.split('/')[:2] + if len(parts) == 2: + actual_namespace, actual_kind = parts + elif len(parts) == 1: + actual_namespace = parts[0] + actual_kind = '' + else: + actual_namespace = actual_kind = '' + actual_minus_version = actual_namespace + '/' + actual_kind + + if not (filter_val == actual_val or + actual_minus_version == filter_val or + actual_namespace == filter_val): return False else: if actual_val != filter_val: diff --git a/deckhand/tests/functional/gabbits/revision-documents-filters-negative.yaml b/deckhand/tests/functional/gabbits/revision-documents-filters-negative.yaml new file mode 100644 index 00000000..6fd5d9ce --- /dev/null +++ b/deckhand/tests/functional/gabbits/revision-documents-filters-negative.yaml @@ -0,0 +1,47 @@ +# 1. Test invalid cases for the "schema" filter: +# * Partial namespace is invalid, e.g.: schema=prom +# * Partial kind is invalid, e.g.: schema=promenade/No +# 2. Test that filtering by wrong version returns no results. + +defaults: + request_headers: + content-type: application/x-yaml + response_headers: + content-type: application/x-yaml + +tests: + - name: purge + desc: Begin testing from known state. + DELETE: /api/v1.0/revisions + status: 204 + response_headers: null + + - name: initialize + desc: Create initial documents + PUT: /api/v1.0/buckets/mop/documents + status: 200 + data: <@resources/design-doc-layering-sample.yaml + + - name: filter_by_schema_partial_namespace + desc: Verify revision documents do not return results for partial namespace + GET: /api/v1.0/revisions/$RESPONSE['$.[0].status.revision']/documents + query_parameters: + schema: exam + status: 200 + response_multidoc_jsonpaths: null + + - name: filter_by_schema_partial_kind + desc: Verify revision documents do not return results for partial kind + GET: /api/v1.0/revisions/$HISTORY['initialize'].$RESPONSE['$.[0].status.revision']/documents + query_parameters: + schema: example/Ki + status: 200 + response_multidoc_jsonpaths: null + + - name: filter_by_schema_incorrect_version + desc: Verify revision documents do not return results for incorrect version + GET: /api/v1.0/revisions/$HISTORY['initialize'].$RESPONSE['$.[0].status.revision']/documents + query_parameters: + schema: example/Kind/v2 + status: 200 + response_multidoc_jsonpaths: null diff --git a/deckhand/tests/functional/gabbits/revision-documents-filters.yaml b/deckhand/tests/functional/gabbits/revision-documents-filters.yaml index 105fbb05..c3998adb 100644 --- a/deckhand/tests/functional/gabbits/revision-documents-filters.yaml +++ b/deckhand/tests/functional/gabbits/revision-documents-filters.yaml @@ -37,6 +37,36 @@ tests: $.[0].metadata.name: layering-policy $.[0].schema: deckhand/LayeringPolicy/v1 + - name: filter_by_schema_namespace + desc: Verify revision documents filtered by schema namespace + GET: /api/v1.0/revisions/$RESPONSE['$.[0].status.revision']/documents + query_parameters: + schema: example + status: 200 + response_multidoc_jsonpaths: + $.`len`: 3 + $.[0].metadata.name: global-1234 + $.[0].schema: example/Kind/v1 + $.[1].metadata.name: region-1234 + $.[1].schema: example/Kind/v1 + $.[2].metadata.name: site-1234 + $.[2].schema: example/Kind/v1 + + - name: filter_by_schema_namespace_and_kind + desc: Verify revision documents filtered by schema namespace and kind + GET: /api/v1.0/revisions/$RESPONSE['$.[0].status.revision']/documents + query_parameters: + schema: example/Kind + status: 200 + response_multidoc_jsonpaths: + $.`len`: 3 + $.[0].metadata.name: global-1234 + $.[0].schema: example/Kind/v1 + $.[1].metadata.name: region-1234 + $.[1].schema: example/Kind/v1 + $.[2].metadata.name: site-1234 + $.[2].schema: example/Kind/v1 + - name: filter_by_metadata_name desc: Verify revision documents filtered by metadata.name GET: /api/v1.0/revisions/$RESPONSE['$.[0].status.revision']/documents