diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py index 5bc2a6ea..4ddc8067 100644 --- a/heatclient/tests/test_shell.py +++ b/heatclient/tests/test_shell.py @@ -678,6 +678,46 @@ class ShellTestUserPass(ShellBase): for r in required: self.assertRegexpMatches(show_text, r) + def test_stack_preview(self): + self._script_keystone_client() + resp_dict = {"stack": { + "id": "1", + "stack_name": "teststack", + "stack_status": 'CREATE_COMPLETE', + "resources": {'1': {'name': 'r1'}}, + "creation_time": "2012-10-25T01:58:47Z", + }} + resp = fakes.FakeHTTPResponse( + 200, + 'OK', + {'location': 'http://no.where/v1/tenant_id/stacks/teststack2/2'}, + jsonutils.dumps(resp_dict)) + http.HTTPClient.json_request( + 'POST', '/stacks/preview', data=mox.IgnoreArg(), + headers={'X-Auth-Key': 'password', 'X-Auth-User': 'username'} + ).AndReturn((resp, resp_dict)) + + self.m.ReplayAll() + + template_file = os.path.join(TEST_VAR_DIR, 'minimal.template') + preview_text = self.shell( + 'stack-preview teststack ' + '--template-file=%s ' + '--parameters="InstanceType=m1.large;DBUsername=wp;' + 'DBPassword=verybadpassword;KeyName=heat_key;' + 'LinuxDistribution=F17"' % template_file) + + required = [ + 'stack_name', + 'id', + 'teststack', + '1', + 'resources' + ] + + for r in required: + self.assertRegexpMatches(preview_text, r) + def test_stack_create(self): self._script_keystone_client() resp = fakes.FakeHTTPResponse( diff --git a/heatclient/tests/test_stacks.py b/heatclient/tests/test_stacks.py index 254945d2..48863422 100644 --- a/heatclient/tests/test_stacks.py +++ b/heatclient/tests/test_stacks.py @@ -98,6 +98,12 @@ class StackOperationsTest(testtools.TestCase): stack = stack.create() manager.create.assert_called_once_with('the_stack/abcd1234') + def test_preview_stack(self): + manager = MagicMock() + stack = mock_stack(manager, 'the_stack', 'abcd1234') + stack = stack.preview() + manager.preview.assert_called_once_with() + class StackManagerNoPaginationTest(testtools.TestCase): diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index eeb4ca0f..22c63f65 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -169,6 +169,59 @@ def do_stack_adopt(hc, args): do_stack_list(hc) +@utils.arg('-f', '--template-file', metavar='', + help='Path to the template.') +@utils.arg('-e', '--environment-file', metavar='', + help='Path to the environment.') +@utils.arg('-u', '--template-url', metavar='', + help='URL of template.') +@utils.arg('-o', '--template-object', metavar='', + help='URL to retrieve template object (e.g from swift)') +@utils.arg('-c', '--create-timeout', metavar='', + default=60, type=int, + help='Stack timeout in minutes. Default: 60') +@utils.arg('-r', '--enable-rollback', default=False, action="store_true", + help='Enable rollback on failure') +@utils.arg('-P', '--parameters', metavar='', + help='Parameter values used to preview the stack. ' + 'This can be specified multiple times, or once with parameters ' + 'separated by semicolon.', + action='append') +@utils.arg('name', metavar='', + help='Name of the stack to preview.') +def do_stack_preview(hc, args): + '''Preview the stack.''' + tpl_files, template = template_utils.get_template_contents( + args.template_file, + args.template_url, + args.template_object, + hc.http_client.raw_request) + env_files, env = template_utils.process_environment_and_files( + env_path=args.environment_file) + + fields = { + 'stack_name': args.name, + 'timeout_mins': args.create_timeout, + 'disable_rollback': not(args.enable_rollback), + 'parameters': utils.format_parameters(args.parameters), + 'template': template, + 'files': dict(tpl_files.items() + env_files.items()), + 'environment': env + } + + stack = hc.stacks.preview(**fields) + formatters = { + 'description': utils.text_wrap_formatter, + 'template_description': utils.text_wrap_formatter, + 'stack_status_reason': utils.text_wrap_formatter, + 'parameters': utils.json_formatter, + 'outputs': utils.json_formatter, + 'resources': utils.json_formatter, + 'links': utils.link_formatter, + } + utils.print_dict(stack.to_dict(), formatters=formatters) + + @utils.arg('id', metavar='', nargs='+', help='Name or ID of stack(s) to delete.') def do_delete(hc, args): diff --git a/heatclient/v1/stacks.py b/heatclient/v1/stacks.py index 2d1ba6da..34d0c31f 100644 --- a/heatclient/v1/stacks.py +++ b/heatclient/v1/stacks.py @@ -23,6 +23,9 @@ class Stack(base.Resource): def __repr__(self): return "" % self._info + def preview(self, **fields): + return self.manager.preview(**fields) + def create(self, **fields): return self.manager.create(self.identifier, **fields) @@ -102,6 +105,13 @@ class StackManager(base.BaseManager): return paginate(params) + def preview(self, **kwargs): + """Preview a stack.""" + headers = self.client.credentials_headers() + resp, body = self.client.json_request('POST', '/stacks/preview', + data=kwargs, headers=headers) + return Stack(self, body['stack']) + def create(self, **kwargs): """Create a stack.""" headers = self.client.credentials_headers()