summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNishant Kumar <nk613n@att.com>2018-10-09 22:08:20 +0000
committerNishant Kumar <nk613n@att.com>2018-11-27 19:29:42 +0000
commit9113d249fff01dc89a399e13fa3ca1ff244d4be1 (patch)
tree5cf8c04df2bd0177ccacbad33747cc4b4b2a4e3f
parente3159d223ad359956315dcd81136f45a7fc2761a (diff)
CLI: Add support for uploading documents to Shipyard
This PS enables Pegleg to upload documents directly to Shipyard thus ensuring that unencrypted data never gets stored in disk. The flow for this new CLI command is as follows: - Collect documents as per the provided site repository - Decrypt the collected documets(TODO) - Upload document to Shipyard: - one collection per repository will be uploaded to Shipyard Eg- pegleg site -r /opt/aic-clcp-site-manifests \ -e global=/opt/aic-clcp-manifests upload <site-name> Two collections will be created in shipyard since there are two repositories provided. The name of the collections will be the name of repositories provided. - Commit the documents in shipyard buffer. Change-Id: I6275252b044ebb82d8bb2009c0bea6ebf7033bce
Notes
Notes (review): Code-Review+2: Scott Hussey <sthussey@att.com> Code-Review+2: Bryan Strassner <strassner.bryan@gmail.com> Workflow+1: Bryan Strassner <strassner.bryan@gmail.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Tue, 04 Dec 2018 17:22:06 +0000 Reviewed-on: https://review.openstack.org/609546 Project: openstack/airship-pegleg Branch: refs/heads/master
-rw-r--r--doc/source/cli/cli.rst40
-rw-r--r--doc/source/exceptions.rst7
-rw-r--r--pegleg/cli.py51
-rw-r--r--pegleg/engine/util/files.py15
-rw-r--r--pegleg/engine/util/shipyard_helper.py181
-rw-r--r--requirements.txt1
-rw-r--r--tests/unit/engine/test_util_files.py14
-rw-r--r--tests/unit/engine/util/test_shipyard_helper.py172
-rw-r--r--tests/unit/test_cli.py17
9 files changed, 498 insertions, 0 deletions
diff --git a/doc/source/cli/cli.rst b/doc/source/cli/cli.rst
index b58bf9b..69e1655 100644
--- a/doc/source/cli/cli.rst
+++ b/doc/source/cli/cli.rst
@@ -389,6 +389,44 @@ A more complex example involves excluding certain linting checks:
389 lint <site_name> \ 389 lint <site_name> \
390 -x P001 -x P002 -w P003 390 -x P001 -x P002 -w P003
391 391
392Upload
393-------
394
395Uploads documents to `Shipyard`_.
396
397**site_name** (Required).
398
399Name of the site. The ``site_name`` must match a ``site`` name in the site
400repository folder structure
401
402**--os-<various>=<value>** (Required).
403
404Shipyard needs these options for authenticating with OpenStack Keystone.
405This option can be set as environment variables or it can be passed via
406the command line.
407
408Please reference Shipyard's `CLI documentation`_ for information related to these options.
409
410**--context-marker=<uuid>** (Optional).
411
412Specifies a UUID (8-4-4-4-12 format) that will be used to correlate logs,
413transactions, etc. in downstream activities triggered by this interaction.
414
415Usage:
416
417::
418
419 ./pegleg.sh site <options> upload <site_name> --context-marker=<uuid>
420
421Examples
422^^^^^^^^
423
424::
425
426 ./pegleg.sh site -r <site_repo> -e <extra_repo> \
427 upload <site_name> <options>
428
429
392.. _command-line-repository-overrides: 430.. _command-line-repository-overrides:
393 431
394Secrets 432Secrets
@@ -641,3 +679,5 @@ P003 - All repos contain expected directories.
641.. _Deckhand: https://airship-deckhand.readthedocs.io/en/latest/users/rendering.html 679.. _Deckhand: https://airship-deckhand.readthedocs.io/en/latest/users/rendering.html
642.. _Deckhand Validations: https://airship-deckhand.readthedocs.io/en/latest/overview.html#validation 680.. _Deckhand Validations: https://airship-deckhand.readthedocs.io/en/latest/overview.html#validation
643.. _Pegleg Managed Documents: https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html#peglegmanageddocument 681.. _Pegleg Managed Documents: https://airship-specs.readthedocs.io/en/latest/specs/approved/pegleg-secrets.html#peglegmanageddocument
682.. _Shipyard: https://github.com/openstack/airship-shipyard
683.. _CLI documentation: https://airship-shipyard.readthedocs.io/en/latest/CLI.html#openstack-keystone-authorization-environment-variables
diff --git a/doc/source/exceptions.rst b/doc/source/exceptions.rst
index a0c5450..8fb8577 100644
--- a/doc/source/exceptions.rst
+++ b/doc/source/exceptions.rst
@@ -56,3 +56,10 @@ Git Exceptions
56 :members: 56 :members:
57 :show-inheritance: 57 :show-inheritance:
58 :undoc-members: 58 :undoc-members:
59
60Authentication Exceptions
61-------------------------
62
63.. autoexception:: pegleg.engine.util.shipyard_helper.AuthValuesError
64 :members:
65 :undoc-members:
diff --git a/pegleg/cli.py b/pegleg/cli.py
index 84b03e4..c7b1019 100644
--- a/pegleg/cli.py
+++ b/pegleg/cli.py
@@ -20,6 +20,7 @@ import click
20 20
21from pegleg import config 21from pegleg import config
22from pegleg import engine 22from pegleg import engine
23from pegleg.engine.util.shipyard_helper import ShipyardHelper
23 24
24LOG = logging.getLogger(__name__) 25LOG = logging.getLogger(__name__)
25 26
@@ -325,6 +326,56 @@ def lint_site(*, fail_on_missing_sub_src, exclude_lint, warn_lint, site_name):
325 warn_lint=warn_lint) 326 warn_lint=warn_lint)
326 327
327 328
329@site.command('upload', help='Upload documents to Shipyard')
330# Keystone authentication parameters
331@click.option('--os-project-domain-name',
332 envvar='OS_PROJECT_DOMAIN_NAME',
333 required=False,
334 default='default')
335@click.option('--os-user-domain-name',
336 envvar='OS_USER_DOMAIN_NAME',
337 required=False,
338 default='default')
339@click.option('--os-project-name', envvar='OS_PROJECT_NAME', required=False)
340@click.option('--os-username', envvar='OS_USERNAME', required=False)
341@click.option('--os-password', envvar='OS_PASSWORD', required=False)
342@click.option(
343 '--os-auth-url', envvar='OS_AUTH_URL', required=False)
344# Option passed to Shipyard client context
345@click.option(
346 '--context-marker',
347 help='Specifies a UUID (8-4-4-4-12 format) that will be used to correlate '
348 'logs, transactions, etc. in downstream activities triggered by this '
349 'interaction ',
350 required=False,
351 type=click.UUID)
352@SITE_REPOSITORY_ARGUMENT
353@click.pass_context
354def upload(ctx, *, os_project_domain_name,
355 os_user_domain_name, os_project_name, os_username,
356 os_password, os_auth_url, context_marker, site_name):
357 if not ctx.obj:
358 ctx.obj = {}
359
360 # Build API parameters required by Shipyard API Client.
361 auth_vars = {
362 'project_domain_name': os_project_domain_name,
363 'user_domain_name': os_user_domain_name,
364 'project_name': os_project_name,
365 'username': os_username,
366 'password': os_password,
367 'auth_url': os_auth_url
368 }
369
370 ctx.obj['API_PARAMETERS'] = {
371 'auth_vars': auth_vars
372 }
373 ctx.obj['context_marker'] = str(context_marker)
374 ctx.obj['site_name'] = site_name
375
376 click.echo(ShipyardHelper(ctx).upload_documents())
377
378
328@main.group(help='Commands related to types') 379@main.group(help='Commands related to types')
329@MAIN_REPOSITORY_OPTION 380@MAIN_REPOSITORY_OPTION
330@REPOSITORY_CLONE_PATH_OPTION 381@REPOSITORY_CLONE_PATH_OPTION
diff --git a/pegleg/engine/util/files.py b/pegleg/engine/util/files.py
index 0ab0cae..7a18285 100644
--- a/pegleg/engine/util/files.py
+++ b/pegleg/engine/util/files.py
@@ -13,11 +13,13 @@
13# limitations under the License. 13# limitations under the License.
14 14
15import click 15import click
16import collections
16import os 17import os
17import yaml 18import yaml
18import logging 19import logging
19 20
20from pegleg import config 21from pegleg import config
22from pegleg.engine import util
21from pegleg.engine.util import pegleg_managed_document as md 23from pegleg.engine.util import pegleg_managed_document as md
22 24
23LOG = logging.getLogger(__name__) 25LOG = logging.getLogger(__name__)
@@ -36,6 +38,7 @@ __all__ = [
36 'search', 38 'search',
37 'slurp', 39 'slurp',
38 'check_file_save_location', 40 'check_file_save_location',
41 'collect_files_by_repo',
39] 42]
40 43
41DIR_DEPTHS = { 44DIR_DEPTHS = {
@@ -366,3 +369,15 @@ def check_file_save_location(save_location):
366 raise click.ClickException( 369 raise click.ClickException(
367 'save_location %s already exists, ' 370 'save_location %s already exists, '
368 'but is not a directory'.format(save_location)) 371 'but is not a directory'.format(save_location))
372
373
374def collect_files_by_repo(site_name):
375 """ Collects file by repo name in memory."""
376
377 collected_files_by_repo = collections.defaultdict(list)
378 for repo_base, filename in util.definition.site_files_by_repo(
379 site_name):
380 repo_name = os.path.normpath(repo_base).split(os.sep)[-1]
381 documents = util.files.read(filename)
382 collected_files_by_repo[repo_name].extend(documents)
383 return collected_files_by_repo
diff --git a/pegleg/engine/util/shipyard_helper.py b/pegleg/engine/util/shipyard_helper.py
new file mode 100644
index 0000000..4c58580
--- /dev/null
+++ b/pegleg/engine/util/shipyard_helper.py
@@ -0,0 +1,181 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import json
16import logging
17import uuid
18
19import yaml
20
21from pegleg.engine.util import files
22from pegleg.engine.exceptions import PeglegBaseException
23
24from shipyard_client.api_client.shipyard_api_client import ShipyardClient
25from shipyard_client.api_client.shipyardclient_context import \
26 ShipyardClientContext
27
28LOG = logging.getLogger(__name__)
29
30
31class AuthValuesError(PeglegBaseException):
32 """Shipyard authentication failed. """
33
34 def __init__(self, *, diagnostic):
35 self.diagnostic = diagnostic
36
37
38class DocumentUploadError(PeglegBaseException):
39 """ Exception occurs while uploading documents"""
40
41 def __init__(self, message):
42 self.message = message
43
44
45class ShipyardHelper(object):
46 """
47 A helper class for Shipyard. It performs the following operation:
48 1. Validates the authentication parameters required for Keystone
49 2. Uploads the document to Shipyard buffer
50 3. Commits the document
51 4. Formats response from Shipyard api_client
52 """
53
54 def __init__(self, context):
55 """
56 Initializes params to be used by Shipyard
57
58 :param context: ShipyardHelper context object that contains
59 params for initializing ShipyardClient with
60 correct client context and the site_name.
61 """
62 self.ctx = context
63 self.api_parameters = self.ctx.obj['API_PARAMETERS']
64 self.auth_vars = self.api_parameters.get('auth_vars')
65 self.context_marker = self.ctx.obj['context_marker']
66 if self.context_marker is None:
67 self.context_marker = str(uuid.uuid4())
68 LOG.debug("context_marker is %s" % self.context_marker)
69 self.site_name = self.ctx.obj['site_name']
70 self.client_context = ShipyardClientContext(
71 self.auth_vars, self.context_marker)
72 self.api_client = ShipyardClient(self.client_context)
73
74 def upload_documents(self):
75 """ Uploads documents to Shipyard """
76
77 collected_documents = files.collect_files_by_repo(self.site_name)
78
79 LOG.info("Uploading %s collection(s) " % len(collected_documents))
80 for idx, document in enumerate(collected_documents):
81 # Append flag is not required for the first
82 # collection being uploaded to Shipyard. It
83 # is needed for subsequent collections.
84 if idx == 0:
85 buffer_mode = None
86 else:
87 buffer_mode = 'append'
88
89 data = yaml.safe_dump_all(collected_documents[document])
90
91 try:
92 self.validate_auth_vars()
93 # Get current buffer status.
94 response = self.api_client.get_configdocs_status()
95 buff_stat = response.json()
96 # If buffer is empty then proceed with existing buffer value
97 # else pass the 'replace' flag.
98 for stat in range(len(buff_stat)):
99 if (buff_stat[stat]['new_status'] != 'unmodified' and
100 buffer_mode != 'append'):
101 buffer_mode = 'replace'
102 resp_text = self.api_client.post_configdocs(
103 collection_id=document,
104 buffer_mode=buffer_mode,
105 document_data=data
106 )
107
108 except AuthValuesError as ave:
109 resp_text = "Error: {}".format(ave.diagnostic)
110 raise DocumentUploadError(resp_text)
111 except Exception as ex:
112 resp_text = (
113 "Error: Unable to invoke action due to: {}"
114 .format(str(ex)))
115 LOG.debug(resp_text, exc_info=True)
116 raise DocumentUploadError(resp_text)
117
118 # FIXME: Standardize status_code in Deckhand to avoid this
119 # workaround.
120 code = 0
121 if hasattr(resp_text, 'status_code'):
122 code = resp_text.status_code
123 elif hasattr(resp_text, 'code'):
124 code = resp_text.code
125 if code >= 400:
126 if hasattr(resp_text, 'content'):
127 raise DocumentUploadError(resp_text.content)
128 else:
129 raise DocumentUploadError(resp_text)
130 else:
131 output = self.formatted_response_handler(resp_text)
132 LOG.info("Uploaded document in buffer %s " % output)
133
134 # Commit in the last iteration of the loop when all the documents
135 # have been pushed to Shipyard buffer.
136 if idx == len(collected_documents) - 1:
137 return self.commit_documents()
138
139 def commit_documents(self):
140 """ Commit Shipyard buffer documents """
141
142 LOG.info("Commiting Shipyard buffer documents")
143
144 try:
145 resp_text = self.formatted_response_handler(
146 self.api_client.commit_configdocs()
147 )
148 except Exception as ex:
149 resp_text = (
150 "Error: Unable to invoke action due to: {}".format(str(ex)))
151 raise DocumentUploadError(resp_text)
152 return resp_text
153
154 def validate_auth_vars(self):
155 """Checks that the required authorization varible have been entered"""
156 required_auth_vars = ['auth_url']
157 err_txt = []
158 for var in required_auth_vars:
159 if self.auth_vars[var] is None:
160 err_txt.append(
161 'Missing the required authorization variable: '
162 '--os-{}'.format(var.replace('_', '-')))
163 if err_txt:
164 for var in self.auth_vars:
165 if (self.auth_vars.get(var) is None and
166 var not in required_auth_vars):
167 err_txt.append('- Also not set: --os-{}'.format(
168 var.replace('_', '-')))
169 raise AuthValuesError(diagnostic='\n'.join(err_txt))
170
171 def formatted_response_handler(self, response):
172 """Base format handler for either json or yaml depending on call"""
173 call = response.headers['Content-Type']
174 if 'json' in call:
175 try:
176 return json.dumps(response.json(), indent=4)
177 except ValueError:
178 return (
179 "This is not json and could not be printed as such. \n" +
180 response.text
181 )
diff --git a/requirements.txt b/requirements.txt
index ca8d659..b019100 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,3 +4,4 @@ jsonschema==2.6.0
4pyyaml==3.12 4pyyaml==3.12
5cryptography==2.3.1 5cryptography==2.3.1
6git+https://github.com/openstack/airship-deckhand.git@7d697012fcbd868b14670aa9cf895acfad5a7f8d 6git+https://github.com/openstack/airship-deckhand.git@7d697012fcbd868b14670aa9cf895acfad5a7f8d
7git+https://github.com/openstack/airship-shipyard.git@44f7022df6438de541501c2fdd5c46df198b82bf#egg=shipyard_client&subdirectory=src/bin/shipyard_client
diff --git a/tests/unit/engine/test_util_files.py b/tests/unit/engine/test_util_files.py
index bf18a8f..a475866 100644
--- a/tests/unit/engine/test_util_files.py
+++ b/tests/unit/engine/test_util_files.py
@@ -12,10 +12,14 @@
12# See the License for the specific language governing permissions and 12# See the License for the specific language governing permissions and
13# limitations under the License. 13# limitations under the License.
14 14
15import mock
16
15from pegleg import config 17from pegleg import config
16from pegleg.engine.util import files 18from pegleg.engine.util import files
17from tests.unit.fixtures import create_tmp_deployment_files 19from tests.unit.fixtures import create_tmp_deployment_files
18 20
21TEST_DATA = [('/tmp/test_repo', 'test_file.yaml')]
22TEST_DATA_2 = [{'schema': 'pegleg/SiteDefinition/v1', 'data': 'test'}]
19 23
20def test_no_non_yamls(tmpdir): 24def test_no_non_yamls(tmpdir):
21 p = tmpdir.mkdir("deployment_files").mkdir("global") 25 p = tmpdir.mkdir("deployment_files").mkdir("global")
@@ -51,3 +55,13 @@ def test_list_all_files(create_tmp_deployment_files):
51 assert len(actual_files) == len(expected_files) 55 assert len(actual_files) == len(expected_files)
52 for idx, file in enumerate(actual_files): 56 for idx, file in enumerate(actual_files):
53 assert file.endswith(expected_files[idx]) 57 assert file.endswith(expected_files[idx])
58
59@mock.patch('pegleg.engine.util.definition.site_files_by_repo',autospec=True,
60 return_value=TEST_DATA)
61@mock.patch('pegleg.engine.util.files.read', autospec=True,
62 return_value=TEST_DATA_2)
63def test_collect_files_by_repo(*args):
64 result = files.collect_files_by_repo('test-site')
65
66 assert 'test_repo' in result
67 assert 'schema' in result['test_repo'][0]
diff --git a/tests/unit/engine/util/test_shipyard_helper.py b/tests/unit/engine/util/test_shipyard_helper.py
new file mode 100644
index 0000000..ac5c316
--- /dev/null
+++ b/tests/unit/engine/util/test_shipyard_helper.py
@@ -0,0 +1,172 @@
1# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import json
16import mock
17import pytest
18
19from tests.unit import test_utils
20from mock import ANY
21
22from pegleg.engine import util
23from pegleg.engine.util.shipyard_helper import ShipyardHelper
24from pegleg.engine.util.shipyard_helper import ShipyardClient
25
26# Dummy data to be used as collected documents
27DATA = {'test-repo':
28 {'test-data': 'RandomData'}}
29
30class context():
31 obj = {}
32
33
34class FakeResponse():
35 code = 404
36
37def _get_context():
38 ctx = context()
39 ctx.obj = {}
40 auth_vars = {
41 'project_domain_name': 'projDomainTest',
42 'user_domain_name': 'userDomainTest',
43 'project_name': 'projectTest',
44 'username': 'usernameTest',
45 'password': 'passwordTest',
46 'auth_url': 'urlTest'
47 }
48 ctx.obj['API_PARAMETERS'] = {
49 'auth_vars': auth_vars
50 }
51 ctx.obj['context_marker'] = '88888888-4444-4444-4444-121212121212'
52 ctx.obj['site_name'] = 'test-site'
53 return ctx
54
55def _get_bad_context():
56 ctx = context()
57 ctx.obj = {}
58 auth_vars = {
59 'project_domain_name': 'projDomainTest',
60 'user_domain_name': 'userDomainTest',
61 'project_name': 'projectTest',
62 'username': 'usernameTest',
63 'password': 'passwordTest',
64 'auth_url': None
65 }
66 ctx.obj['API_PARAMETERS'] = {
67 'auth_vars': auth_vars
68 }
69 ctx.obj['context_marker'] = '88888888-4444-4444-4444-121212121212'
70 ctx.obj['site_name'] = 'test-site'
71 return ctx
72
73
74def test_shipyard_helper_init_():
75 """ Tests ShipyardHelper init method """
76 # Scenario:
77 #
78 # 1) Get a dummy context Object
79 # 2) Check that site name is as expected
80 # 3) Check api client is instance of ShipyardClient
81
82 context = _get_context()
83 shipyard_helper = ShipyardHelper(context)
84
85 assert shipyard_helper.site_name == context.obj['site_name']
86 assert isinstance(shipyard_helper.api_client, ShipyardClient)
87
88@mock.patch('pegleg.engine.util.files.collect_files_by_repo', autospec=True,
89 return_value=DATA)
90@mock.patch.object(ShipyardHelper, 'formatted_response_handler',
91 autospec=True, return_value=None)
92def test_upload_documents(*args):
93 """ Tests upload document """
94 # Scenario:
95 #
96 # 1) Get a dummy context Object
97 # 2) Mock external calls
98 # 3) Check documents uploaded to Shipyard with correct parameters
99
100 context = _get_context()
101 shipyard_helper = ShipyardHelper(context)
102
103 with mock.patch('pegleg.engine.util.shipyard_helper.ShipyardClient',
104 autospec=True) as mock_shipyard:
105 mock_api_client = mock_shipyard.return_value
106 mock_api_client.post_configdocs.return_value = 'Success'
107 result = ShipyardHelper(context).upload_documents()
108
109 # Validate Shipyard call to post configdocs was invoked with correct
110 # collection name and buffer mode.
111 mock_api_client.post_configdocs.assert_called_with('test-repo', None, ANY)
112 mock_api_client.post_configdocs.assert_called_once()
113
114@mock.patch('pegleg.engine.util.files.collect_files_by_repo', autospec=True,
115 return_value=DATA)
116@mock.patch.object(ShipyardHelper, 'formatted_response_handler',
117 autospec=True, return_value=None)
118def test_upload_documents_fail(*args):
119 """ Tests Document upload error """
120 # Scenario:
121 #
122 # 1) Get a bad context object with empty auth_url
123 # 2) Mock external calls
124 # 3) Check DocumentUploadError is raised
125
126 context = _get_context()
127 shipyard_helper = ShipyardHelper(context)
128
129 with mock.patch('pegleg.engine.util.shipyard_helper.ShipyardClient',
130 autospec=True) as mock_shipyard:
131 mock_api_client = mock_shipyard.return_value
132 mock_api_client.post_configdocs.return_value = FakeResponse()
133 with pytest.raises(util.shipyard_helper.DocumentUploadError):
134 ShipyardHelper(context).upload_documents()
135
136@mock.patch('pegleg.engine.util.files.collect_files_by_repo', autospec=True,
137 return_value=DATA)
138@mock.patch.object(ShipyardHelper, 'formatted_response_handler',
139 autospec=True, return_value=None)
140def test_fail_auth(*args):
141 """ Tests Auth Failure """
142 # Scenario:
143 #
144 # 1) Get a bad context object with empty auth_url
145 # 2) Check AuthValuesError is raised
146
147 context = _get_bad_context()
148 shipyard_helper = ShipyardHelper(context)
149
150 with pytest.raises(util.shipyard_helper.AuthValuesError):
151 ShipyardHelper(context).validate_auth_vars()
152
153@mock.patch.object(ShipyardHelper, 'formatted_response_handler',
154 autospec=True, return_value=None)
155def test_commit_documents(*args):
156 """Tests commit document """
157 # Scenario:
158 #
159 # 1) Get a dummy context Object
160 # 2) Mock external calls
161 # 3) Check commit documents was called
162
163 context = _get_context()
164 shipyard_helper = ShipyardHelper(context)
165
166 with mock.patch('pegleg.engine.util.shipyard_helper.ShipyardClient',
167 autospec=True) as mock_shipyard:
168 mock_api_client = mock_shipyard.return_value
169 mock_api_client.commit_configdocs.return_value = 'Success'
170 result = ShipyardHelper(context).commit_documents()
171
172 mock_api_client.commit_configdocs.assert_called_once()
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index 4f73054..59323c4 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -16,6 +16,7 @@ import os
16import shutil 16import shutil
17 17
18from click.testing import CliRunner 18from click.testing import CliRunner
19from mock import ANY
19import mock 20import mock
20import pytest 21import pytest
21 22
@@ -354,6 +355,22 @@ class TestSiteCliActions(BaseCLIActionTest):
354 repo_path = self.treasuremap_path 355 repo_path = self.treasuremap_path
355 self._validate_render_site_action(repo_path) 356 self._validate_render_site_action(repo_path)
356 357
358 def test_upload_documents_shipyard_using_local_repo_path(self):
359 """Validates ShipyardHelper is called with correct arguments."""
360 # Scenario:
361 #
362 # 1) Mock out ShipyardHelper
363 # 2) Check ShipyardHelper was called with correct arguments
364
365 repo_path = self.treasuremap_path
366
367 with mock.patch('pegleg.cli.ShipyardHelper') as mock_obj:
368 result = self.runner.invoke(cli.site,
369 ['-r', repo_path, 'upload', self.site_name])
370
371 assert result.exit_code == 0
372 mock_obj.assert_called_once()
373
357 374
358class TestRepoCliActions(BaseCLIActionTest): 375class TestRepoCliActions(BaseCLIActionTest):
359 """Tests repo-level CLI actions.""" 376 """Tests repo-level CLI actions."""