summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Strassner <bryan.strassner@gmail.com>2018-10-17 17:33:40 -0500
committerBryan Strassner <bryan.strassner@gmail.com>2018-11-30 13:36:14 -0600
commit667a538330480cb3502b006071ee928258f0abcf (patch)
treefd841fabd315f8196d6d94e7bec0eb0789e15ee4
parent03d7269b6ad9d7b734cb3a7bc1693139e639a9c5 (diff)
Support clearing collections of configdocs
Adds an option to create configdocs as an empty colleciton. This is done as an explicit flag (--empty-collection) on the command as opposed to using empty files to prevent accidental emtpy file loads leading to frustration. Since this introduced a new flag value for the CLI, the CLIs using flag values were updated to use the standard is_flag=True instead of the flag_value=True or some other value when a boolean flag is expected. Minor updates to CLI tests due to moving to responses 0.10.2 Depends-On: https://review.openstack.org/#/c/614421/ Change-Id: I489b0e1183335cbfbaa2014c1458a84dadf6bb0b
Notes
Notes (review): Code-Review+2: Aaron Sheffield <ajs@sheffieldfamily.net> Code-Review+1: Nishant Kumar <nishant.e.kumar@ericsson.com> Code-Review+1: Evgeniy L <eli@mirantis.com> Code-Review+2: Scott Hussey <sthussey@att.com> Workflow+1: Scott Hussey <sthussey@att.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Fri, 21 Dec 2018 14:40:00 +0000 Reviewed-on: https://review.openstack.org/611457 Project: openstack/airship-shipyard Branch: refs/heads/master
-rw-r--r--doc/source/API.rst11
-rw-r--r--doc/source/CLI.rst19
-rw-r--r--src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py84
-rw-r--r--src/bin/shipyard_airflow/shipyard_airflow/control/helpers/configdocs_helper.py19
-rw-r--r--src/bin/shipyard_airflow/test-requirements.txt2
-rw-r--r--src/bin/shipyard_airflow/tests/unit/control/test_configdocs_helper.py32
-rw-r--r--src/bin/shipyard_client/shipyard_client/api_client/shipyard_api_client.py5
-rw-r--r--src/bin/shipyard_client/shipyard_client/cli/commit/commands.py4
-rw-r--r--src/bin/shipyard_client/shipyard_client/cli/create/actions.py34
-rw-r--r--src/bin/shipyard_client/shipyard_client/cli/create/commands.py128
-rw-r--r--src/bin/shipyard_client/shipyard_client/cli/get/commands.py16
-rw-r--r--src/bin/shipyard_client/test-requirements.txt2
-rw-r--r--src/bin/shipyard_client/tests/unit/cli/commit/test_commit_actions.py13
-rw-r--r--src/bin/shipyard_client/tests/unit/cli/create/test_create_actions.py45
-rw-r--r--src/bin/shipyard_client/tests/unit/cli/create/test_create_commands.py109
15 files changed, 393 insertions, 130 deletions
diff --git a/doc/source/API.rst b/doc/source/API.rst
index 1abce10..f463b67 100644
--- a/doc/source/API.rst
+++ b/doc/source/API.rst
@@ -158,10 +158,9 @@ Deckhand
158 158
159POST /v1.0/configdocs/{collection_id} 159POST /v1.0/configdocs/{collection_id}
160^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 160^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
161Ingests a collection of documents. Synchronous. POSTing an empty body 161Ingests a collection of documents. Synchronous. If a POST to the
162indicates that the specified collection should be deleted when the 162commitconfigdocs is already in progress, this POST should be rejected with a
163Shipyard Buffer is committed. If a POST to the commitconfigdocs is in 163409 error.
164progress, this POST should be rejected with a 409 error.
165 164
166.. note:: 165.. note::
167 166
@@ -183,6 +182,10 @@ Query Parameters
183 - replace: Clear the Shipyard Buffer before adding the specified 182 - replace: Clear the Shipyard Buffer before adding the specified
184 collection. 183 collection.
185 184
185- empty-collection: Set to true to indicate that this collection should be
186 made empty and effectively deleted when the Shipyard Buffer is committed.
187 If this parameter is specified, the POST body will be ignored.
188
186Responses 189Responses
187''''''''' 190'''''''''
188201 Created 191201 Created
diff --git a/doc/source/CLI.rst b/doc/source/CLI.rst
index bca045f..651ede1 100644
--- a/doc/source/CLI.rst
+++ b/doc/source/CLI.rst
@@ -293,7 +293,7 @@ or one or more directory options must be specified.
293 293
294 shipyard create configdocs 294 shipyard create configdocs
295 <collection> 295 <collection>
296 [--append | --replace] 296 [--append | --replace] [--empty-collection]
297 --filename=<filename> (repeatable) 297 --filename=<filename> (repeatable)
298 | 298 |
299 --directory=<directory> (repeatable) 299 --directory=<directory> (repeatable)
@@ -308,8 +308,8 @@ or one or more directory options must be specified.
308 308
309.. note:: 309.. note::
310 310
311 Either --filename or --directory must be specified, but both may not be 311 --filename and/or --directory must be specified unless --empty-collection
312 specified for the same invocation of shipyard. 312 is used.
313 313
314<collection> 314<collection>
315 The collection to load. 315 The collection to load.
@@ -321,17 +321,22 @@ or one or more directory options must be specified.
321\--replace 321\--replace
322 Clear the shipyard buffer and replace it with the specified contents. 322 Clear the shipyard buffer and replace it with the specified contents.
323 323
324\--empty-collection
325 Indicate to Shipyard that the named collection should be made empty (contain
326 no documents). If --empty-collection is specified, the files named by
327 --filename or --directory will be ignored.
328
324\--filename=<filename> 329\--filename=<filename>
325 The file name to use as the contents of the collection. (repeatable) If 330 The file name to use as the contents of the collection. (repeatable) If
326 any documents specified fail basic validation, all of the documents will 331 any documents specified fail basic validation, all of the documents will
327 be rejected. Use of filename parameters may not be used in conjunction 332 be rejected. Use of ``filename`` parameters may not be used in conjunction
328 with the directory parameter. 333 with the directory parameter.
329 334
330\--directory=<directory> 335\--directory=<directory>
331 A directory containing documents that will be joined and loaded as a 336 A directory containing documents that will be joined and loaded as a
332 collection. (Repeatable) Any documents that fail basic validation will reject the 337 collection. (Repeatable) Any documents that fail basic validation will
333 whole set. Use of the directory parameter may not be used with the 338 reject the whole set. Use of the ``directory`` parameter may not be used
334 filename parameter. 339 with the ``filename`` parameter.
335 340
336\--recurse 341\--recurse
337 Recursively search through all directories for sub-directories that 342 Recursively search through all directories for sub-directories that
diff --git a/src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py b/src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py
index 2b2f61e..92c8ac4 100644
--- a/src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py
+++ b/src/bin/shipyard_airflow/shipyard_airflow/control/configdocs/configdocs_api.py
@@ -14,6 +14,8 @@
14""" 14"""
15Resources representing the configdocs API for shipyard 15Resources representing the configdocs API for shipyard
16""" 16"""
17import logging
18
17import falcon 19import falcon
18from oslo_config import cfg 20from oslo_config import cfg
19 21
@@ -26,6 +28,7 @@ from shipyard_airflow.control.helpers.configdocs_helper import (
26from shipyard_airflow.errors import ApiError 28from shipyard_airflow.errors import ApiError
27 29
28CONF = cfg.CONF 30CONF = cfg.CONF
31LOG = logging.getLogger(__name__)
29VERSION_VALUES = ['buffer', 32VERSION_VALUES = ['buffer',
30 'committed', 33 'committed',
31 'last_site_action', 34 'last_site_action',
@@ -59,23 +62,17 @@ class ConfigDocsResource(BaseResource):
59 """ 62 """
60 Ingests a collection of documents 63 Ingests a collection of documents
61 """ 64 """
62 content_length = req.content_length or 0 65 # Determine if this request is clearing the collection's contents.
63 if (content_length == 0): 66 empty_coll = req.get_param_as_bool('empty-collection') or False
64 raise ApiError( 67 if empty_coll:
65 title=('Content-Length is a required header'), 68 document_data = ""
66 description='Content Length is 0 or not specified', 69 LOG.debug("Collection %s is being emptied", collection_id)
67 status=falcon.HTTP_400, 70 else:
68 error_list=[{ 71 # Note, a newline in a prior header can trigger subsequent
69 'message': ( 72 # headers to be "missing" (and hence cause this code to think
70 'The Content-Length specified is 0 or not set. Check ' 73 # that the content length is missing)
71 'that a valid payload is included with this request ' 74 content_length = self.validate_content_length(req.content_length)
72 'and that your client is properly including a ' 75 document_data = req.stream.read(content_length)
73 'Content-Length header. Note that a newline character '
74 'in a prior header can trigger subsequent headers to '
75 'be ignored and trigger this failure.')
76 }],
77 retry=False, )
78 document_data = req.stream.read(content_length)
79 76
80 buffer_mode = req.get_param('buffermode') 77 buffer_mode = req.get_param('buffermode')
81 78
@@ -84,7 +81,8 @@ class ConfigDocsResource(BaseResource):
84 helper=helper, 81 helper=helper,
85 collection_id=collection_id, 82 collection_id=collection_id,
86 document_data=document_data, 83 document_data=document_data,
87 buffer_mode_param=buffer_mode) 84 buffer_mode_param=buffer_mode,
85 empty_collection=empty_coll)
88 86
89 resp.status = falcon.HTTP_201 87 resp.status = falcon.HTTP_201
90 if validations and validations['status'] == 'Success': 88 if validations and validations['status'] == 'Success':
@@ -92,6 +90,30 @@ class ConfigDocsResource(BaseResource):
92 resp.location = '/api/v1.0/configdocs/{}'.format(collection_id) 90 resp.location = '/api/v1.0/configdocs/{}'.format(collection_id)
93 resp.body = self.to_json(validations) 91 resp.body = self.to_json(validations)
94 92
93 def validate_content_length(self, content_length):
94 """Validates that the content length header is valid
95
96 :param content_length: the value of the content-length header.
97 :returns: the validate content length value
98 """
99 content_length = content_length or 0
100 if (content_length == 0):
101 raise ApiError(
102 title=('Content-Length is a required header'),
103 description='Content Length is 0 or not specified',
104 status=falcon.HTTP_400,
105 error_list=[{
106 'message': (
107 "The Content-Length specified is 0 or not set. To "
108 "clear a collection's contents, please specify "
109 "the query parameter 'empty-collection=true'."
110 "Otherwise, a non-zero length payload and "
111 "matching Content-Length header is required to "
112 "post a collection.")
113 }],
114 retry=False, )
115 return content_length
116
95 @policy.ApiEnforcer(policy.GET_CONFIGDOCS) 117 @policy.ApiEnforcer(policy.GET_CONFIGDOCS)
96 def on_get(self, req, resp, collection_id): 118 def on_get(self, req, resp, collection_id):
97 """ 119 """
@@ -132,7 +154,8 @@ class ConfigDocsResource(BaseResource):
132 helper, 154 helper,
133 collection_id, 155 collection_id,
134 document_data, 156 document_data,
135 buffer_mode_param=None): 157 buffer_mode_param=None,
158 empty_collection=False):
136 """ 159 """
137 Ingest the collection after checking preconditions 160 Ingest the collection after checking preconditions
138 """ 161 """
@@ -141,23 +164,28 @@ class ConfigDocsResource(BaseResource):
141 if helper.is_buffer_valid_for_bucket(collection_id, buffer_mode): 164 if helper.is_buffer_valid_for_bucket(collection_id, buffer_mode):
142 buffer_revision = helper.add_collection(collection_id, 165 buffer_revision = helper.add_collection(collection_id,
143 document_data) 166 document_data)
144 if helper.is_collection_in_buffer(collection_id): 167 if not (empty_collection or helper.is_collection_in_buffer(
145 return helper.get_deckhand_validation_status(buffer_revision) 168 collection_id)):
146 else: 169 # raise an error if adding the collection resulted in no new
170 # revision (meaning it was unchanged) and we're not explicitly
171 # clearing the collection
147 raise ApiError( 172 raise ApiError(
148 title=('Collection {} not added to Shipyard ' 173 title=('Collection {} not added to Shipyard '
149 'buffer'.format(collection_id)), 174 'buffer'.format(collection_id)),
150 description='Collection empty or resulted in no revision', 175 description='Collection created no new revision',
151 status=falcon.HTTP_400, 176 status=falcon.HTTP_400,
152 error_list=[{ 177 error_list=[{
153 'message': ( 178 'message':
154 'Empty collections are not supported. After ' 179 ('The collection {} added no new revision, and has '
155 'processing, the collection {} added no new ' 180 'been rejected as invalid input. This likely '
156 'revision, and has been rejected as invalid ' 181 'means that the collection already exists and '
157 'input'.format(collection_id)) 182 'was reloaded with the same contents'.format(
183 collection_id))
158 }], 184 }],
159 retry=False, 185 retry=False,
160 ) 186 )
187 else:
188 return helper.get_deckhand_validation_status(buffer_revision)
161 else: 189 else:
162 raise ApiError( 190 raise ApiError(
163 title='Invalid collection specified for buffer', 191 title='Invalid collection specified for buffer',
diff --git a/src/bin/shipyard_airflow/shipyard_airflow/control/helpers/configdocs_helper.py b/src/bin/shipyard_airflow/shipyard_airflow/control/helpers/configdocs_helper.py
index 19982e0..749c4ff 100644
--- a/src/bin/shipyard_airflow/shipyard_airflow/control/helpers/configdocs_helper.py
+++ b/src/bin/shipyard_airflow/shipyard_airflow/control/helpers/configdocs_helper.py
@@ -105,8 +105,23 @@ class ConfigdocsHelper(object):
105 return BufferMode.REJECTONCONTENTS 105 return BufferMode.REJECTONCONTENTS
106 106
107 def is_buffer_empty(self): 107 def is_buffer_empty(self):
108 """ Check if the buffer is empty. """ 108 """ Check if the buffer is empty.
109 return self._get_revision(BUFFER) is None 109
110 This can occur if there is no buffer revision, or if the buffer
111 revision is unchanged since the last committed version (or version 0)
112 """
113 if self._get_revision(BUFFER) is None:
114 return True
115 # Get the "diff" of the collctions for Buffer vs. Committed (or 0)
116 collections = self.get_configdocs_status()
117 # If there are no collections or they are all unmodified, return True
118 # Deleted, created, or modified means there's something in the buffer.
119 if not collections:
120 return True
121 for c in collections:
122 if c['new_status'] != 'unmodified':
123 return False
124 return True
110 125
111 def is_collection_in_buffer(self, collection_id): 126 def is_collection_in_buffer(self, collection_id):
112 """ 127 """
diff --git a/src/bin/shipyard_airflow/test-requirements.txt b/src/bin/shipyard_airflow/test-requirements.txt
index 0e8884d..5ea8b8f 100644
--- a/src/bin/shipyard_airflow/test-requirements.txt
+++ b/src/bin/shipyard_airflow/test-requirements.txt
@@ -1,7 +1,7 @@
1# Testing 1# Testing
2pytest==3.4 2pytest==3.4
3pytest-cov==2.5.1 3pytest-cov==2.5.1
4responses==0.8.1 4responses==0.10.2
5testfixtures==5.1.1 5testfixtures==5.1.1
6apache-airflow[crypto,celery,postgres,hive,hdfs,jdbc]==1.10.0 6apache-airflow[crypto,celery,postgres,hive,hdfs,jdbc]==1.10.0
7 7
diff --git a/src/bin/shipyard_airflow/tests/unit/control/test_configdocs_helper.py b/src/bin/shipyard_airflow/tests/unit/control/test_configdocs_helper.py
index 4ac2982..842defd 100644
--- a/src/bin/shipyard_airflow/tests/unit/control/test_configdocs_helper.py
+++ b/src/bin/shipyard_airflow/tests/unit/control/test_configdocs_helper.py
@@ -61,6 +61,8 @@ REV_BUFFER_DICT = {
61} 61}
62 62
63DIFF_BUFFER_DICT = {'mop': 'unmodified', 'chum': 'created', 'slop': 'deleted'} 63DIFF_BUFFER_DICT = {'mop': 'unmodified', 'chum': 'created', 'slop': 'deleted'}
64UNMOD_BUFFER_DICT = {'mop': 'unmodified', 'chum': 'unmodified'}
65EMPTY_BUFFER_DICT = {}
64 66
65ORDERED_VER = ['committed', 'buffer'] 67ORDERED_VER = ['committed', 'buffer']
66REV_NAME_ID = ('committed', 'buffer', 3, 5) 68REV_NAME_ID = ('committed', 'buffer', 3, 5)
@@ -183,21 +185,41 @@ def test_get_buffer_mode():
183 assert ConfigdocsHelper.get_buffer_mode('hippopotomus') is None 185 assert ConfigdocsHelper.get_buffer_mode('hippopotomus') is None
184 186
185 187
186def test_is_buffer_emtpy(): 188def test_is_buffer_empty():
187 """ 189 """
188 Test the method to check if the configdocs buffer is empty 190 Test the method to check if the configdocs buffer is empty
189 """ 191 """
190 helper = ConfigdocsHelper(CTX) 192 helper = ConfigdocsHelper(CTX)
191 helper._get_revision_dict = lambda: REV_BUFFER_DICT
192 assert not helper.is_buffer_empty()
193 193
194 # BUFFER revision is none, short circuit case (no buffer revision)
195 # buffer is empty.
194 helper._get_revision_dict = lambda: REV_BUFF_EMPTY_DICT 196 helper._get_revision_dict = lambda: REV_BUFF_EMPTY_DICT
195 assert helper.is_buffer_empty() 197 assert helper.is_buffer_empty()
196 198
197 helper._get_revision_dict = lambda: REV_NO_COMMIT_DICT 199 # BUFFER revision is none, also a short circuit case (no revisions at all)
200 # buffer is empty
201 helper._get_revision_dict = lambda: REV_EMPTY_DICT
202 assert helper.is_buffer_empty()
203
204 # BUFFER revision is not none, collections have been modified
205 # buffer is NOT empty.
206 helper._get_revision_dict = lambda: REV_BUFFER_DICT
207 helper.deckhand.get_diff = (
208 lambda old_revision_id, new_revision_id: DIFF_BUFFER_DICT)
198 assert not helper.is_buffer_empty() 209 assert not helper.is_buffer_empty()
199 210
200 helper._get_revision_dict = lambda: REV_EMPTY_DICT 211 # BUFFER revision is not none, all collections unmodified
212 # buffer is empty.
213 helper._get_revision_dict = lambda: REV_NO_COMMIT_DICT
214 helper.deckhand.get_diff = (
215 lambda old_revision_id, new_revision_id: UNMOD_BUFFER_DICT)
216 assert helper.is_buffer_empty()
217
218 # BUFFER revision is not none, no collections listed (deleted, rollback 0)
219 # buffer is empty.
220 helper._get_revision_dict = lambda: REV_NO_COMMIT_DICT
221 helper.deckhand.get_diff = (
222 lambda old_revision_id, new_revision_id: EMPTY_BUFFER_DICT)
201 assert helper.is_buffer_empty() 223 assert helper.is_buffer_empty()
202 224
203 225
diff --git a/src/bin/shipyard_client/shipyard_client/api_client/shipyard_api_client.py b/src/bin/shipyard_client/shipyard_client/api_client/shipyard_api_client.py
index 1b25140..4eb5ac6 100644
--- a/src/bin/shipyard_client/shipyard_client/api_client/shipyard_api_client.py
+++ b/src/bin/shipyard_client/shipyard_client/api_client/shipyard_api_client.py
@@ -52,16 +52,21 @@ class ShipyardClient(BaseClient):
52 def post_configdocs(self, 52 def post_configdocs(self,
53 collection_id=None, 53 collection_id=None,
54 buffer_mode='rejectoncontents', 54 buffer_mode='rejectoncontents',
55 empty_collection=False,
55 document_data=None): 56 document_data=None):
56 """ 57 """
57 Ingests a collection of documents 58 Ingests a collection of documents
58 :param str collection_id: identifies a collection of docs.Bucket_id 59 :param str collection_id: identifies a collection of docs.Bucket_id
59 :param str buffermode: append|replace|rejectOnContents 60 :param str buffermode: append|replace|rejectOnContents
61 :param empty_collection: True if the collection is empty. Document
62 data will be ignored if this flag is set to True. Default: False
60 :param str document_data: data in a format understood by Deckhand(YAML) 63 :param str document_data: data in a format understood by Deckhand(YAML)
61 :returns: diff from last committed revision to new revision 64 :returns: diff from last committed revision to new revision
62 :rtype: Response object 65 :rtype: Response object
63 """ 66 """
64 query_params = {"buffermode": buffer_mode} 67 query_params = {"buffermode": buffer_mode}
68 if empty_collection:
69 query_params['empty-collection'] = True
65 url = ApiPaths.POST_GET_CONFIG.value.format( 70 url = ApiPaths.POST_GET_CONFIG.value.format(
66 self.get_endpoint(), 71 self.get_endpoint(),
67 collection_id 72 collection_id
diff --git a/src/bin/shipyard_client/shipyard_client/cli/commit/commands.py b/src/bin/shipyard_client/shipyard_client/cli/commit/commands.py
index 554e841..b1cee04 100644
--- a/src/bin/shipyard_client/shipyard_client/cli/commit/commands.py
+++ b/src/bin/shipyard_client/shipyard_client/cli/commit/commands.py
@@ -47,11 +47,11 @@ SHORT_DESC_CONFIGDOCS = ("Attempts to commit the Shipyard Buffer documents, "
47@click.option( 47@click.option(
48 '--force', 48 '--force',
49 '-f', 49 '-f',
50 flag_value=True, 50 is_flag=True,
51 help='Force the commit to occur, even if validations fail.') 51 help='Force the commit to occur, even if validations fail.')
52@click.option( 52@click.option(
53 '--dryrun', 53 '--dryrun',
54 flag_value=True, 54 is_flag=True,
55 help='Retrieve validation status for the contents of the buffer without ' 55 help='Retrieve validation status for the contents of the buffer without '
56 'committing.') 56 'committing.')
57@click.pass_context 57@click.pass_context
diff --git a/src/bin/shipyard_client/shipyard_client/cli/create/actions.py b/src/bin/shipyard_client/shipyard_client/cli/create/actions.py
index f37f901..a9a30ab 100644
--- a/src/bin/shipyard_client/shipyard_client/cli/create/actions.py
+++ b/src/bin/shipyard_client/shipyard_client/cli/create/actions.py
@@ -22,8 +22,8 @@ class CreateAction(CliAction):
22 super().__init__(ctx) 22 super().__init__(ctx)
23 self.logger.debug( 23 self.logger.debug(
24 "CreateAction action initialized with action command " 24 "CreateAction action initialized with action command "
25 "%s, parameters %s and allow-intermediate-commits=%s", 25 "%s, parameters %s and allow-intermediate-commits=%s", action_name,
26 action_name, param, allow_intermediate_commits) 26 param, allow_intermediate_commits)
27 self.action_name = action_name 27 self.action_name = action_name
28 self.param = param 28 self.param = param
29 self.allow_intermediate_commits = allow_intermediate_commits 29 self.allow_intermediate_commits = allow_intermediate_commits
@@ -57,27 +57,34 @@ class CreateAction(CliAction):
57class CreateConfigdocs(CliAction): 57class CreateConfigdocs(CliAction):
58 """Action to Create Configdocs""" 58 """Action to Create Configdocs"""
59 59
60 def __init__(self, ctx, collection, buffer, data, filename): 60 def __init__(self, ctx, collection, buffer_mode, empty_collection, data,
61 filenames):
61 """Sets parameters.""" 62 """Sets parameters."""
62 super().__init__(ctx) 63 super().__init__(ctx)
63 self.logger.debug("CreateConfigdocs action initialized with " 64 self.logger.debug(
64 "collection=%s,buffer=%s, " 65 "CreateConfigdocs action initialized with collection: %s, "
65 "Processed Files=" % (collection, buffer)) 66 "buffer mode: %s, empty collection: %s, data length: %s. "
66 for file in filename: 67 "Processed Files:", collection, buffer_mode, empty_collection,
68 len(data))
69 for file in filenames:
67 self.logger.debug(file) 70 self.logger.debug(file)
68 self.logger.debug("data=%s" % str(data))
69 self.collection = collection 71 self.collection = collection
70 self.buffer = buffer 72 self.buffer_mode = buffer_mode
73 self.empty_collection = empty_collection
71 self.data = data 74 self.data = data
72 75
73 def invoke(self): 76 def invoke(self):
74 """Calls API Client and formats response from API Client""" 77 """Calls API Client and formats response from API Client"""
75 self.logger.debug("Calling API Client post_configdocs.") 78 self.logger.debug("Calling API Client post_configdocs.")
79
80 # Only send data payload if not empty_collection
81 data_to_send = "" if self.empty_collection else self.data
82
76 return self.get_api_client().post_configdocs( 83 return self.get_api_client().post_configdocs(
77 collection_id=self.collection, 84 collection_id=self.collection,
78 buffer_mode=self.buffer, 85 buffer_mode=self.buffer_mode,
79 document_data=self.data 86 empty_collection=self.empty_collection,
80 ) 87 document_data=data_to_send)
81 88
82 # Handle 409 with default error handler for cli. 89 # Handle 409 with default error handler for cli.
83 cli_handled_err_resp_codes = [409] 90 cli_handled_err_resp_codes = [409]
@@ -94,5 +101,4 @@ class CreateConfigdocs(CliAction):
94 """ 101 """
95 outfmt_string = "Configuration documents added.\n{}" 102 outfmt_string = "Configuration documents added.\n{}"
96 return outfmt_string.format( 103 return outfmt_string.format(
97 format_utils.cli_format_status_handler(response) 104 format_utils.cli_format_status_handler(response))
98 )
diff --git a/src/bin/shipyard_client/shipyard_client/cli/create/commands.py b/src/bin/shipyard_client/shipyard_client/cli/create/commands.py
index aabce76..6ef0779 100644
--- a/src/bin/shipyard_client/shipyard_client/cli/create/commands.py
+++ b/src/bin/shipyard_client/shipyard_client/cli/create/commands.py
@@ -59,7 +59,7 @@ SHORT_DESC_ACTION = (
59@click.option( 59@click.option(
60 '--allow-intermediate-commits', 60 '--allow-intermediate-commits',
61 'allow_intermediate_commits', 61 'allow_intermediate_commits',
62 flag_value=True, 62 is_flag=True,
63 help="Allow site action to go through even though there are prior commits " 63 help="Allow site action to go through even though there are prior commits "
64 "that have not been used as part of a site action.") 64 "that have not been used as part of a site action.")
65@click.pass_context 65@click.pass_context
@@ -82,6 +82,7 @@ DESC_CONFIGDOCS = """
82COMMAND: configdocs \n 82COMMAND: configdocs \n
83DESCRIPTION: Load documents into the Shipyard Buffer. \n 83DESCRIPTION: Load documents into the Shipyard Buffer. \n
84FORMAT: shipyard create configdocs <collection> [--append | --replace] 84FORMAT: shipyard create configdocs <collection> [--append | --replace]
85[--empty-collection]
85[--filename=<filename> (repeatable) | --directory=<directory>] (repeatable) 86[--filename=<filename> (repeatable) | --directory=<directory>] (repeatable)
86 --recurse\n 87 --recurse\n
87EXAMPLE: shipyard create configdocs design --append 88EXAMPLE: shipyard create configdocs design --append
@@ -96,15 +97,16 @@ SHORT_DESC_CONFIGDOCS = "Load documents into the Shipyard Buffer."
96@click.argument('collection') 97@click.argument('collection')
97@click.option( 98@click.option(
98 '--append', 99 '--append',
99 flag_value=True, 100 is_flag=True,
100 help='Add the collection to the Shipyard Buffer. ') 101 help='Add the collection to the Shipyard Buffer. ')
101@click.option( 102@click.option(
102 '--replace', 103 '--replace',
103 flag_value=True, 104 is_flag=True,
104 help='Clear the Shipyard Buffer and replace it with the specified ' 105 help='Clear the Shipyard Buffer and replace it with the specified '
105 'contents. ') 106 'contents. ')
106@click.option( 107@click.option(
107 '--filename', 108 '--filename',
109 'filenames',
108 multiple=True, 110 multiple=True,
109 type=click.Path(exists=True), 111 type=click.Path(exists=True),
110 help='The file name to use as the contents of the collection. ' 112 help='The file name to use as the contents of the collection. '
@@ -117,59 +119,89 @@ SHORT_DESC_CONFIGDOCS = "Load documents into the Shipyard Buffer."
117 'a collection. (Repeatable).') 119 'a collection. (Repeatable).')
118@click.option( 120@click.option(
119 '--recurse', 121 '--recurse',
120 flag_value=True, 122 is_flag=True,
121 help='Recursively search through directories for yaml files.' 123 help='Recursively search through directories for yaml files.'
122) 124)
125# The --empty-collection flag is explicit to prevent a user from accidentally
126# loading an empty file and deleting things. This requires the user to clearly
127# state their intention.
128@click.option(
129 '--empty-collection',
130 is_flag=True,
131 help='Creates a version of the specified collection with no contents. '
132 'This option is the method by which a collection can be effectively '
133 'deleted. Any file and directory parameters will be ignored if this '
134 'option is used.'
135)
123@click.pass_context 136@click.pass_context
124def create_configdocs(ctx, collection, filename, directory, append, 137def create_configdocs(ctx, collection, filenames, directory, append, replace,
125 replace, recurse): 138 recurse, empty_collection):
126 if (append and replace): 139 if (append and replace):
127 ctx.fail('Either append or replace may be selected but not both') 140 ctx.fail('Either append or replace may be selected but not both')
128 if (not filename and not directory) or (filename and directory):
129 ctx.fail('Please specify one or more filenames using '
130 '--filename="<filename>" OR one or more directories using '
131 '--directory="<directory>"')
132 141
133 if append: 142 if append:
134 create_buffer = 'append' 143 buffer_mode = 'append'
135 elif replace: 144 elif replace:
136 create_buffer = 'replace' 145 buffer_mode = 'replace'
146 else:
147 buffer_mode = None
148
149 if empty_collection:
150 # Use an empty string as the document payload, and indicate no files.
151 data = ""
152 filenames = []
137 else: 153 else:
138 create_buffer = None 154 # Validate that appropriate file/directory params were specified.
139 155 if (not filenames and not directory) or (filenames and directory):
140 if directory: 156 ctx.fail('Please specify one or more filenames using '
141 for dir in directory: 157 '--filename="<filename>" OR one or more directories '
142 if recurse: 158 'using --directory="<directory>"')
143 for path, dirs, files in os.walk(dir): 159 # Scan and parse the input directories and files
144 filename += tuple( 160 if directory:
145 [os.path.join(path, name) for name in files 161 for _dir in directory:
146 if name.endswith('.yaml')]) 162 if recurse:
147 else: 163 for path, dirs, files in os.walk(_dir):
148 filename += tuple( 164 filenames += tuple([
149 [os.path.join(dir, each) for each in os.listdir(dir) 165 os.path.join(path, name) for name in files
150 if each.endswith('.yaml')]) 166 if is_yaml(name)
151 167 ])
152 if not filename: 168 else:
153 # None or empty list should raise this error 169 filenames += tuple([
154 ctx.fail('The directory does not contain any YAML files. ' 170 os.path.join(_dir, each) for each in os.listdir(_dir)
155 'Please enter one or more YAML files or a ' 171 if is_yaml(each)
156 'directory that contains one or more YAML files.') 172 ])
157 173
158 docs = [] 174 if not filenames:
159 for file in filename: 175 # None or empty list should raise this error
160 with open(file, 'r') as stream: 176 ctx.fail('The directory does not contain any YAML files. '
161 if file.endswith(".yaml"): 177 'Please enter one or more YAML files or a '
162 try: 178 'directory that contains one or more YAML files.')
163 docs += list(yaml.safe_load_all(stream)) 179
164 except yaml.YAMLError as exc: 180 docs = []
165 ctx.fail('YAML file {} is invalid because {}' 181 for _file in filenames:
166 .format(file, exc)) 182 with open(_file, 'r') as stream:
167 else: 183 if is_yaml(_file):
168 ctx.fail('The file {} is not a YAML file. Please enter ' 184 try:
169 'only YAML files.'.format(file)) 185 docs += list(yaml.safe_load_all(stream))
170 186 except yaml.YAMLError as exc:
171 data = yaml.safe_dump_all(docs) 187 ctx.fail('YAML file {} is invalid because {}'.format(
188 _file, exc))
189 else:
190 ctx.fail('The file {} is not a YAML file. Please enter '
191 'only YAML files.'.format(_file))
192
193 data = yaml.safe_dump_all(docs)
172 194
173 click.echo( 195 click.echo(
174 CreateConfigdocs(ctx, collection, create_buffer, data, filename) 196 CreateConfigdocs(
175 .invoke_and_return_resp()) 197 ctx=ctx,
198 collection=collection,
199 buffer_mode=buffer_mode,
200 empty_collection=empty_collection,
201 data=data,
202 filenames=filenames).invoke_and_return_resp())
203
204
205def is_yaml(filename):
206 """Test if the filename should be regarded as a yaml file"""
207 return filename.endswith(".yaml") or filename.endswith(".yml")
diff --git a/src/bin/shipyard_client/shipyard_client/cli/get/commands.py b/src/bin/shipyard_client/shipyard_client/cli/get/commands.py
index 1bbffa2..69892d0 100644
--- a/src/bin/shipyard_client/shipyard_client/cli/get/commands.py
+++ b/src/bin/shipyard_client/shipyard_client/cli/get/commands.py
@@ -77,25 +77,25 @@ SHORT_DESC_CONFIGDOCS = ("Retrieve documents loaded into Shipyard, either "
77@click.option( 77@click.option(
78 '--committed', 78 '--committed',
79 '-c', 79 '-c',
80 flag_value='committed', 80 is_flag=True,
81 help='Retrieve the documents that have last been committed for this ' 81 help='Retrieve the documents that have last been committed for this '
82 'collection') 82 'collection')
83@click.option( 83@click.option(
84 '--buffer', 84 '--buffer',
85 '-b', 85 '-b',
86 flag_value='buffer', 86 is_flag=True,
87 help='Retrieve the documents that have been loaded into Shipyard since ' 87 help='Retrieve the documents that have been loaded into Shipyard since '
88 'the prior commit. If no documents have been loaded into the buffer for ' 88 'the prior commit. If no documents have been loaded into the buffer for '
89 'this collection, this will return an empty response (default)') 89 'this collection, this will return an empty response (default)')
90@click.option( 90@click.option(
91 '--last-site-action', 91 '--last-site-action',
92 '-l', 92 '-l',
93 flag_value='last_site_action', 93 is_flag=True,
94 help='Holds the revision information for the most recent site action') 94 help='Holds the revision information for the most recent site action')
95@click.option( 95@click.option(
96 '--successful-site-action', 96 '--successful-site-action',
97 '-s', 97 '-s',
98 flag_value='successful_site_action', 98 is_flag=True,
99 help='Holds the revision information for the most recent successfully ' 99 help='Holds the revision information for the most recent successfully '
100 'executed site action.') 100 'executed site action.')
101@click.option( 101@click.option(
@@ -150,23 +150,23 @@ SHORT_DESC_RENDEREDCONFIGDOCS = (
150@click.option( 150@click.option(
151 '--committed', 151 '--committed',
152 '-c', 152 '-c',
153 flag_value='committed', 153 is_flag=True,
154 help='Retrieve the documents that have last been committed.') 154 help='Retrieve the documents that have last been committed.')
155@click.option( 155@click.option(
156 '--buffer', 156 '--buffer',
157 '-b', 157 '-b',
158 flag_value='buffer', 158 is_flag=True,
159 help='Retrieve the documents that have been loaded into Shipyard since the' 159 help='Retrieve the documents that have been loaded into Shipyard since the'
160 ' prior commit. (default)') 160 ' prior commit. (default)')
161@click.option( 161@click.option(
162 '--last-site-action', 162 '--last-site-action',
163 '-l', 163 '-l',
164 flag_value='last_site_action', 164 is_flag=True,
165 help='Holds the revision information for the most recent site action') 165 help='Holds the revision information for the most recent site action')
166@click.option( 166@click.option(
167 '--successful-site-action', 167 '--successful-site-action',
168 '-s', 168 '-s',
169 flag_value='successful_site_action', 169 is_flag=True,
170 help='Holds the revision information for the most recent successfully ' 170 help='Holds the revision information for the most recent successfully '
171 'executed site action.') 171 'executed site action.')
172@click.option( 172@click.option(
diff --git a/src/bin/shipyard_client/test-requirements.txt b/src/bin/shipyard_client/test-requirements.txt
index e280508..32098ef 100644
--- a/src/bin/shipyard_client/test-requirements.txt
+++ b/src/bin/shipyard_client/test-requirements.txt
@@ -1,7 +1,7 @@
1# Testing 1# Testing
2pytest==3.4 2pytest==3.4
3pytest-cov==2.5.1 3pytest-cov==2.5.1
4responses==0.8.1 4responses==0.10.2
5testfixtures==5.1.1 5testfixtures==5.1.1
6 6
7# Linting 7# Linting
diff --git a/src/bin/shipyard_client/tests/unit/cli/commit/test_commit_actions.py b/src/bin/shipyard_client/tests/unit/cli/commit/test_commit_actions.py
index 079e24a..aeac1f0 100644
--- a/src/bin/shipyard_client/tests/unit/cli/commit/test_commit_actions.py
+++ b/src/bin/shipyard_client/tests/unit/cli/commit/test_commit_actions.py
@@ -19,13 +19,18 @@ from shipyard_client.api_client.base_client import BaseClient
19from shipyard_client.cli.commit.actions import CommitConfigdocs 19from shipyard_client.cli.commit.actions import CommitConfigdocs
20from tests.unit.cli import stubs 20from tests.unit.cli import stubs
21 21
22# TODO: refactor these tests to use responses callbacks (or other features)
23# so that query parameter passing can be validated.
24# moving to responses > 0.8 (e.g. 0.10.2) changed how URLS for responses
25# seem to operate.
26
22 27
23@responses.activate 28@responses.activate
24@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest') 29@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
25@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc') 30@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
26def test_commit_configdocs(*args): 31def test_commit_configdocs(*args):
27 responses.add(responses.POST, 32 responses.add(responses.POST,
28 'http://shiptest/commitconfigdocs?force=false', 33 'http://shiptest/commitconfigdocs',
29 body=None, 34 body=None,
30 status=200) 35 status=200)
31 response = CommitConfigdocs(stubs.StubCliContext(), 36 response = CommitConfigdocs(stubs.StubCliContext(),
@@ -44,7 +49,7 @@ def test_commit_configdocs_409(*args):
44 reason='Conflicts reason', 49 reason='Conflicts reason',
45 code=409) 50 code=409)
46 responses.add(responses.POST, 51 responses.add(responses.POST,
47 'http://shiptest/commitconfigdocs?force=false', 52 'http://shiptest/commitconfigdocs',
48 body=api_resp, 53 body=api_resp,
49 status=409) 54 status=409)
50 response = CommitConfigdocs(stubs.StubCliContext(), 55 response = CommitConfigdocs(stubs.StubCliContext(),
@@ -65,7 +70,7 @@ def test_commit_configdocs_forced(*args):
65 reason='Conflicts reason', 70 reason='Conflicts reason',
66 code=200) 71 code=200)
67 responses.add(responses.POST, 72 responses.add(responses.POST,
68 'http://shiptest/commitconfigdocs?force=true', 73 'http://shiptest/commitconfigdocs',
69 body=api_resp, 74 body=api_resp,
70 status=200) 75 status=200)
71 response = CommitConfigdocs(stubs.StubCliContext(), 76 response = CommitConfigdocs(stubs.StubCliContext(),
@@ -80,7 +85,7 @@ def test_commit_configdocs_forced(*args):
80@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc') 85@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
81def test_commit_configdocs_dryrun(*args): 86def test_commit_configdocs_dryrun(*args):
82 responses.add(responses.POST, 87 responses.add(responses.POST,
83 'http://shiptest/commitconfigdocs?force=false', 88 'http://shiptest/commitconfigdocs',
84 body=None, 89 body=None,
85 status=200) 90 status=200)
86 response = CommitConfigdocs(stubs.StubCliContext(), 91 response = CommitConfigdocs(stubs.StubCliContext(),
diff --git a/src/bin/shipyard_client/tests/unit/cli/create/test_create_actions.py b/src/bin/shipyard_client/tests/unit/cli/create/test_create_actions.py
index 5fa71f8..dbbae50 100644
--- a/src/bin/shipyard_client/tests/unit/cli/create/test_create_actions.py
+++ b/src/bin/shipyard_client/tests/unit/cli/create/test_create_actions.py
@@ -116,6 +116,7 @@ def test_create_configdocs(*args):
116 response = CreateConfigdocs(stubs.StubCliContext(), 116 response = CreateConfigdocs(stubs.StubCliContext(),
117 'design', 117 'design',
118 'append', 118 'append',
119 False,
119 document_data, 120 document_data,
120 file_list).invoke_and_return_resp() 121 file_list).invoke_and_return_resp()
121 assert 'Configuration documents added.' 122 assert 'Configuration documents added.'
@@ -145,6 +146,7 @@ def test_create_configdocs_201_with_val_fails(*args):
145 response = CreateConfigdocs(stubs.StubCliContext(), 146 response = CreateConfigdocs(stubs.StubCliContext(),
146 'design', 147 'design',
147 'append', 148 'append',
149 False,
148 document_data, 150 document_data,
149 file_list).invoke_and_return_resp() 151 file_list).invoke_and_return_resp()
150 assert 'Configuration documents added.' in response 152 assert 'Configuration documents added.' in response
@@ -175,8 +177,51 @@ def test_create_configdocs_409(*args):
175 response = CreateConfigdocs(stubs.StubCliContext(), 177 response = CreateConfigdocs(stubs.StubCliContext(),
176 'design', 178 'design',
177 'append', 179 'append',
180 False,
178 document_data, 181 document_data,
179 file_list).invoke_and_return_resp() 182 file_list).invoke_and_return_resp()
180 assert 'Error: Invalid collection' in response 183 assert 'Error: Invalid collection' in response
181 assert 'Reason: Buffermode : append' in response 184 assert 'Reason: Buffermode : append' in response
182 assert 'Buffer is either not...' in response 185 assert 'Buffer is either not...' in response
186
187
188@responses.activate
189@mock.patch.object(BaseClient, 'get_endpoint', lambda x: 'http://shiptest')
190@mock.patch.object(BaseClient, 'get_token', lambda x: 'abc')
191def test_create_configdocs_empty(*args):
192 def validating_callback(request):
193 # a request that has empty_collection should have no body.
194 assert request.body is None
195 resp_body = stubs.gen_err_resp(
196 message='Validations succeeded',
197 sub_error_count=0,
198 sub_info_count=0,
199 reason='Validation',
200 code=200)
201 return (201, {}, resp_body)
202
203 responses.add_callback(
204 responses.POST,
205 'http://shiptest/configdocs/design',
206 callback=validating_callback,
207 content_type='application/json')
208
209 filename = 'tests/unit/cli/create/sample_yaml/sample.yaml'
210 document_data = yaml.dump_all(filename)
211 file_list = (filename, )
212
213 # pass data and empty_collection = True - should init with data, but
214 # not send the data on invoke
215 action = CreateConfigdocs(
216 stubs.StubCliContext(),
217 collection='design',
218 buffer_mode='append',
219 empty_collection=True,
220 data=document_data,
221 filenames=file_list)
222
223 assert action.data == document_data
224 assert action.empty_collection == True
225
226 response = action.invoke_and_return_resp()
227 assert response.startswith("Configuration documents added.")
diff --git a/src/bin/shipyard_client/tests/unit/cli/create/test_create_commands.py b/src/bin/shipyard_client/tests/unit/cli/create/test_create_commands.py
index 0464917..f05c29d 100644
--- a/src/bin/shipyard_client/tests/unit/cli/create/test_create_commands.py
+++ b/src/bin/shipyard_client/tests/unit/cli/create/test_create_commands.py
@@ -66,8 +66,93 @@ def test_create_configdocs():
66 auth_vars, 'create', 'configdocs', collection, '--' + append, 66 auth_vars, 'create', 'configdocs', collection, '--' + append,
67 '--filename=' + filename 67 '--filename=' + filename
68 ]) 68 ])
69 mock_method.assert_called_once_with(ANY, collection, 'append', 69 mock_method.assert_called_once_with(ctx=ANY, collection=collection,
70 ANY, file_list) 70 buffer_mode='append', empty_collection=False, data=ANY,
71 filenames=file_list)
72
73
74def test_create_configdocs_empty():
75 """test create configdocs with the --empty-collection flag"""
76
77 collection = 'design'
78 filename = 'tests/unit/cli/create/sample_yaml/sample.yaml'
79 directory = 'tests/unit/cli/create/sample_yaml'
80 runner = CliRunner()
81 tests = [
82 {
83 # replace mode, no file, no data, empty collection
84 'kwargs': {
85 'buffer_mode': 'replace',
86 'empty_collection': True,
87 'filenames': [],
88 'data': ""
89 },
90 'args': [
91 '--replace',
92 '--empty-collection',
93 ],
94 },
95 {
96 # Append mode, no file, no data, empty collection
97 'kwargs': {
98 'buffer_mode': 'append',
99 'empty_collection': True,
100 'filenames': [],
101 'data': ""
102 },
103 'args': [
104 '--append',
105 '--empty-collection',
106 ],
107 },
108 {
109 # No buffer mode specified, empty collection
110 'kwargs': {
111 'buffer_mode': None,
112 'empty_collection': True,
113 'filenames': [],
114 'data': ""
115 },
116 'args': [
117 '--empty-collection',
118 ],
119 },
120 {
121 # Filename should be ignored and not passed, empty collection
122 'kwargs': {
123 'buffer_mode': None,
124 'empty_collection': True,
125 'filenames': [],
126 'data': ""
127 },
128 'args': [
129 '--empty-collection',
130 '--filename={}'.format(filename)
131 ],
132 },
133 {
134 # Directory should be ignored and not passed, empty collection
135 'kwargs': {
136 'buffer_mode': None,
137 'empty_collection': True,
138 'filenames': [],
139 'data': ""
140 },
141 'args': [
142 '--empty-collection',
143 '--directory={}'.format(directory)
144 ],
145 },
146 ]
147
148 for tc in tests:
149 with patch.object(CreateConfigdocs, '__init__') as mock_method:
150 runner.invoke(shipyard, [
151 auth_vars, 'create', 'configdocs', collection, *tc['args']
152 ])
153
154 mock_method.assert_called_once_with(ctx=ANY, collection=collection,
155 **tc['kwargs'])
71 156
72 157
73def test_create_configdocs_directory(): 158def test_create_configdocs_directory():
@@ -82,7 +167,11 @@ def test_create_configdocs_directory():
82 auth_vars, 'create', 'configdocs', collection, '--' + append, 167 auth_vars, 'create', 'configdocs', collection, '--' + append,
83 '--directory=' + directory 168 '--directory=' + directory
84 ]) 169 ])
85 mock_method.assert_called_once_with(ANY, collection, 'append', ANY, ANY) 170 # TODO(bryan-strassner) Make this test useful to show directory parsing
171 # happened.
172 mock_method.assert_called_once_with(ctx=ANY, collection=collection,
173 buffer_mode='append', empty_collection=False, data=ANY,
174 filenames=ANY)
86 175
87 176
88def test_create_configdocs_directory_empty(): 177def test_create_configdocs_directory_empty():
@@ -114,11 +203,15 @@ def test_create_configdocs_multi_directory():
114 auth_vars, 'create', 'configdocs', collection, '--' + append, 203 auth_vars, 'create', 'configdocs', collection, '--' + append,
115 '--directory=' + dir1, '--directory=' + dir2 204 '--directory=' + dir1, '--directory=' + dir2
116 ]) 205 ])
117 mock_method.assert_called_once_with(ANY, collection, 'append', ANY, ANY) 206 # TODO(bryan-strassner) Make this test useful to show multiple directories
207 # were actually traversed.
208 mock_method.assert_called_once_with(ctx=ANY, collection=collection,
209 buffer_mode='append', empty_collection=False, data=ANY,
210 filenames=ANY)
118 211
119 212
120def test_create_configdocs_multi_directory_recurse(): 213def test_create_configdocs_multi_directory_recurse():
121 """test create configdocs with multiple directories""" 214 """test create configdocs with multiple directories recursively"""
122 215
123 collection = 'design' 216 collection = 'design'
124 dir1 = 'tests/unit/cli/create/sample_yaml/' 217 dir1 = 'tests/unit/cli/create/sample_yaml/'
@@ -130,7 +223,11 @@ def test_create_configdocs_multi_directory_recurse():
130 auth_vars, 'create', 'configdocs', collection, '--' + append, 223 auth_vars, 'create', 'configdocs', collection, '--' + append,
131 '--directory=' + dir1, '--directory=' + dir2, '--recurse' 224 '--directory=' + dir1, '--directory=' + dir2, '--recurse'
132 ]) 225 ])
133 mock_method.assert_called_once_with(ANY, collection, 'append', ANY, ANY) 226 # TODO(bryan-strassner) Make this test useful to show multiple directories
227 # were actually traversed and recursed.
228 mock_method.assert_called_once_with(ctx=ANY, collection=collection,
229 buffer_mode='append', empty_collection=False, data=ANY,
230 filenames=ANY)
134 231
135 232
136def test_create_configdocs_negative(): 233def test_create_configdocs_negative():