diff --git a/doc/source/user/proxies/orchestration.rst b/doc/source/user/proxies/orchestration.rst index 93ee46fc3..86662b7b1 100644 --- a/doc/source/user/proxies/orchestration.rst +++ b/doc/source/user/proxies/orchestration.rst @@ -20,7 +20,8 @@ Stack Operations :noindex: :members: create_stack, check_stack, update_stack, delete_stack, find_stack, get_stack, get_stack_environment, get_stack_files, - get_stack_template, stacks, validate_template, resources + get_stack_template, stacks, validate_template, resources, + export_stack Software Configuration Operations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/openstack/orchestration/v1/_proxy.py b/openstack/orchestration/v1/_proxy.py index 27200f2e5..9ada733e7 100644 --- a/openstack/orchestration/v1/_proxy.py +++ b/openstack/orchestration/v1/_proxy.py @@ -24,6 +24,9 @@ from openstack import proxy from openstack import resource +# TODO(rladntjr4): Some of these methods support lookup by ID, while +# others support lookup by ID or name. We should choose one and use +# it consistently. class Proxy(proxy.Proxy): _resource_registry = { "resource": _resource.Resource, @@ -222,6 +225,21 @@ class Proxy(proxy.Proxy): res = self._get_resource(_stack.Stack, stack) return res.abandon(self) + def export_stack(self, stack): + """Get the stack data in JSON format + + :param stack: The value can be the ID or a name or + an instance of :class:`~openstack.orchestration.v1.stack.Stack` + :returns: A dictionary containing the stack data. + :raises: :class:`~openstack.exceptions.ResourceNotFound` + when no resource can be found. + """ + if isinstance(stack, _stack.Stack): + obj = stack + else: + obj = self._find(_stack.Stack, stack, ignore_missing=False) + return obj.export(self) + def get_stack_template(self, stack): """Get template used by a stack diff --git a/openstack/orchestration/v1/stack.py b/openstack/orchestration/v1/stack.py index 387bd7a33..a62e0017e 100644 --- a/openstack/orchestration/v1/stack.py +++ b/openstack/orchestration/v1/stack.py @@ -179,6 +179,19 @@ class Stack(resource.Resource): resp = session.delete(url) return resp.json() + def export(self, session): + """Export a stack data + + :param session: The session to use for making this request. + :return: A dictionary containing the stack data. + """ + url = utils.urljoin( + self.base_path, self.name, self._get_id(self), 'export' + ) + resp = session.get(url) + exceptions.raise_from_response(resp) + return resp.json() + def fetch( self, session, diff --git a/openstack/tests/unit/orchestration/v1/test_proxy.py b/openstack/tests/unit/orchestration/v1/test_proxy.py index fe37c70e0..0e3af53df 100644 --- a/openstack/tests/unit/orchestration/v1/test_proxy.py +++ b/openstack/tests/unit/orchestration/v1/test_proxy.py @@ -121,6 +121,35 @@ class TestOrchestrationStack(TestOrchestrationProxy): expected_args=[self.proxy], ) + @mock.patch.object(stack.Stack, 'find') + def test_export_stack_with_identity(self, mock_find): + stack_id = '1234' + stack_name = 'test_stack' + stk = stack.Stack(id=stack_id, name=stack_name) + mock_find.return_value = stk + + self._verify( + 'openstack.orchestration.v1.stack.Stack.export', + self.proxy.export_stack, + method_args=['IDENTITY'], + expected_args=[self.proxy], + ) + mock_find.assert_called_once_with( + mock.ANY, 'IDENTITY', ignore_missing=False + ) + + def test_export_stack_with_object(self): + stack_id = '1234' + stack_name = 'test_stack' + stk = stack.Stack(id=stack_id, name=stack_name) + + self._verify( + 'openstack.orchestration.v1.stack.Stack.export', + self.proxy.export_stack, + method_args=[stk], + expected_args=[self.proxy], + ) + def test_delete_stack(self): self.verify_delete(self.proxy.delete_stack, stack.Stack, False) diff --git a/openstack/tests/unit/orchestration/v1/test_stack.py b/openstack/tests/unit/orchestration/v1/test_stack.py index 7ac4fef96..1a0bd8f96 100644 --- a/openstack/tests/unit/orchestration/v1/test_stack.py +++ b/openstack/tests/unit/orchestration/v1/test_stack.py @@ -272,6 +272,23 @@ class TestStack(base.TestCase): 'stacks/%s/%s/abandon' % (FAKE_NAME, FAKE_ID), ) + def test_export(self): + sess = mock.Mock() + sess.default_microversion = None + + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.json.return_value = {} + sess.get = mock.Mock(return_value=mock_response) + sot = stack.Stack(**FAKE) + + sot.export(sess) + + sess.get.assert_called_with( + 'stacks/%s/%s/export' % (FAKE_NAME, FAKE_ID), + ) + def test_update(self): sess = mock.Mock() sess.default_microversion = None diff --git a/releasenotes/notes/add-stack-export-3ace746a8c80d766.yaml b/releasenotes/notes/add-stack-export-3ace746a8c80d766.yaml new file mode 100644 index 000000000..00d680b0f --- /dev/null +++ b/releasenotes/notes/add-stack-export-3ace746a8c80d766.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Add ``export_stack`` to print stack infomation in a json format