diff --git a/etc/heat/policy.json b/etc/heat/policy.json index a6e16ea4cf..89422e0b76 100644 --- a/etc/heat/policy.json +++ b/etc/heat/policy.json @@ -55,6 +55,7 @@ "stacks:show": "rule:deny_stack_user", "stacks:template": "rule:deny_stack_user", "stacks:environment": "rule:deny_stack_user", + "stacks:files": "rule:deny_stack_user", "stacks:update": "rule:deny_stack_user", "stacks:update_patch": "rule:deny_stack_user", "stacks:preview_update": "rule:deny_stack_user", diff --git a/heat/api/openstack/v1/__init__.py b/heat/api/openstack/v1/__init__.py index 8162a6725d..7a782f1f0c 100644 --- a/heat/api/openstack/v1/__init__.py +++ b/heat/api/openstack/v1/__init__.py @@ -171,7 +171,7 @@ class API(wsgi.Router): 'name': 'stack_lookup_subpath', 'url': '/stacks/{stack_name}/' '{path:resources|events|template|actions' - '|environment}', + '|environment|files}', 'action': 'lookup', 'method': 'GET' }, @@ -200,6 +200,12 @@ class API(wsgi.Router): 'action': 'environment', 'method': 'GET' }, + { + 'name': 'stack_lookup', + 'url': '/stacks/{stack_name}/{stack_id}/files', + 'action': 'files', + 'method': 'GET' + }, # Stack update/delete { diff --git a/heat/api/openstack/v1/stacks.py b/heat/api/openstack/v1/stacks.py index 31f909e7f3..003d65aed2 100644 --- a/heat/api/openstack/v1/stacks.py +++ b/heat/api/openstack/v1/stacks.py @@ -463,6 +463,11 @@ class StackController(object): return env + @util.identified_stack + def files(self, req, identity): + """Get the files for an existing stack.""" + return self.rpc_client.get_files(req.context, identity) + @util.identified_stack def update(self, req, identity, body): """Update an existing stack with a new template and/or parameters.""" diff --git a/heat/engine/service.py b/heat/engine/service.py index 90df2aa52d..f064882eac 100644 --- a/heat/engine/service.py +++ b/heat/engine/service.py @@ -297,7 +297,7 @@ class EngineService(service.Service): by the RPC caller. """ - RPC_API_VERSION = '1.31' + RPC_API_VERSION = '1.32' def __init__(self, host, topic): super(EngineService, self).__init__() @@ -1285,6 +1285,19 @@ class EngineService(service.Service): return s.raw_template.environment return None + @context.request_context + def get_files(self, cnxt, stack_identity): + """Returns the files for an existing stack. + + :param cnxt: RPC context + :param stack_identity: identifies the stack + :rtype: dict + """ + s = self._get_stack(cnxt, stack_identity, show_deleted=True) + stack = parser.Stack.load( + cnxt, stack=s, resolve_data=False, show_deleted=True) + return dict(stack.t.files) + @context.request_context def list_outputs(self, cntx, stack_identity): """Get a list of stack outputs. diff --git a/heat/rpc/client.py b/heat/rpc/client.py index 3fa37bc770..7640b2aebb 100644 --- a/heat/rpc/client.py +++ b/heat/rpc/client.py @@ -53,6 +53,7 @@ class EngineClient(object): 1.30 - Add possibility to resource_type_* return descriptions 1.31 - Add nested_depth to list_events, when nested_depth is specified add root_stack_id to response + 1.32 - Add get_files call """ BASE_RPC_API_VERSION = '1.0' @@ -408,6 +409,19 @@ class EngineClient(object): stack_identity=stack_identity), version='1.28') + def get_files(self, context, stack_identity): + """Returns the files for an existing stack. + + :param context: RPC context + :param stack_identity: identifies the stack + :rtype: dict + """ + + return self.call(context, + self.make_msg('get_files', + stack_identity=stack_identity), + version='1.32') + def delete_stack(self, ctxt, stack_identity, cast=True): """Deletes a given stack. diff --git a/heat/tests/api/openstack_v1/test_stacks.py b/heat/tests/api/openstack_v1/test_stacks.py index af47c031d4..a0509bec64 100644 --- a/heat/tests/api/openstack_v1/test_stacks.py +++ b/heat/tests/api/openstack_v1/test_stacks.py @@ -1739,6 +1739,27 @@ class StackControllerTest(tools.ControllerTest, common.HeatTestCase): self.m.VerifyAll() + def test_get_files(self, mock_enforce): + self._mock_enforce_setup(mock_enforce, 'files', True) + identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6') + req = self._get('/stacks/%(stack_name)s/%(stack_id)s' % identity) + files = {'foo.yaml': 'i am yaml'} + + self.m.StubOutWithMock(rpc_client.EngineClient, 'call') + rpc_client.EngineClient.call( + req.context, + ('get_files', {'stack_identity': dict(identity)},), + version='1.32', + ).AndReturn(files) + self.m.ReplayAll() + + response = self.controller.files(req, tenant_id=identity.tenant, + stack_name=identity.stack_name, + stack_id=identity.stack_id) + + self.assertEqual(files, response) + self.m.VerifyAll() + def test_get_template_err_denied_policy(self, mock_enforce): self._mock_enforce_setup(mock_enforce, 'template', False) identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6') diff --git a/heat/tests/engine/service/test_service_engine.py b/heat/tests/engine/service/test_service_engine.py index fdd7b525c0..8a284e237b 100644 --- a/heat/tests/engine/service/test_service_engine.py +++ b/heat/tests/engine/service/test_service_engine.py @@ -40,7 +40,7 @@ class ServiceEngineTest(common.HeatTestCase): def test_make_sure_rpc_version(self): self.assertEqual( - '1.31', + '1.32', service.EngineService.RPC_API_VERSION, ('RPC version is changed, please update this test to new version ' 'and make sure additional test cases are added for RPC APIs ' diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 958721a095..ae390ece70 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -1083,6 +1083,24 @@ class StackServiceTest(common.HeatTestCase): self.ctx, 'irrelevant') + def test_get_files(self): + # Setup + t = template_format.parse(tools.wp_template) + files = {'foo.yaml': 'i am a file'} + tmpl = templatem.Template(t, files=files) + stack = parser.Stack(self.ctx, 'get_env_stack', tmpl) + stack.store() + + mock_get_stack = self.patchobject(self.eng, '_get_stack') + mock_get_stack.return_value = mock.MagicMock() + self.patchobject(parser.Stack, 'load', return_value=stack) + + # Test + found = self.eng.get_files(self.ctx, stack.identifier()) + + # Verify + self.assertEqual(files, found) + def test_stack_show_output(self): t = template_format.parse(tools.wp_template) t['outputs'] = {'test': {'value': 'first', 'description': 'sec'}, diff --git a/heat/tests/test_rpc_client.py b/heat/tests/test_rpc_client.py index 0fc0a5c173..0a29759f48 100644 --- a/heat/tests/test_rpc_client.py +++ b/heat/tests/test_rpc_client.py @@ -420,3 +420,8 @@ class EngineRpcAPITestCase(common.HeatTestCase): self._test_engine_api( 'get_environment', 'call', stack_identity=self.identity, version='1.28') + + def test_get_files(self): + self._test_engine_api( + 'get_files', 'call', stack_identity=self.identity, + version='1.32')