diff options
Diffstat (limited to 'ansible_collections/amazon/aws/tests/unit')
126 files changed, 12479 insertions, 0 deletions
diff --git a/ansible_collections/amazon/aws/tests/unit/compat/__init__.py b/ansible_collections/amazon/aws/tests/unit/compat/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/compat/__init__.py diff --git a/ansible_collections/amazon/aws/tests/unit/compat/builtins.py b/ansible_collections/amazon/aws/tests/unit/compat/builtins.py new file mode 100644 index 000000000..349d310e8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/compat/builtins.py @@ -0,0 +1,33 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# +# Compat for python2.7 +# + +# One unittest needs to import builtins via __import__() so we need to have +# the string that represents it +try: + import __builtin__ # pylint: disable=unused-import +except ImportError: + BUILTINS = 'builtins' +else: + BUILTINS = '__builtin__' diff --git a/ansible_collections/amazon/aws/tests/unit/compat/mock.py b/ansible_collections/amazon/aws/tests/unit/compat/mock.py new file mode 100644 index 000000000..0972cd2e8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/compat/mock.py @@ -0,0 +1,122 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python3.x's unittest.mock module +''' +import sys + +# Python 2.7 + +# Note: Could use the pypi mock library on python3.x as well as python2.x. It +# is the same as the python3 stdlib mock library + +try: + # Allow wildcard import because we really do want to import all of mock's + # symbols into this compat shim + # pylint: disable=wildcard-import,unused-wildcard-import + from unittest.mock import * +except ImportError: + # Python 2 + # pylint: disable=wildcard-import,unused-wildcard-import + try: + from mock import * + except ImportError: + print('You need the mock library installed on python2.x to run tests') + + +# Prior to 3.4.4, mock_open cannot handle binary read_data +if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): + file_spec = None + + def _iterate_read_data(read_data): + # Helper for mock_open: + # Retrieve lines from read_data via a generator so that separate calls to + # readline, read, and readlines are properly interleaved + sep = b'\n' if isinstance(read_data, bytes) else '\n' + data_as_list = [l + sep for l in read_data.split(sep)] + + if data_as_list[-1] == sep: + # If the last line ended in a newline, the list comprehension will have an + # extra entry that's just a newline. Remove this. + data_as_list = data_as_list[:-1] + else: + # If there wasn't an extra newline by itself, then the file being + # emulated doesn't have a newline to end the last line remove the + # newline that our naive format() added + data_as_list[-1] = data_as_list[-1][:-1] + + for line in data_as_list: + yield line + + def mock_open(mock=None, read_data=''): + """ + A helper function to create a mock to replace the use of `open`. It works + for `open` called directly or used as a context manager. + + The `mock` argument is the mock object to configure. If `None` (the + default) then a `MagicMock` will be created for you, with the API limited + to methods or attributes available on standard file handles. + + `read_data` is a string for the `read` methoddline`, and `readlines` of the + file handle to return. This is an empty string by default. + """ + def _readlines_side_effect(*args, **kwargs): + if handle.readlines.return_value is not None: + return handle.readlines.return_value + return list(_data) + + def _read_side_effect(*args, **kwargs): + if handle.read.return_value is not None: + return handle.read.return_value + return type(read_data)().join(_data) + + def _readline_side_effect(): + if handle.readline.return_value is not None: + while True: + yield handle.readline.return_value + for line in _data: + yield line + + global file_spec + if file_spec is None: + import _io + file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) + + if mock is None: + mock = MagicMock(name='open', spec=open) + + handle = MagicMock(spec=file_spec) + handle.__enter__.return_value = handle + + _data = _iterate_read_data(read_data) + + handle.write.return_value = None + handle.read.return_value = None + handle.readline.return_value = None + handle.readlines.return_value = None + + handle.read.side_effect = _read_side_effect + handle.readline.side_effect = _readline_side_effect() + handle.readlines.side_effect = _readlines_side_effect + + mock.return_value = handle + return mock diff --git a/ansible_collections/amazon/aws/tests/unit/compat/unittest.py b/ansible_collections/amazon/aws/tests/unit/compat/unittest.py new file mode 100644 index 000000000..98f08ad6a --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/compat/unittest.py @@ -0,0 +1,38 @@ +# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +''' +Compat module for Python2.7's unittest module +''' + +import sys + +# Allow wildcard import because we really do want to import all of +# unittests's symbols into this compat shim +# pylint: disable=wildcard-import,unused-wildcard-import +if sys.version_info < (2, 7): + try: + # Need unittest2 on python2.6 + from unittest2 import * + except ImportError: + print('You need unittest2 installed on python2.6.x to run tests') +else: + from unittest import * diff --git a/ansible_collections/amazon/aws/tests/unit/constraints.txt b/ansible_collections/amazon/aws/tests/unit/constraints.txt new file mode 100644 index 000000000..cd546e7c2 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/constraints.txt @@ -0,0 +1,7 @@ +# Specifically run tests against the oldest versions that we support +boto3==1.18.0 +botocore==1.21.0 + +# AWS CLI has `botocore==` dependencies, provide the one that matches botocore +# to avoid needing to download over a years worth of awscli wheels. +awscli==1.20.0 diff --git a/ansible_collections/amazon/aws/tests/unit/mock/loader.py b/ansible_collections/amazon/aws/tests/unit/mock/loader.py new file mode 100644 index 000000000..00a584127 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/mock/loader.py @@ -0,0 +1,116 @@ +# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os + +from ansible.errors import AnsibleParserError +from ansible.parsing.dataloader import DataLoader +from ansible.module_utils._text import to_bytes, to_text + + +class DictDataLoader(DataLoader): + + def __init__(self, file_mapping=None): + file_mapping = {} if file_mapping is None else file_mapping + assert type(file_mapping) == dict + + super(DictDataLoader, self).__init__() + + self._file_mapping = file_mapping + self._build_known_directories() + self._vault_secrets = None + + def load_from_file(self, path, cache=True, unsafe=False): + path = to_text(path) + if path in self._file_mapping: + return self.load(self._file_mapping[path], path) + return None + + # TODO: the real _get_file_contents returns a bytestring, so we actually convert the + # unicode/text it's created with to utf-8 + def _get_file_contents(self, file_name): + file_name = to_text(file_name) + if file_name in self._file_mapping: + return (to_bytes(self._file_mapping[file_name]), False) + else: + raise AnsibleParserError("file not found: %s" % file_name) + + def path_exists(self, path): + path = to_text(path) + return path in self._file_mapping or path in self._known_directories + + def is_file(self, path): + path = to_text(path) + return path in self._file_mapping + + def is_directory(self, path): + path = to_text(path) + return path in self._known_directories + + def list_directory(self, path): + ret = [] + path = to_text(path) + for x in (list(self._file_mapping.keys()) + self._known_directories): + if x.startswith(path): + if os.path.dirname(x) == path: + ret.append(os.path.basename(x)) + return ret + + def is_executable(self, path): + # FIXME: figure out a way to make paths return true for this + return False + + def _add_known_directory(self, directory): + if directory not in self._known_directories: + self._known_directories.append(directory) + + def _build_known_directories(self): + self._known_directories = [] + for path in self._file_mapping: + dirname = os.path.dirname(path) + while dirname not in ('/', ''): + self._add_known_directory(dirname) + dirname = os.path.dirname(dirname) + + def push(self, path, content): + rebuild_dirs = False + if path not in self._file_mapping: + rebuild_dirs = True + + self._file_mapping[path] = content + + if rebuild_dirs: + self._build_known_directories() + + def pop(self, path): + if path in self._file_mapping: + del self._file_mapping[path] + self._build_known_directories() + + def clear(self): + self._file_mapping = dict() + self._known_directories = [] + + def get_basedir(self): + return os.getcwd() + + def set_vault_secrets(self, vault_secrets): + self._vault_secrets = vault_secrets diff --git a/ansible_collections/amazon/aws/tests/unit/mock/path.py b/ansible_collections/amazon/aws/tests/unit/mock/path.py new file mode 100644 index 000000000..8de2aec25 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/mock/path.py @@ -0,0 +1,8 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock +from ansible.utils.path import unfrackpath + + +mock_unfrackpath_noop = MagicMock(spec_set=unfrackpath, side_effect=lambda x, *args, **kwargs: x) diff --git a/ansible_collections/amazon/aws/tests/unit/mock/procenv.py b/ansible_collections/amazon/aws/tests/unit/mock/procenv.py new file mode 100644 index 000000000..273959e4b --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/mock/procenv.py @@ -0,0 +1,90 @@ +# (c) 2016, Matt Davis <mdavis@ansible.com> +# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import sys +import json + +from contextlib import contextmanager +from io import BytesIO, StringIO +from ansible_collections.amazon.aws.tests.unit.compat import unittest +from ansible.module_utils.six import PY3 +from ansible.module_utils._text import to_bytes + + +@contextmanager +def swap_stdin_and_argv(stdin_data='', argv_data=tuple()): + """ + context manager that temporarily masks the test runner's values for stdin and argv + """ + real_stdin = sys.stdin + real_argv = sys.argv + + if PY3: + fake_stream = StringIO(stdin_data) + fake_stream.buffer = BytesIO(to_bytes(stdin_data)) + else: + fake_stream = BytesIO(to_bytes(stdin_data)) + + try: + sys.stdin = fake_stream + sys.argv = argv_data + + yield + finally: + sys.stdin = real_stdin + sys.argv = real_argv + + +@contextmanager +def swap_stdout(): + """ + context manager that temporarily replaces stdout for tests that need to verify output + """ + old_stdout = sys.stdout + + if PY3: + fake_stream = StringIO() + else: + fake_stream = BytesIO() + + try: + sys.stdout = fake_stream + + yield fake_stream + finally: + sys.stdout = old_stdout + + +class ModuleTestCase(unittest.TestCase): + def setUp(self, module_args=None): + if module_args is None: + module_args = {'_ansible_remote_tmp': '/tmp', '_ansible_keep_remote_files': False} + + args = json.dumps(dict(ANSIBLE_MODULE_ARGS=module_args)) + + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap = swap_stdin_and_argv(stdin_data=args) + self.stdin_swap.__enter__() + + def tearDown(self): + # unittest doesn't have a clean place to use a context manager, so we have to enter/exit manually + self.stdin_swap.__exit__(None, None, None) diff --git a/ansible_collections/amazon/aws/tests/unit/mock/vault_helper.py b/ansible_collections/amazon/aws/tests/unit/mock/vault_helper.py new file mode 100644 index 000000000..dcce9c784 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/mock/vault_helper.py @@ -0,0 +1,39 @@ +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils._text import to_bytes + +from ansible.parsing.vault import VaultSecret + + +class TextVaultSecret(VaultSecret): + '''A secret piece of text. ie, a password. Tracks text encoding. + + The text encoding of the text may not be the default text encoding so + we keep track of the encoding so we encode it to the same bytes.''' + + def __init__(self, text, encoding=None, errors=None, _bytes=None): + super(TextVaultSecret, self).__init__() + self.text = text + self.encoding = encoding or 'utf-8' + self._bytes = _bytes + self.errors = errors or 'strict' + + @property + def bytes(self): + '''The text encoded with encoding, unless we specifically set _bytes.''' + return self._bytes or to_bytes(self.text, encoding=self.encoding, errors=self.errors) diff --git a/ansible_collections/amazon/aws/tests/unit/mock/yaml_helper.py b/ansible_collections/amazon/aws/tests/unit/mock/yaml_helper.py new file mode 100644 index 000000000..1ef172159 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/mock/yaml_helper.py @@ -0,0 +1,124 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import io +import yaml + +from ansible.module_utils.six import PY3 +from ansible.parsing.yaml.loader import AnsibleLoader +from ansible.parsing.yaml.dumper import AnsibleDumper + + +class YamlTestUtils(object): + """Mixin class to combine with a unittest.TestCase subclass.""" + def _loader(self, stream): + """Vault related tests will want to override this. + + Vault cases should setup a AnsibleLoader that has the vault password.""" + return AnsibleLoader(stream) + + def _dump_stream(self, obj, stream, dumper=None): + """Dump to a py2-unicode or py3-string stream.""" + if PY3: + return yaml.dump(obj, stream, Dumper=dumper) + else: + return yaml.dump(obj, stream, Dumper=dumper, encoding=None) + + def _dump_string(self, obj, dumper=None): + """Dump to a py2-unicode or py3-string""" + if PY3: + return yaml.dump(obj, Dumper=dumper) + else: + return yaml.dump(obj, Dumper=dumper, encoding=None) + + def _dump_load_cycle(self, obj): + # Each pass though a dump or load revs the 'generation' + # obj to yaml string + string_from_object_dump = self._dump_string(obj, dumper=AnsibleDumper) + + # wrap a stream/file like StringIO around that yaml + stream_from_object_dump = io.StringIO(string_from_object_dump) + loader = self._loader(stream_from_object_dump) + # load the yaml stream to create a new instance of the object (gen 2) + obj_2 = loader.get_data() + + # dump the gen 2 objects directory to strings + string_from_object_dump_2 = self._dump_string(obj_2, + dumper=AnsibleDumper) + + # The gen 1 and gen 2 yaml strings + self.assertEqual(string_from_object_dump, string_from_object_dump_2) + # the gen 1 (orig) and gen 2 py object + self.assertEqual(obj, obj_2) + + # again! gen 3... load strings into py objects + stream_3 = io.StringIO(string_from_object_dump_2) + loader_3 = self._loader(stream_3) + obj_3 = loader_3.get_data() + + string_from_object_dump_3 = self._dump_string(obj_3, dumper=AnsibleDumper) + + self.assertEqual(obj, obj_3) + # should be transitive, but... + self.assertEqual(obj_2, obj_3) + self.assertEqual(string_from_object_dump, string_from_object_dump_3) + + def _old_dump_load_cycle(self, obj): + '''Dump the passed in object to yaml, load it back up, dump again, compare.''' + stream = io.StringIO() + + yaml_string = self._dump_string(obj, dumper=AnsibleDumper) + self._dump_stream(obj, stream, dumper=AnsibleDumper) + + yaml_string_from_stream = stream.getvalue() + + # reset stream + stream.seek(0) + + loader = self._loader(stream) + # loader = AnsibleLoader(stream, vault_password=self.vault_password) + obj_from_stream = loader.get_data() + + stream_from_string = io.StringIO(yaml_string) + loader2 = self._loader(stream_from_string) + # loader2 = AnsibleLoader(stream_from_string, vault_password=self.vault_password) + obj_from_string = loader2.get_data() + + stream_obj_from_stream = io.StringIO() + stream_obj_from_string = io.StringIO() + + if PY3: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper) + else: + yaml.dump(obj_from_stream, stream_obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml.dump(obj_from_stream, stream_obj_from_string, Dumper=AnsibleDumper, encoding=None) + + yaml_string_stream_obj_from_stream = stream_obj_from_stream.getvalue() + yaml_string_stream_obj_from_string = stream_obj_from_string.getvalue() + + stream_obj_from_stream.seek(0) + stream_obj_from_string.seek(0) + + if PY3: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper) + else: + yaml_string_obj_from_stream = yaml.dump(obj_from_stream, Dumper=AnsibleDumper, encoding=None) + yaml_string_obj_from_string = yaml.dump(obj_from_string, Dumper=AnsibleDumper, encoding=None) + + assert yaml_string == yaml_string_obj_from_stream + assert yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + assert (yaml_string == yaml_string_obj_from_stream == yaml_string_obj_from_string == yaml_string_stream_obj_from_stream == + yaml_string_stream_obj_from_string) + assert obj == obj_from_stream + assert obj == obj_from_string + assert obj == yaml_string_obj_from_stream + assert obj == yaml_string_obj_from_string + assert obj == obj_from_stream == obj_from_string == yaml_string_obj_from_stream == yaml_string_obj_from_string + return {'obj': obj, + 'yaml_string': yaml_string, + 'yaml_string_from_stream': yaml_string_from_stream, + 'obj_from_stream': obj_from_stream, + 'obj_from_string': obj_from_string, + 'yaml_string_obj_from_string': yaml_string_obj_from_string} diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_is_outpost_arn.py b/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_is_outpost_arn.py new file mode 100644 index 000000000..7c2e21eb2 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_is_outpost_arn.py @@ -0,0 +1,27 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.arn import is_outpost_arn + +outpost_arn_test_inputs = [ + ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0", True), + ("arn:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0123", False), + ("arn:aws:outpost:us-east-1:123456789012:outpost/op-1234567890abcdef0", False), + ("ars:aws:outposts:us-east-1:123456789012:outpost/op-1234567890abcdef0", False), + ("arn:was:outposts:us-east-1:123456789012:outpost/ op-1234567890abcdef0", False), + ("arn:aws:outpost:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), + ("ars:aws:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), + ("arn:was:outposts:us-east-1: 123456789012:outpost/ op-1234567890abcdef0", False), +] + + +@pytest.mark.parametrize("outpost_arn, result", outpost_arn_test_inputs) +def test_is_outpost_arn(outpost_arn, result): + assert is_outpost_arn(outpost_arn) == result diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_parse_aws_arn.py b/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_parse_aws_arn.py new file mode 100644 index 000000000..87dada4a9 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/arn/test_parse_aws_arn.py @@ -0,0 +1,95 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.arn import parse_aws_arn + +arn_bad_values = [ + ("arn:aws:outpost:us-east-1: 123456789012:outpost/op-1234567890abcdef0"), + ("arn:aws:out post:us-east-1:123456789012:outpost/op-1234567890abcdef0"), + ("arn:aws:outpost:us east 1:123456789012:outpost/op-1234567890abcdef0"), + ("invalid:aws:outpost:us-east-1:123456789012:outpost/op-1234567890abcdef0"), + ("arn:junk:outpost:us-east-1:123456789012:outpost/op-1234567890abcdef0"), + ("arn:aws:outpost:us-east-1:junk:outpost/op-1234567890abcdef0"), +] + +arn_good_values = [ + # Play about with partition name in valid ways + dict(partition='aws', service='outpost', region='us-east-1', account_id='123456789012', + resource='outpost/op-1234567890abcdef0'), + dict(partition='aws-gov', service='outpost', region='us-gov-east-1', account_id='123456789012', + resource='outpost/op-1234567890abcdef0'), + dict(partition='aws-cn', service='outpost', region='us-east-1', account_id='123456789012', + resource='outpost/op-1234567890abcdef0'), + # Start the account ID with 0s, it's a 12 digit *string*, if someone treats + # it as an integer the leading 0s can disappear. + dict(partition='aws-cn', service='outpost', region='us-east-1', account_id='000123000123', + resource='outpost/op-1234567890abcdef0'), + # S3 doesn't "need" region/account_id as bucket names are globally unique + dict(partition='aws', service='s3', region='', account_id='', resource='bucket/object'), + # IAM is a 'global' service, so the ARNs don't have regions + dict(partition='aws', service='iam', region='', account_id='123456789012', + resource='policy/foo/bar/PolicyName'), + dict(partition='aws', service='iam', region='', account_id='123456789012', + resource='instance-profile/ExampleProfile'), + dict(partition='aws', service='iam', region='', account_id='123456789012', resource='root'), + # Some examples with different regions + dict(partition='aws', service='sqs', region='eu-west-3', account_id='123456789012', + resource='example-queue'), + dict(partition='aws', service='sqs', region='us-gov-east-1', account_id='123456789012', + resource='example-queue'), + dict(partition='aws', service='sqs', region='sa-east-1', account_id='123456789012', + resource='example-queue'), + dict(partition='aws', service='sqs', region='ap-northeast-2', account_id='123456789012', + resource='example-queue'), + dict(partition='aws', service='sqs', region='ca-central-1', account_id='123456789012', + resource='example-queue'), + # Some more unusual service names + dict(partition='aws', service='network-firewall', region='us-east-1', account_id='123456789012', + resource='stateful-rulegroup/ExampleDomainList'), + dict(partition='aws', service='resource-groups', region='us-east-1', account_id='123456789012', + resource='group/group-name'), + # A special case for resources AWS curate + dict(partition='aws', service='network-firewall', region='us-east-1', account_id='aws-managed', + resource='stateful-rulegroup/BotNetCommandAndControlDomainsActionOrder'), + dict(partition='aws', service='iam', region='', account_id='aws', + resource='policy/AWSDirectConnectReadOnlyAccess'), + # Examples merged in from test_arn.py + dict(partition="aws-us-gov", service="iam", region="", account_id="0123456789", + resource="role/foo-role"), + dict(partition="aws", service='iam', region="", account_id="123456789012", + resource="user/dev/*"), + dict(partition="aws", service="iam", region="", account_id="123456789012", + resource="user:test"), + dict(partition="aws-cn", service="iam", region="", account_id="123456789012", + resource="user:test"), + dict(partition="aws", service="iam", region="", account_id="123456789012", + resource="user"), + dict(partition="aws", service="s3", region="", account_id="", + resource="my_corporate_bucket/*"), + dict(partition="aws", service="s3", region="", account_id="", + resource="my_corporate_bucket/Development/*"), + dict(partition="aws", service="rds", region="es-east-1", account_id="000000000000", + resource="snapshot:rds:my-db-snapshot"), + dict(partition="aws", service="cloudformation", region="us-east-1", account_id="012345678901", + resource="changeSet/Ansible-StackName-c6884247ede41eb0"), +] + + +@pytest.mark.parametrize("arn", arn_bad_values) +def test_parse_aws_arn_bad_values(arn): + # Make sure we get the expected 'None' for various 'bad' ARNs. + assert parse_aws_arn(arn) is None + + +@pytest.mark.parametrize("result", arn_good_values) +def test_parse_aws_arn_good_values(result): + # Something of a cheat, but build the ARN from the result we expect + arn = 'arn:{partition}:{service}:{region}:{account_id}:{resource}'.format(**result) + assert parse_aws_arn(arn) == result diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py new file mode 100644 index 000000000..627ae4cb3 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_code.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_is_boto3_error_code.py requires the python modules 'boto3' and 'botocore'") + + +class TestIsBoto3ErrorCode(): + + def _make_denied_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "User: arn:aws:iam::123456789012:user/ExampleUser " + + "is not authorized to perform: iam:GetUser on resource: user ExampleUser" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'getUser') + + def _make_unexpected_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "SomeThingWentWrong", + "Message": "Boom!" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_encoded_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "PermissionDenied", + "Message": "You are not authorized to perform this operation. Encoded authorization failure message: " + + "fEwXX6llx3cClm9J4pURgz1XPnJPrYexEbrJcLhFkwygMdOgx_-aEsj0LqRM6Kxt2HVI6prUhDwbJqBo9U2V7iRKZ" + + "T6ZdJvHH02cXmD0Jwl5vrTsf0PhBcWYlH5wl2qME7xTfdolEUr4CzumCiti7ETiO-RDdHqWlasBOW5bWsZ4GSpPdU" + + "06YAX0TfwVBs48uU5RpCHfz1uhSzez-3elbtp9CmTOHLt5pzJodiovccO55BQKYLPtmJcs6S9YLEEogmpI4Cb1D26" + + "fYahDh51jEmaohPnW5pb1nQe2yPEtuIhtRzNjhFCOOMwY5DBzNsymK-Gj6eJLm7FSGHee4AHLU_XmZMe_6bcLAiOx" + + "6Zdl65Kdd0hLcpwVxyZMi27HnYjAdqRlV3wuCW2PkhAW14qZQLfiuHZDEwnPe2PBGSlFcCmkQvJvX-YLoA7Uyc2wf" + + "NX5RJm38STwfiJSkQaNDhHKTWKiLOsgY4Gze6uZoG7zOcFXFRyaA4cbMmI76uyBO7j-9uQUCtBYqYto8x_9CUJcxI" + + "VC5SPG_C1mk-WoDMew01f0qy-bNaCgmJ9TOQGd08FyuT1SaMpCC0gX6mHuOnEgkFw3veBIowMpp9XcM-yc42fmIOp" + + "FOdvQO6uE9p55Qc-uXvsDTTvT3A7EeFU8a_YoAIt9UgNYM6VTvoprLz7dBI_P6C-bdPPZCY2amm-dJNVZelT6TbJB" + + "H_Vxh0fzeiSUBersy_QzB0moc-vPWgnB-IkgnYLV-4L3K0L2" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_botocore_exception(self): + return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') + + ### + # Test that is_boto3_error_code does what's expected when used in a try/except block + # (where we don't explicitly pass an exception to the function) + ### + + def _do_try_code(self, exception, codes): + try: + raise exception + except is_boto3_error_code(codes) as e: + return e + + def test_is_boto3_error_code_single__raise__client(self): + # 'AccessDenied' error, should be caught in our try/except in _do_try_code + thrown_exception = self._make_denied_exception() + codes_to_catch = 'AccessDenied' + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + def test_is_boto3_error_code_single__raise__unexpected(self): + # 'SomeThingWentWrong' error, shouldn't be caught because the Code doesn't match + thrown_exception = self._make_unexpected_exception() + codes_to_catch = 'AccessDenied' + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + def test_is_boto3_error_code_single__raise__botocore(self): + # BotoCoreExceptions don't have an error code, so shouldn't be caught (and shouldn't throw + # some other error due to the missing 'Code' data on the exception) + thrown_exception = self._make_botocore_exception() + codes_to_catch = 'AccessDenied' + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + + assert context.value == thrown_exception + + def test_is_boto3_error_code_multiple__raise__client(self): + # 'AccessDenied' error, should be caught in our try/except in _do_try_code + # test with multiple possible codes to catch + thrown_exception = self._make_denied_exception() + codes_to_catch = ['AccessDenied', 'NotAccessDenied'] + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + thrown_exception = self._make_denied_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + caught_exception = self._do_try_code(thrown_exception, codes_to_catch) + assert caught_exception == thrown_exception + + def test_is_boto3_error_code_multiple__raise__unexpected(self): + # 'SomeThingWentWrong' error, shouldn't be caught because the Code doesn't match + # test with multiple possible codes to catch + thrown_exception = self._make_unexpected_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + def test_is_boto3_error_code_multiple__raise__botocore(self): + # BotoCoreErrors don't have an error code, so shouldn't be caught (and shouldn't throw + # some other error due to the missing 'Code' data on the exception) + # test with multiple possible codes to catch + thrown_exception = self._make_botocore_exception() + codes_to_catch = ['NotAccessDenied', 'AccessDenied'] + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_code(thrown_exception, codes_to_catch) + assert context.value == thrown_exception + + ### + # Test that is_boto3_error_code returns what we expect when explicitly passed an exception + ### + + def test_is_boto3_error_code_single__pass__client(self): + passed_exception = self._make_denied_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + def test_is_boto3_error_code_single__pass__unexpected(self): + passed_exception = self._make_unexpected_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_single__pass__botocore(self): + passed_exception = self._make_botocore_exception() + returned_exception = is_boto3_error_code('AccessDenied', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__client(self): + passed_exception = self._make_denied_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + returned_exception = is_boto3_error_code(['AccessDenied', 'NotAccessDenied'], e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__unexpected(self): + passed_exception = self._make_unexpected_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_code_multiple__pass__botocore(self): + passed_exception = self._make_botocore_exception() + returned_exception = is_boto3_error_code(['NotAccessDenied', 'AccessDenied'], e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_message.py b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_message.py new file mode 100644 index 000000000..cd40a58dd --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_is_boto3_error_message.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_message +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_is_boto3_error_message.py requires the python modules 'boto3' and 'botocore'") + + +class TestIsBoto3ErrorMessaged(): + + def _make_denied_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "User: arn:aws:iam::123456789012:user/ExampleUser " + + "is not authorized to perform: iam:GetUser on resource: user ExampleUser" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'getUser') + + def _make_unexpected_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "SomeThingWentWrong", + "Message": "Boom!" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_encoded_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "You are not authorized to perform this operation. Encoded authorization failure message: " + + "fEwXX6llx3cClm9J4pURgz1XPnJPrYexEbrJcLhFkwygMdOgx_-aEsj0LqRM6Kxt2HVI6prUhDwbJqBo9U2V7iRKZ" + + "T6ZdJvHH02cXmD0Jwl5vrTsf0PhBcWYlH5wl2qME7xTfdolEUr4CzumCiti7ETiO-RDdHqWlasBOW5bWsZ4GSpPdU" + + "06YAX0TfwVBs48uU5RpCHfz1uhSzez-3elbtp9CmTOHLt5pzJodiovccO55BQKYLPtmJcs6S9YLEEogmpI4Cb1D26" + + "fYahDh51jEmaohPnW5pb1nQe2yPEtuIhtRzNjhFCOOMwY5DBzNsymK-Gj6eJLm7FSGHee4AHLU_XmZMe_6bcLAiOx" + + "6Zdl65Kdd0hLcpwVxyZMi27HnYjAdqRlV3wuCW2PkhAW14qZQLfiuHZDEwnPe2PBGSlFcCmkQvJvX-YLoA7Uyc2wf" + + "NX5RJm38STwfiJSkQaNDhHKTWKiLOsgY4Gze6uZoG7zOcFXFRyaA4cbMmI76uyBO7j-9uQUCtBYqYto8x_9CUJcxI" + + "VC5SPG_C1mk-WoDMew01f0qy-bNaCgmJ9TOQGd08FyuT1SaMpCC0gX6mHuOnEgkFw3veBIowMpp9XcM-yc42fmIOp" + + "FOdvQO6uE9p55Qc-uXvsDTTvT3A7EeFU8a_YoAIt9UgNYM6VTvoprLz7dBI_P6C-bdPPZCY2amm-dJNVZelT6TbJB" + + "H_Vxh0fzeiSUBersy_QzB0moc-vPWgnB-IkgnYLV-4L3K0L2" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_botocore_exception(self): + return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') + + def _do_try_message(self, exception, messages): + try: + raise exception + except is_boto3_error_message(messages) as e: + return e + + ### + # Test that is_boto3_error_message does what's expected when used in a try/except block + # (where we don't explicitly pass an exception to the function) + ### + + def test_is_boto3_error_message_single__raise__client(self): + # error with 'is not authorized to perform' in the message, should be caught in our try/except in _do_try_code + thrown_exception = self._make_denied_exception() + messages_to_catch = 'is not authorized to perform' + + caught_exception = self._do_try_message(thrown_exception, messages_to_catch) + + assert caught_exception == thrown_exception + + def test_is_boto3_error_message_single__raise__unexpected(self): + # error with 'Boom!' as the message, shouldn't match and should fall through + thrown_exception = self._make_unexpected_exception() + messages_to_catch = 'is not authorized to perform' + + with pytest.raises(botocore.exceptions.ClientError) as context: + self._do_try_message(thrown_exception, messages_to_catch) + + assert context.value == thrown_exception + + def test_is_boto3_error_message_single__raise__botocore(self): + # Test that we don't catch BotoCoreError + thrown_exception = self._make_botocore_exception() + messages_to_catch = 'is not authorized to perform' + + with pytest.raises(botocore.exceptions.BotoCoreError) as context: + self._do_try_message(thrown_exception, messages_to_catch) + + assert context.value == thrown_exception + + ### + # Test that is_boto3_error_message returns what we expect when explicitly passed an exception + ### + + def test_is_boto3_error_message_single__pass__client(self): + passed_exception = self._make_denied_exception() + returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) + assert isinstance(passed_exception, returned_exception) + assert issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ != "NeverEverRaisedException" + + def test_is_boto3_error_message_single__pass__unexpected(self): + passed_exception = self._make_unexpected_exception() + returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" + + def test_is_boto3_error_message_single__pass__botocore(self): + passed_exception = self._make_botocore_exception() + returned_exception = is_boto3_error_message('is not authorized to perform', e=passed_exception) + assert not isinstance(passed_exception, returned_exception) + assert not issubclass(returned_exception, botocore.exceptions.ClientError) + assert not issubclass(returned_exception, botocore.exceptions.BotoCoreError) + assert issubclass(returned_exception, Exception) + assert returned_exception.__name__ == "NeverEverRaisedException" diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_normalize_boto3_result.py b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_normalize_boto3_result.py new file mode 100644 index 000000000..71da9d66d --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/botocore/test_normalize_boto3_result.py @@ -0,0 +1,59 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import normalize_boto3_result + +example_date_txt = '2020-12-30T00:00:00.000Z' +example_date_iso = '2020-12-30T00:00:00+00:00' + +try: + from dateutil import parser as date_parser + example_date = date_parser.parse(example_date_txt) +except ImportError: + example_date = None + pytestmark = pytest.mark.skip("test_normalize_boto3_result.py requires the python module dateutil (python-dateutil)") + + +normalize_boto3_result_data = [ + (dict(), + dict() + ), + # Bool + (dict(param1=False), + dict(param1=False) + ), + # Simple string (shouldn't be touched + (dict(date_example=example_date_txt), + dict(date_example=example_date_txt) + ), + (dict(date_example=example_date_iso), + dict(date_example=example_date_iso) + ), + # Datetime -> String + (dict(date_example=example_date), + dict(date_example=example_date_iso) + ), + (list(), + list() + ), + (list([False]), + list([False]) + ), + (list([example_date_txt]), + list([example_date_txt]) + ), + (list([example_date_iso]), + list([example_date_iso]) + ), + (list([example_date]), + list([example_date_iso]) + ), +] + + +@pytest.mark.parametrize("input_params, output_params", normalize_boto3_result_data) +def test_normalize_boto3_result(input_params, output_params): + + assert normalize_boto3_result(input_params) == output_params diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_backoff_iterator.py b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_backoff_iterator.py new file mode 100644 index 000000000..5fee115c2 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_backoff_iterator.py @@ -0,0 +1,45 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.cloud import BackoffIterator + + +def test_backoff_value_generator(): + max_delay = 60 + initial = 3 + backoff = 2 + + min_sleep = initial + counter = 0 + for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay): + if counter > 4: + assert sleep == max_delay + else: + assert sleep == min_sleep + min_sleep *= backoff + counter += 1 + if counter == 10: + break + + +def test_backoff_value_generator_with_jitter(): + max_delay = 60 + initial = 3 + backoff = 2 + + min_sleep = initial + counter = 0 + for sleep in BackoffIterator(delay=initial, backoff=backoff, max_delay=max_delay, jitter=True): + if counter > 4: + assert sleep <= max_delay + else: + assert sleep <= min_sleep + min_sleep *= backoff + counter += 1 + if counter == 10: + break diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_cloud_retry.py b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_cloud_retry.py new file mode 100644 index 000000000..ce5f03f11 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_cloud_retry.py @@ -0,0 +1,236 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import random +from datetime import datetime +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.cloud import CloudRetry + + +class TestCloudRetry(): + + error_codes = [400, 500, 600] + custom_error_codes = [100, 200, 300] + + class OurTestException(Exception): + """ + custom exception class for testing + """ + def __init__(self, status): + self.status = status + + def __str__(self): + return "TestException with status: {0}".format(self.status) + + class UnitTestsRetry(CloudRetry): + base_class = Exception + + @staticmethod + def status_code_from_exception(error): + return getattr(error, "status") if hasattr(error, "status") else None + + class CustomRetry(CloudRetry): + base_class = Exception + + @staticmethod + def status_code_from_exception(error): + return error.status['response']['status'] + + @staticmethod + def found(response_code, catch_extra_error_codes=None): + if catch_extra_error_codes: + return response_code in catch_extra_error_codes + TestCloudRetry.custom_error_codes + else: + return response_code in TestCloudRetry.custom_error_codes + + class KeyRetry(CloudRetry): + base_class = KeyError + + @staticmethod + def status_code_from_exception(error): + return True + + @staticmethod + def found(response_code, catch_extra_error_codes=None): + return True + + class KeyAndIndexRetry(CloudRetry): + base_class = (KeyError, IndexError) + + @staticmethod + def status_code_from_exception(error): + return True + + @staticmethod + def found(response_code, catch_extra_error_codes=None): + return True + + # ======================================================== + # retry original backoff + # ======================================================== + def test_retry_backoff(self): + + @TestCloudRetry.UnitTestsRetry.backoff(tries=3, delay=1, backoff=1.1, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter < 2: + test_retry_func.counter += 1 + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + else: + return True + + test_retry_func.counter = 0 + ret = test_retry_func() + assert ret is True + + # ======================================================== + # retry exponential backoff + # ======================================================== + def test_retry_exponential_backoff(self): + + @TestCloudRetry.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter < 2: + test_retry_func.counter += 1 + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + else: + return True + + test_retry_func.counter = 0 + ret = test_retry_func() + assert ret is True + + def test_retry_exponential_backoff_with_unexpected_exception(self): + unexpected_except = self.OurTestException(status=100) + + @TestCloudRetry.UnitTestsRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter == 0: + test_retry_func.counter += 1 + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + else: + raise unexpected_except + + test_retry_func.counter = 0 + with pytest.raises(self.OurTestException) as context: + test_retry_func() + + assert context.value.status == unexpected_except.status + + # ======================================================== + # retry jittered backoff + # ======================================================== + def test_retry_jitter_backoff(self): + @TestCloudRetry.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter < 2: + test_retry_func.counter += 1 + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + else: + return True + + test_retry_func.counter = 0 + ret = test_retry_func() + assert ret is True + + def test_retry_jittered_backoff_with_unexpected_exception(self): + unexpected_except = self.OurTestException(status=100) + + @TestCloudRetry.UnitTestsRetry.jittered_backoff(retries=3, delay=1, max_delay=3, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter == 0: + test_retry_func.counter += 1 + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + else: + raise unexpected_except + + test_retry_func.counter = 0 + with pytest.raises(self.OurTestException) as context: + test_retry_func() + + assert context.value.status == unexpected_except.status + + # ======================================================== + # retry with custom class + # ======================================================== + def test_retry_exponential_backoff_custom_class(self): + def build_response(): + return dict(response=dict(status=random.choice(TestCloudRetry.custom_error_codes))) + + @self.CustomRetry.exponential_backoff(retries=3, delay=1, backoff=1.1, max_delay=3, + catch_extra_error_codes=TestCloudRetry.error_codes) + def test_retry_func(): + if test_retry_func.counter < 2: + test_retry_func.counter += 1 + raise self.OurTestException(build_response()) + else: + return True + + test_retry_func.counter = 0 + + ret = test_retry_func() + assert ret is True + + # ============================================================= + # Test wrapped function multiple times will restart the sleep + # ============================================================= + def test_wrapped_function_called_several_times(self): + @TestCloudRetry.UnitTestsRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100, + catch_extra_error_codes=TestCloudRetry.error_codes) + def _fail(): + raise self.OurTestException(status=random.choice(TestCloudRetry.error_codes)) + + # run the method 3 times and assert that each it is retrying after 2secs + # the elapsed execution time should be closed to 2sec + for _i in range(3): + start = datetime.now() + with pytest.raises(self.OurTestException): + _fail() + duration = (datetime.now() - start).seconds + assert duration == 2 + + def test_only_base_exception(self): + def _fail_index(): + my_list = list() + return my_list[5] + + def _fail_key(): + my_dict = dict() + return my_dict['invalid_key'] + + def _fail_exception(): + raise Exception('bang') + + key_retry_decorator = TestCloudRetry.KeyRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) + key_and_index_retry_decorator = TestCloudRetry.KeyAndIndexRetry.exponential_backoff(retries=2, delay=2, backoff=4, max_delay=100) + + expectations = [ + [key_retry_decorator, _fail_exception, 0, Exception], + [key_retry_decorator, _fail_index, 0, IndexError], + [key_retry_decorator, _fail_key, 2, KeyError], + [key_and_index_retry_decorator, _fail_exception, 0, Exception], + [key_and_index_retry_decorator, _fail_index, 2, IndexError], + [key_and_index_retry_decorator, _fail_key, 2, KeyError], + ] + + for expectation in expectations: + decorator = expectation[0] + function = expectation[1] + duration = expectation[2] + exception = expectation[3] + + start = datetime.now() + with pytest.raises(exception): + decorator(function)() + _duration = (datetime.now() - start).seconds + assert duration == _duration diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_decorator_generation.py b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_decorator_generation.py new file mode 100644 index 000000000..23b446763 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_decorator_generation.py @@ -0,0 +1,156 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import sys + +from ansible_collections.amazon.aws.plugins.module_utils.cloud import CloudRetry +from ansible_collections.amazon.aws.plugins.module_utils.cloud import BackoffIterator +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel + +if sys.version_info < (3, 8): + pytest.skip("accessing call_args.kwargs by keyword (instead of index) was introduced in Python 3.8", allow_module_level=True) + + +@pytest.fixture +def patch_cloud_retry(monkeypatch): + """ + replaces CloudRetry.base_decorator with a MagicMock so that we can exercise the generation of + the various "public" decorators. We can then check that base_decorator was called as expected. + Note: this doesn't test the operation of CloudRetry.base_decorator itself, but does make sure + we can fully exercise the various wrapper functions built over the top of it. + """ + def perform_patch(): + decorator_generator = MagicMock() + decorator_generator.return_value = sentinel.decorator + monkeypatch.setattr(CloudRetry, 'base_decorator', decorator_generator) + return CloudRetry, decorator_generator + + return perform_patch + + +def check_common_side_effects(decorator_generator): + """ + By invoking CloudRetry.(exponential_backoff|jittered_backoff|backoff) we expect certain things + to have happend, specifically CloudRetry.base_decorator should have been called *once* with a + number of keyword arguments. + "found" should be CloudRetry.found + "status_code_from_exception" should be CloudRetry.status_code_from_exception (this is replaced when the abstract class is realised) + "sleep_time_generator" should be an instance of CloudRetry.BackoffIterator + """ + + assert decorator_generator.called is True + assert decorator_generator.call_count == 1 + + gen_kw_args = decorator_generator.call_args.kwargs + assert gen_kw_args['found'] is CloudRetry.found + assert gen_kw_args['status_code_from_exception'] is CloudRetry.status_code_from_exception + + sleep_time_generator = gen_kw_args['sleep_time_generator'] + assert isinstance(sleep_time_generator, BackoffIterator) + + # Return the KW args used when CloudRetry.base_decorator was called and the sleep_time_generator + # passed, these are what should change between the different decorators + return gen_kw_args, sleep_time_generator + + +def test_create_exponential_backoff_with_defaults(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + decorator = cloud_retry.exponential_backoff() + + assert decorator is sentinel.decorator + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['retries'] == 10 + assert gen_kw_args['catch_extra_error_codes'] is None + assert sleep_time_generator.delay == 3 + assert sleep_time_generator.backoff == 2 + assert sleep_time_generator.max_delay == 60 + assert sleep_time_generator.jitter is False + + +def test_create_exponential_backoff_with_args(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + decorator = cloud_retry.exponential_backoff(retries=11, delay=4, backoff=3, max_delay=61, catch_extra_error_codes=[42]) + assert decorator is sentinel.decorator + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['catch_extra_error_codes'] == [42] + assert gen_kw_args['retries'] == 11 + assert sleep_time_generator.delay == 4 + assert sleep_time_generator.backoff == 3 + assert sleep_time_generator.max_delay == 61 + assert sleep_time_generator.jitter is False + + +def test_create_jittered_backoff_with_defaults(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + decorator = cloud_retry.jittered_backoff() + assert decorator is sentinel.decorator + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['catch_extra_error_codes'] is None + assert gen_kw_args['retries'] == 10 + assert sleep_time_generator.delay == 3 + assert sleep_time_generator.backoff == 2 + assert sleep_time_generator.max_delay == 60 + assert sleep_time_generator.jitter is True + + +def test_create_jittered_backoff_with_args(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + decorator = cloud_retry.jittered_backoff(retries=11, delay=4, backoff=3, max_delay=61, catch_extra_error_codes=[42]) + assert decorator is sentinel.decorator + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['catch_extra_error_codes'] == [42] + assert gen_kw_args['retries'] == 11 + assert sleep_time_generator.delay == 4 + assert sleep_time_generator.backoff == 3 + assert sleep_time_generator.max_delay == 61 + assert sleep_time_generator.jitter is True + + +def test_create_legacy_backoff_with_defaults(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + decorator = cloud_retry.backoff() + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['catch_extra_error_codes'] is None + assert gen_kw_args['retries'] == 10 + assert sleep_time_generator.delay == 3 + assert sleep_time_generator.backoff == 1.1 + assert sleep_time_generator.max_delay is None + assert sleep_time_generator.jitter is False + + +def test_create_legacy_backoff_with_args(patch_cloud_retry): + cloud_retry, decorator_generator = patch_cloud_retry() + + # Note: the Keyword Args have different names here, and not all of them can be passed... + decorator = cloud_retry.backoff(tries=11, delay=4, backoff=3, catch_extra_error_codes=[42]) + + gen_kw_args, sleep_time_generator = check_common_side_effects(decorator_generator) + + assert gen_kw_args['catch_extra_error_codes'] == [42] + assert gen_kw_args['retries'] == 11 + assert sleep_time_generator.delay == 4 + assert sleep_time_generator.backoff == 3 + assert sleep_time_generator.max_delay is None + assert sleep_time_generator.jitter is False diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retries_found.py b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retries_found.py new file mode 100644 index 000000000..21ad74d42 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retries_found.py @@ -0,0 +1,34 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.cloud import CloudRetry + + +def test_found_not_itterable(): + assert CloudRetry.found('404', 5) is False + assert CloudRetry.found('404', None) is False + assert CloudRetry.found('404', 404) is False + # This seems counter intuitive, but the second argument is supposed to be iterable... + assert CloudRetry.found(404, 404) is False + + +def test_found_no_match(): + assert CloudRetry.found('404', ['403']) is False + assert CloudRetry.found('404', ['500', '403']) is False + assert CloudRetry.found('404', {'403'}) is False + assert CloudRetry.found('404', {'500', '403'}) is False + + +def test_found_match(): + assert CloudRetry.found('404', ['404']) is True + assert CloudRetry.found('404', ['403', '404']) is True + assert CloudRetry.found('404', ['404', '403']) is True + assert CloudRetry.found('404', {'404'}) is True + assert CloudRetry.found('404', {'403', '404'}) is True + # Beware, this will generally only work with strings (they're iterable) + assert CloudRetry.found('404', '404') is True diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retry_func.py b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retry_func.py new file mode 100644 index 000000000..609c0718b --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/cloud/test_retry_func.py @@ -0,0 +1,129 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import sys + +import ansible_collections.amazon.aws.plugins.module_utils.cloud as cloud_utils +from ansible_collections.amazon.aws.tests.unit.compat.mock import Mock +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel + +if sys.version_info < (3, 8): + pytest.skip("accessing call_args.kwargs by keyword (instead of index) was introduced in Python 3.8", allow_module_level=True) + + +class ExceptionA(Exception): + def __init__(self): + pass + + +class ExceptionB(Exception): + def __init__(self): + pass + + +@pytest.fixture +def retrier(): + def do_retry( + func=None, + sleep_generator=None, + retries=4, + catch_extra_error_codes=None, + found_f=None, + extract_code=None, + base_class=None, + ): + if not func: + func = Mock(return_value=sentinel.successful_run) + if not sleep_generator: + sleep_generator = cloud_utils.BackoffIterator(0, 0) + if not found_f: + found_f = Mock(return_value=False) + if not extract_code: + extract_code = Mock(return_value=sentinel.extracted_code) + if not base_class: + base_class = ExceptionA + + result = cloud_utils._retry_func( + func, + sleep_generator, + retries, + catch_extra_error_codes, + found_f, + extract_code, + base_class, + ) + return func, result + + return do_retry + + +def test_success(retrier): + func, result = retrier() + assert result is sentinel.successful_run + assert func.called is True + assert func.call_count == 1 + + +def test_not_base(retrier): + func = Mock(side_effect=ExceptionB) + with pytest.raises(ExceptionB): + _f, _result = retrier(func=func) + assert func.called is True + assert func.call_count == 1 + + +def test_no_match(retrier): + found_f = Mock(return_value=False) + func = Mock(side_effect=ExceptionA) + + with pytest.raises(ExceptionA): + _f, _result = retrier(func=func, found_f=found_f) + assert func.called is True + assert func.call_count == 1 + assert found_f.called is True + assert found_f.call_count == 1 + assert found_f.call_args.args[0] is sentinel.extracted_code + assert found_f.call_args.args[1] is None + + +def test_no_match_with_extra_error_codes(retrier): + found_f = Mock(return_value=False) + func = Mock(side_effect=ExceptionA) + catch_extra_error_codes = sentinel.extra_codes + + with pytest.raises(ExceptionA): + _f, _result = retrier( + func=func, found_f=found_f, catch_extra_error_codes=catch_extra_error_codes + ) + assert func.called is True + assert func.call_count == 1 + assert found_f.called is True + assert found_f.call_count == 1 + assert found_f.call_args.args[0] is sentinel.extracted_code + assert found_f.call_args.args[1] is sentinel.extra_codes + + +def test_simple_retries_4_times(retrier): + found_f = Mock(return_value=True) + func = Mock(side_effect=ExceptionA) + + with pytest.raises(ExceptionA): + _f, _result = retrier(func=func, found_f=found_f) + assert func.called is True + assert func.call_count == 4 + + +def test_simple_retries_2_times(retrier): + found_f = Mock(return_value=True) + func = Mock(side_effect=ExceptionA) + + with pytest.raises(ExceptionA): + _f, _result = retrier(func=func, found_f=found_f, retries=2) + assert func.called is True + assert func.call_count == 2 diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/conftest.py b/ansible_collections/amazon/aws/tests/unit/module_utils/conftest.py new file mode 100644 index 000000000..f90055615 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/conftest.py @@ -0,0 +1,81 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import sys +from io import BytesIO +import warnings + +import pytest + +import ansible.module_utils.basic +import ansible.module_utils.common +from ansible.module_utils.six import PY3, string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def stdin(mocker, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ['ansible_unittest'] + + for var in ["_global_warnings", "_global_deprecations"]: + if hasattr(ansible.module_utils.common.warnings, var): + setattr(ansible.module_utils.common.warnings, var, []) + else: + # No need to reset the value + warnings.warn("deprecated") + + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the stdin pytest fixture') + + fake_stdin = BytesIO(to_bytes(args, errors='surrogate_or_strict')) + if PY3: + mocker.patch('ansible.module_utils.basic.sys.stdin', mocker.MagicMock()) + mocker.patch('ansible.module_utils.basic.sys.stdin.buffer', fake_stdin) + else: + mocker.patch('ansible.module_utils.basic.sys.stdin', fake_stdin) + + yield fake_stdin + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv + + +@pytest.fixture +def am(stdin, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ['ansible_unittest'] + + argspec = {} + if hasattr(request, 'param'): + if isinstance(request.param, dict): + argspec = request.param + + am = ansible.module_utils.basic.AnsibleModule( + argument_spec=argspec, + ) + am._name = 'ansible_unittest' + + yield am + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/elbv2/test_prune.py b/ansible_collections/amazon/aws/tests/unit/module_utils/elbv2/test_prune.py new file mode 100644 index 000000000..3a02b9e2e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/elbv2/test_prune.py @@ -0,0 +1,188 @@ +# +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils import elbv2 + +example_arn = 'arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/nlb-123456789abc/abcdef0123456789' +example_arn2 = 'arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/nlb-0123456789ab/0123456789abcdef' + +one_action = [ + dict( + ForwardConfig=dict( + TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[ + dict(TargetGroupArn=example_arn, Weight=1), + ] + ), + TargetGroupArn=example_arn, Type='forward', + ) +] + +one_action_two_tg = [ + dict( + ForwardConfig=dict( + TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[ + dict(TargetGroupArn=example_arn, Weight=1), + dict(TargetGroupArn=example_arn2, Weight=1), + ] + ), + TargetGroupArn=example_arn, Type='forward', + ) +] + +simplified_action = dict(Type='forward', TargetGroupArn=example_arn) +# Examples of various minimalistic actions which are all the same +simple_actions = [ + dict(Type='forward', TargetGroupArn=example_arn), + + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn)])), + dict(Type='forward', ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn)])), + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn, Weight=1)])), + dict(Type='forward', ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn, Weight=1)])), + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn, Weight=42)])), + dict(Type='forward', ForwardConfig=dict(TargetGroups=[dict(TargetGroupArn=example_arn, Weight=42)])), + + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[dict(TargetGroupArn=example_arn)])), + dict(Type='forward', ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), TargetGroups=[dict(TargetGroupArn=example_arn)])), + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[dict(TargetGroupArn=example_arn, Weight=1)])), + dict(Type='forward', ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), TargetGroups=[dict(TargetGroupArn=example_arn, Weight=1)])), + dict(Type='forward', TargetGroupArn=example_arn, ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[dict(TargetGroupArn=example_arn, Weight=42)])), + dict(Type='forward', ForwardConfig=dict(TargetGroupStickinessConfig=dict(Enabled=False), TargetGroups=[dict(TargetGroupArn=example_arn, Weight=42)])), +] + +# Test that _prune_ForwardConfig() doesn't mangle things we don't expect +complex_actions = [ + # Non-Forwarding + dict( + Type='authenticate-oidc', TargetGroupArn=example_arn, + AuthenticateOidcConfig=dict( + Issuer='https://idp.ansible.test/oidc-config', + AuthorizationEndpoint='https://idp.ansible.test/authz', + TokenEndpoint='https://idp.ansible.test/token', + UserInfoEndpoint='https://idp.ansible.test/user', + ClientId='ExampleClient', + UseExistingClientSecret=False, + ), + ), + dict( + Type='redirect', + RedirectConfig=dict(Protocol='HTTPS', Port=443, Host='redirect.ansible.test', Path='/', StatusCode='HTTP_302'), + ), + # Multiple TGs + dict( + TargetGroupArn=example_arn, Type='forward', + ForwardConfig=dict( + TargetGroupStickinessConfig=dict(Enabled=False), + TargetGroups=[ + dict(TargetGroupArn=example_arn, Weight=1), + dict(TargetGroupArn=example_arn2, Weight=1), + ] + ), + ), + # Sticky-Sessions + dict( + Type='forward', TargetGroupArn=example_arn, + ForwardConfig=dict( + TargetGroupStickinessConfig=dict(Enabled=True, DurationSeconds=3600), + TargetGroups=[dict(TargetGroupArn=example_arn)] + ) + ), +] + +simplified_oidc_action = dict( + Type='authenticate-oidc', TargetGroupArn=example_arn, + AuthenticateOidcConfig=dict( + Issuer='https://idp.ansible.test/oidc-config', + AuthorizationEndpoint='https://idp.ansible.test/authz', + TokenEndpoint='https://idp.ansible.test/token', + UserInfoEndpoint='https://idp.ansible.test/user', + ClientId='ExampleClient', + Scope='openid', + SessionTimeout=604800, + UseExistingClientSecret=True, + ), +) +oidc_actions = [ + dict( + Type='authenticate-oidc', TargetGroupArn=example_arn, + AuthenticateOidcConfig=dict( + Issuer='https://idp.ansible.test/oidc-config', + AuthorizationEndpoint='https://idp.ansible.test/authz', + TokenEndpoint='https://idp.ansible.test/token', + UserInfoEndpoint='https://idp.ansible.test/user', + ClientId='ExampleClient', + UseExistingClientSecret=True, + Scope='openid', + SessionTimeout=604800 + ), + ), + dict( + Type='authenticate-oidc', TargetGroupArn=example_arn, + AuthenticateOidcConfig=dict( + Issuer='https://idp.ansible.test/oidc-config', + AuthorizationEndpoint='https://idp.ansible.test/authz', + TokenEndpoint='https://idp.ansible.test/token', + UserInfoEndpoint='https://idp.ansible.test/user', + ClientId='ExampleClient', + ClientSecret='MyVerySecretString', + UseExistingClientSecret=True, + ), + ), +] + + +#### + + +# Original tests +def test__prune_secret(): + assert elbv2._prune_secret(one_action[0]) == one_action[0] + + +def test__prune_ForwardConfig(): + expectation = {"TargetGroupArn": example_arn, "Type": "forward"} + pruned_config = elbv2._prune_ForwardConfig(one_action[0]) + assert pruned_config == expectation + + # https://github.com/ansible-collections/community.aws/issues/1089 + pruned_config = elbv2._prune_ForwardConfig(one_action_two_tg[0]) + assert pruned_config == one_action_two_tg[0] + + +#### + + +@pytest.mark.parametrize("action", simple_actions) +def test__prune_ForwardConfig_simplifiable_actions(action): + pruned_config = elbv2._prune_ForwardConfig(action) + assert pruned_config == simplified_action + + +@pytest.mark.parametrize("action", complex_actions) +def test__prune_ForwardConfig_non_simplifiable_actions(action): + pruned_config = elbv2._prune_ForwardConfig(action) + assert pruned_config == action + + +@pytest.mark.parametrize("action", oidc_actions) +def test__prune_secret_simplifiable_actions(action): + pruned_config = elbv2._prune_secret(action) + assert pruned_config == simplified_oidc_action + + +@pytest.mark.parametrize("action", complex_actions) +def test__prune_secret_non_simplifiable_actions(action): + pruned_config = elbv2._prune_secret(action) + assert pruned_config == action diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py new file mode 100644 index 000000000..51e64490f --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_fail_json_aws.py @@ -0,0 +1,330 @@ +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest + +try: + import botocore + import boto3 +except ImportError: + pass + +from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_fail_json_aws.py requires the python modules 'boto3' and 'botocore'") + + +class TestFailJsonAwsTestSuite(object): + # ======================================================== + # Prepare some data for use in our testing + # ======================================================== + def setup_method(self): + # Basic information that ClientError needs to spawn off an error + self.EXAMPLE_EXCEPTION_DATA = { + "Error": { + "Code": "InvalidParameterValue", + "Message": "The filter 'exampleFilter' is invalid" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef", + "HTTPStatusCode": 400, + "HTTPHeaders": { + "transfer-encoding": "chunked", + "date": "Fri, 13 Nov 2020 00:00:00 GMT", + "connection": "close", + "server": "AmazonEC2" + }, + "RetryAttempts": 0 + } + } + self.CAMEL_RESPONSE = camel_dict_to_snake_dict(self.EXAMPLE_EXCEPTION_DATA.get("ResponseMetadata")) + self.CAMEL_ERROR = camel_dict_to_snake_dict(self.EXAMPLE_EXCEPTION_DATA.get("Error")) + # ClientError(EXAMPLE_EXCEPTION_DATA, "testCall") will generate this + self.EXAMPLE_MSG = "An error occurred (InvalidParameterValue) when calling the testCall operation: The filter 'exampleFilter' is invalid" + self.DEFAULT_CORE_MSG = "An unspecified error occurred" + self.FAIL_MSG = "I Failed!" + + # ======================================================== + # Passing fail_json_aws nothing more than a ClientError + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_client_minimal(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.ClientError(self.EXAMPLE_EXCEPTION_DATA, "testCall") + except botocore.exceptions.ClientError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.EXAMPLE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert return_val.get("response_metadata") == self.CAMEL_RESPONSE + assert return_val.get("error") == self.CAMEL_ERROR + + # ======================================================== + # Passing fail_json_aws a ClientError and a message + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_client_msg(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.ClientError(self.EXAMPLE_EXCEPTION_DATA, "testCall") + except botocore.exceptions.ClientError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, msg=self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert return_val.get("response_metadata") == self.CAMEL_RESPONSE + assert return_val.get("error") == self.CAMEL_ERROR + + # ======================================================== + # Passing fail_json_aws a ClientError and a message as a positional argument + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_client_positional_msg(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.ClientError(self.EXAMPLE_EXCEPTION_DATA, "testCall") + except botocore.exceptions.ClientError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert return_val.get("response_metadata") == self.CAMEL_RESPONSE + assert return_val.get("error") == self.CAMEL_ERROR + + # ======================================================== + # Passing fail_json_aws a ClientError and an arbitrary key + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_client_key(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.ClientError(self.EXAMPLE_EXCEPTION_DATA, "testCall") + except botocore.exceptions.ClientError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, extra_key="Some Value") + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.EXAMPLE_MSG + assert return_val.get("extra_key") == "Some Value" + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert return_val.get("response_metadata") == self.CAMEL_RESPONSE + assert return_val.get("error") == self.CAMEL_ERROR + + # ======================================================== + # Passing fail_json_aws a ClientError, and arbitraty key and a message + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_client_msg_and_key(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.ClientError(self.EXAMPLE_EXCEPTION_DATA, "testCall") + except botocore.exceptions.ClientError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, extra_key="Some Value", msg=self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.EXAMPLE_MSG + assert return_val.get("extra_key") == "Some Value" + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert return_val.get("response_metadata") == self.CAMEL_RESPONSE + assert return_val.get("error") == self.CAMEL_ERROR + + # ======================================================== + # Passing fail_json_aws nothing more than a BotoCoreError + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_botocore_minimal(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.BotoCoreError() + except botocore.exceptions.BotoCoreError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.DEFAULT_CORE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert "response_metadata" not in return_val + assert "error" not in return_val + + # ======================================================== + # Passing fail_json_aws BotoCoreError and a message + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_botocore_msg(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.BotoCoreError() + except botocore.exceptions.BotoCoreError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, msg=self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert "response_metadata" not in return_val + assert "error" not in return_val + + # ======================================================== + # Passing fail_json_aws BotoCoreError and a message as a positional + # argument + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_botocore_positional_msg(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.BotoCoreError() + except botocore.exceptions.BotoCoreError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert "response_metadata" not in return_val + assert "error" not in return_val + + # ======================================================== + # Passing fail_json_aws a BotoCoreError and an arbitrary key + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_botocore_key(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.BotoCoreError() + except botocore.exceptions.BotoCoreError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, extra_key="Some Value") + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.DEFAULT_CORE_MSG + assert return_val.get("extra_key") == "Some Value" + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert "response_metadata" not in return_val + assert "error" not in return_val + + # ======================================================== + # Passing fail_json_aws BotoCoreError, an arbitry key and a message + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_fail_botocore_msg_and_key(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", "1.2.3") + monkeypatch.setattr(boto3, "__version__", "1.2.4") + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + try: + raise botocore.exceptions.BotoCoreError() + except botocore.exceptions.BotoCoreError as e: + with pytest.raises(SystemExit) as ctx: + module.fail_json_aws(e, extra_key="Some Value", msg=self.FAIL_MSG) + assert ctx.value.code == 1 + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("msg") == self.FAIL_MSG + ": " + self.DEFAULT_CORE_MSG + assert return_val.get("extra_key") == "Some Value" + assert return_val.get("boto3_version") == "1.2.4" + assert return_val.get("botocore_version") == "1.2.3" + assert return_val.get("exception") is not None + assert return_val.get("failed") + assert "response_metadata" not in return_val + assert "error" not in return_val diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py new file mode 100644 index 000000000..17e69ecb5 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_minimal_versions.py @@ -0,0 +1,191 @@ +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from pprint import pprint +import pytest +import json +import warnings + +try: + import botocore + import boto3 +except ImportError: + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_minimal_versions.py requires the python modules 'boto3' and 'botocore'") + + +class TestMinimalVersionTestSuite(object): + # ======================================================== + # Prepare some data for use in our testing + # ======================================================== + def setup_method(self): + self.MINIMAL_BOTO3 = '1.18.0' + self.MINIMAL_BOTOCORE = '1.21.0' + self.OLD_BOTO3 = '1.17.999' + self.OLD_BOTOCORE = '1.20.999' + + # ======================================================== + # Test we don't warn when using valid versions + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_no_warn(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", self.MINIMAL_BOTOCORE) + monkeypatch.setattr(boto3, "__version__", self.MINIMAL_BOTO3) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + assert return_val.get("failed") is None + assert return_val.get("error") is None + assert return_val.get("warnings") is None + + # ======================================================== + # Test we don't warn when botocore/boto3 isn't required + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_no_check(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", self.OLD_BOTOCORE) + monkeypatch.setattr(boto3, "__version__", self.OLD_BOTO3) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict(), check_boto3=False) + + with pytest.raises(SystemExit): + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + assert return_val.get("failed") is None + assert return_val.get("error") is None + assert return_val.get("warnings") is None + + # ======================================================== + # Test we warn when using an old version of boto3 + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_warn_boto3(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", self.MINIMAL_BOTOCORE) + monkeypatch.setattr(boto3, "__version__", self.OLD_BOTO3) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.exit_json() + + out, err = capfd.readouterr() + return_val = json.loads(out) + + pprint(out) + pprint(err) + pprint(return_val) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + assert return_val.get("failed") is None + assert return_val.get("error") is None + assert return_val.get("warnings") is not None + warnings = return_val.get("warnings") + assert len(warnings) == 1 + # Assert that we have a warning about the version but be + # relaxed about the exact message + assert 'boto3' in warnings[0] + assert self.MINIMAL_BOTO3 in warnings[0] + + # ======================================================== + # Test we warn when using an old version of botocore + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_warn_botocore(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", self.OLD_BOTOCORE) + monkeypatch.setattr(boto3, "__version__", self.MINIMAL_BOTO3) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.exit_json() + + out, err = capfd.readouterr() + return_val = json.loads(out) + + pprint(out) + pprint(err) + pprint(return_val) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + assert return_val.get("failed") is None + assert return_val.get("error") is None + assert return_val.get("warnings") is not None + warnings = return_val.get("warnings") + assert len(warnings) == 1 + # Assert that we have a warning about the version but be + # relaxed about the exact message + assert 'botocore' in warnings[0] + assert self.MINIMAL_BOTOCORE in warnings[0] + + # ======================================================== + # Test we warn when using an old version of botocore and boto3 + # ======================================================== + @pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) + def test_warn_boto3_and_botocore(self, monkeypatch, stdin, capfd): + monkeypatch.setattr(botocore, "__version__", self.OLD_BOTOCORE) + monkeypatch.setattr(boto3, "__version__", self.OLD_BOTO3) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.exit_json() + + out, err = capfd.readouterr() + return_val = json.loads(out) + + pprint(out) + pprint(err) + pprint(return_val) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + assert return_val.get("failed") is None + assert return_val.get("error") is None + assert return_val.get("warnings") is not None + + warnings = return_val.get("warnings") + assert len(warnings) == 2 + + warning_dict = dict() + for warning in warnings: + if 'boto3' in warning: + warning_dict['boto3'] = warning + if 'botocore' in warning: + warning_dict['botocore'] = warning + + # Assert that we have a warning about the version but be + # relaxed about the exact message + assert warning_dict.get('boto3') is not None + assert self.MINIMAL_BOTO3 in warning_dict.get('boto3') + assert warning_dict.get('botocore') is not None + assert self.MINIMAL_BOTOCORE in warning_dict.get('botocore') diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py new file mode 100644 index 000000000..adf2bf558 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/modules/ansible_aws_module/test_require_at_least.py @@ -0,0 +1,220 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json +import pytest + +try: + import botocore + import boto3 +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 +from ansible_collections.amazon.aws.plugins.module_utils.modules import AnsibleAWSModule + +DUMMY_VERSION = '5.5.5.5' + +TEST_VERSIONS = [ + ['1.1.1', '2.2.2', True], + ['1.1.1', '0.0.1', False], + ['9.9.9', '9.9.9', True], + ['9.9.9', '9.9.10', True], + ['9.9.9', '9.10.9', True], + ['9.9.9', '10.9.9', True], + ['9.9.9', '9.9.8', False], + ['9.9.9', '9.8.9', False], + ['9.9.9', '8.9.9', False], + ['10.10.10', '10.10.10', True], + ['10.10.10', '10.10.11', True], + ['10.10.10', '10.11.10', True], + ['10.10.10', '11.10.10', True], + ['10.10.10', '10.10.9', False], + ['10.10.10', '10.9.10', False], + ['10.10.10', '9.19.10', False], +] + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_require_at_least.py requires the python modules 'boto3' and 'botocore'") + + +class TestRequireAtLeastTestSuite(object): + # ======================================================== + # Prepare some data for use in our testing + # ======================================================== + def setup_method(self): + pass + + # ======================================================== + # Test botocore_at_least + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_botocore_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + monkeypatch.setattr(botocore, "__version__", compare_version) + # Set boto3 version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + assert at_least == module.botocore_at_least(desired_version) + + # ======================================================== + # Test boto3_at_least + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_boto3_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + # Set botocore version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION) + monkeypatch.setattr(boto3, "__version__", compare_version) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + assert at_least == module.boto3_at_least(desired_version) + + # ======================================================== + # Test require_botocore_at_least + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_require_botocore_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + monkeypatch.setattr(botocore, "__version__", compare_version) + # Set boto3 version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.require_botocore_at_least(desired_version) + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + if at_least: + assert return_val.get("failed") is None + else: + assert return_val.get("failed") + # The message is generated by Ansible, don't test for an exact + # message + assert desired_version in return_val.get("msg") + assert "botocore" in return_val.get("msg") + assert return_val.get("boto3_version") == DUMMY_VERSION + assert return_val.get("botocore_version") == compare_version + + # ======================================================== + # Test require_boto3_at_least + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_require_boto3_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION) + # Set boto3 version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(boto3, "__version__", compare_version) + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.require_boto3_at_least(desired_version) + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + if at_least: + assert return_val.get("failed") is None + else: + assert return_val.get("failed") + # The message is generated by Ansible, don't test for an exact + # message + assert desired_version in return_val.get("msg") + assert "boto3" in return_val.get("msg") + assert return_val.get("botocore_version") == DUMMY_VERSION + assert return_val.get("boto3_version") == compare_version + + # ======================================================== + # Test require_botocore_at_least with reason + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_require_botocore_at_least_with_reason(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + monkeypatch.setattr(botocore, "__version__", compare_version) + # Set boto3 version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION) + + reason = 'testing in progress' + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.require_botocore_at_least(desired_version, reason=reason) + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + if at_least: + assert return_val.get("failed") is None + else: + assert return_val.get("failed") + # The message is generated by Ansible, don't test for an exact + # message + assert desired_version in return_val.get("msg") + assert " {0}".format(reason) in return_val.get("msg") + assert "botocore" in return_val.get("msg") + assert return_val.get("boto3_version") == DUMMY_VERSION + assert return_val.get("botocore_version") == compare_version + + # ======================================================== + # Test require_boto3_at_least with reason + # ======================================================== + @pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"]) + def test_require_boto3_at_least_with_reason(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd): + monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION) + # Set boto3 version to a known value (tests are on both sides) to make + # sure we're comparing the right library + monkeypatch.setattr(boto3, "__version__", compare_version) + + reason = 'testing in progress' + + # Create a minimal module that we can call + module = AnsibleAWSModule(argument_spec=dict()) + + with pytest.raises(SystemExit): + module.require_boto3_at_least(desired_version, reason=reason) + module.exit_json() + + out, _err = capfd.readouterr() + return_val = json.loads(out) + + assert return_val.get("exception") is None + assert return_val.get("invocation") is not None + if at_least: + assert return_val.get("failed") is None + else: + assert return_val.get("failed") + # The message is generated by Ansible, don't test for an exact + # message + assert desired_version in return_val.get("msg") + assert " {0}".format(reason) in return_val.get("msg") + assert "boto3" in return_val.get("msg") + assert return_val.get("botocore_version") == DUMMY_VERSION + assert return_val.get("boto3_version") == compare_version diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_compare_policies.py b/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_compare_policies.py new file mode 100644 index 000000000..eb6de22db --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/policy/test_compare_policies.py @@ -0,0 +1,339 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.policy import compare_policies + + +class TestComparePolicy(): + + # ======================================================== + # Setup some initial data that we can use within our tests + # ======================================================== + def setup_method(self): + # A pair of simple IAM Trust relationships using bools, the first a + # native bool the second a quoted string + self.bool_policy_bool = { + 'Version': '2012-10-17', + 'Statement': [ + { + "Action": "sts:AssumeRole", + "Condition": { + "Bool": {"aws:MultiFactorAuthPresent": True} + }, + "Effect": "Allow", + "Principal": {"AWS": "arn:aws:iam::XXXXXXXXXXXX:root"}, + "Sid": "AssumeRoleWithBoolean" + } + ] + } + + self.bool_policy_string = { + 'Version': '2012-10-17', + 'Statement': [ + { + "Action": "sts:AssumeRole", + "Condition": { + "Bool": {"aws:MultiFactorAuthPresent": "true"} + }, + "Effect": "Allow", + "Principal": {"AWS": "arn:aws:iam::XXXXXXXXXXXX:root"}, + "Sid": "AssumeRoleWithBoolean" + } + ] + } + + # A pair of simple bucket policies using numbers, the first a + # native int the second a quoted string + self.numeric_policy_number = { + 'Version': '2012-10-17', + 'Statement': [ + { + "Action": "s3:ListBucket", + "Condition": { + "NumericLessThanEquals": {"s3:max-keys": 15} + }, + "Effect": "Allow", + "Resource": "arn:aws:s3:::examplebucket", + "Sid": "s3ListBucketWithNumericLimit" + } + ] + } + + self.numeric_policy_string = { + 'Version': '2012-10-17', + 'Statement': [ + { + "Action": "s3:ListBucket", + "Condition": { + "NumericLessThanEquals": {"s3:max-keys": "15"} + }, + "Effect": "Allow", + "Resource": "arn:aws:s3:::examplebucket", + "Sid": "s3ListBucketWithNumericLimit" + } + ] + } + + self.small_policy_one = { + 'Version': '2012-10-17', + 'Statement': [ + { + 'Action': 's3:PutObjectAcl', + 'Sid': 'AddCannedAcl2', + 'Resource': 'arn:aws:s3:::test_policy/*', + 'Effect': 'Allow', + 'Principal': {'AWS': ['arn:aws:iam::XXXXXXXXXXXX:user/username1', 'arn:aws:iam::XXXXXXXXXXXX:user/username2']} + } + ] + } + + # The same as small_policy_one, except the single resource is in a list and the contents of Statement are jumbled + self.small_policy_two = { + 'Version': '2012-10-17', + 'Statement': [ + { + 'Effect': 'Allow', + 'Action': 's3:PutObjectAcl', + 'Principal': {'AWS': ['arn:aws:iam::XXXXXXXXXXXX:user/username1', 'arn:aws:iam::XXXXXXXXXXXX:user/username2']}, + 'Resource': ['arn:aws:s3:::test_policy/*'], + 'Sid': 'AddCannedAcl2' + } + ] + } + + self.version_policy_missing = { + 'Statement': [ + { + 'Action': 's3:PutObjectAcl', + 'Sid': 'AddCannedAcl2', + 'Resource': 'arn:aws:s3:::test_policy/*', + 'Effect': 'Allow', + 'Principal': {'AWS': ['arn:aws:iam::XXXXXXXXXXXX:user/username1', 'arn:aws:iam::XXXXXXXXXXXX:user/username2']} + } + ] + } + + self.version_policy_old = { + 'Version': '2008-10-17', + 'Statement': [ + { + 'Action': 's3:PutObjectAcl', + 'Sid': 'AddCannedAcl2', + 'Resource': 'arn:aws:s3:::test_policy/*', + 'Effect': 'Allow', + 'Principal': {'AWS': ['arn:aws:iam::XXXXXXXXXXXX:user/username1', 'arn:aws:iam::XXXXXXXXXXXX:user/username2']} + } + ] + } + + self.version_policy_new = { + 'Version': '2012-10-17', + 'Statement': [ + { + 'Action': 's3:PutObjectAcl', + 'Sid': 'AddCannedAcl2', + 'Resource': 'arn:aws:s3:::test_policy/*', + 'Effect': 'Allow', + 'Principal': {'AWS': ['arn:aws:iam::XXXXXXXXXXXX:user/username1', 'arn:aws:iam::XXXXXXXXXXXX:user/username2']} + } + ] + } + + self.larger_policy_one = { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Test", + "Effect": "Allow", + "Principal": { + "AWS": [ + "arn:aws:iam::XXXXXXXXXXXX:user/testuser1", + "arn:aws:iam::XXXXXXXXXXXX:user/testuser2" + ] + }, + "Action": "s3:PutObjectAcl", + "Resource": "arn:aws:s3:::test_policy/*" + }, + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::XXXXXXXXXXXX:user/testuser2" + }, + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": "arn:aws:s3:::test_policy/*" + } + ] + } + + # The same as larger_policy_one, except having a list of length 1 and jumbled contents + self.larger_policy_two = { + "Version": "2012-10-17", + "Statement": [ + { + "Principal": { + "AWS": ["arn:aws:iam::XXXXXXXXXXXX:user/testuser2"] + }, + "Effect": "Allow", + "Resource": "arn:aws:s3:::test_policy/*", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl" + ] + }, + { + "Action": "s3:PutObjectAcl", + "Principal": { + "AWS": [ + "arn:aws:iam::XXXXXXXXXXXX:user/testuser1", + "arn:aws:iam::XXXXXXXXXXXX:user/testuser2" + ] + }, + "Sid": "Test", + "Resource": "arn:aws:s3:::test_policy/*", + "Effect": "Allow" + } + ] + } + + # Different than larger_policy_two: a different principal is given + self.larger_policy_three = { + "Version": "2012-10-17", + "Statement": [ + { + "Principal": { + "AWS": ["arn:aws:iam::XXXXXXXXXXXX:user/testuser2"] + }, + "Effect": "Allow", + "Resource": "arn:aws:s3:::test_policy/*", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl"] + }, + { + "Action": "s3:PutObjectAcl", + "Principal": { + "AWS": [ + "arn:aws:iam::XXXXXXXXXXXX:user/testuser1", + "arn:aws:iam::XXXXXXXXXXXX:user/testuser3" + ] + }, + "Sid": "Test", + "Resource": "arn:aws:s3:::test_policy/*", + "Effect": "Allow" + } + ] + } + + # Minimal policy using wildcarded Principal + self.wildcard_policy_one = { + "Version": "2012-10-17", + "Statement": [ + { + "Principal": { + "AWS": ["*"] + }, + "Effect": "Allow", + "Resource": "arn:aws:s3:::test_policy/*", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl"] + } + ] + } + + # Minimal policy using wildcarded Principal + self.wildcard_policy_two = { + "Version": "2012-10-17", + "Statement": [ + { + "Principal": "*", + "Effect": "Allow", + "Resource": "arn:aws:s3:::test_policy/*", + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl"] + } + ] + } + + # ======================================================== + # ec2.compare_policies + # ======================================================== + + def test_compare_small_policies_without_differences(self): + """ Testing two small policies which are identical except for: + * The contents of the statement are in different orders + * The second policy contains a list of length one whereas in the first it is a string + """ + assert compare_policies(self.small_policy_one, self.small_policy_two) is False + + def test_compare_large_policies_without_differences(self): + """ Testing two larger policies which are identical except for: + * The statements are in different orders + * The contents of the statements are also in different orders + * The second contains a list of length one for the Principal whereas in the first it is a string + """ + assert compare_policies(self.larger_policy_one, self.larger_policy_two) is False + + def test_compare_larger_policies_with_difference(self): + """ Testing two larger policies which are identical except for: + * one different principal + """ + assert compare_policies(self.larger_policy_two, self.larger_policy_three) is True + + def test_compare_smaller_policy_with_larger(self): + """ Testing two policies of different sizes """ + assert compare_policies(self.larger_policy_one, self.small_policy_one) is True + + def test_compare_boolean_policy_bool_and_string_are_equal(self): + """ Testing two policies one using a quoted boolean, the other a bool """ + assert compare_policies(self.bool_policy_string, self.bool_policy_bool) is False + + def test_compare_numeric_policy_number_and_string_are_equal(self): + """ Testing two policies one using a quoted number, the other an int """ + assert compare_policies(self.numeric_policy_string, self.numeric_policy_number) is False + + def test_compare_version_policies_defaults_old(self): + """ Testing that a policy without Version is considered identical to one + with the 'old' Version (by default) + """ + assert compare_policies(self.version_policy_old, self.version_policy_missing) is False + assert compare_policies(self.version_policy_new, self.version_policy_missing) is True + + def test_compare_version_policies_default_disabled(self): + """ Testing that a policy without Version not considered identical when default_version=None + """ + assert compare_policies(self.version_policy_missing, self.version_policy_missing, default_version=None) is False + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version=None) is True + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version=None) is True + + def test_compare_version_policies_default_set(self): + """ Testing that a policy without Version is only considered identical + when default_version="2008-10-17" + """ + assert compare_policies(self.version_policy_missing, self.version_policy_missing, default_version="2012-10-17") is False + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2012-10-17") is True + assert compare_policies(self.version_policy_old, self.version_policy_missing, default_version="2008-10-17") is False + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2012-10-17") is False + assert compare_policies(self.version_policy_new, self.version_policy_missing, default_version="2008-10-17") is True + + def test_compare_version_policies_with_none(self): + """ Testing that comparing with no policy works + """ + assert compare_policies(self.small_policy_one, None) is True + assert compare_policies(None, self.small_policy_one) is True + assert compare_policies(None, None) is False + + def test_compare_wildcard_policies_without_differences(self): + """ Testing two small wildcard policies which are identical except for: + * Principal: "*" vs Principal: ["AWS": "*"] + """ + assert compare_policies(self.wildcard_policy_one, self.wildcard_policy_two) is False diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/retries/test_awsretry.py b/ansible_collections/amazon/aws/tests/unit/module_utils/retries/test_awsretry.py new file mode 100644 index 000000000..e08700382 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/retries/test_awsretry.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +# (c) 2015, Allen Sanabria <asanabria@linuxdynasty.org> +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +try: + import botocore +except ImportError: + pass + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_awsretry.py requires the python modules 'boto3' and 'botocore'") + + +class TestAWSRetry(): + + def test_no_failures(self): + self.counter = 0 + + @AWSRetry.backoff(tries=2, delay=0.1) + def no_failures(): + self.counter += 1 + + no_failures() + assert self.counter == 1 + + def test_extend_boto3_failures(self): + self.counter = 0 + err_response = {'Error': {'Code': 'MalformedPolicyDocument'}} + + @AWSRetry.backoff(tries=2, delay=0.1, catch_extra_error_codes=['MalformedPolicyDocument']) + def extend_failures(): + self.counter += 1 + if self.counter < 2: + raise botocore.exceptions.ClientError(err_response, 'You did something wrong.') + else: + return 'success' + + result = extend_failures() + assert result == 'success' + assert self.counter == 2 + + def test_retry_once(self): + self.counter = 0 + err_response = {'Error': {'Code': 'InternalFailure'}} + + @AWSRetry.backoff(tries=2, delay=0.1) + def retry_once(): + self.counter += 1 + if self.counter < 2: + raise botocore.exceptions.ClientError(err_response, 'Something went wrong!') + else: + return 'success' + + result = retry_once() + assert result == 'success' + assert self.counter == 2 + + def test_reached_limit(self): + self.counter = 0 + err_response = {'Error': {'Code': 'RequestLimitExceeded'}} + + @AWSRetry.backoff(tries=4, delay=0.1) + def fail(): + self.counter += 1 + raise botocore.exceptions.ClientError(err_response, 'toooo fast!!') + + with pytest.raises(botocore.exceptions.ClientError) as context: + fail() + response = context.value.response + assert response['Error']['Code'] == 'RequestLimitExceeded' + assert self.counter == 4 + + def test_unexpected_exception_does_not_retry(self): + self.counter = 0 + err_response = {'Error': {'Code': 'AuthFailure'}} + + @AWSRetry.backoff(tries=4, delay=0.1) + def raise_unexpected_error(): + self.counter += 1 + raise botocore.exceptions.ClientError(err_response, 'unexpected error') + + with pytest.raises(botocore.exceptions.ClientError) as context: + raise_unexpected_error() + response = context.value.response + assert response['Error']['Code'] == 'AuthFailure' + assert self.counter == 1 diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py new file mode 100644 index 000000000..48c32c78e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_elbv2.py @@ -0,0 +1,214 @@ +# +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils import elbv2 +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock + +one_action = [ + { + "ForwardConfig": { + "TargetGroupStickinessConfig": {"Enabled": False}, + "TargetGroups": [ + { + "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg-58045486/5b231e04f663ae21", + "Weight": 1, + } + ], + }, + "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg-58045486/5b231e04f663ae21", + "Type": "forward", + } +] + +one_action_two_tg = [ + { + "ForwardConfig": { + "TargetGroupStickinessConfig": {"Enabled": False}, + "TargetGroups": [ + { + "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg-58045486/5b231e04f663ae21", + "Weight": 1, + }, + { + "TargetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-tg-dadf7b62/be2f50b4041f11ed", + "Weight": 1, + } + ], + }, + "Type": "forward", + } +] + + +def _sort_actions_one_entry(): + assert elbv2._sort_actions(one_action) == one_action + + +class TestElBV2Utils(): + + def setup_method(self): + self.connection = MagicMock(name="connection") + self.module = MagicMock(name="module") + + self.module.params = dict() + + self.conn_paginator = MagicMock(name="connection.paginator") + self.paginate = MagicMock(name="paginator.paginate") + + self.connection.get_paginator.return_value = self.conn_paginator + self.conn_paginator.paginate.return_value = self.paginate + + self.loadbalancer = { + "Type": "application", + "Scheme": "internet-facing", + "IpAddressType": "ipv4", + "VpcId": "vpc-3ac0fb5f", + "AvailabilityZones": [ + { + "ZoneName": "us-west-2a", + "SubnetId": "subnet-8360a9e7" + }, + { + "ZoneName": "us-west-2b", + "SubnetId": "subnet-b7d581c0" + } + ], + "CreatedTime": "2016-03-25T21:26:12.920Z", + "CanonicalHostedZoneId": "Z2P70J7EXAMPLE", + "DNSName": "my-load-balancer-424835706.us-west-2.elb.amazonaws.com", + "SecurityGroups": [ + "sg-5943793c" + ], + "LoadBalancerName": "my-load-balancer", + "State": { + "Code": "active" + }, + "LoadBalancerArn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188" + } + self.paginate.build_full_result.return_value = { + 'LoadBalancers': [self.loadbalancer] + } + + self.connection.describe_load_balancer_attributes.return_value = { + "Attributes": [ + { + "Value": "false", + "Key": "access_logs.s3.enabled" + }, + { + "Value": "", + "Key": "access_logs.s3.bucket" + }, + { + "Value": "", + "Key": "access_logs.s3.prefix" + }, + { + "Value": "60", + "Key": "idle_timeout.timeout_seconds" + }, + { + "Value": "false", + "Key": "deletion_protection.enabled" + }, + { + "Value": "true", + "Key": "routing.http2.enabled" + }, + { + "Value": "defensive", + "Key": "routing.http.desync_mitigation_mode" + }, + { + "Value": "true", + "Key": "routing.http.drop_invalid_header_fields.enabled" + }, + { + "Value": "true", + "Key": "routing.http.x_amzn_tls_version_and_cipher_suite.enabled" + }, + { + "Value": "true", + "Key": "routing.http.xff_client_port.enabled" + }, + { + "Value": "true", + "Key": "waf.fail_open.enabled" + }, + ] + } + self.connection.describe_tags.return_value = { + "TagDescriptions": [ + { + "ResourceArn": "arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/my-load-balancer/50dc6c495c0c9188", + "Tags": [ + { + "Value": "ansible", + "Key": "project" + }, + { + "Value": "RedHat", + "Key": "company" + } + ] + } + ] + } + self.elbv2obj = elbv2.ElasticLoadBalancerV2(self.connection, self.module) + + # Test the simplest case - Read the ip address type + def test_get_elb_ip_address_type(self): + # Run module + return_value = self.elbv2obj.get_elb_ip_address_type() + # check that no method was called and this has been retrieved from elb attributes + self.connection.describe_load_balancer_attributes.assert_called_once() + self.connection.get_paginator.assert_called_once() + self.connection.describe_tags.assert_called_once() + self.conn_paginator.paginate.assert_called_once() + # assert we got the expected value + assert return_value == 'ipv4' + + # Test modify_ip_address_type idempotency + def test_modify_ip_address_type_idempotency(self): + # Run module + self.elbv2obj.modify_ip_address_type("ipv4") + # check that no method was called and this has been retrieved from elb attributes + self.connection.set_ip_address_type.assert_not_called() + # assert we got the expected value + assert self.elbv2obj.changed is False + + # Test modify_ip_address_type + def test_modify_ip_address_type_update(self): + # Run module + self.elbv2obj.modify_ip_address_type("dualstack") + # check that no method was called and this has been retrieved from elb attributes + self.connection.set_ip_address_type.assert_called_once() + # assert we got the expected value + assert self.elbv2obj.changed is True + + # Test get_elb_attributes + def test_get_elb_attributes(self): + # Build expected result + expected_elb_attributes = { + "access_logs_s3_bucket": "", + "access_logs_s3_enabled": "false", + "access_logs_s3_prefix": "", + "deletion_protection_enabled": "false", + "idle_timeout_timeout_seconds": "60", + "routing_http2_enabled": "true", + "routing_http_desync_mitigation_mode": "defensive", + "routing_http_drop_invalid_header_fields_enabled": "true", + "routing_http_x_amzn_tls_version_and_cipher_suite_enabled": "true", + "routing_http_xff_client_port_enabled": "true", + "waf_fail_open_enabled": "true" + } + # Run module + actual_elb_attributes = self.elbv2obj.get_elb_attributes() + # Assert we got the expected result + assert actual_elb_attributes == expected_elb_attributes diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_iam.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_iam.py new file mode 100644 index 000000000..4ce430262 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_iam.py @@ -0,0 +1,300 @@ +# +# (c) 2020 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock + +import ansible_collections.amazon.aws.plugins.module_utils.iam as utils_iam +from ansible_collections.amazon.aws.plugins.module_utils.ec2 import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_iam.py requires the python modules 'boto3' and 'botocore'") + + +class TestIamUtils(): + + def _make_denied_exception(self, partition): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "User: arn:" + partition + ":iam::123456789012:user/ExampleUser " + + "is not authorized to perform: iam:GetUser on resource: user ExampleUser" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'getUser') + + def _make_unexpected_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "SomeThingWentWrong", + "Message": "Boom!" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_encoded_exception(self): + return botocore.exceptions.ClientError( + { + "Error": { + "Code": "AccessDenied", + "Message": "You are not authorized to perform this operation. Encoded authorization failure message: " + + "fEwXX6llx3cClm9J4pURgz1XPnJPrYexEbrJcLhFkwygMdOgx_-aEsj0LqRM6Kxt2HVI6prUhDwbJqBo9U2V7iRKZ" + + "T6ZdJvHH02cXmD0Jwl5vrTsf0PhBcWYlH5wl2qME7xTfdolEUr4CzumCiti7ETiO-RDdHqWlasBOW5bWsZ4GSpPdU" + + "06YAX0TfwVBs48uU5RpCHfz1uhSzez-3elbtp9CmTOHLt5pzJodiovccO55BQKYLPtmJcs6S9YLEEogmpI4Cb1D26" + + "fYahDh51jEmaohPnW5pb1nQe2yPEtuIhtRzNjhFCOOMwY5DBzNsymK-Gj6eJLm7FSGHee4AHLU_XmZMe_6bcLAiOx" + + "6Zdl65Kdd0hLcpwVxyZMi27HnYjAdqRlV3wuCW2PkhAW14qZQLfiuHZDEwnPe2PBGSlFcCmkQvJvX-YLoA7Uyc2wf" + + "NX5RJm38STwfiJSkQaNDhHKTWKiLOsgY4Gze6uZoG7zOcFXFRyaA4cbMmI76uyBO7j-9uQUCtBYqYto8x_9CUJcxI" + + "VC5SPG_C1mk-WoDMew01f0qy-bNaCgmJ9TOQGd08FyuT1SaMpCC0gX6mHuOnEgkFw3veBIowMpp9XcM-yc42fmIOp" + + "FOdvQO6uE9p55Qc-uXvsDTTvT3A7EeFU8a_YoAIt9UgNYM6VTvoprLz7dBI_P6C-bdPPZCY2amm-dJNVZelT6TbJB" + + "H_Vxh0fzeiSUBersy_QzB0moc-vPWgnB-IkgnYLV-4L3K0L2" + }, + "ResponseMetadata": { + "RequestId": "01234567-89ab-cdef-0123-456789abcdef" + } + }, 'someCall') + + def _make_botocore_exception(self): + return botocore.exceptions.EndpointConnectionError(endpoint_url='junk.endpoint') + + def setup_method(self): + self.sts_client = MagicMock() + self.iam_client = MagicMock() + self.module = MagicMock() + clients = {'sts': self.sts_client, 'iam': self.iam_client} + + def get_client(*args, **kwargs): + return clients[args[0]] + + self.module.client.side_effect = get_client + self.module.fail_json_aws.side_effect = SystemExit(1) + self.module.fail_json.side_effect = SystemExit(2) + + # ========== get_aws_account_id ============ + # This is just a minimal (compatibility) wrapper around get_aws_account_info + # Perform some basic testing and call it a day. + + # Test the simplest case - We're permitted to call GetCallerIdentity + def test_get_aws_account_id__caller_success(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [{'UserId': 'AIDA12345EXAMPLE54321', + 'Account': '123456789012', + 'Arn': 'arn:aws:iam::123456789012:user/ExampleUser'}] + # Run module + return_value = utils_iam.get_aws_account_id(self.module) + # Check we only saw the calls we mocked out + self.module.client.assert_called_once() + self.sts_client.get_caller_identity.assert_called_once() + # Check we got the values back we expected. + assert return_value == '123456789012' + + # Test the simplest case - We're permitted to call GetCallerIdentity + # (China partition) + def test_get_aws_account_id__caller_success_cn(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [{'UserId': 'AIDA12345EXAMPLE54321', + 'Account': '123456789012', + 'Arn': 'arn:aws-cn:iam::123456789012:user/ExampleUser'}] + # Run module + return_value = utils_iam.get_aws_account_id(self.module) + # Check we only saw the calls we mocked out + self.module.client.assert_called_once() + self.sts_client.get_caller_identity.assert_called_once() + # Check we got the values back we expected. + assert return_value == '123456789012' + + # ========== get_aws_account_info ============ + # Test the simplest case - We're permitted to call GetCallerIdentity + def test_get_aws_account_info__caller_success(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [{'UserId': 'AIDA12345EXAMPLE54321', + 'Account': '123456789012', + 'Arn': 'arn:aws:iam::123456789012:user/ExampleUser'}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + self.module.client.assert_called_once() + self.sts_client.get_caller_identity.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws',) + + # (China partition) + def test_get_aws_account_info__caller_success_cn(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [{'UserId': 'AIDA12345EXAMPLE54321', + 'Account': '123456789012', + 'Arn': 'arn:aws-cn:iam::123456789012:user/ExampleUser'}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + self.module.client.assert_called_once() + self.sts_client.get_caller_identity.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-cn',) + + # (US-Gov partition) + def test_get_aws_account_info__caller_success_gov(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [{'UserId': 'AIDA12345EXAMPLE54321', + 'Account': '123456789012', + 'Arn': 'arn:aws-us-gov:iam::123456789012:user/ExampleUser'}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + self.module.client.assert_called_once() + self.sts_client.get_caller_identity.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-us-gov',) + + # If sts:get_caller_identity fails (most likely something wierd on the + # client side), then try a few extra options. + # Test response if STS fails and we need to fall back to GetUser + def test_get_aws_account_info__user_success(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [{"User": {"Path": "/", "UserName": "ExampleUser", "UserId": "AIDA12345EXAMPLE54321", + "Arn": "arn:aws:iam::123456789012:user/ExampleUser", "CreateDate": "2020-09-08T14:04:32Z"}}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws',) + + # (China partition) + def test_get_aws_account_info__user_success_cn(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [{"User": {"Path": "/", "UserName": "ExampleUser", "UserId": "AIDA12345EXAMPLE54321", + "Arn": "arn:aws-cn:iam::123456789012:user/ExampleUser", "CreateDate": "2020-09-08T14:04:32Z"}}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-cn',) + + # (US-Gov partition) + def test_get_aws_account_info__user_success_gov(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [{"User": {"Path": "/", "UserName": "ExampleUser", "UserId": "AIDA12345EXAMPLE54321", + "Arn": "arn:aws-us-gov:iam::123456789012:user/ExampleUser", "CreateDate": "2020-09-08T14:04:32Z"}}] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-us-gov',) + + # Test response if STS and IAM fails and we need to fall back to the denial message + def test_get_aws_account_info__user_denied(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [self._make_denied_exception('aws')] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws',) + + # (China partition) + def test_get_aws_account_info__user_denied_cn(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [self._make_denied_exception('aws-cn')] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-cn',) + + # (US-Gov partition) + def test_get_aws_account_info__user_denied_gov(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [self._make_denied_exception('aws-us-gov')] + # Run module + return_value = utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert return_value == ('123456789012', 'aws-us-gov',) + + # Test that we fail gracefully if Boto throws exceptions at us... + def test_get_aws_account_info__boto_failures(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_botocore_exception()] + self.iam_client.get_user.side_effect = [self._make_botocore_exception()] + # Run module + with pytest.raises(SystemExit) as e: + utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert e.type == SystemExit + assert e.value.code == 1 # 1 == fail_json_aws + + def test_get_aws_account_info__client_failures(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_unexpected_exception()] + self.iam_client.get_user.side_effect = [self._make_unexpected_exception()] + # Run module + with pytest.raises(SystemExit) as e: + utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert e.type == SystemExit + assert e.value.code == 1 # 1 == fail_json_aws + + def test_get_aws_account_info__encoded_failures(self): + # Prepare + self.sts_client.get_caller_identity.side_effect = [self._make_encoded_exception()] + self.iam_client.get_user.side_effect = [self._make_encoded_exception()] + # Run module + with pytest.raises(SystemExit) as e: + utils_iam.get_aws_account_info(self.module) + # Check we only saw the calls we mocked out + assert self.module.client.call_count == 2 + self.sts_client.get_caller_identity.assert_called_once() + self.iam_client.get_user.assert_called_once() + # Check we got the values back we expected. + assert e.type == SystemExit + assert e.value.code == 1 # 1 == fail_json (we couldn't parse the AccessDenied errors) diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_rds.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_rds.py new file mode 100644 index 000000000..9d96d44a8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_rds.py @@ -0,0 +1,805 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import sys +import pytest + +if sys.version_info < (3, 7): + pytest.skip("contextlib.nullcontext was introduced in Python 3.7", allow_module_level=True) + +from contextlib import nullcontext + +try: + import botocore +except ImportError: + # Handled by HAS_BOTO3 + pass + +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock + +from ansible_collections.amazon.aws.plugins.module_utils import rds +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +if not HAS_BOTO3: + pytestmark = pytest.mark.skip("test_rds.py requires the python modules 'boto3' and 'botocore'") + + +def expected(x): + return x, nullcontext() + + +def error(*args, **kwargs): + return MagicMock(), pytest.raises(*args, **kwargs) + + +def build_exception( + operation_name, code=None, message=None, http_status_code=None, error=True +): + # Support skipping the test is botocore isn't installed + # (called by parametrize before skip is evaluated) + if not HAS_BOTO3: + return Exception('MissingBotoCore') + response = {} + if error or code or message: + response["Error"] = {} + if code: + response["Error"]["Code"] = code + if message: + response["Error"]["Message"] = message + if http_status_code: + response["ResponseMetadata"] = {"HTTPStatusCode": http_status_code} + + return botocore.exceptions.ClientError(response, operation_name) + + +@pytest.mark.parametrize("waiter_name", ["", "db_snapshot_available"]) +def test__wait_for_instance_snapshot_status(waiter_name): + rds.wait_for_instance_snapshot_status(MagicMock(), MagicMock(), "test", waiter_name) + + +@pytest.mark.parametrize("waiter_name", ["", "db_cluster_snapshot_available"]) +def test__wait_for_cluster_snapshot_status(waiter_name): + rds.wait_for_cluster_snapshot_status(MagicMock(), MagicMock(), "test", waiter_name) + + +@pytest.mark.parametrize( + "input, expected", + [ + ( + "db_snapshot_available", + "Failed to wait for DB snapshot test to be available", + ), + ( + "db_snapshot_deleted", + "Failed to wait for DB snapshot test to be deleted"), + ], +) +def test__wait_for_instance_snapshot_status_failed(input, expected): + spec = {"get_waiter.side_effect": [botocore.exceptions.WaiterError(None, None, None)]} + client = MagicMock(**spec) + module = MagicMock() + + rds.wait_for_instance_snapshot_status(client, module, "test", input) + module.fail_json_aws.assert_called_once + module.fail_json_aws.call_args[1]["msg"] == expected + + +@pytest.mark.parametrize( + "input, expected", + [ + ( + "db_cluster_snapshot_available", + "Failed to wait for DB cluster snapshot test to be available", + ), + ( + "db_cluster_snapshot_deleted", + "Failed to wait for DB cluster snapshot test to be deleted", + ), + ], +) +def test__wait_for_cluster_snapshot_status_failed(input, expected): + spec = {"get_waiter.side_effect": [botocore.exceptions.WaiterError(None, None, None)]} + client = MagicMock(**spec) + module = MagicMock() + + rds.wait_for_cluster_snapshot_status(client, module, "test", input) + module.fail_json_aws.assert_called_once + module.fail_json_aws.call_args[1]["msg"] == expected + + +@pytest.mark.parametrize( + "method_name, params, expected, error", + [ + ( + "delete_db_cluster", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="delete_db_cluster", + waiter="cluster_deleted", + operation_description="delete DB cluster", + resource='cluster', + retry_codes=['InvalidDBClusterState'] + ) + ), + ), + ( + "create_db_cluster", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="create_db_cluster", + waiter="cluster_available", + operation_description="create DB cluster", + resource='cluster', + retry_codes=['InvalidDBClusterState'] + ) + ), + ), + ( + "restore_db_cluster_from_snapshot", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="restore_db_cluster_from_snapshot", + waiter="cluster_available", + operation_description="restore DB cluster from snapshot", + resource='cluster', + retry_codes=['InvalidDBClusterSnapshotState'] + ) + ), + ), + ( + "modify_db_cluster", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="modify_db_cluster", + waiter="cluster_available", + operation_description="modify DB cluster", + resource='cluster', + retry_codes=['InvalidDBClusterState'] + ) + ), + ), + ( + "list_tags_for_resource", + { + "new_db_cluster_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="list_tags_for_resource", + waiter="cluster_available", + operation_description="list tags for resource", + resource='cluster', + retry_codes=['InvalidDBClusterState'] + ) + ), + ), + ( + "fake_method", + { + "wait": False + }, + *expected( + rds.Boto3ClientMethod( + name="fake_method", + waiter="", + operation_description="fake method", + resource='', + retry_codes=[] + ) + ), + ), + ( + "fake_method", + { + "wait": True + }, + *error( + NotImplementedError, + match="method fake_method hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ], +) +def test__get_rds_method_attribute_cluster(method_name, params, expected, error): + module = MagicMock() + module.params = params + with error: + assert rds.get_rds_method_attribute(method_name, module) == expected + + +@pytest.mark.parametrize( + "method_name, params, expected, error", + [ + ( + "delete_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="delete_db_instance", + waiter="db_instance_deleted", + operation_description="delete DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "create_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="create_db_instance", + waiter="db_instance_available", + operation_description="create DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "stop_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="stop_db_instance", + waiter="db_instance_stopped", + operation_description="stop DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "promote_read_replica", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="promote_read_replica", + waiter="read_replica_promoted", + operation_description="promote read replica", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "restore_db_instance_from_db_snapshot", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="restore_db_instance_from_db_snapshot", + waiter="db_instance_available", + operation_description="restore DB instance from DB snapshot", + resource='instance', + retry_codes=['InvalidDBSnapshotState'] + ) + ), + ), + ( + "modify_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="modify_db_instance", + waiter="db_instance_available", + operation_description="modify DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "add_role_to_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="add_role_to_db_instance", + waiter="role_associated", + operation_description="add role to DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "remove_role_from_db_instance", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="remove_role_from_db_instance", + waiter="role_disassociated", + operation_description="remove role from DB instance", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "list_tags_for_resource", + { + "new_db_instance_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="list_tags_for_resource", + waiter="db_instance_available", + operation_description="list tags for resource", + resource='instance', + retry_codes=['InvalidDBInstanceState', 'InvalidDBSecurityGroupState'] + ) + ), + ), + ( + "fake_method", + { + "wait": False + }, + *expected( + rds.Boto3ClientMethod( + name="fake_method", + waiter="", + operation_description="fake method", + resource='', + retry_codes=[] + ) + ), + ), + ( + "fake_method", + { + "wait": True + }, + *error( + NotImplementedError, + match="method fake_method hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ], +) +def test__get_rds_method_attribute_instance(method_name, params, expected, error): + module = MagicMock() + module.params = params + with error: + assert rds.get_rds_method_attribute(method_name, module) == expected + + +@pytest.mark.parametrize( + "method_name, params, expected, error", + [ + ( + "delete_db_snapshot", + { + "db_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="delete_db_snapshot", + waiter="db_snapshot_deleted", + operation_description="delete DB snapshot", + resource='instance_snapshot', + retry_codes=['InvalidDBSnapshotState'] + ) + ), + ), + ( + "create_db_snapshot", + { + "db_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="create_db_snapshot", + waiter="db_snapshot_available", + operation_description="create DB snapshot", + resource='instance_snapshot', + retry_codes=['InvalidDBInstanceState'] + ) + ), + ), + ( + "copy_db_snapshot", + { + "source_db_snapshot_identifier": "test", + "db_snapshot_identifier": "test-copy" + }, + *expected( + rds.Boto3ClientMethod( + name="copy_db_snapshot", + waiter="db_snapshot_available", + operation_description="copy DB snapshot", + resource='instance_snapshot', + retry_codes=['InvalidDBSnapshotState'] + ) + ), + ), + ( + "list_tags_for_resource", + { + "db_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="list_tags_for_resource", + waiter="db_snapshot_available", + operation_description="list tags for resource", + resource='instance_snapshot', + retry_codes=['InvalidDBSnapshotState'] + ) + ), + ), + ( + "delete_db_cluster_snapshot", + { + "db_cluster_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="delete_db_cluster_snapshot", + waiter="db_cluster_snapshot_deleted", + operation_description="delete DB cluster snapshot", + resource='cluster_snapshot', + retry_codes=['InvalidDBClusterSnapshotState'] + ) + ), + ), + ( + "create_db_cluster_snapshot", + { + "db_cluster_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="create_db_cluster_snapshot", + waiter="db_cluster_snapshot_available", + operation_description="create DB cluster snapshot", + resource='cluster_snapshot', + retry_codes=['InvalidDBClusterState'] + ) + ), + ), + ( + "copy_db_cluster_snapshot", + { + "source_db_cluster_snapshot_identifier": "test", + "db_cluster_snapshot_identifier": "test-copy" + }, + *expected( + rds.Boto3ClientMethod( + name="copy_db_cluster_snapshot", + waiter="db_cluster_snapshot_available", + operation_description="copy DB cluster snapshot", + resource='cluster_snapshot', + retry_codes=['InvalidDBClusterSnapshotState'] + ) + ), + ), + ( + "list_tags_for_resource", + { + "db_cluster_snapshot_identifier": "test", + }, + *expected( + rds.Boto3ClientMethod( + name="list_tags_for_resource", + waiter="db_cluster_snapshot_available", + operation_description="list tags for resource", + resource='cluster_snapshot', + retry_codes=['InvalidDBClusterSnapshotState'] + ) + ), + ), + ( + "fake_method", + { + "wait": False + }, + *expected( + rds.Boto3ClientMethod( + name="fake_method", + waiter="", + operation_description="fake method", + resource='', + retry_codes=[] + ) + ), + ), + ( + "fake_method", + { + "wait": True + }, + *error( + NotImplementedError, + match="method fake_method hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ], +) +def test__get_rds_method_attribute_snapshot(method_name, params, expected, error): + module = MagicMock() + module.params = params + with error: + assert rds.get_rds_method_attribute(method_name, module) == expected + + +@pytest.mark.parametrize( + "method_name, params, expected", + [ + ( + "create_db_snapshot", + { + "db_snapshot_identifier": "test" + }, + "test" + ), + ( + "create_db_snapshot", + { + "db_snapshot_identifier": "test", + "apply_immediately": True + }, + "test", + ), + ( + "create_db_instance", + { + "db_instance_identifier": "test", + "new_db_instance_identifier": "test_updated", + }, + "test", + ), + ( + "create_db_snapshot", + { + "db_snapshot_identifier": "test", + "apply_immediately": True + }, + "test", + ), + ( + "create_db_instance", + { + "db_instance_identifier": "test", + "new_db_instance_identifier": "test_updated", + "apply_immediately": True, + }, + "test_updated", + ), + ( + "create_db_cluster", + { + "db_cluster_identifier": "test", + "new_db_cluster_identifier": "test_updated", + }, + "test", + ), + ( + "create_db_snapshot", + { + "db_snapshot_identifier": "test", + "apply_immediately": True + }, + "test", + ), + ( + "create_db_cluster", + { + "db_cluster_identifier": "test", + "new_db_cluster_identifier": "test_updated", + "apply_immediately": True, + }, + "test_updated", + ), + ], +) +def test__get_final_identifier(method_name, params, expected): + module = MagicMock() + module.params = params + module.check_mode = False + + assert rds.get_final_identifier(method_name, module) == expected + + +@pytest.mark.parametrize( + "method_name, exception, expected", + [ + ( + "modify_db_instance", + build_exception( + "modify_db_instance", + code="InvalidParameterCombination", + message="No modifications were requested", + ), + False, + ), + ( + "promote_read_replica", + build_exception( + "promote_read_replica", + code="InvalidDBInstanceState", + message="DB Instance is not a read replica", + ), + False, + ), + ( + "promote_read_replica_db_cluster", + build_exception( + "promote_read_replica_db_cluster", + code="InvalidDBClusterStateFault", + message="DB Cluster that is not a read replica", + ), + False, + ), + ], +) +def test__handle_errors(method_name, exception, expected): + assert rds.handle_errors(MagicMock(), exception, method_name, {}) == expected + + +@pytest.mark.parametrize( + "method_name, exception, expected, error", + [ + ( + "modify_db_instance", + build_exception( + "modify_db_instance", + code="InvalidParameterCombination", + message="ModifyDbCluster API", + ), + *expected( + "It appears you are trying to modify attributes that are managed at the cluster level. Please see rds_cluster" + ), + ), + ( + "modify_db_instance", + build_exception("modify_db_instance", code="InvalidParameterCombination"), + *error( + NotImplementedError, + match="method modify_db_instance hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ( + "promote_read_replica", + build_exception("promote_read_replica", code="InvalidDBInstanceState"), + *error( + NotImplementedError, + match="method promote_read_replica hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ( + "promote_read_replica_db_cluster", + build_exception( + "promote_read_replica_db_cluster", code="InvalidDBClusterStateFault" + ), + *error( + NotImplementedError, + match="method promote_read_replica_db_cluster hasn't been added to the list of accepted methods to use a waiter in module_utils/rds.py", + ), + ), + ( + "create_db_cluster", + build_exception("create_db_cluster", code="InvalidParameterValue"), + *expected( + "DB engine fake_engine should be one of aurora, aurora-mysql, aurora-postgresql" + ), + ), + ], +) +def test__handle_errors_failed(method_name, exception, expected, error): + module = MagicMock() + + with error: + rds.handle_errors(module, exception, method_name, {"Engine": "fake_engine"}) + module.fail_json_aws.assert_called_once + module.fail_json_aws.call_args[1]["msg"] == expected + + +class TestRdsUtils(): + + # ======================================================== + # Setup some initial data that we can use within our tests + # ======================================================== + def setup_method(self): + self.target_role_list = [ + { + 'role_arn': 'role_won', + 'feature_name': 's3Export' + }, + { + 'role_arn': 'role_too', + 'feature_name': 'Lambda' + }, + { + 'role_arn': 'role_thrie', + 'feature_name': 's3Import' + } + ] + + # ======================================================== + # rds.compare_iam_roles + # ======================================================== + + def test_compare_iam_roles_equal(self): + existing_list = self.target_role_list + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) + assert [] == roles_to_add + assert [] == roles_to_delete + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) + assert [] == roles_to_add + assert [] == roles_to_delete + + def test_compare_iam_roles_empty_arr_existing(self): + roles_to_add, roles_to_delete = rds.compare_iam_roles([], self.target_role_list, purge_roles=False) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete + roles_to_add, roles_to_delete = rds.compare_iam_roles([], self.target_role_list, purge_roles=True) + assert self.target_role_list, roles_to_add + assert [] == roles_to_delete + + def test_compare_iam_roles_empty_arr_target(self): + existing_list = self.target_role_list + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, [], purge_roles=False) + assert [] == roles_to_add + assert [] == roles_to_delete + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, [], purge_roles=True) + assert [] == roles_to_add + assert self.target_role_list == roles_to_delete + + def test_compare_iam_roles_different(self): + existing_list = [ + { + 'role_arn': 'role_wonn', + 'feature_name': 's3Export' + }] + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) + assert self.target_role_list == roles_to_add + assert existing_list == roles_to_delete + + existing_list = self.target_role_list.copy() + self.target_role_list = [ + { + 'role_arn': 'role_wonn', + 'feature_name': 's3Export' + }] + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=False) + assert self.target_role_list == roles_to_add + assert [] == roles_to_delete + roles_to_add, roles_to_delete = rds.compare_iam_roles(existing_list, self.target_role_list, purge_roles=True) + assert self.target_role_list == roles_to_add + assert existing_list == roles_to_delete diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_s3.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_s3.py new file mode 100644 index 000000000..42c8ecfd0 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_s3.py @@ -0,0 +1,86 @@ +# +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock +from ansible_collections.amazon.aws.plugins.module_utils import s3 +from ansible.module_utils.basic import AnsibleModule + +import pytest + + +class FakeAnsibleModule(AnsibleModule): + def __init__(self): + pass + + +def test_calculate_etag_single_part(tmp_path_factory): + module = FakeAnsibleModule() + my_image = tmp_path_factory.mktemp("data") / "my.txt" + my_image.write_text("Hello World!") + + etag = s3.calculate_etag( + module, str(my_image), etag="", s3=None, bucket=None, obj=None + ) + assert etag == '"ed076287532e86365e841e92bfc50d8c"' + + +def test_calculate_etag_multi_part(tmp_path_factory): + module = FakeAnsibleModule() + my_image = tmp_path_factory.mktemp("data") / "my.txt" + my_image.write_text("Hello World!" * 1000) + + mocked_s3 = MagicMock() + mocked_s3.head_object.side_effect = [{"ContentLength": "1000"} for _i in range(12)] + + etag = s3.calculate_etag( + module, + str(my_image), + etag='"f20e84ac3d0c33cea77b3f29e3323a09-12"', + s3=mocked_s3, + bucket="my-bucket", + obj="my-obj", + ) + assert etag == '"f20e84ac3d0c33cea77b3f29e3323a09-12"' + mocked_s3.head_object.assert_called_with( + Bucket="my-bucket", Key="my-obj", PartNumber=12 + ) + + +def test_validate_bucket_name(): + module = MagicMock() + + assert s3.validate_bucket_name(module, "docexamplebucket1") is True + assert not module.fail_json.called + assert s3.validate_bucket_name(module, "log-delivery-march-2020") is True + assert not module.fail_json.called + assert s3.validate_bucket_name(module, "my-hosted-content") is True + assert not module.fail_json.called + + assert s3.validate_bucket_name(module, "docexamplewebsite.com") is True + assert not module.fail_json.called + assert s3.validate_bucket_name(module, "www.docexamplewebsite.com") is True + assert not module.fail_json.called + assert s3.validate_bucket_name(module, "my.example.s3.bucket") is True + assert not module.fail_json.called + assert s3.validate_bucket_name(module, "doc") is True + assert not module.fail_json.called + + module.fail_json.reset_mock() + s3.validate_bucket_name(module, "doc_example_bucket") + assert module.fail_json.called + + module.fail_json.reset_mock() + s3.validate_bucket_name(module, "DocExampleBucket") + assert module.fail_json.called + module.fail_json.reset_mock() + s3.validate_bucket_name(module, "doc-example-bucket-") + assert module.fail_json.called + s3.validate_bucket_name(module, "my") + assert module.fail_json.called diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_tagging.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_tagging.py new file mode 100644 index 000000000..04ec96eb0 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_tagging.py @@ -0,0 +1,203 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.tagging import ansible_dict_to_boto3_tag_list +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict +from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_specifications +from ansible_collections.amazon.aws.plugins.module_utils.tagging import compare_aws_tags + + +class TestTagging(): + + # ======================================================== + # Setup some initial data that we can use within our tests + # ======================================================== + def setup_method(self): + + self.tag_example_boto3_list = [ + {'Key': 'lowerCamel', 'Value': 'lowerCamelValue'}, + {'Key': 'UpperCamel', 'Value': 'upperCamelValue'}, + {'Key': 'Normal case', 'Value': 'Normal Value'}, + {'Key': 'lower case', 'Value': 'lower case value'} + ] + + self.tag_example_dict = { + 'lowerCamel': 'lowerCamelValue', + 'UpperCamel': 'upperCamelValue', + 'Normal case': 'Normal Value', + 'lower case': 'lower case value' + } + + self.tag_minimal_boto3_list = [ + {'Key': 'mykey', 'Value': 'myvalue'}, + ] + + self.tag_minimal_dict = {'mykey': 'myvalue'} + + self.tag_aws_dict = {'aws:cloudformation:stack-name': 'ExampleStack'} + self.tag_aws_changed = {'aws:cloudformation:stack-name': 'AnotherStack'} + + # ======================================================== + # tagging.ansible_dict_to_boto3_tag_list + # ======================================================== + + def test_ansible_dict_to_boto3_tag_list(self): + converted_list = ansible_dict_to_boto3_tag_list(self.tag_example_dict) + sorted_converted_list = sorted(converted_list, key=lambda i: (i['Key'])) + sorted_list = sorted(self.tag_example_boto3_list, key=lambda i: (i['Key'])) + assert sorted_converted_list == sorted_list + + # ======================================================== + # tagging.boto3_tag_list_to_ansible_dict + # ======================================================== + + def test_boto3_tag_list_to_ansible_dict(self): + converted_dict = boto3_tag_list_to_ansible_dict(self.tag_example_boto3_list) + assert converted_dict == self.tag_example_dict + + def test_boto3_tag_list_to_ansible_dict_empty(self): + # AWS returns [] when there are no tags + assert boto3_tag_list_to_ansible_dict([]) == {} + # Minio returns [{}] when there are no tags + assert boto3_tag_list_to_ansible_dict([{}]) == {} + + # ======================================================== + # tagging.compare_aws_tags + # ======================================================== + + def test_compare_aws_tags_equal(self): + new_dict = dict(self.tag_example_dict) + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) + assert {} == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) + assert {} == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) + assert {} == keys_to_set + assert [] == keys_to_unset + + def test_compare_aws_tags_removed(self): + new_dict = dict(self.tag_example_dict) + del new_dict['lowerCamel'] + del new_dict['Normal case'] + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) + assert {} == keys_to_set + assert set(['lowerCamel', 'Normal case']) == set(keys_to_unset) + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) + assert {} == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) + assert {} == keys_to_set + assert set(['lowerCamel', 'Normal case']) == set(keys_to_unset) + + def test_compare_aws_tags_added(self): + new_dict = dict(self.tag_example_dict) + new_keys = {'add_me': 'lower case', 'Me too!': 'Contributing'} + new_dict.update(new_keys) + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) + assert new_keys == keys_to_set + assert [] == keys_to_unset + + def test_compare_aws_tags_changed(self): + new_dict = dict(self.tag_example_dict) + new_keys = {'UpperCamel': 'anotherCamelValue', 'Normal case': 'normal value'} + new_dict.update(new_keys) + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) + assert new_keys == keys_to_set + assert [] == keys_to_unset + + def test_compare_aws_tags_complex_update(self): + # Adds 'Me too!', Changes 'UpperCamel' and removes 'Normal case' + new_dict = dict(self.tag_example_dict) + new_keys = {'UpperCamel': 'anotherCamelValue', 'Me too!': 'Contributing'} + new_dict.update(new_keys) + del new_dict['Normal case'] + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=False) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(self.tag_example_dict, new_dict, purge_tags=True) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset + + def test_compare_aws_tags_aws(self): + starting_tags = dict(self.tag_aws_dict) + desired_tags = dict(self.tag_minimal_dict) + tags_to_set, tags_to_unset = compare_aws_tags(starting_tags, desired_tags, purge_tags=True) + assert desired_tags == tags_to_set + assert [] == tags_to_unset + # If someone explicitly passes a changed 'aws:' key the APIs will probably + # throw an error, but this is their responsibility. + desired_tags.update(self.tag_aws_changed) + tags_to_set, tags_to_unset = compare_aws_tags(starting_tags, desired_tags, purge_tags=True) + assert desired_tags == tags_to_set + assert [] == tags_to_unset + + def test_compare_aws_tags_aws_complex(self): + old_dict = dict(self.tag_example_dict) + old_dict.update(self.tag_aws_dict) + # Adds 'Me too!', Changes 'UpperCamel' and removes 'Normal case' + new_dict = dict(self.tag_example_dict) + new_keys = {'UpperCamel': 'anotherCamelValue', 'Me too!': 'Contributing'} + new_dict.update(new_keys) + del new_dict['Normal case'] + keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict, purge_tags=False) + assert new_keys == keys_to_set + assert [] == keys_to_unset + keys_to_set, keys_to_unset = compare_aws_tags(old_dict, new_dict, purge_tags=True) + assert new_keys == keys_to_set + assert ['Normal case'] == keys_to_unset + + # ======================================================== + # tagging.boto3_tag_specifications + # ======================================================== + + # Builds upon ansible_dict_to_boto3_tag_list, assume that if a minimal tag + # dictionary behaves as expected, then all will behave + def test_boto3_tag_specifications_no_type(self): + tag_specification = boto3_tag_specifications(self.tag_minimal_dict) + expected_specification = [{'Tags': self.tag_minimal_boto3_list}] + assert tag_specification == expected_specification + + def test_boto3_tag_specifications_string_type(self): + tag_specification = boto3_tag_specifications(self.tag_minimal_dict, 'instance') + expected_specification = [{'ResourceType': 'instance', 'Tags': self.tag_minimal_boto3_list}] + assert tag_specification == expected_specification + + def test_boto3_tag_specifications_single_type(self): + tag_specification = boto3_tag_specifications(self.tag_minimal_dict, ['instance']) + expected_specification = [{'ResourceType': 'instance', 'Tags': self.tag_minimal_boto3_list}] + assert tag_specification == expected_specification + + def test_boto3_tag_specifications_multipe_types(self): + tag_specification = boto3_tag_specifications(self.tag_minimal_dict, ['instance', 'volume']) + expected_specification = [ + {'ResourceType': 'instance', 'Tags': self.tag_minimal_boto3_list}, + {'ResourceType': 'volume', 'Tags': self.tag_minimal_boto3_list}, + ] + sorted_tag_spec = sorted(tag_specification, key=lambda i: (i['ResourceType'])) + sorted_expected = sorted(expected_specification, key=lambda i: (i['ResourceType'])) + assert sorted_tag_spec == sorted_expected diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/test_tower.py b/ansible_collections/amazon/aws/tests/unit/module_utils/test_tower.py new file mode 100644 index 000000000..9e1d90213 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/test_tower.py @@ -0,0 +1,40 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# import pytest + +import ansible_collections.amazon.aws.plugins.module_utils.tower as utils_tower + +WINDOWS_DOWNLOAD = "Invoke-Expression ((New-Object System.Net.Webclient).DownloadString(" \ + "'https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1'))" +EXAMPLE_PASSWORD = 'MY_EXAMPLE_PASSWORD' +WINDOWS_INVOKE = "$admin.PSBase.Invoke('SetPassword', 'MY_EXAMPLE_PASSWORD'" + +EXAMPLE_TOWER = "tower.example.com" +EXAMPLE_TEMPLATE = 'My Template' +EXAMPLE_KEY = '123EXAMPLE123' +LINUX_TRIGGER_V1 = 'https://tower.example.com/api/v1/job_templates/My%20Template/callback/' +LINUX_TRIGGER_V2 = 'https://tower.example.com/api/v2/job_templates/My%20Template/callback/' + + +def test_windows_callback_no_password(): + user_data = utils_tower._windows_callback_script() + assert WINDOWS_DOWNLOAD in user_data + assert 'SetPassword' not in user_data + + +def test_windows_callback_password(): + user_data = utils_tower._windows_callback_script(EXAMPLE_PASSWORD) + assert WINDOWS_DOWNLOAD in user_data + assert WINDOWS_INVOKE in user_data + + +def test_linux_callback_with_name(): + user_data = utils_tower._linux_callback_script(EXAMPLE_TOWER, EXAMPLE_TEMPLATE, EXAMPLE_KEY) + assert LINUX_TRIGGER_V1 in user_data + assert LINUX_TRIGGER_V2 in user_data diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py new file mode 100644 index 000000000..23c82b173 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_ansible_dict_to_boto3_filter_list.py @@ -0,0 +1,73 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.transformation import ansible_dict_to_boto3_filter_list + + +class TestAnsibleDictToBoto3FilterList(): + + # ======================================================== + # ec2.ansible_dict_to_boto3_filter_list + # ======================================================== + + def test_ansible_dict_with_string_to_boto3_filter_list(self): + filters = {'some-aws-id': 'i-01234567'} + filter_list_string = [ + { + 'Name': 'some-aws-id', + 'Values': [ + 'i-01234567', + ] + } + ] + + converted_filters_list = ansible_dict_to_boto3_filter_list(filters) + assert converted_filters_list == filter_list_string + + def test_ansible_dict_with_boolean_to_boto3_filter_list(self): + filters = {'enabled': True} + filter_list_boolean = [ + { + 'Name': 'enabled', + 'Values': [ + 'true', + ] + } + ] + + converted_filters_bool = ansible_dict_to_boto3_filter_list(filters) + assert converted_filters_bool == filter_list_boolean + + def test_ansible_dict_with_integer_to_boto3_filter_list(self): + filters = {'version': 1} + filter_list_integer = [ + { + 'Name': 'version', + 'Values': [ + '1', + ] + } + ] + + converted_filters_int = ansible_dict_to_boto3_filter_list(filters) + assert converted_filters_int == filter_list_integer + + def test_ansible_dict_with_list_to_boto3_filter_list(self): + filters = {'version': ['1', '2', '3']} + filter_list_integer = [ + { + 'Name': 'version', + 'Values': [ + '1', '2', '3' + ] + } + ] + + converted_filters_int = ansible_dict_to_boto3_filter_list(filters) + assert converted_filters_int == filter_list_integer diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_map_complex_type.py b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_map_complex_type.py new file mode 100644 index 000000000..2300e2351 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_map_complex_type.py @@ -0,0 +1,100 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.module_utils.transformation import map_complex_type + +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel + + +def test_map_complex_type_over_dict(): + type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} + complex_type_dict = {'minimum_healthy_percent': "75", 'maximum_percent': "150"} + complex_type_expected = {'minimum_healthy_percent': 75, 'maximum_percent': 150} + + complex_type_mapped = map_complex_type(complex_type_dict, type_map) + + assert complex_type_mapped == complex_type_expected + + +def test_map_complex_type_empty(): + type_map = {'minimum_healthy_percent': 'int', 'maximum_percent': 'int'} + assert map_complex_type({}, type_map) == {} + assert map_complex_type([], type_map) == [] + assert map_complex_type(None, type_map) is None + + +def test_map_complex_type_no_type(): + type_map = {'some_entry': 'int'} + complex_dict = {'another_entry': sentinel.UNSPECIFIED_MAPPING} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == complex_dict + # we should have the original sentinel object, even if it's a new dictionary + assert mapped_dict['another_entry'] is sentinel.UNSPECIFIED_MAPPING + + +def test_map_complex_type_list(): + type_map = {'some_entry': 'int'} + complex_dict = {'some_entry': ["1", "2", "3"]} + expected_dict = {'some_entry': [1, 2, 3]} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + +def test_map_complex_type_list_type(): + type_map = {'some_entry': ['int']} + complex_dict = {'some_entry': ["1", "2", "3"]} + expected_dict = {'some_entry': [1, 2, 3]} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + type_map = {'some_entry': ['int']} + complex_dict = {'some_entry': "1"} + expected_dict = {'some_entry': 1} + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict + + +def test_map_complex_type_complex(): + type_map = { + 'my_integer': 'int', + 'my_bool': 'bool', + 'my_string': 'str', + 'my_typelist_of_int': ['int'], + 'my_maplist_of_int': 'int', + 'my_unused': 'bool', + } + complex_dict = { + 'my_integer': '-24', + 'my_bool': 'true', + 'my_string': 43, + 'my_typelist_of_int': '5', + 'my_maplist_of_int': ['-26', '47'], + 'my_unconverted': sentinel.UNSPECIFIED_MAPPING, + } + expected_dict = { + 'my_integer': -24, + 'my_bool': True, + 'my_string': '43', + 'my_typelist_of_int': 5, + 'my_maplist_of_int': [-26, 47], + 'my_unconverted': sentinel.UNSPECIFIED_MAPPING, + } + + mapped_dict = map_complex_type(complex_dict, type_map) + + assert mapped_dict == expected_dict + assert mapped_dict['my_unconverted'] is sentinel.UNSPECIFIED_MAPPING + assert mapped_dict['my_bool'] is True + + +def test_map_complex_type_nested_list(): + type_map = {'my_integer': 'int'} + complex_dict = [{'my_integer': '5'}, {'my_integer': '-24'}] + expected_dict = [{'my_integer': 5}, {'my_integer': -24}] + mapped_dict = map_complex_type(complex_dict, type_map) + assert mapped_dict == expected_dict diff --git a/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_scrub_none_parameters.py b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_scrub_none_parameters.py new file mode 100644 index 000000000..82fd41ed3 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/module_utils/transformation/test_scrub_none_parameters.py @@ -0,0 +1,88 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters + +scrub_none_test_data = [ + (dict(), # Input + dict(), # Output with descend_into_lists=False + dict(), # Output with descend_into_lists=True + ), + (dict(param1=None, param2=None), + dict(), + dict(), + ), + (dict(param1='something'), + dict(param1='something'), + dict(param1='something'), + ), + (dict(param1=False), + dict(param1=False), + dict(param1=False), + ), + (dict(param1=None, param2=[]), + dict(param2=[]), + dict(param2=[]), + ), + (dict(param1=None, param2=["list_value"]), + dict(param2=["list_value"]), + dict(param2=["list_value"]), + ), + (dict(param1='something', param2='something_else'), + dict(param1='something', param2='something_else'), + dict(param1='something', param2='something_else'), + ), + (dict(param1='something', param2=dict()), + dict(param1='something', param2=dict()), + dict(param1='something', param2=dict()), + ), + (dict(param1='something', param2=None), + dict(param1='something'), + dict(param1='something'), + ), + (dict(param1='something', param2=None, param3=None), + dict(param1='something'), + dict(param1='something'), + ), + (dict(param1='something', param2=None, param3=None, param4='something_else'), + dict(param1='something', param4='something_else'), + dict(param1='something', param4='something_else'), + ), + (dict(param1=dict(sub_param1='something', sub_param2=dict(sub_sub_param1='another_thing')), param2=None, param3=None, param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=dict(sub_sub_param1='another_thing')), param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=dict(sub_sub_param1='another_thing')), param4='something_else'), + ), + (dict(param1=dict(sub_param1='something', sub_param2=dict()), param2=None, param3=None, param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=dict()), param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=dict()), param4='something_else'), + ), + (dict(param1=dict(sub_param1='something', sub_param2=False), param2=None, param3=None, param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=False), param4='something_else'), + dict(param1=dict(sub_param1='something', sub_param2=False), param4='something_else'), + ), + (dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1', sub_param2='my_dict_nested_in_a_list_2')], param2=[]), + dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1', sub_param2='my_dict_nested_in_a_list_2')], param2=[]), + dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1', sub_param2='my_dict_nested_in_a_list_2')], param2=[]), + ), + (dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1', sub_param2=None)], param2=[]), + dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1', sub_param2=None)], param2=[]), + dict(param1=[dict(sub_param1='my_dict_nested_in_a_list_1')], param2=[]), + ), + (dict(param1=[dict(sub_param1=[dict(sub_sub_param1=None)], sub_param2=None)], param2=[]), + dict(param1=[dict(sub_param1=[dict(sub_sub_param1=None)], sub_param2=None)], param2=[]), + dict(param1=[dict(sub_param1=[dict()])], param2=[]), + ), + (dict(param1=[dict(sub_param1=[dict(sub_sub_param1=None)], sub_param2=None)], param2=None), + dict(param1=[dict(sub_param1=[dict(sub_sub_param1=None)], sub_param2=None)]), + dict(param1=[dict(sub_param1=[dict()])]), + ), +] + + +@pytest.mark.parametrize("input_params, output_params_no_descend, output_params_descend", scrub_none_test_data) +def test_scrub_none_parameters(input_params, output_params_no_descend, output_params_descend): + assert scrub_none_parameters(input_params) == output_params_descend + assert scrub_none_parameters(input_params, descend_into_lists=False) == output_params_no_descend + assert scrub_none_parameters(input_params, descend_into_lists=True) == output_params_descend diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py b/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py new file mode 100644 index 000000000..5386fe6c7 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/inventory/test_aws_ec2.py @@ -0,0 +1,514 @@ +# -*- coding: utf-8 -*- + +# Copyright 2017 Sloane Hertel <shertel@redhat.com> +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see <http://www.gnu.org/licenses/>. + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import datetime +from unittest.mock import Mock, MagicMock + +from ansible.errors import AnsibleError +from ansible.parsing.dataloader import DataLoader +from ansible_collections.amazon.aws.plugins.inventory.aws_ec2 import InventoryModule, instance_data_filter_to_boto_attr + + +instances = { + 'Instances': [ + {'Monitoring': {'State': 'disabled'}, + 'PublicDnsName': 'ec2-12-345-67-890.compute-1.amazonaws.com', + 'State': {'Code': 16, 'Name': 'running'}, + 'EbsOptimized': False, + 'LaunchTime': datetime.datetime(2017, 10, 31, 12, 59, 25), + 'PublicIpAddress': '12.345.67.890', + 'PrivateIpAddress': '098.76.54.321', + 'ProductCodes': [], + 'VpcId': 'vpc-12345678', + 'StateTransitionReason': '', + 'InstanceId': 'i-00000000000000000', + 'EnaSupport': True, + 'ImageId': 'ami-12345678', + 'PrivateDnsName': 'ip-098-76-54-321.ec2.internal', + 'KeyName': 'testkey', + 'SecurityGroups': [{'GroupName': 'default', 'GroupId': 'sg-12345678'}], + 'ClientToken': '', + 'SubnetId': 'subnet-12345678', + 'InstanceType': 't2.micro', + 'NetworkInterfaces': [ + {'Status': 'in-use', + 'MacAddress': '12:a0:50:42:3d:a4', + 'SourceDestCheck': True, + 'VpcId': 'vpc-12345678', + 'Description': '', + 'NetworkInterfaceId': 'eni-12345678', + 'PrivateIpAddresses': [ + {'PrivateDnsName': 'ip-098-76-54-321.ec2.internal', + 'PrivateIpAddress': '098.76.54.321', + 'Primary': True, + 'Association': + {'PublicIp': '12.345.67.890', + 'PublicDnsName': 'ec2-12-345-67-890.compute-1.amazonaws.com', + 'IpOwnerId': 'amazon'}}], + 'PrivateDnsName': 'ip-098-76-54-321.ec2.internal', + 'Attachment': + {'Status': 'attached', + 'DeviceIndex': 0, + 'DeleteOnTermination': True, + 'AttachmentId': 'eni-attach-12345678', + 'AttachTime': datetime.datetime(2017, 10, 31, 12, 59, 25)}, + 'Groups': [ + {'GroupName': 'default', + 'GroupId': 'sg-12345678'}], + 'Ipv6Addresses': [], + 'OwnerId': '123456789012', + 'PrivateIpAddress': '098.76.54.321', + 'SubnetId': 'subnet-12345678', + 'Association': + {'PublicIp': '12.345.67.890', + 'PublicDnsName': 'ec2-12-345-67-890.compute-1.amazonaws.com', + 'IpOwnerId': 'amazon'}}], + 'SourceDestCheck': True, + 'Placement': + {'Tenancy': 'default', + 'GroupName': '', + 'AvailabilityZone': 'us-east-1c'}, + 'Hypervisor': 'xen', + 'BlockDeviceMappings': [ + {'DeviceName': '/dev/xvda', + 'Ebs': + {'Status': 'attached', + 'DeleteOnTermination': True, + 'VolumeId': 'vol-01234567890000000', + 'AttachTime': datetime.datetime(2017, 10, 31, 12, 59, 26)}}], + 'Architecture': 'x86_64', + 'RootDeviceType': 'ebs', + 'RootDeviceName': '/dev/xvda', + 'VirtualizationType': 'hvm', + 'Tags': [{'Value': 'test', 'Key': 'ansible'}, {'Value': 'aws_ec2', 'Key': 'Name'}], + 'AmiLaunchIndex': 0}], + 'ReservationId': 'r-01234567890000000', + 'Groups': [], + 'OwnerId': '123456789012' +} + + +@pytest.fixture() +def inventory(): + inventory = InventoryModule() + inventory._options = { + "aws_profile": "first_precedence", + "aws_access_key": "test_access_key", + "aws_secret_key": "test_secret_key", + "aws_security_token": "test_security_token", + "iam_role_arn": None, + "use_contrib_script_compatible_ec2_tag_keys": False, + "hostvars_prefix": "", + "hostvars_suffix": "", + "strict": True, + "compose": {}, + "groups": {}, + "keyed_groups": [], + "regions": ["us-east-1"], + "filters": [], + "include_filters": [], + "exclude_filters": [], + "hostnames": [], + "strict_permissions": False, + "allow_duplicated_hosts": False, + "cache": False, + "include_extra_api_calls": False, + "use_contrib_script_compatible_sanitization": False, + } + inventory.inventory = MagicMock() + return inventory + + +def test_compile_values(inventory): + found_value = instances['Instances'][0] + chain_of_keys = instance_data_filter_to_boto_attr['instance.group-id'] + for attr in chain_of_keys: + found_value = inventory._compile_values(found_value, attr) + assert found_value == "sg-12345678" + + +def test_get_boto_attr_chain(inventory): + instance = instances['Instances'][0] + assert inventory._get_boto_attr_chain('network-interface.addresses.private-ip-address', instance) == "098.76.54.321" + + +def test_boto3_conn(inventory): + inventory._options = {"aws_profile": "first_precedence", + "aws_access_key": "test_access_key", + "aws_secret_key": "test_secret_key", + "aws_security_token": "test_security_token", + "iam_role_arn": None} + loader = DataLoader() + inventory._set_credentials(loader) + with pytest.raises(AnsibleError) as error_message: + for _connection, _region in inventory._boto3_conn(regions=['us-east-1']): + assert "Insufficient credentials found" in error_message + + +def testget_all_hostnames_default(inventory): + instance = instances['Instances'][0] + assert inventory.get_all_hostnames(instance, hostnames=None) == ["ec2-12-345-67-890.compute-1.amazonaws.com", "ip-098-76-54-321.ec2.internal"] + + +def testget_all_hostnames(inventory): + hostnames = ['ip-address', 'dns-name'] + instance = instances['Instances'][0] + assert inventory.get_all_hostnames(instance, hostnames) == ["12.345.67.890", "ec2-12-345-67-890.compute-1.amazonaws.com"] + + +def testget_all_hostnames_dict(inventory): + hostnames = [{'name': 'private-ip-address', 'separator': '_', 'prefix': 'tag:Name'}] + instance = instances['Instances'][0] + assert inventory.get_all_hostnames(instance, hostnames) == ["aws_ec2_098.76.54.321"] + + +def testget_all_hostnames_with_2_tags(inventory): + hostnames = ['tag:ansible', 'tag:Name'] + instance = instances['Instances'][0] + assert inventory.get_all_hostnames(instance, hostnames) == ["test", "aws_ec2"] + + +def test_get_preferred_hostname_default(inventory): + instance = instances['Instances'][0] + assert inventory._get_preferred_hostname(instance, hostnames=None) == "ec2-12-345-67-890.compute-1.amazonaws.com" + + +def test_get_preferred_hostname(inventory): + hostnames = ['ip-address', 'dns-name'] + instance = instances['Instances'][0] + assert inventory._get_preferred_hostname(instance, hostnames) == "12.345.67.890" + + +def test_get_preferred_hostname_dict(inventory): + hostnames = [{'name': 'private-ip-address', 'separator': '_', 'prefix': 'tag:Name'}] + instance = instances['Instances'][0] + assert inventory._get_preferred_hostname(instance, hostnames) == "aws_ec2_098.76.54.321" + + +def test_get_preferred_hostname_with_2_tags(inventory): + hostnames = ['tag:ansible', 'tag:Name'] + instance = instances['Instances'][0] + assert inventory._get_preferred_hostname(instance, hostnames) == "test" + + +def test_set_credentials(inventory): + inventory._options = {'aws_access_key': 'test_access_key', + 'aws_secret_key': 'test_secret_key', + 'aws_security_token': 'test_security_token', + 'aws_profile': 'test_profile', + 'iam_role_arn': 'arn:aws:iam::123456789012:role/test-role'} + loader = DataLoader() + inventory._set_credentials(loader) + + assert inventory.boto_profile == "test_profile" + assert inventory.aws_access_key_id == "test_access_key" + assert inventory.aws_secret_access_key == "test_secret_key" + assert inventory.aws_security_token == "test_security_token" + assert inventory.iam_role_arn == "arn:aws:iam::123456789012:role/test-role" + + +def test_insufficient_credentials(inventory): + inventory._options = { + 'aws_access_key': None, + 'aws_secret_key': None, + 'aws_security_token': None, + 'aws_profile': None, + 'iam_role_arn': None + } + with pytest.raises(AnsibleError) as error_message: + loader = DataLoader() + inventory._set_credentials(loader) + assert "Insufficient credentials found" in error_message + + +def test_verify_file_bad_config(inventory): + assert inventory.verify_file('not_aws_config.yml') is False + + +def test_include_filters_with_no_filter(inventory): + inventory._options = { + 'filters': {}, + 'include_filters': [], + } + print(inventory.build_include_filters()) + assert inventory.build_include_filters() == [{}] + + +def test_include_filters_with_include_filters_only(inventory): + inventory._options = { + 'filters': {}, + 'include_filters': [{"foo": "bar"}], + } + assert inventory.build_include_filters() == [{"foo": "bar"}] + + +def test_include_filters_with_filter_and_include_filters(inventory): + inventory._options = { + 'filters': {"from_filter": 1}, + 'include_filters': [{"from_include_filter": "bar"}], + } + print(inventory.build_include_filters()) + assert inventory.build_include_filters() == [ + {"from_filter": 1}, + {"from_include_filter": "bar"}] + + +def test_add_host_empty_hostnames(inventory): + hosts = [ + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "ip-10-85-0-4.ec2.internal" + }, + ] + inventory._add_hosts(hosts, "aws_ec2", []) + inventory.inventory.add_host.assert_called_with("ip-10-85-0-4.ec2.internal", group="aws_ec2") + + +def test_add_host_with_hostnames_no_criteria(inventory): + hosts = [{}] + + inventory._add_hosts( + hosts, "aws_ec2", hostnames=["tag:Name", "private-dns-name", "dns-name"] + ) + assert inventory.inventory.add_host.call_count == 0 + + +def test_add_host_with_hostnames_and_one_criteria(inventory): + hosts = [ + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "sample-host", + } + ] + + inventory._add_hosts( + hosts, "aws_ec2", hostnames=["tag:Name", "private-dns-name", "dns-name"] + ) + assert inventory.inventory.add_host.call_count == 1 + inventory.inventory.add_host.assert_called_with("sample-host", group="aws_ec2") + + +def test_add_host_with_hostnames_and_two_matching_criteria(inventory): + hosts = [ + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "name-from-PublicDnsName", + "Tags": [{"Value": "name-from-tag-Name", "Key": "Name"}], + } + ] + + inventory._add_hosts( + hosts, "aws_ec2", hostnames=["tag:Name", "private-dns-name", "dns-name"] + ) + assert inventory.inventory.add_host.call_count == 1 + inventory.inventory.add_host.assert_called_with( + "name-from-tag-Name", group="aws_ec2" + ) + + +def test_add_host_with_hostnames_and_two_matching_criteria_and_allow_duplicated_hosts( + inventory, +): + hosts = [ + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "name-from-PublicDnsName", + "Tags": [{"Value": "name-from-tag-Name", "Key": "Name"}], + } + ] + + inventory._add_hosts( + hosts, + "aws_ec2", + hostnames=["tag:Name", "private-dns-name", "dns-name"], + allow_duplicated_hosts=True, + ) + assert inventory.inventory.add_host.call_count == 2 + inventory.inventory.add_host.assert_any_call( + "name-from-PublicDnsName", group="aws_ec2" + ) + inventory.inventory.add_host.assert_any_call("name-from-tag-Name", group="aws_ec2") + + +def test_sanitize_hostname(inventory): + assert inventory._sanitize_hostname(1) == "1" + assert inventory._sanitize_hostname("a:b") == "a_b" + assert inventory._sanitize_hostname("a:/b") == "a__b" + assert inventory._sanitize_hostname("example") == "example" + + +def test_sanitize_hostname_legacy(inventory): + inventory._sanitize_group_name = ( + inventory._legacy_script_compatible_group_sanitization + ) + assert inventory._sanitize_hostname("a:/b") == "a__b" + + +@pytest.mark.parametrize( + "hostvars_prefix,hostvars_suffix,use_contrib_script_compatible_ec2_tag_keys,expectation", + [ + ( + None, + None, + False, + { + "my_var": 1, + "placement": {"availability_zone": "us-east-1a", "region": "us-east-1"}, + "tags": {"Name": "my-name"}, + }, + ), + ( + "pre", + "post", + False, + { + "premy_varpost": 1, + "preplacementpost": { + "availability_zone": "us-east-1a", + "region": "us-east-1", + }, + "pretagspost": {"Name": "my-name"}, + }, + ), + ( + None, + None, + True, + { + "my_var": 1, + "ec2_tag_Name": "my-name", + "placement": {"availability_zone": "us-east-1a", "region": "us-east-1"}, + "tags": {"Name": "my-name"}, + }, + ), + ], +) +def test_prepare_host_vars( + inventory, + hostvars_prefix, + hostvars_suffix, + use_contrib_script_compatible_ec2_tag_keys, + expectation, +): + original_host_vars = { + "my_var": 1, + "placement": {"availability_zone": "us-east-1a"}, + "Tags": [{"Key": "Name", "Value": "my-name"}], + } + assert ( + inventory.prepare_host_vars( + original_host_vars, + hostvars_prefix, + hostvars_suffix, + use_contrib_script_compatible_ec2_tag_keys, + ) + == expectation + ) + + +def test_iter_entry(inventory): + hosts = [ + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "first-host://", + }, + { + "Placement": { + "AvailabilityZone": "us-east-1a", + }, + "PublicDnsName": "second-host", + "Tags": [{"Key": "Name", "Value": "my-name"}], + }, + ] + + entries = list(inventory.iter_entry(hosts, hostnames=[])) + assert len(entries) == 2 + assert entries[0][0] == "first_host___" + assert entries[1][0] == "second-host" + assert entries[1][1]["tags"]["Name"] == "my-name" + + entries = list( + inventory.iter_entry( + hosts, + hostnames=[], + hostvars_prefix="a_", + hostvars_suffix="_b", + use_contrib_script_compatible_ec2_tag_keys=True, + ) + ) + assert len(entries) == 2 + assert entries[0][0] == "first_host___" + assert entries[1][1]["a_tags_b"]["Name"] == "my-name" + + +def test_query_empty(inventory): + result = inventory._query("us-east-1", [], [], strict_permissions=True) + assert result == {"aws_ec2": []} + + +instance_foobar = {"InstanceId": "foobar"} +instance_barfoo = {"InstanceId": "barfoo"} + + +def test_query_empty_include_only(inventory): + inventory._get_instances_by_region = Mock(side_effect=[[instance_foobar]]) + result = inventory._query("us-east-1", [{"tag:Name": ["foobar"]}], [], strict_permissions=True) + assert result == {"aws_ec2": [instance_foobar]} + + +def test_query_empty_include_ordered(inventory): + inventory._get_instances_by_region = Mock(side_effect=[[instance_foobar], [instance_barfoo]]) + result = inventory._query("us-east-1", [{"tag:Name": ["foobar"]}, {"tag:Name": ["barfoo"]}], [], strict_permissions=True) + assert result == {"aws_ec2": [instance_barfoo, instance_foobar]} + inventory._get_instances_by_region.assert_called_with('us-east-1', [{'Name': 'tag:Name', 'Values': ['barfoo']}], True) + + +def test_query_empty_include_exclude(inventory): + inventory._get_instances_by_region = Mock(side_effect=[[instance_foobar], [instance_foobar]]) + result = inventory._query("us-east-1", [{"tag:Name": ["foobar"]}], [{"tag:Name": ["foobar"]}], strict_permissions=True) + assert result == {"aws_ec2": []} + + +def test_include_extra_api_calls_deprecated(inventory): + inventory.display.deprecate = Mock() + inventory._read_config_data = Mock() + inventory._set_credentials = Mock() + inventory._query = Mock(return_value=[]) + + inventory.parse(inventory=[], loader=None, path=None) + assert inventory.display.deprecate.call_count == 0 + + inventory._options["include_extra_api_calls"] = True + inventory.parse(inventory=[], loader=None, path=None) + assert inventory.display.deprecate.call_count == 1 diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/conftest.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/conftest.py new file mode 100644 index 000000000..a7d1e0475 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/conftest.py @@ -0,0 +1,31 @@ +# Copyright (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +import pytest + +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def patch_ansible_module(request, mocker): + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if 'ANSIBLE_MODULE_ARGS' not in request.param: + request.param = {'ANSIBLE_MODULE_ARGS': request.param} + if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']: + request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False + args = json.dumps(request.param) + else: + raise Exception('Malformed data to the patch_ansible_module pytest fixture') + + mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args)) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_build_run_instance_spec.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_build_run_instance_spec.py new file mode 100644 index 000000000..e889b676a --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_build_run_instance_spec.py @@ -0,0 +1,126 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel +import ansible_collections.amazon.aws.plugins.modules.ec2_instance as ec2_instance_module + + +@pytest.fixture +def params_object(): + params = { + 'iam_instance_profile': None, + 'exact_count': None, + 'count': None, + 'launch_template': None, + 'instance_type': None, + } + return params + + +@pytest.fixture +def ec2_instance(monkeypatch): + # monkey patches various ec2_instance module functions, we'll separately test the operation of + # these functions, we just care that it's passing the results into the right place in the + # instance spec. + monkeypatch.setattr(ec2_instance_module, 'build_top_level_options', lambda params: {'TOP_LEVEL_OPTIONS': sentinel.TOP_LEVEL}) + monkeypatch.setattr(ec2_instance_module, 'build_network_spec', lambda params: sentinel.NETWORK_SPEC) + monkeypatch.setattr(ec2_instance_module, 'build_volume_spec', lambda params: sentinel.VOlUME_SPEC) + monkeypatch.setattr(ec2_instance_module, 'build_instance_tags', lambda params: sentinel.TAG_SPEC) + monkeypatch.setattr(ec2_instance_module, 'determine_iam_role', lambda params: sentinel.IAM_PROFILE_ARN) + return ec2_instance_module + + +def _assert_defaults(instance_spec, to_skip=None): + if not to_skip: + to_skip = [] + + assert isinstance(instance_spec, dict) + + if 'TagSpecifications' not in to_skip: + assert 'TagSpecifications' in instance_spec + assert instance_spec['TagSpecifications'] is sentinel.TAG_SPEC + + if 'NetworkInterfaces' not in to_skip: + assert 'NetworkInterfaces' in instance_spec + assert instance_spec['NetworkInterfaces'] is sentinel.NETWORK_SPEC + + if 'BlockDeviceMappings' not in to_skip: + assert 'BlockDeviceMappings' in instance_spec + assert instance_spec['BlockDeviceMappings'] is sentinel.VOlUME_SPEC + + if 'IamInstanceProfile' not in to_skip: + # By default, this shouldn't be returned + assert 'IamInstanceProfile' not in instance_spec + + if 'MinCount' not in to_skip: + assert 'MinCount' in instance_spec + instance_spec['MinCount'] == 1 + + if 'MaxCount' not in to_skip: + assert 'MaxCount' in instance_spec + instance_spec['MaxCount'] == 1 + + if 'TOP_LEVEL_OPTIONS' not in to_skip: + assert 'TOP_LEVEL_OPTIONS' in instance_spec + assert instance_spec['TOP_LEVEL_OPTIONS'] is sentinel.TOP_LEVEL + + +def test_build_run_instance_spec_defaults(params_object, ec2_instance): + instance_spec = ec2_instance.build_run_instance_spec(params_object) + _assert_defaults(instance_spec) + + +def test_build_run_instance_spec_tagging(params_object, ec2_instance, monkeypatch): + # build_instance_tags can return None, RunInstance doesn't like this + monkeypatch.setattr(ec2_instance_module, 'build_instance_tags', lambda params: None) + instance_spec = ec2_instance.build_run_instance_spec(params_object) + _assert_defaults(instance_spec, ['TagSpecifications']) + assert 'TagSpecifications' not in instance_spec + + # if someone *explicitly* passes {} (rather than not setting it), then [] can be returned + monkeypatch.setattr(ec2_instance_module, 'build_instance_tags', lambda params: []) + instance_spec = ec2_instance.build_run_instance_spec(params_object) + _assert_defaults(instance_spec, ['TagSpecifications']) + assert 'TagSpecifications' in instance_spec + assert instance_spec['TagSpecifications'] == [] + + +def test_build_run_instance_spec_instance_profile(params_object, ec2_instance): + params_object['iam_instance_profile'] = sentinel.INSTANCE_PROFILE_NAME + instance_spec = ec2_instance.build_run_instance_spec(params_object) + _assert_defaults(instance_spec, ['IamInstanceProfile']) + assert 'IamInstanceProfile' in instance_spec + assert instance_spec['IamInstanceProfile'] == {'Arn': sentinel.IAM_PROFILE_ARN} + + +def test_build_run_instance_spec_count(params_object, ec2_instance): + # When someone passes 'count', that number of instances will be *launched* + params_object['count'] = sentinel.COUNT + instance_spec = ec2_instance.build_run_instance_spec(params_object) + _assert_defaults(instance_spec, ['MaxCount', 'MinCount']) + assert 'MaxCount' in instance_spec + assert 'MinCount' in instance_spec + assert instance_spec['MaxCount'] == sentinel.COUNT + assert instance_spec['MinCount'] == sentinel.COUNT + + +def test_build_run_instance_spec_exact_count(params_object, ec2_instance): + # The "exact_count" logic relies on enforce_count doing the math to figure out how many + # instances to start/stop. The enforce_count call is responsible for ensuring that 'to_launch' + # is set and is a positive integer. + params_object['exact_count'] = sentinel.EXACT_COUNT + params_object['to_launch'] = sentinel.TO_LAUNCH + instance_spec = ec2_instance.build_run_instance_spec(params_object) + + _assert_defaults(instance_spec, ['MaxCount', 'MinCount']) + assert 'MaxCount' in instance_spec + assert 'MinCount' in instance_spec + assert instance_spec['MaxCount'] == sentinel.TO_LAUNCH + assert instance_spec['MinCount'] == sentinel.TO_LAUNCH diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_determine_iam_role.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_determine_iam_role.py new file mode 100644 index 000000000..cdde74c97 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/ec2_instance/test_determine_iam_role.py @@ -0,0 +1,102 @@ +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +import sys + +from ansible_collections.amazon.aws.tests.unit.compat.mock import MagicMock +from ansible_collections.amazon.aws.tests.unit.compat.mock import sentinel +import ansible_collections.amazon.aws.plugins.modules.ec2_instance as ec2_instance_module +import ansible_collections.amazon.aws.plugins.module_utils.arn as utils_arn +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 + +try: + import botocore +except ImportError: + pass + +pytest.mark.skipif(not HAS_BOTO3, reason="test_determine_iam_role.py requires the python modules 'boto3' and 'botocore'") + + +def _client_error(code='GenericError'): + return botocore.exceptions.ClientError( + {'Error': {'Code': code, 'Message': 'Something went wrong'}, + 'ResponseMetadata': {'RequestId': '01234567-89ab-cdef-0123-456789abcdef'}}, + 'some_called_method') + + +@pytest.fixture +def params_object(): + params = { + 'instance_role': None, + 'exact_count': None, + 'count': None, + 'launch_template': None, + 'instance_type': None, + } + return params + + +class FailJsonException(Exception): + def __init__(self): + pass + + +@pytest.fixture +def ec2_instance(monkeypatch): + monkeypatch.setattr(ec2_instance_module, 'parse_aws_arn', lambda arn: None) + monkeypatch.setattr(ec2_instance_module, 'module', MagicMock()) + ec2_instance_module.module.fail_json.side_effect = FailJsonException() + ec2_instance_module.module.fail_json_aws.side_effect = FailJsonException() + return ec2_instance_module + + +def test_determine_iam_role_arn(params_object, ec2_instance, monkeypatch): + # Revert the default monkey patch to make it simple to try passing a valid ARNs + monkeypatch.setattr(ec2_instance, 'parse_aws_arn', utils_arn.parse_aws_arn) + + # Simplest example, someone passes a valid instance profile ARN + arn = ec2_instance.determine_iam_role('arn:aws:iam::123456789012:instance-profile/myprofile') + assert arn == 'arn:aws:iam::123456789012:instance-profile/myprofile' + + +def test_determine_iam_role_name(params_object, ec2_instance): + profile_description = {'InstanceProfile': {'Arn': sentinel.IAM_PROFILE_ARN}} + iam_client = MagicMock(**{"get_instance_profile.return_value": profile_description}) + ec2_instance_module.module.client.return_value = iam_client + + arn = ec2_instance.determine_iam_role(sentinel.IAM_PROFILE_NAME) + assert arn == sentinel.IAM_PROFILE_ARN + + +def test_determine_iam_role_missing(params_object, ec2_instance): + missing_exception = _client_error('NoSuchEntity') + iam_client = MagicMock(**{"get_instance_profile.side_effect": missing_exception}) + ec2_instance_module.module.client.return_value = iam_client + + with pytest.raises(FailJsonException) as exception: + arn = ec2_instance.determine_iam_role(sentinel.IAM_PROFILE_NAME) + + assert ec2_instance_module.module.fail_json_aws.call_count == 1 + assert ec2_instance_module.module.fail_json_aws.call_args.args[0] is missing_exception + assert 'Could not find' in ec2_instance_module.module.fail_json_aws.call_args.kwargs['msg'] + + +@pytest.mark.skipif(sys.version_info < (3, 8), reason='call_args behaviour changed in Python 3.8') +def test_determine_iam_role_missing(params_object, ec2_instance): + missing_exception = _client_error() + iam_client = MagicMock(**{"get_instance_profile.side_effect": missing_exception}) + ec2_instance_module.module.client.return_value = iam_client + + with pytest.raises(FailJsonException) as exception: + arn = ec2_instance.determine_iam_role(sentinel.IAM_PROFILE_NAME) + + assert ec2_instance_module.module.fail_json_aws.call_count == 1 + assert ec2_instance_module.module.fail_json_aws.call_args.args[0] is missing_exception + assert 'An error occurred while searching' in ec2_instance_module.module.fail_json_aws.call_args.kwargs['msg'] + assert 'Please try supplying the full ARN' in ec2_instance_module.module.fail_json_aws.call_args.kwargs['msg'] diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/a.pem b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/a.pem new file mode 100644 index 000000000..4412f3258 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/a.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFVTCCBD2gAwIBAgISAx4pnfwvGxYrrQhr/UXiN7HCMA0GCSqGSIb3DQEBCwUA +MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD +ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTA3MjUwMDI4NTdaFw0x +OTEwMjMwMDI4NTdaMBoxGDAWBgNVBAMTD2NyeXB0b2dyYXBoeS5pbzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAKJDpCL99DVo83587MrVp6gunmKRoUfY +vcgk5u2v0tB9OmZkcIY37z6AunHWr18Yj55zHmm6G8Nf35hmu3ql2A26WThCbmOe +WXbxhgarkningZI9opUWnI2dIllguVIsq99GzhpNnDdCb26s5+SRhJI4cr4hYaKC +XGDKooKWyXUX09SJTq7nW/1+pq3y9ZMvldRKjJALeAdwnC7kmUB6pK7q8J2VlpfQ +wqGu6q/WHVdgnhWARw3GEFJWDn9wkxBAF08CpzhVaEj+iK+Ut/1HBgNYwqI47h7S +q+qv0G2qklRVUtEM0zYRsp+y/6vivdbFLlPw8VaerbpJN3gLtpVNcGECAwEAAaOC +AmMwggJfMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUjbe0bE1aZ8HiqtwqUfCe15bF +V8UwHwYDVR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEE +YzBhMC4GCCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQu +b3JnMC8GCCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQu +b3JnLzAaBgNVHREEEzARgg9jcnlwdG9ncmFwaHkuaW8wTAYDVR0gBEUwQzAIBgZn +gQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5s +ZXRzZW5jcnlwdC5vcmcwggEDBgorBgEEAdZ5AgQCBIH0BIHxAO8AdgB0ftqDMa0z +EJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAAAWwmvtnXAAAEAwBHMEUCIFXHYX/E +xtbYCvjjQ3dN0HOLW1d8+aduktmax4mu3KszAiEAvTpxuSVVXJnVGA4tU2GOnI60 +sqTh/IK6hvrFN1k1HBUAdQApPFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0 +eAAAAWwmvtm9AAAEAwBGMEQCIDn7sgzD+7JzR+XTvjKf7VyLWwX37O8uwCfCTKo7 ++tEhAiB05bHiICU5wkfRBrwcvqXf4bPF7NT5LVlRQYzJ/hbpvzANBgkqhkiG9w0B +AQsFAAOCAQEAcMU8E6D+5WC07QSeTppRTboC++7YgQg5NiSWm7OE2FlyiRZXnu0Y +uBoaqAkZIqj7dom9wy1c1UauxOfM9lUZKhYnDTBu9tIhBAvCS0J0avv1j1KQygQ1 +qV+urJsunUwqV/vPWo1GfWophvyXVN6MAycv34ZXZvAjtG7oDcoQVLLvK1SIo2vu +4/dNkOQzaeZez8q6Ij9762TbBWaK5C789VMdUWZCADWoToPIK533cWbDEp4IhBU/ +K73d7lGGl7S59SjT2V/XE6eJS9Zlj0M+A8pf/8tjM/ImHAjlOHB02sM/VfZ7HAuZ +61TPxohL+e+X1FYeqIXYGXJmCEuB8WEmBg== +-----END CERTIFICATE----- diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/b.pem b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/b.pem new file mode 100644 index 000000000..2be4bca53 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/b.pem @@ -0,0 +1,47 @@ +-----BEGIN CERTIFICATE----- +MIIIUjCCB/egAwIBAgIRALiJR3zQjp0MevT/Hk89sfAwCgYIKoZIzj0EAwIwgZIx +CzAJBgNVBAYTAkdCMRswGQYDVQQIExJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAOBgNV +BAcTB1NhbGZvcmQxGjAYBgNVBAoTEUNPTU9ETyBDQSBMaW1pdGVkMTgwNgYDVQQD +Ey9DT01PRE8gRUNDIERvbWFpbiBWYWxpZGF0aW9uIFNlY3VyZSBTZXJ2ZXIgQ0Eg +MjAeFw0xOTA3MzEwMDAwMDBaFw0yMDAyMDYyMzU5NTlaMGwxITAfBgNVBAsTGERv +bWFpbiBDb250cm9sIFZhbGlkYXRlZDEhMB8GA1UECxMYUG9zaXRpdmVTU0wgTXVs +dGktRG9tYWluMSQwIgYDVQQDExtzc2wzODczMzcuY2xvdWRmbGFyZXNzbC5jb20w +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARPFdjdnBIJRPnHCPsCBJ/MmPytXnZX +KV6lD2bbG5EVNuUQln4Na8heCY+sfpV+SPuuiNzZxgDA46GvyzdRYFhxo4IGUTCC +Bk0wHwYDVR0jBBgwFoAUQAlhZ/C8g3FP3hIILG/U1Ct2PZYwHQYDVR0OBBYEFGLh +bHk1KAYIRfVwXA3L+yDf0CxjMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAA +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBPBgNVHSAESDBGMDoGCysG +AQQBsjEBAgIHMCswKQYIKwYBBQUHAgEWHWh0dHBzOi8vc2VjdXJlLmNvbW9kby5j +b20vQ1BTMAgGBmeBDAECATBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLmNv +bW9kb2NhNC5jb20vQ09NT0RPRUNDRG9tYWluVmFsaWRhdGlvblNlY3VyZVNlcnZl +ckNBMi5jcmwwgYgGCCsGAQUFBwEBBHwwejBRBggrBgEFBQcwAoZFaHR0cDovL2Ny +dC5jb21vZG9jYTQuY29tL0NPTU9ET0VDQ0RvbWFpblZhbGlkYXRpb25TZWN1cmVT +ZXJ2ZXJDQTIuY3J0MCUGCCsGAQUFBzABhhlodHRwOi8vb2NzcC5jb21vZG9jYTQu +Y29tMIIDkAYDVR0RBIIDhzCCA4OCG3NzbDM4NzMzNy5jbG91ZGZsYXJlc3NsLmNv +bYIMKi5hanJ0Y3QuY29tghMqLmFrcmVwYnVyY3UuZ2VuLnRyghUqLmFuZHJlYXNr +YW5lbGxvcy5jb22CDSouYW5zaWJsZS5jb22CGSouYXJ0b2Z0b3VjaC1raW5nd29v +ZC5jb22CFyouYm91bGRlcnN3YXRlcmhvbGUuY29tghcqLmJyb2Nrc3RlY2hzdXBw +b3J0LmNvbYIQKi5idXJjbGFyLndlYi50coIcKi5ob3Blc29uZ2ZyZW5jaGJ1bGxk +b2dzLm5ldIIMKi5odXJyZW0uY29tghAqLmh5dmVsaWNvbnMuY29tghAqLmthcm1h +Zml0LmNvLnVrghUqLmxvd3J5c3lzdGVtc2luYy5jb22CDioubWFuaWNydW4uY29t +ghUqLm11dHVvZmluYW5jaWVyYS5jb22CDyoucGlsZ3JpbWFnZS5waIINKi5wa2dh +bWVzLm9yZ4IbKi5ybHBjb25zdWx0aW5nc2VydmljZXMuY29tghYqLnJ1eWF0YWJp +cmxlcmkuZ2VuLnRyghQqLnJ5YW5hcHBoeXNpY3NjLmNvbYIVKi53ZWFyaXRiYWNr +d2FyZHMub3Jngg8qLnlldGlzbmFjay5jb22CCmFqcnRjdC5jb22CEWFrcmVwYnVy +Y3UuZ2VuLnRyghNhbmRyZWFza2FuZWxsb3MuY29tggthbnNpYmxlLmNvbYIXYXJ0 +b2Z0b3VjaC1raW5nd29vZC5jb22CFWJvdWxkZXJzd2F0ZXJob2xlLmNvbYIVYnJv +Y2tzdGVjaHN1cHBvcnQuY29tgg5idXJjbGFyLndlYi50coIaaG9wZXNvbmdmcmVu +Y2hidWxsZG9ncy5uZXSCCmh1cnJlbS5jb22CDmh5dmVsaWNvbnMuY29tgg5rYXJt +YWZpdC5jby51a4ITbG93cnlzeXN0ZW1zaW5jLmNvbYIMbWFuaWNydW4uY29tghNt +dXR1b2ZpbmFuY2llcmEuY29tgg1waWxncmltYWdlLnBoggtwa2dhbWVzLm9yZ4IZ +cmxwY29uc3VsdGluZ3NlcnZpY2VzLmNvbYIUcnV5YXRhYmlybGVyaS5nZW4udHKC +EnJ5YW5hcHBoeXNpY3NjLmNvbYITd2Vhcml0YmFja3dhcmRzLm9yZ4INeWV0aXNu +YWNrLmNvbTCCAQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2ALIeBcyLos2KIE6HZvkr +uYolIGdr2vpw57JJUy3vi5BeAAABbEVw8SgAAAQDAEcwRQIgE2YeTfb/d4BBUwpZ +ihWXSR+vRyNNUg8GlOak2MFMHv0CIQCLBvtU401m5/Psg9KirQZs321BSxgUKgSQ +m9M691d3eQB2AF6nc/nfVsDntTZIfdBJ4DJ6kZoMhKESEoQYdZaBcUVYAAABbEVw +8VgAAAQDAEcwRQIgGYsGfr3/mekjzMS9+ALAjx1ryfIfhXB/+UghTcw4Y8ICIQDS +K2L18WX3+Oh4TjJhjh5tV1iYyZVYivcwwbr7mtmOqjAKBggqhkjOPQQDAgNJADBG +AiEAjNt7LF78GV7snky9jwFcBsLH55ndzduvsrkJ7Ne1SgYCIQDsMJsTr9VP6kar +4Kv8V9zNBmpGrGNuE7A1GixBvzNaHA== +-----END CERTIFICATE----- diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.0.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.0.cert new file mode 100644 index 000000000..6997766ac --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.0.cert @@ -0,0 +1,121 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +MIIIHTCCBgWgAwIBAgIUCqrrzSfjzaoyB3DOxst2kMxFp/MwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyMTIyMjIy +OFoXDTIxMDgyMTIyMzIwMFowgZsxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBv +cmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MSYwJAYDVQQD +DB1kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMPAPH2y206qios2NMzlCNJv1mrwC1/8tH2HOqJGiYZB +O7QOBRSvJsV++IozCB8ap99e8B64OOAQPOyykrdXd2axhftmMb1SFMF56eukHSuz +KhKWRUgHs0UFRU51lDcBcOvphwJ+5SOgqrqKFFFBgJ0ZpcP54JpFwKIdh3ac10x2 +mBaW5ccqdv5X9oEMu1D/yivBmy34tsbLYyfttCjP76iVT7UVYHjHWynnIhsEyMsU +gdM90NzrTlrvTSi/EcCD1W3+8b0f+G1TI5rhHbKwR0n/mv5QLFm7EABoYPhxS8bX +B+9tE67yb0RyWbgvUiHySRynQLNMRpRx8Y9bA8uC8n8CAwEAAaOCA6QwggOgMAkG +A1UdEwQCMAAwHwYDVR0jBBgwFoAUsxKJtalLNbwVAPCA6dh4h/ETfHYwcwYIKwYB +BQUHAQEEZzBlMDcGCCsGAQUFBzAChitodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9i +YWwuY29tL3F2c3NsZzMuY3J0MCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5xdW92 +YWRpc2dsb2JhbC5jb20wgZ8GA1UdEQSBlzCBlIIdZGV2LmVuZXJneS5pbnNpZGUu +dGVsc3RyYS5jb22CJXJlcG9ydHMuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5j +b22CJ2dyZWVuc3luYy5kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbYIjbmdv +c3MuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wUQYDVR0gBEowSDBGBgwr +BgEEAb5YAAJkAQEwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2ds +b2JhbC5jb20vcmVwb3NpdG9yeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwEwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5j +b20vcXZzc2xnMy5jcmwwHQYDVR0OBBYEFEoJQRpPC/V5ZK3mMkszZE2v6vh+MA4G +A1UdDwEB/wQEAwIFoDCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAVhQGmi/X +wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFstk9Y+gAABAMARjBEAiBFMZa6 +O9iXVjy2kqQa54vgNFdU7shgFJJhm//fSAQZUAIgBIL/yPdh+XiuQS2xPhCzNYkh +bxf7BbN4qUISESgiZpsAdgBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZ +EwAAAWy2T1nKAAAEAwBHMEUCIG0tp63jLsDsfCTDlcvV5ItjRkbUJBnkxlPdP2PH +88sTAiEApgaPofVdn2hdI12iDDex72ta+9wpwQ1MxoaJn2nt+qEAdQDuS723dc5g +uuFCaR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWy2T1iJAAAEAwBGMEQCIE/mzEFp +CJUc71jvwJa4Px86R3ZYK4mHmUlQAUZqd0ZkAiBdEmT8xxTuleSUlYHEkKCK/FZX +L+vsYJpPrA9TsO5IsTANBgkqhkiG9w0BAQsFAAOCAgEApE9WLz3S8tqA9Dk3r9LF +rJy8km9cBt1O9SQZwFsduGKGdF3Fd+/Y0V7UrFDzrX+NIzqcmgBHKxaIXorMBF70 +ajMaaROP2ymkpEXnruEwoR47fbW+JRAWDRm2xnouQveQX9ZcgCLbBvAWBqpndQj2 +DGmLJhNz5GlFBjh3PQZlU1w8hU7TrDxa7M1GMtVnk8X+o3l/MX9iPeEs+PiC4dHD +hpj84RY1VQJz8+10rql47SB5YgbwcqaizTG4ax/OAv1JHNWtfAodIMX8Y8X00zoz +A20LQv880jCCNANVNbrXJ3h4X3xwW/C1X9vYk0shymZJbT5u17JbPD1cy39bA7kT +F4L7scdQRxvcqazYN4/IdgvgMji9OltiYufP88Ti8KB2tcl2accpiC5St/zllGD1 +hqEeYLMzjyvUKR/1uvURQQtc0DPvBRmvkB+aI4g+sLkTTFWj5bsA1vKU8SDCyMuB +RQV11DId5+RNNCmWnskORUZJQssvY49pnfCxCES2nt3l/XzTzVtLYmd6G9uAqVac +e2ibnmDrFVlmlyRsCiMfZl5/OTJzt7Cj3az59m5Syfw/lnS9YP82t/r/ufuKkO5Q +q5a9aI8DuNNmAjR4lpIJNqIpX/y+dG2aGmx4XTc31MR9szWtiTgOHe0MkMupOAL0 +qkHrBgwo1zjuTMf3QOg6Z5Q= +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0y +MjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vK +EymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPan +H05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jz +kE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5B +xxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uD +JGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNV +HSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1Ud +IwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4E +FgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/A +Vn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTs +Y4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48 +gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/ +TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMb +SXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.1.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.1.cert new file mode 100644 index 000000000..51f64f08d --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.1.cert @@ -0,0 +1,69 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +MIIIHTCCBgWgAwIBAgIUCqrrzSfjzaoyB3DOxst2kMxFp/MwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyMTIyMjIyOFoXDTIxMDgyMTIyMzIwMFowgZsxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBvcmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MSYwJAYDVQQD +DB1kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMPAPH2y206qios2NMzlCNJv1mrwC1/8tH2HOqJGiYZB +O7QOBRSvJsV++IozCB8ap99e8B64OOAQPOyykrdXd2axhftmMb1SFMF56eukHSuzKhKWRUgHs0UFRU51lDcBcOvphwJ+5SOgqrqKFFFBgJ0ZpcP54JpFwKIdh3ac10x2 +mBaW5ccqdv5X9oEMu1D/yivBmy34tsbLYyfttCjP76iVT7UVYHjHWynnIhsEyMsUgdM90NzrTlrvTSi/EcCD1W3+8b0f+G1TI5rhHbKwR0n/mv5QLFm7EABoYPhxS8bX +B+9tE67yb0RyWbgvUiHySRynQLNMRpRx8Y9bA8uC8n8CAwEAAaOCA6QwggOgMAkGA1UdEwQCMAAwHwYDVR0jBBgwFoAUsxKJtalLNbwVAPCA6dh4h/ETfHYwcwYIKwYB +BQUHAQEEZzBlMDcGCCsGAQUFBzAChitodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9iYWwuY29tL3F2c3NsZzMuY3J0MCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5xdW92 +YWRpc2dsb2JhbC5jb20wgZ8GA1UdEQSBlzCBlIIdZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb22CJXJlcG9ydHMuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5j +b22CJ2dyZWVuc3luYy5kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbYIjbmdvc3MuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wUQYDVR0gBEowSDBGBgwr +BgEEAb5YAAJkAQEwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2dsb2JhbC5jb20vcmVwb3NpdG9yeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwEwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5jb20vcXZzc2xnMy5jcmwwHQYDVR0OBBYEFEoJQRpPC/V5ZK3mMkszZE2v6vh+MA4G +A1UdDwEB/wQEAwIFoDCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAVhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFstk9Y+gAABAMARjBEAiBFMZa6 +O9iXVjy2kqQa54vgNFdU7shgFJJhm//fSAQZUAIgBIL/yPdh+XiuQS2xPhCzNYkhbxf7BbN4qUISESgiZpsAdgBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZ +EwAAAWy2T1nKAAAEAwBHMEUCIG0tp63jLsDsfCTDlcvV5ItjRkbUJBnkxlPdP2PH88sTAiEApgaPofVdn2hdI12iDDex72ta+9wpwQ1MxoaJn2nt+qEAdQDuS723dc5g +uuFCaR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWy2T1iJAAAEAwBGMEQCIE/mzEFpCJUc71jvwJa4Px86R3ZYK4mHmUlQAUZqd0ZkAiBdEmT8xxTuleSUlYHEkKCK/FZX +L+vsYJpPrA9TsO5IsTANBgkqhkiG9w0BAQsFAAOCAgEApE9WLz3S8tqA9Dk3r9LFrJy8km9cBt1O9SQZwFsduGKGdF3Fd+/Y0V7UrFDzrX+NIzqcmgBHKxaIXorMBF70 +ajMaaROP2ymkpEXnruEwoR47fbW+JRAWDRm2xnouQveQX9ZcgCLbBvAWBqpndQj2DGmLJhNz5GlFBjh3PQZlU1w8hU7TrDxa7M1GMtVnk8X+o3l/MX9iPeEs+PiC4dHD +hpj84RY1VQJz8+10rql47SB5YgbwcqaizTG4ax/OAv1JHNWtfAodIMX8Y8X00zozA20LQv880jCCNANVNbrXJ3h4X3xwW/C1X9vYk0shymZJbT5u17JbPD1cy39bA7kT +F4L7scdQRxvcqazYN4/IdgvgMji9OltiYufP88Ti8KB2tcl2accpiC5St/zllGD1hqEeYLMzjyvUKR/1uvURQQtc0DPvBRmvkB+aI4g+sLkTTFWj5bsA1vKU8SDCyMuB +RQV11DId5+RNNCmWnskORUZJQssvY49pnfCxCES2nt3l/XzTzVtLYmd6G9uAqVace2ibnmDrFVlmlyRsCiMfZl5/OTJzt7Cj3az59m5Syfw/lnS9YP82t/r/ufuKkO5Q +q5a9aI8DuNNmAjR4lpIJNqIpX/y+dG2aGmx4XTc31MR9szWtiTgOHe0MkMupOAL0qkHrBgwo1zjuTMf3QOg6Z5Q= +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQELBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0yMjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vKEymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPanH05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jzkE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5BxxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uDJGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNVHSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1UdIwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4EFgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/AVn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTsY4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMbSXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQELBQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMWn4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0jIaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0GaW/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtddbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeMHVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.2.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.2.cert new file mode 100644 index 000000000..ce2992411 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.2.cert @@ -0,0 +1,113 @@ +-----BEGIN CERTIFICATE----- +MIIIHTCCBgWgAwIBAgIUCqrrzSfjzaoyB3DOxst2kMxFp/MwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyMTIyMjIy +OFoXDTIxMDgyMTIyMzIwMFowgZsxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBv +cmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MSYwJAYDVQQD +DB1kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMPAPH2y206qios2NMzlCNJv1mrwC1/8tH2HOqJGiYZB +O7QOBRSvJsV++IozCB8ap99e8B64OOAQPOyykrdXd2axhftmMb1SFMF56eukHSuz +KhKWRUgHs0UFRU51lDcBcOvphwJ+5SOgqrqKFFFBgJ0ZpcP54JpFwKIdh3ac10x2 +mBaW5ccqdv5X9oEMu1D/yivBmy34tsbLYyfttCjP76iVT7UVYHjHWynnIhsEyMsU +gdM90NzrTlrvTSi/EcCD1W3+8b0f+G1TI5rhHbKwR0n/mv5QLFm7EABoYPhxS8bX +B+9tE67yb0RyWbgvUiHySRynQLNMRpRx8Y9bA8uC8n8CAwEAAaOCA6QwggOgMAkG +A1UdEwQCMAAwHwYDVR0jBBgwFoAUsxKJtalLNbwVAPCA6dh4h/ETfHYwcwYIKwYB +BQUHAQEEZzBlMDcGCCsGAQUFBzAChitodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9i +YWwuY29tL3F2c3NsZzMuY3J0MCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5xdW92 +YWRpc2dsb2JhbC5jb20wgZ8GA1UdEQSBlzCBlIIdZGV2LmVuZXJneS5pbnNpZGUu +dGVsc3RyYS5jb22CJXJlcG9ydHMuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5j +b22CJ2dyZWVuc3luYy5kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbYIjbmdv +c3MuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wUQYDVR0gBEowSDBGBgwr +BgEEAb5YAAJkAQEwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2ds +b2JhbC5jb20vcmVwb3NpdG9yeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwEwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5j +b20vcXZzc2xnMy5jcmwwHQYDVR0OBBYEFEoJQRpPC/V5ZK3mMkszZE2v6vh+MA4G +A1UdDwEB/wQEAwIFoDCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAVhQGmi/X +wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFstk9Y+gAABAMARjBEAiBFMZa6 +O9iXVjy2kqQa54vgNFdU7shgFJJhm//fSAQZUAIgBIL/yPdh+XiuQS2xPhCzNYkh +bxf7BbN4qUISESgiZpsAdgBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZ +EwAAAWy2T1nKAAAEAwBHMEUCIG0tp63jLsDsfCTDlcvV5ItjRkbUJBnkxlPdP2PH +88sTAiEApgaPofVdn2hdI12iDDex72ta+9wpwQ1MxoaJn2nt+qEAdQDuS723dc5g +uuFCaR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWy2T1iJAAAEAwBGMEQCIE/mzEFp +CJUc71jvwJa4Px86R3ZYK4mHmUlQAUZqd0ZkAiBdEmT8xxTuleSUlYHEkKCK/FZX +L+vsYJpPrA9TsO5IsTANBgkqhkiG9w0BAQsFAAOCAgEApE9WLz3S8tqA9Dk3r9LF +rJy8km9cBt1O9SQZwFsduGKGdF3Fd+/Y0V7UrFDzrX+NIzqcmgBHKxaIXorMBF70 +ajMaaROP2ymkpEXnruEwoR47fbW+JRAWDRm2xnouQveQX9ZcgCLbBvAWBqpndQj2 +DGmLJhNz5GlFBjh3PQZlU1w8hU7TrDxa7M1GMtVnk8X+o3l/MX9iPeEs+PiC4dHD +hpj84RY1VQJz8+10rql47SB5YgbwcqaizTG4ax/OAv1JHNWtfAodIMX8Y8X00zoz +A20LQv880jCCNANVNbrXJ3h4X3xwW/C1X9vYk0shymZJbT5u17JbPD1cy39bA7kT +F4L7scdQRxvcqazYN4/IdgvgMji9OltiYufP88Ti8KB2tcl2accpiC5St/zllGD1 +hqEeYLMzjyvUKR/1uvURQQtc0DPvBRmvkB+aI4g+sLkTTFWj5bsA1vKU8SDCyMuB +RQV11DId5+RNNCmWnskORUZJQssvY49pnfCxCES2nt3l/XzTzVtLYmd6G9uAqVac +e2ibnmDrFVlmlyRsCiMfZl5/OTJzt7Cj3az59m5Syfw/lnS9YP82t/r/ufuKkO5Q +q5a9aI8DuNNmAjR4lpIJNqIpX/y+dG2aGmx4XTc31MR9szWtiTgOHe0MkMupOAL0 +qkHrBgwo1zjuTMf3QOg6Z5Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0y +MjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vK +EymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPan +H05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jz +kE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5B +xxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uD +JGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNV +HSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1Ud +IwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4E +FgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/A +Vn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTs +Y4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48 +gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/ +TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMb +SXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.3.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.3.cert new file mode 100644 index 000000000..0c947b17b --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.3.cert @@ -0,0 +1,124 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +MIIIHTCCBgWgAwIBAgIUCqrrzSfjzaoyB3DOxst2kMxFp/MwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyMTIyMjIy +OFoXDTIxMDgyMTIyMzIwMFowgZsxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBv +cmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MSYwJAYDVQQD +DB1kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMPAPH2y206qios2NMzlCNJv1mrwC1/8tH2HOqJGiYZB +O7QOBRSvJsV++IozCB8ap99e8B64OOAQPOyykrdXd2axhftmMb1SFMF56eukHSuz +KhKWRUgHs0UFRU51lDcBcOvphwJ+5SOgqrqKFFFBgJ0ZpcP54JpFwKIdh3ac10x2 +mBaW5ccqdv5X9oEMu1D/yivBmy34tsbLYyfttCjP76iVT7UVYHjHWynnIhsEyMsU +gdM90NzrTlrvTSi/EcCD1W3+8b0f+G1TI5rhHbKwR0n/mv5QLFm7EABoYPhxS8bX +B+9tE67yb0RyWbgvUiHySRynQLNMRpRx8Y9bA8uC8n8CAwEAAaOCA6QwggOgMAkG +A1UdEwQCMAAwHwYDVR0jBBgwFoAUsxKJtalLNbwVAPCA6dh4h/ETfHYwcwYIKwYB +BQUHAQEEZzBlMDcGCCsGAQUFBzAChitodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9i +YWwuY29tL3F2c3NsZzMuY3J0MCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5xdW92 +YWRpc2dsb2JhbC5jb20wgZ8GA1UdEQSBlzCBlIIdZGV2LmVuZXJneS5pbnNpZGUu +dGVsc3RyYS5jb22CJXJlcG9ydHMuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5j +b22CJ2dyZWVuc3luYy5kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbYIjbmdv +c3MuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wUQYDVR0gBEowSDBGBgwr +BgEEAb5YAAJkAQEwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2ds +b2JhbC5jb20vcmVwb3NpdG9yeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwEwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5j +b20vcXZzc2xnMy5jcmwwHQYDVR0OBBYEFEoJQRpPC/V5ZK3mMkszZE2v6vh+MA4G +A1UdDwEB/wQEAwIFoDCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAVhQGmi/X +wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFstk9Y+gAABAMARjBEAiBFMZa6 +O9iXVjy2kqQa54vgNFdU7shgFJJhm//fSAQZUAIgBIL/yPdh+XiuQS2xPhCzNYkh +bxf7BbN4qUISESgiZpsAdgBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZ +EwAAAWy2T1nKAAAEAwBHMEUCIG0tp63jLsDsfCTDlcvV5ItjRkbUJBnkxlPdP2PH +88sTAiEApgaPofVdn2hdI12iDDex72ta+9wpwQ1MxoaJn2nt+qEAdQDuS723dc5g +uuFCaR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWy2T1iJAAAEAwBGMEQCIE/mzEFp +CJUc71jvwJa4Px86R3ZYK4mHmUlQAUZqd0ZkAiBdEmT8xxTuleSUlYHEkKCK/FZX +L+vsYJpPrA9TsO5IsTANBgkqhkiG9w0BAQsFAAOCAgEApE9WLz3S8tqA9Dk3r9LF +rJy8km9cBt1O9SQZwFsduGKGdF3Fd+/Y0V7UrFDzrX+NIzqcmgBHKxaIXorMBF70 +ajMaaROP2ymkpEXnruEwoR47fbW+JRAWDRm2xnouQveQX9ZcgCLbBvAWBqpndQj2 +DGmLJhNz5GlFBjh3PQZlU1w8hU7TrDxa7M1GMtVnk8X+o3l/MX9iPeEs+PiC4dHD +hpj84RY1VQJz8+10rql47SB5YgbwcqaizTG4ax/OAv1JHNWtfAodIMX8Y8X00zoz +A20LQv880jCCNANVNbrXJ3h4X3xwW/C1X9vYk0shymZJbT5u17JbPD1cy39bA7kT +F4L7scdQRxvcqazYN4/IdgvgMji9OltiYufP88Ti8KB2tcl2accpiC5St/zllGD1 +hqEeYLMzjyvUKR/1uvURQQtc0DPvBRmvkB+aI4g+sLkTTFWj5bsA1vKU8SDCyMuB +RQV11DId5+RNNCmWnskORUZJQssvY49pnfCxCES2nt3l/XzTzVtLYmd6G9uAqVac +e2ibnmDrFVlmlyRsCiMfZl5/OTJzt7Cj3az59m5Syfw/lnS9YP82t/r/ufuKkO5Q +q5a9aI8DuNNmAjR4lpIJNqIpX/y+dG2aGmx4XTc31MR9szWtiTgOHe0MkMupOAL0 +qkHrBgwo1zjuTMf3QOg6Z5Q= +-----END CERTIFICATE----- + + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + + + + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0y +MjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vK +EymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPan +H05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jz +kE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5B +xxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uD +JGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNV +HSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1Ud +IwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4E +FgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/A +Vn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTs +Y4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48 +gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/ +TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMb +SXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.4.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.4.cert new file mode 100644 index 000000000..adbb8edca --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-1.4.cert @@ -0,0 +1,86 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +MIIIHTCCBgWgAwIBAgIUCqrrzSfjzaoyB3DOxst2kMxFp/MwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyMTIyMjIy +OFoXDTIxMDgyMTIyMzIwMFowgZsxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBv +cmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MSYwJAYDVQQD +DB1kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMPAPH2y206qios2NMzlCNJv1mrwC1/8tH2HOqJGiYZB +O7QOBRSvJsV++IozCB8ap99e8B64OOAQPOyykrdXd2axhftmMb1SFMF56eukHSuz +KhKWRUgHs0UFRU51lDcBcOvphwJ+5SOgqrqKFFFBgJ0ZpcP54JpFwKIdh3ac10x2 +mBaW5ccqdv5X9oEMu1D/yivBmy34tsbLYyfttCjP76iVT7UVYHjHWynnIhsEyMsU +gdM90NzrTlrvTSi/EcCD1W3+8b0f+G1TI5rhHbKwR0n/mv5QLFm7EABoYPhxS8bX +B+9tE67yb0RyWbgvUiHySRynQLNMRpRx8Y9bA8uC8n8CAwEAAaOCA6QwggOgMAkG +A1UdEwQCMAAwHwYDVR0jBBgwFoAUsxKJtalLNbwVAPCA6dh4h/ETfHYwcwYIKwYB +BQUHAQEEZzBlMDcGCCsGAQUFBzAChitodHRwOi8vdHJ1c3QucXVvdmFkaXNnbG9i +YWwuY29tL3F2c3NsZzMuY3J0MCoGCCsGAQUFBzABhh5odHRwOi8vb2NzcC5xdW92 +YWRpc2dsb2JhbC5jb20wgZ8GA1UdEQSBlzCBlIIdZGV2LmVuZXJneS5pbnNpZGUu +dGVsc3RyYS5jb22CJXJlcG9ydHMuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5j +b22CJ2dyZWVuc3luYy5kZXYuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbYIjbmdv +c3MuZGV2LmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wUQYDVR0gBEowSDBGBgwr +BgEEAb5YAAJkAQEwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2ds +b2JhbC5jb20vcmVwb3NpdG9yeTAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH +AwEwOgYDVR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5xdW92YWRpc2dsb2JhbC5j +b20vcXZzc2xnMy5jcmwwHQYDVR0OBBYEFEoJQRpPC/V5ZK3mMkszZE2v6vh+MA4G +A1UdDwEB/wQEAwIFoDCCAXwGCisGAQQB1nkCBAIEggFsBIIBaAFmAHUAVhQGmi/X +wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFstk9Y+gAABAMARjBEAiBFMZa6 +O9iXVjy2kqQa54vgNFdU7shgFJJhm//fSAQZUAIgBIL/yPdh+XiuQS2xPhCzNYkh +bxf7BbN4qUISESgiZpsAdgBvU3asMfAxGdiZAKRRFf93FRwR2QLBACkGjbIImjfZ +EwAAAWy2T1nKAAAEAwBHMEUCIG0tp63jLsDsfCTDlcvV5ItjRkbUJBnkxlPdP2PH +88sTAiEApgaPofVdn2hdI12iDDex72ta+9wpwQ1MxoaJn2nt+qEAdQDuS723dc5g +uuFCaR+r4Z5mow9+X7By2IMAxHuJeqj9ywAAAWy2T1iJAAAEAwBGMEQCIE/mzEFp +CJUc71jvwJa4Px86R3ZYK4mHmUlQAUZqd0ZkAiBdEmT8xxTuleSUlYHEkKCK/FZX +L+vsYJpPrA9TsO5IsTANBgkqhkiG9w0BAQsFAAOCAgEApE9WLz3S8tqA9Dk3r9LF +rJy8km9cBt1O9SQZwFsduGKGdF3Fd+/Y0V7UrFDzrX+NIzqcmgBHKxaIXorMBF70 +ajMaaROP2ymkpEXnruEwoR47fbW+JRAWDRm2xnouQveQX9ZcgCLbBvAWBqpndQj2 +DGmLJhNz5GlFBjh3PQZlU1w8hU7TrDxa7M1GMtVnk8X+o3l/MX9iPeEs+PiC4dHD +hpj84RY1VQJz8+10rql47SB5YgbwcqaizTG4ax/OAv1JHNWtfAodIMX8Y8X00zoz +A20LQv880jCCNANVNbrXJ3h4X3xwW/C1X9vYk0shymZJbT5u17JbPD1cy39bA7kT +F4L7scdQRxvcqazYN4/IdgvgMji9OltiYufP88Ti8KB2tcl2accpiC5St/zllGD1 +hqEeYLMzjyvUKR/1uvURQQtc0DPvBRmvkB+aI4g+sLkTTFWj5bsA1vKU8SDCyMuB +RQV11DId5+RNNCmWnskORUZJQssvY49pnfCxCES2nt3l/XzTzVtLYmd6G9uAqVac +e2ibnmDrFVlmlyRsCiMfZl5/OTJzt7Cj3az59m5Syfw/lnS9YP82t/r/ufuKkO5Q +q5a9aI8DuNNmAjR4lpIJNqIpX/y+dG2aGmx4XTc31MR9szWtiTgOHe0MkMupOAL0 +qkHrBgwo1zjuTMf3QOg6Z5Q= +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0y +MjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vK +EymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPan +H05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jz +kE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5B +xxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uD +JGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNV +HSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1Ud +IwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4E +FgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/A +Vn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTs +Y4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48 +gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/ +TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMb +SXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-4.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-4.cert new file mode 100644 index 000000000..2b82edf6c --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/chain-4.cert @@ -0,0 +1,121 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=prod.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +MIIIJDCCBgygAwIBAgIUP9S/56XvOFzWk1vp1+7JJT17brEwDQYJKoZIhvcNAQEL +BQAwTTELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxIzAh +BgNVBAMTGlF1b1ZhZGlzIEdsb2JhbCBTU0wgSUNBIEczMB4XDTE5MDgyNzAzMTU1 +NFoXDTIxMDgyNzAzMjUwMFowgZwxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0 +b3JpYTESMBAGA1UEBwwJTWVsYm91cm5lMSQwIgYDVQQKDBtUZWxzdHJhIENvcnBv +cmF0aW9uIExpbWl0ZWQxFzAVBgNVBAsMDlRlbHN0cmEgRW5lcmd5MScwJQYDVQQD +DB5wcm9kLmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb20wggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCrRouNZFOZwM1qyAU6v6ag9fzSx3y8zz36nR8HuqbA +/wqrbMmnpofwdx/9u1bilsHfJzIODv0hm7aGk+neTK3DIapiII3m0HKW0v+GLsl7 +JkDuc2o3XlakcXlA45qDKCZXbXZtY4/kdxKG0OSUZi7oQqohhYl/c/ojrTiey+4G +KhEVqWwOuQ1OC1DRw4qMH54d0koFxxSLPJ8JiiztLlK/e9n8BoJikj5fBqWy5R1F +bGXCdzjcfmPV6iSOzJShpUgj4ga91mO6j3S6LLfK5ibbTlY+pmUxUT+m9nKMon3h +mFptTYo9t9vUF/a/owjRxNLg01fJLNjYn8QV2vQvODGfAgMBAAGjggOqMIIDpjAJ +BgNVHRMEAjAAMB8GA1UdIwQYMBaAFLMSibWpSzW8FQDwgOnYeIfxE3x2MHMGCCsG +AQUFBwEBBGcwZTA3BggrBgEFBQcwAoYraHR0cDovL3RydXN0LnF1b3ZhZGlzZ2xv +YmFsLmNvbS9xdnNzbGczLmNydDAqBggrBgEFBQcwAYYeaHR0cDovL29jc3AucXVv +dmFkaXNnbG9iYWwuY29tMIGjBgNVHREEgZswgZiCHnByb2QuZW5lcmd5Lmluc2lk +ZS50ZWxzdHJhLmNvbYImcmVwb3J0cy5wcm9kLmVuZXJneS5pbnNpZGUudGVsc3Ry +YS5jb22CKGdyZWVuc3luYy5wcm9kLmVuZXJneS5pbnNpZGUudGVsc3RyYS5jb22C +JG5nb3NzLnByb2QuZW5lcmd5Lmluc2lkZS50ZWxzdHJhLmNvbTBRBgNVHSAESjBI +MEYGDCsGAQQBvlgAAmQBATA2MDQGCCsGAQUFBwIBFihodHRwOi8vd3d3LnF1b3Zh +ZGlzZ2xvYmFsLmNvbS9yZXBvc2l0b3J5MB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr +BgEFBQcDATA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnF1b3ZhZGlzZ2xv +YmFsLmNvbS9xdnNzbGczLmNybDAdBgNVHQ4EFgQUoIME5TykVAI8VF5g0zeh0xdv +i3owDgYDVR0PAQH/BAQDAgWgMIIBfgYKKwYBBAHWeQIEAgSCAW4EggFqAWgAdgBW +FAaaL9fC7NP14b1Esj7HRna5vJkRXMDvlJhV1onQ3QAAAWzRG8r0AAAEAwBHMEUC +IQDShuQyYMiy7KKxWOzffolVIcPRgWD7ClNEbIcUATHKyQIgXnTZBXcpcbXBQXLs +tFuvY36TbKIYc2ql2nmdydGQ9wcAdgCkuQmQtBhYFIe7E6LMZ3AKPDWYBPkb37jj +d80OyA3cEAAAAWzRG8sAAAAEAwBHMEUCIGsLEoA9S7pNE3VoNZHxl2IAdeP3Dy2Q +Mk0rM46hp6CRAiEA08rOjswSdcn7qgDEoiyvlcrOTIFJAEcMlxSY65yLVUwAdgBV +gdTCFpA2AUrqC5tXPFPwwOQ4eHAlCBcvo6odBxPTDAAAAWzRG8q7AAAEAwBHMEUC +IAkVCcTFG8MBDI58JKIhMlPbzkdrKnYY3Kp9KqWuTAvMAiEAipeI7RCLBk8+T/p+ +gY7+vtFZxKDthcJMUpZz7qmica0wDQYJKoZIhvcNAQELBQADggIBAESe0U1qArxL +F2uk65q6x6HBcZuSocpceokzcUBv07Kxs6UJU9ybTbl8VYPuC+OUdpvut1kOJCJm +1TRrr5KMh+9as42xkbKRZnh5TQt7aHmVcLHLfA4x0UrELfNX3fVTDxwDAPAhE5oM +0w+d1foLakh7dXKKSxobEI3KRwFp19iuZeIqwI8XMWMr9ajhTC0T7D2QvKotpNBS +sNDHiIE3IXoa9o7UiOG8IfW0wAt7CEygv0F7ctHRTcQSP/SJIGYOUZ7uotULVL5i +elG31Y83Jx3sPNCy4IZfCip6Gw7MgsN2CZGApqi49edSqDWyRIfmCeXtMc7XI7Md +kqqWxbqGGTdYJCucoGqahqRR+BI9anEqTD9T5Gy0TpCi2pgp1i7czza71nfz0PcN +R0pw/1lqb9AqmJ2XELpBpo82B9XGple9thpincai7jPk3ezY5eEvDTmkHRlUFCp8 +8M66Ga19hZTgnHPWDKZYZzuZ7Lcl2WbapFOYYHJggSpBRy4GkH6eTSkUB9G9k8vU +gbvtS7sR5ggecbCBu0M4TWYmnUojR8UXtr0oOTlXysTHVGs5Tx9ChhOLyUqhX8tM +1zSDT8JJvbbw4RqpGzBKTNaO5nxRLgKVQOQdM8f1kjMr9/U58Lc4UiaTkJM14VfK +8GfV8+K/vRCBtME53ILvm1l18jtakG3c +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIGFzCCA/+gAwIBAgIUftbnnMmtgcTIGT75XUQodw40ExcwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjExMDYxNDUwMThaFw0y +MjExMDYxNDUwMThaME0xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMSMwIQYDVQQDExpRdW9WYWRpcyBHbG9iYWwgU1NMIElDQSBHMzCCAiIw +DQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANf8Od17be6c6lTGJDhEXpmkTs4y +Q39Rr5VJyBeWCg06nSS71s6xF3sZvKcV0MbXlXCYM2ZX7cNTbJ81gs7uDsKFp+vK +EymiKyEiI2SImOtECNnSg+RVR4np/xz/UlC0yFUisH75cZsJ8T1pkGMfiEouR0EM +7O0uFgoboRfUP582TTWy0F7ynSA6YfGKnKj0OFwZJmGHVkLs1VevWjhj3R1fsPan +H05P5moePFnpQdj1FofoSxUHZ0c7VB+sUimboHm/uHNY1LOsk77qiSuVC5/yrdg3 +2EEfP/mxJYT4r/5UiD7VahySzeZHzZ2OibQm2AfgfMN3l57lCM3/WPQBhMAPS1jz +kE+7MjajM2f0aZctimW4Hasrj8AQnfAdHqZehbhtXaAlffNEzCdpNK584oCTVR7N +UR9iZFx83ruTqpo+GcLP/iSYqhM4g7fy45sNhU+IS+ca03zbxTl3TTlkofXunI5B +xxE30eGSQpDZ5+iUJcEOAuVKrlYocFbB3KF45hwcbzPWQ1DcO2jFAapOtQzeS+MZ +yZzT2YseJ8hQHKu8YrXZWwKaNfyl8kFkHUBDICowNEoZvBwRCQp8sgqL6YRZy0uD +JGxmnC2e0BVKSjcIvmq/CRWH7yiTk9eWm73xrsg9iIyD/kwJEnLyIk8tR5V8p/hc +1H2AjDrZH12PsZ45AgMBAAGjgfMwgfAwEgYDVR0TAQH/BAgwBgEB/wIBATARBgNV +HSAECjAIMAYGBFUdIAAwOgYIKwYBBQUHAQEELjAsMCoGCCsGAQUFBzABhh5odHRw +Oi8vb2NzcC5xdW92YWRpc2dsb2JhbC5jb20wDgYDVR0PAQH/BAQDAgEGMB8GA1Ud +IwQYMBaAFO3nb3Zav2DsSVvGpXe7chZxm8Q9MDsGA1UdHwQ0MDIwMKAuoCyGKmh0 +dHA6Ly9jcmwucXVvdmFkaXNnbG9iYWwuY29tL3F2cmNhMmczLmNybDAdBgNVHQ4E +FgQUsxKJtalLNbwVAPCA6dh4h/ETfHYwDQYJKoZIhvcNAQELBQADggIBAFGm1Fqp +RMiKr7a6h707M+km36PVXZnX1NZocCn36MrfRvphotbOCDm+GmRkar9ZMGhc8c/A +Vn7JSCjwF9jNOFIOUyNLq0w4luk+Pt2YFDbgF8IDdx53xIo8Gv05e9xpTvQYaIto +qeHbQjGXfSGc91olfX6JUwZlxxbhdJH+rxTFAg0jcbqToJoScWTfXSr1QRcNbSTs +Y4CPG6oULsnhVvrzgldGSK+DxFi2OKcDsOKkV7W4IGg8Do2L/M588AfBnV8ERzpl +qgMBBQxC2+0N6RdFHbmZt0HQE/NIg1s0xcjGx1XW3YTOfje31rmAXKHOehm4Bu48 +gr8gePq5cdQ2W9tA0Dnytb9wzH2SyPPIXRI7yNxaX9H8wYeDeeiKSSmQtfh1v5cV +7RXvm8F6hLJkkco/HOW3dAUwZFcKsUH+1eUJKLN18eDGwB8yGawjHvOKqcfg5Lf/ +TvC7hgcx7pDYaCCaqHaekgUwXbB2Enzqr1fdwoU1c01W5YuQAtAx5wk1bf34Yq/J +ph7wNXGvo88N0/EfP9AdVGmJzy7VuRXeVAOyjKAIeADMlwpjBRhcbs9m3dkqvoMb +SXKJxv/hFmNgEOvOlaFsXX1dbKg1v+C1AzKAFdiuAIa62JzASiEhigqNSdqdTsOh +8W8hdONuKKpe9zKedhBFAvuxhDgKmnySglYc +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-a.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-a.cert new file mode 100644 index 000000000..1d9bbe213 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-a.cert @@ -0,0 +1,18 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +aaa +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +bbb +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +ccc +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-b.cert b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-b.cert new file mode 100644 index 000000000..1d9bbe213 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/certs/simple-chain-b.cert @@ -0,0 +1,18 @@ +subject=/C=AU/ST=Victoria/L=Melbourne/O=Telstra Corporation Limited/OU=Telstra Energy/CN=dev.energy.inside.telstra.com +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +-----BEGIN CERTIFICATE----- +aaa +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Global SSL ICA G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +bbb +-----END CERTIFICATE----- + +subject=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +issuer=/C=BM/O=QuoVadis Limited/CN=QuoVadis Root CA 2 G3 +-----BEGIN CERTIFICATE----- +ccc +-----END CERTIFICATE----- + diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/thezip.zip b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/thezip.zip Binary files differnew file mode 100644 index 000000000..6eaefdd5e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/fixtures/thezip.zip diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/.gitkeep b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/.gitkeep diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..36f1489ba --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.CreateStack_1.json @@ -0,0 +1,17 @@ +{ + "status_code": 200, + "data": { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "03fbfc36-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "03fbfc36-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:07 GMT", + "content-length": "393", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DeleteStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DeleteStack_1.json new file mode 100644 index 000000000..d526155a5 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DeleteStack_1.json @@ -0,0 +1,16 @@ +{ + "status_code": 200, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "170d1e02-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "170d1e02-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:39 GMT", + "content-length": "212", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..3758c77b7 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,38 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "043d4a05-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "043d4a05-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:08 GMT", + "content-length": "1183", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..2c5a7655e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,80 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "075d9d71-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "075d9d71-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:13 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_3.json new file mode 100644 index 000000000..cf2c24502 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_3.json @@ -0,0 +1,80 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0a7eb31b-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0a7eb31b-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:19 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_4.json new file mode 100644 index 000000000..32ee9c1c5 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_4.json @@ -0,0 +1,80 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0d9e1c06-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0d9e1c06-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:24 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_5.json new file mode 100644 index 000000000..b547cd4d8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_5.json @@ -0,0 +1,80 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "10bd84ca-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "10bd84ca-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:29 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_6.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_6.json new file mode 100644 index 000000000..15bd043ab --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_6.json @@ -0,0 +1,100 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_COMPLETE-2017-10-20T19:51:33.200Z", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 33, + "microsecond": 200000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "13dbb3fd-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "13dbb3fd-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "3490", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:34 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_7.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_7.json new file mode 100644 index 000000000..87db7c59e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStackEvents_7.json @@ -0,0 +1,119 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "140d7220-b5d0-11e7-933f-50a686be7356", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 35, + "microsecond": 121000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_COMPLETE-2017-10-20T19:51:33.200Z", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 33, + "microsecond": 200000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-basic-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "LogicalResourceId": "ansible-test-basic-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "16faf590-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "16faf590-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "4276", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:39 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..7acdb3acf --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_1.json @@ -0,0 +1,40 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EnableTerminationProtection": false, + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "StackStatusReason": "User Initiated", + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "042974db-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "042974db-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:08 GMT", + "content-length": "975", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_2.json new file mode 100644 index 000000000..0ed674b20 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_2.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "074b26dc-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "074b26dc-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:13 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_3.json new file mode 100644 index 000000000..633c5e159 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_3.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0a6cb1b3-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0a6cb1b3-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:18 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_4.json new file mode 100644 index 000000000..e5ca69dda --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_4.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0d8cddf1-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0d8cddf1-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:23 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_5.json new file mode 100644 index 000000000..31a3057cd --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_5.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "10ac94d5-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "10ac94d5-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:28 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_6.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_6.json new file mode 100644 index 000000000..90ca7467c --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_6.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "13caeb1b-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "13caeb1b-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:33 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_7.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_7.json new file mode 100644 index 000000000..905c04f48 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/basic_s3_stack/cloudformation.DescribeStacks_7.json @@ -0,0 +1,45 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-basic-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "Outputs": [ + { + "OutputKey": "TheName", + "OutputValue": "ansible-test-basic-yaml-mybucket-13m2y4v8bptj4" + } + ], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-basic-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_COMPLETE", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "16ea53bb-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "16ea53bb-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:39 GMT", + "content-length": "1115", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..9084936a4 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.CreateStack_1.json @@ -0,0 +1,17 @@ +{ + "status_code": 200, + "data": { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "03fbfc36-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "03fbfc36-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:07 GMT", + "content-length": "393", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DeleteStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DeleteStack_1.json new file mode 100644 index 000000000..d526155a5 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DeleteStack_1.json @@ -0,0 +1,16 @@ +{ + "status_code": 200, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "170d1e02-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "170d1e02-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:39 GMT", + "content-length": "212", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..399eab496 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "043d4a05-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "043d4a05-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:08 GMT", + "content-length": "1183", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..f57dbf536 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,83 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "075d9d71-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "075d9d71-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:13 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_3.json new file mode 100644 index 000000000..c8b4d694d --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_3.json @@ -0,0 +1,83 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0a7eb31b-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0a7eb31b-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:19 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_4.json new file mode 100644 index 000000000..8bb03eded --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_4.json @@ -0,0 +1,83 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0d9e1c06-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0d9e1c06-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:24 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_5.json new file mode 100644 index 000000000..311949d08 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_5.json @@ -0,0 +1,83 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "10bd84ca-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "10bd84ca-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "2730", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:29 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_6.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_6.json new file mode 100644 index 000000000..ddab94a51 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_6.json @@ -0,0 +1,104 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_COMPLETE-2017-10-20T19:51:33.200Z", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 33, + "microsecond": 200000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "13dbb3fd-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "13dbb3fd-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "3490", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:34 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_7.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_7.json new file mode 100644 index 000000000..86da5fb45 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStackEvents_7.json @@ -0,0 +1,124 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "140d7220-b5d0-11e7-933f-50a686be7356", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 35, + "microsecond": 121000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_COMPLETE-2017-10-20T19:51:33.200Z", + "ResourceStatus": "CREATE_COMPLETE", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 33, + "microsecond": 200000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:12.754Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 12, + "microsecond": 754000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "Resource creation Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "MyBucket-CREATE_IN_PROGRESS-2017-10-20T19:51:11.159Z", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::S3::Bucket", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 11, + "microsecond": 159000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "ResourceProperties": "{}\n", + "PhysicalResourceId": "", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "MyBucket" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EventId": "04032730-b5d0-11e7-86b8-503ac93168c5", + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "ResourceStatusReason": "User Initiated", + "StackName": "ansible-test-client-request-token-yaml", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "ClientRequestToken": "3faf3fb5-b289-41fc-b940-44151828f6cf", + "LogicalResourceId": "ansible-test-client-request-token-yaml" + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "16faf590-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "16faf590-b5d0-11e7-ae09-550cfe4b2358", + "vary": "Accept-Encoding", + "content-length": "4276", + "content-type": "text/xml", + "date": "Fri, 20 Oct 2017 19:51:39 GMT" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..7734b0ca3 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_1.json @@ -0,0 +1,40 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "EnableTerminationProtection": false, + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "StackStatusReason": "User Initiated", + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "042974db-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "042974db-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:08 GMT", + "content-length": "975", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_2.json new file mode 100644 index 000000000..0a1e74d70 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_2.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "074b26dc-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "074b26dc-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:13 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_3.json new file mode 100644 index 000000000..12d5839f8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_3.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0a6cb1b3-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0a6cb1b3-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:18 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_4.json new file mode 100644 index 000000000..a3cb0a8ca --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_4.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "0d8cddf1-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "0d8cddf1-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:23 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_5.json new file mode 100644 index 000000000..251d71fa1 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_5.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "10ac94d5-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "10ac94d5-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:28 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_6.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_6.json new file mode 100644 index 000000000..2251125f6 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_6.json @@ -0,0 +1,39 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "13caeb1b-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "13caeb1b-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:33 GMT", + "content-length": "913", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_7.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_7.json new file mode 100644 index 000000000..aa8c7fd09 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/client_request_token_s3_stack/cloudformation.DescribeStacks_7.json @@ -0,0 +1,45 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-client-request-token-yaml/04023cd0-b5d0-11e7-86b8-503ac93168c5", + "Description": "Basic template that creates an S3 bucket", + "Tags": [], + "Outputs": [ + { + "OutputKey": "TheName", + "OutputValue": "ansible-test-client-request-token-yaml-mybucket-13m2y4v8bptj4" + } + ], + "EnableTerminationProtection": false, + "CreationTime": { + "hour": 19, + "__class__": "datetime", + "month": 10, + "second": 8, + "microsecond": 324000, + "year": 2017, + "day": 20, + "minute": 51 + }, + "StackName": "ansible-test-client-request-token-yaml", + "NotificationARNs": [], + "StackStatus": "CREATE_COMPLETE", + "DisableRollback": false, + "RollbackConfiguration": {} + } + ], + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 200, + "RequestId": "16ea53bb-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "16ea53bb-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:39 GMT", + "content-length": "1115", + "content-type": "text/xml" + } + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..109feacd9 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,22 @@ +{ + "status_code": 400, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 400, + "RequestId": "179d9e46-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "179d9e46-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:40 GMT", + "content-length": "301", + "content-type": "text/xml", + "connection": "close" + } + }, + "Error": { + "Message": "Stack [ansible-test-nonexist] does not exist", + "Code": "ValidationError", + "Type": "Sender" + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..589f92cc6 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,22 @@ +{ + "status_code": 400, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 400, + "RequestId": "17d80f44-b5d0-11e7-80c4-9f499f779cdb", + "HTTPHeaders": { + "x-amzn-requestid": "17d80f44-b5d0-11e7-80c4-9f499f779cdb", + "date": "Fri, 20 Oct 2017 19:51:40 GMT", + "content-length": "301", + "content-type": "text/xml", + "connection": "close" + } + }, + "Error": { + "Message": "Stack [ansible-test-nonexist] does not exist", + "Code": "ValidationError", + "Type": "Sender" + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..ea227415c --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/delete_nonexistent_stack/cloudformation.DescribeStacks_1.json @@ -0,0 +1,22 @@ +{ + "status_code": 400, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 400, + "RequestId": "175fab26-b5d0-11e7-9d9b-45815c77100a", + "HTTPHeaders": { + "x-amzn-requestid": "175fab26-b5d0-11e7-9d9b-45815c77100a", + "date": "Fri, 20 Oct 2017 19:51:40 GMT", + "content-length": "307", + "content-type": "text/xml", + "connection": "close" + } + }, + "Error": { + "Message": "Stack with id ansible-test-nonexist does not exist", + "Code": "ValidationError", + "Type": "Sender" + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/get_nonexistent_stack/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/get_nonexistent_stack/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..cf29c6c76 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/get_nonexistent_stack/cloudformation.DescribeStacks_1.json @@ -0,0 +1,22 @@ +{ + "status_code": 400, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 400, + "RequestId": "181566c8-b5d0-11e7-9d9b-45815c77100a", + "HTTPHeaders": { + "x-amzn-requestid": "181566c8-b5d0-11e7-9d9b-45815c77100a", + "date": "Fri, 20 Oct 2017 19:51:41 GMT", + "content-length": "307", + "content-type": "text/xml", + "connection": "close" + } + }, + "Error": { + "Message": "Stack with id ansible-test-nonexist does not exist", + "Code": "ValidationError", + "Type": "Sender" + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/invalid_template_json/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/invalid_template_json/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..7ad6cac96 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/invalid_template_json/cloudformation.CreateStack_1.json @@ -0,0 +1,22 @@ +{ + "status_code": 400, + "data": { + "ResponseMetadata": { + "RetryAttempts": 0, + "HTTPStatusCode": 400, + "RequestId": "03b1107f-b5d0-11e7-ae09-550cfe4b2358", + "HTTPHeaders": { + "x-amzn-requestid": "03b1107f-b5d0-11e7-ae09-550cfe4b2358", + "date": "Fri, 20 Oct 2017 19:51:07 GMT", + "content-length": "320", + "content-type": "text/xml", + "connection": "close" + } + }, + "Error": { + "Message": "Template format error: JSON not well-formed. (line 4, column 4)", + "Code": "ValidationError", + "Type": "Sender" + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..64c8e1f23 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.CreateStack_1.json @@ -0,0 +1,17 @@ +{ + "status_code": 200, + "data": { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResponseMetadata": { + "RequestId": "c741ebcd-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "c741ebcd-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "407", + "date": "Tue, 26 Feb 2019 21:37:55 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..7a6a49644 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,38 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "c74b1310-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "c7b0b337-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "c7b0b337-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "1153", + "date": "Tue, 26 Feb 2019 21:37:56 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..6218ed8b8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,101 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:38:01.107Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 1, + "microsecond": 107000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: ca5769ae-3a0e-11e9-a183-3f277586a4cb)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.657Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 657000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.221Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 221000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "c74b1310-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "caf667e9-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "caf667e9-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "4312", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:38:01 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_3.json new file mode 100644 index 000000000..cde6beb8e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_3.json @@ -0,0 +1,121 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "cafc8250-3a0e-11e9-86c5-02035744c0fa", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Delete requested by user." + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:38:01.107Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 1, + "microsecond": 107000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: ca5769ae-3a0e-11e9-a183-3f277586a4cb)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.657Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 657000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.221Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 221000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "c74b1310-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "ce498af1-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "ce498af1-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "5207", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:38:06 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_4.json new file mode 100644 index 000000000..4f35d6ddc --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_4.json @@ -0,0 +1,180 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "d19c8600-3a0e-11e9-a4ba-0a3524ef8042", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 13, + "microsecond": 177000 + }, + "ResourceStatus": "DELETE_COMPLETE" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-DELETE_COMPLETE-2019-02-26T21:38:12.486Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 12, + "microsecond": 486000 + }, + "ResourceStatus": "DELETE_COMPLETE", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-DELETE_IN_PROGRESS-2019-02-26T21:38:12.139Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 12, + "microsecond": 139000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "cafc8250-3a0e-11e9-86c5-02035744c0fa", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Delete requested by user." + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:38:01.107Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 1, + "microsecond": 107000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: ca5769ae-3a0e-11e9-a183-3f277586a4cb)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.657Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 657000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.221Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 221000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "c74b1310-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "d19fbb1b-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "d19fbb1b-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "7857", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:38:12 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_5.json new file mode 100644 index 000000000..68a743f89 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStackEvents_5.json @@ -0,0 +1,180 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "d19c8600-3a0e-11e9-a4ba-0a3524ef8042", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 13, + "microsecond": 177000 + }, + "ResourceStatus": "DELETE_COMPLETE" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-DELETE_COMPLETE-2019-02-26T21:38:12.486Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 12, + "microsecond": 486000 + }, + "ResourceStatus": "DELETE_COMPLETE", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-DELETE_IN_PROGRESS-2019-02-26T21:38:12.139Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 12, + "microsecond": 139000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "cafc8250-3a0e-11e9-86c5-02035744c0fa", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Delete requested by user." + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:38:01.107Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 1, + "microsecond": 107000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: ca5769ae-3a0e-11e9-a183-3f277586a4cb)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.657Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-8jlpw72yz5x8", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 657000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:38:00.221Z", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 0, + "microsecond": 221000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "EventId": "c74b1310-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "LogicalResourceId": "ansible-test-on-create-failure-delete", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "d4fbddab-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "d4fbddab-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "7857", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:38:18 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..cf5f86acb --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_1.json @@ -0,0 +1,42 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "RollbackConfiguration": {}, + "StackStatus": "CREATE_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "c77fb823-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "c77fb823-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "1041", + "date": "Tue, 26 Feb 2019 21:37:56 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_2.json new file mode 100644 index 000000000..71a9f54b6 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_2.json @@ -0,0 +1,41 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "RollbackConfiguration": {}, + "StackStatus": "CREATE_IN_PROGRESS", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "cad153b2-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "cad153b2-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "979", + "date": "Tue, 26 Feb 2019 21:38:01 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_3.json new file mode 100644 index 000000000..c2028183b --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_3.json @@ -0,0 +1,52 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "DeletionTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "RollbackConfiguration": {}, + "StackStatus": "DELETE_IN_PROGRESS", + "StackStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Delete requested by user.", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "ce24289a-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "ce24289a-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "1171", + "date": "Tue, 26 Feb 2019 21:38:06 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_4.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_4.json new file mode 100644 index 000000000..89f835531 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_4.json @@ -0,0 +1,51 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "DeletionTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "RollbackConfiguration": {}, + "StackStatus": "DELETE_IN_PROGRESS", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "d16c27f2-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "d16c27f2-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "1041", + "date": "Tue, 26 Feb 2019 21:38:12 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_5.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_5.json new file mode 100644 index 000000000..739c82937 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_delete/cloudformation.DescribeStacks_5.json @@ -0,0 +1,50 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-delete/c74a4fc0-3a0e-11e9-9a48-067794494828", + "StackName": "ansible-test-on-create-failure-delete", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 37, + "second": 55, + "microsecond": 909000 + }, + "DeletionTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 38, + "second": 2, + "microsecond": 76000 + }, + "RollbackConfiguration": {}, + "StackStatus": "DELETE_COMPLETE", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "d4c90dd6-3a0e-11e9-b25f-d1217e6893bf", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "d4c90dd6-3a0e-11e9-b25f-d1217e6893bf", + "content-type": "text/xml", + "content-length": "965", + "date": "Tue, 26 Feb 2019 21:38:18 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..86f1945fd --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.CreateStack_1.json @@ -0,0 +1,17 @@ +{ + "status_code": 200, + "data": { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "ResponseMetadata": { + "RequestId": "a396a58a-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a396a58a-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "411", + "date": "Tue, 26 Feb 2019 21:44:05 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DeleteStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DeleteStack_1.json new file mode 100644 index 000000000..1a3a67c64 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DeleteStack_1.json @@ -0,0 +1,16 @@ +{ + "status_code": 200, + "data": { + "ResponseMetadata": { + "RequestId": "a78f0832-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a78f0832-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "212", + "date": "Tue, 26 Feb 2019 21:44:11 GMT" + }, + "RetryAttempts": 0 + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..58d7a89e4 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,38 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "a39e6ce0-3a0f-11e9-96ca-02f46dd00950", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ansible-test-on-create-failure-do-nothing", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 5, + "microsecond": 553000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "a406cc84-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a406cc84-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "1169", + "date": "Tue, 26 Feb 2019 21:44:06 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..0a7e32e46 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,121 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "a6c32c80-3a0f-11e9-ac5e-06deb474fa52", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ansible-test-on-create-failure-do-nothing", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 10, + "microsecond": 804000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. " + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:44:09.905Z", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-a8g0mh5il4t5", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 9, + "microsecond": 905000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: a62a6f71-3a0f-11e9-9164-457e0a3a5e1b)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:44:09.497Z", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-a8g0mh5il4t5", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 9, + "microsecond": 497000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:44:09.076Z", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 9, + "microsecond": 76000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "EventId": "a39e6ce0-3a0f-11e9-96ca-02f46dd00950", + "StackName": "ansible-test-on-create-failure-do-nothing", + "LogicalResourceId": "ansible-test-on-create-failure-do-nothing", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 5, + "microsecond": 553000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "a75fbad0-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a75fbad0-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "5231", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:44:11 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..532143313 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_1.json @@ -0,0 +1,42 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "StackName": "ansible-test-on-create-failure-do-nothing", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 5, + "microsecond": 553000 + }, + "RollbackConfiguration": {}, + "StackStatus": "CREATE_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "DisableRollback": true, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "a3d44acf-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a3d44acf-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "1048", + "date": "Tue, 26 Feb 2019 21:44:05 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_2.json new file mode 100644 index 000000000..df17f5a73 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_do_nothing/cloudformation.DescribeStacks_2.json @@ -0,0 +1,42 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-do-nothing/a39dd0a0-3a0f-11e9-96ca-02f46dd00950", + "StackName": "ansible-test-on-create-failure-do-nothing", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 44, + "second": 5, + "microsecond": 553000 + }, + "RollbackConfiguration": {}, + "StackStatus": "CREATE_FAILED", + "StackStatusReason": "The following resource(s) failed to create: [ECRRepo]. ", + "DisableRollback": true, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "a7301f4a-3a0f-11e9-b7db-3fe3824c73cb", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "a7301f4a-3a0f-11e9-b7db-3fe3824c73cb", + "content-type": "text/xml", + "content-length": "1084", + "date": "Tue, 26 Feb 2019 21:44:11 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.CreateStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.CreateStack_1.json new file mode 100644 index 000000000..f71422b92 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.CreateStack_1.json @@ -0,0 +1,17 @@ +{ + "status_code": 200, + "data": { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResponseMetadata": { + "RequestId": "9139de54-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "9139de54-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "409", + "date": "Tue, 26 Feb 2019 21:43:34 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DeleteStack_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DeleteStack_1.json new file mode 100644 index 000000000..111dc90d8 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DeleteStack_1.json @@ -0,0 +1,16 @@ +{ + "status_code": 200, + "data": { + "ResponseMetadata": { + "RequestId": "988b3097-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "988b3097-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "212", + "date": "Tue, 26 Feb 2019 21:43:46 GMT" + }, + "RetryAttempts": 0 + } + } +}
\ No newline at end of file diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_1.json new file mode 100644 index 000000000..2bcac7f0e --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_1.json @@ -0,0 +1,38 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "9140bc10-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "9199b1a7-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "9199b1a7-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "1161", + "date": "Tue, 26 Feb 2019 21:43:35 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_2.json new file mode 100644 index 000000000..3992fd397 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_2.json @@ -0,0 +1,121 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "945b90a0-3a0f-11e9-adaf-0211d8bec7e2", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 920000 + }, + "ResourceStatus": "ROLLBACK_IN_PROGRESS", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Rollback requested by user." + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:43:39.210Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 210000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: 93e0bb60-3a0f-11e9-a53c-7162bb423e4d)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:43:38.793Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 38, + "microsecond": 793000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:43:38.266Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 38, + "microsecond": 266000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "9140bc10-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "94e16307-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "94e16307-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "5241", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:43:40 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_3.json new file mode 100644 index 000000000..e272c734b --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStackEvents_3.json @@ -0,0 +1,180 @@ +{ + "status_code": 200, + "data": { + "StackEvents": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "9743bc70-3a0f-11e9-b335-0ade61d04ee6", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 44, + "microsecond": 797000 + }, + "ResourceStatus": "ROLLBACK_COMPLETE" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-DELETE_COMPLETE-2019-02-26T21:43:43.908Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 43, + "microsecond": 908000 + }, + "ResourceStatus": "DELETE_COMPLETE", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-DELETE_IN_PROGRESS-2019-02-26T21:43:43.478Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 43, + "microsecond": 478000 + }, + "ResourceStatus": "DELETE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "945b90a0-3a0f-11e9-adaf-0211d8bec7e2", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 920000 + }, + "ResourceStatus": "ROLLBACK_IN_PROGRESS", + "ResourceStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Rollback requested by user." + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_FAILED-2019-02-26T21:43:39.210Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 210000 + }, + "ResourceStatus": "CREATE_FAILED", + "ResourceStatusReason": "Invalid parameter at 'PolicyText' failed to satisfy constraint: 'Invalid repository policy provided' (Service: AmazonECR; Status Code: 400; Error Code: InvalidParameterException; Request ID: 93e0bb60-3a0f-11e9-a53c-7162bb423e4d)", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:43:38.793Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "ansib-ecrre-1lsnxu2zpb20l", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 38, + "microsecond": 793000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "Resource creation Initiated", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "ECRRepo-CREATE_IN_PROGRESS-2019-02-26T21:43:38.266Z", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ECRRepo", + "PhysicalResourceId": "", + "ResourceType": "AWS::ECR::Repository", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 38, + "microsecond": 266000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceProperties": "{\"RepositoryPolicyText\":{\"Version\":\"3000-10-17\",\"Statement\":[{\"Action\":[\"ecr:*\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:root\"}}]}}" + }, + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "EventId": "9140bc10-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "LogicalResourceId": "ansible-test-on-create-failure-rollback", + "PhysicalResourceId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "ResourceType": "AWS::CloudFormation::Stack", + "Timestamp": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "ResourceStatus": "CREATE_IN_PROGRESS", + "ResourceStatusReason": "User Initiated" + } + ], + "ResponseMetadata": { + "RequestId": "982d0bff-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "982d0bff-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "7911", + "vary": "Accept-Encoding", + "date": "Tue, 26 Feb 2019 21:43:45 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_1.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_1.json new file mode 100644 index 000000000..25facea18 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_1.json @@ -0,0 +1,42 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "RollbackConfiguration": {}, + "StackStatus": "CREATE_IN_PROGRESS", + "StackStatusReason": "User Initiated", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "91725383-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "91725383-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "1045", + "date": "Tue, 26 Feb 2019 21:43:35 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_2.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_2.json new file mode 100644 index 000000000..55a80d8af --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_2.json @@ -0,0 +1,52 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "DeletionTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 920000 + }, + "RollbackConfiguration": {}, + "StackStatus": "ROLLBACK_IN_PROGRESS", + "StackStatusReason": "The following resource(s) failed to create: [ECRRepo]. . Rollback requested by user.", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "94bb1651-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "94bb1651-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "1179", + "date": "Tue, 26 Feb 2019 21:43:40 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_3.json b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_3.json new file mode 100644 index 000000000..7c00a8364 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/placebo_recordings/cloudformation/on_create_failure_rollback/cloudformation.DescribeStacks_3.json @@ -0,0 +1,51 @@ +{ + "status_code": 200, + "data": { + "Stacks": [ + { + "StackId": "arn:aws:cloudformation:us-west-2:123456789012:stack/ansible-test-on-create-failure-rollback/914046e0-3a0f-11e9-94bf-0a9edf17d014", + "StackName": "ansible-test-on-create-failure-rollback", + "CreationTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 34, + "microsecond": 740000 + }, + "DeletionTime": { + "__class__": "datetime", + "year": 2019, + "month": 2, + "day": 26, + "hour": 21, + "minute": 43, + "second": 39, + "microsecond": 920000 + }, + "RollbackConfiguration": {}, + "StackStatus": "ROLLBACK_COMPLETE", + "DisableRollback": false, + "NotificationARNs": [], + "Tags": [], + "EnableTerminationProtection": false, + "DriftInformation": { + "StackDriftStatus": "NOT_CHECKED" + } + } + ], + "ResponseMetadata": { + "RequestId": "98016814-3a0f-11e9-b938-97983b40cabe", + "HTTPStatusCode": 200, + "HTTPHeaders": { + "x-amzn-requestid": "98016814-3a0f-11e9-b938-97983b40cabe", + "content-type": "text/xml", + "content-length": "1044", + "date": "Tue, 26 Feb 2019 21:43:45 GMT" + }, + "RetryAttempts": 0 + } + } +} diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_cloudformation.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_cloudformation.py new file mode 100644 index 000000000..f46bc1113 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_cloudformation.py @@ -0,0 +1,227 @@ +# (c) 2017 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +# Magic... +from ansible_collections.amazon.aws.tests.unit.utils.amazon_placebo_fixtures import maybe_sleep, placeboify # pylint: disable=unused-import + +from ansible_collections.amazon.aws.plugins.module_utils.botocore import boto_exception +from ansible_collections.amazon.aws.plugins.module_utils.modules import _RetryingBotoClientWrapper +from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry + +from ansible_collections.amazon.aws.plugins.modules import cloudformation as cfn_module + +basic_yaml_tpl = """ +--- +AWSTemplateFormatVersion: '2010-09-09' +Description: 'Basic template that creates an S3 bucket' +Resources: + MyBucket: + Type: "AWS::S3::Bucket" +Outputs: + TheName: + Value: + !Ref MyBucket +""" + +bad_json_tpl = """{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Broken template, no comma here ->" + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +}""" + +failing_yaml_tpl = """ +--- +AWSTemplateFormatVersion: 2010-09-09 +Resources: + ECRRepo: + Type: AWS::ECR::Repository + Properties: + RepositoryPolicyText: + Version: 3000-10-17 # <--- invalid version + Statement: + - Effect: Allow + Action: + - 'ecr:*' + Principal: + AWS: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:root +""" + +default_events_limit = 10 + + +class FakeModule(object): + def __init__(self, **kwargs): + self.params = kwargs + + def fail_json(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('FAIL') + + def fail_json_aws(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('FAIL') + + def exit_json(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('EXIT') + + +def _create_wrapped_client(placeboify): + connection = placeboify.client('cloudformation') + retry_decorator = AWSRetry.jittered_backoff() + wrapped_conn = _RetryingBotoClientWrapper(connection, retry_decorator) + return wrapped_conn + + +def test_invalid_template_json(placeboify): + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-wrong-json', + 'TemplateBody': bad_json_tpl, + } + m = FakeModule(disable_rollback=False) + with pytest.raises(Exception) as exc_info: + cfn_module.create_stack(m, params, connection, default_events_limit) + pytest.fail('Expected malformed JSON to have caused the call to fail') + + assert exc_info.match('FAIL') + assert "ValidationError" in boto_exception(m.exit_args[0]) + + +def test_client_request_token_s3_stack(maybe_sleep, placeboify): + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-client-request-token-yaml', + 'TemplateBody': basic_yaml_tpl, + 'ClientRequestToken': '3faf3fb5-b289-41fc-b940-44151828f6cf', + } + m = FakeModule(disable_rollback=False) + result = cfn_module.create_stack(m, params, connection, default_events_limit) + assert result['changed'] + assert len(result['events']) > 1 + # require that the final recorded stack state was CREATE_COMPLETE + # events are retrieved newest-first, so 0 is the latest + assert 'CREATE_COMPLETE' in result['events'][0] + connection.delete_stack(StackName='ansible-test-client-request-token-yaml') + + +def test_basic_s3_stack(maybe_sleep, placeboify): + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-basic-yaml', + 'TemplateBody': basic_yaml_tpl + } + m = FakeModule(disable_rollback=False) + result = cfn_module.create_stack(m, params, connection, default_events_limit) + assert result['changed'] + assert len(result['events']) > 1 + # require that the final recorded stack state was CREATE_COMPLETE + # events are retrieved newest-first, so 0 is the latest + assert 'CREATE_COMPLETE' in result['events'][0] + connection.delete_stack(StackName='ansible-test-basic-yaml') + + +def test_delete_nonexistent_stack(maybe_sleep, placeboify): + connection = _create_wrapped_client(placeboify) + # module is only used if we threw an unexpected error + module = None + result = cfn_module.stack_operation(module, connection, 'ansible-test-nonexist', 'DELETE', default_events_limit) + assert result['changed'] + assert 'Stack does not exist.' in result['log'] + + +def test_get_nonexistent_stack(placeboify): + connection = _create_wrapped_client(placeboify) + # module is only used if we threw an unexpected error + module = None + assert cfn_module.get_stack_facts(module, connection, 'ansible-test-nonexist') is None + + +def test_missing_template_body(): + m = FakeModule() + with pytest.raises(Exception) as exc_info: + cfn_module.create_stack( + module=m, + stack_params={}, + cfn=None, + events_limit=default_events_limit + ) + pytest.fail('Expected module to have failed with no template') + + assert exc_info.match('FAIL') + assert not m.exit_args + assert "Either 'template', 'template_body' or 'template_url' is required when the stack does not exist." == m.exit_kwargs['msg'] + + +def test_on_create_failure_delete(maybe_sleep, placeboify): + m = FakeModule( + on_create_failure='DELETE', + disable_rollback=False, + ) + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-on-create-failure-delete', + 'TemplateBody': failing_yaml_tpl + } + result = cfn_module.create_stack(m, params, connection, default_events_limit) + assert result['changed'] + assert result['failed'] + assert len(result['events']) > 1 + # require that the final recorded stack state was DELETE_COMPLETE + # events are retrieved newest-first, so 0 is the latest + assert 'DELETE_COMPLETE' in result['events'][0] + + +def test_on_create_failure_rollback(maybe_sleep, placeboify): + m = FakeModule( + on_create_failure='ROLLBACK', + disable_rollback=False, + ) + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-on-create-failure-rollback', + 'TemplateBody': failing_yaml_tpl + } + result = cfn_module.create_stack(m, params, connection, default_events_limit) + assert result['changed'] + assert result['failed'] + assert len(result['events']) > 1 + # require that the final recorded stack state was ROLLBACK_COMPLETE + # events are retrieved newest-first, so 0 is the latest + assert 'ROLLBACK_COMPLETE' in result['events'][0] + connection.delete_stack(StackName=params['StackName']) + + +def test_on_create_failure_do_nothing(maybe_sleep, placeboify): + m = FakeModule( + on_create_failure='DO_NOTHING', + disable_rollback=False, + ) + connection = _create_wrapped_client(placeboify) + params = { + 'StackName': 'ansible-test-on-create-failure-do-nothing', + 'TemplateBody': failing_yaml_tpl + } + result = cfn_module.create_stack(m, params, connection, default_events_limit) + assert result['changed'] + assert result['failed'] + assert len(result['events']) > 1 + # require that the final recorded stack state was CREATE_FAILED + # events are retrieved newest-first, so 0 is the latest + assert 'CREATE_FAILED' in result['events'][0] + connection.delete_stack(StackName=params['StackName']) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_ami.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_ami.py new file mode 100644 index 000000000..5e8140d4a --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_ami.py @@ -0,0 +1,44 @@ +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from unittest.mock import MagicMock, Mock, patch, call + +import pytest + +from ansible_collections.amazon.aws.plugins.modules import ec2_ami + +module_name = "ansible_collections.amazon.aws.plugins.modules.ec2_ami" + + +@patch(module_name + ".get_image_by_id") +def test_create_image_uefi_data(m_get_image_by_id): + module = MagicMock() + connection = MagicMock() + + m_get_image_by_id.return_value = { + "ImageId": "ami-0c7a795306730b288", + "BootMode": "uefi", + "TpmSupport": "v2.0", + } + + module.params = { + "name": "my-image", + "boot_mode": "uefi", + "tpm_support": "v2.0", + "uefi_data": "QU1aTlVFRkk9xcN0AAAAAHj5a7fZ9+3aT2gcVRgA8Ek3NipiPST0pCiCIlTJtj20FzENCcQa", + } + + ec2_ami.create_image(module, connection) + assert connection.register_image.call_count == 1 + connection.register_image.assert_has_calls( + [ + call( + aws_retry=True, + Description=None, + Name="my-image", + BootMode="uefi", + TpmSupport="v2.0", + UefiData="QU1aTlVFRkk9xcN0AAAAAHj5a7fZ9+3aT2gcVRgA8Ek3NipiPST0pCiCIlTJtj20FzENCcQa" + ) + ] + ) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_key.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_key.py new file mode 100644 index 000000000..2660ced63 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_key.py @@ -0,0 +1,654 @@ +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from unittest.mock import MagicMock +from unittest.mock import patch +from unittest.mock import call, ANY + +import pytest +import botocore +import datetime +from dateutil.tz import tzutc +from ansible.module_utils._text import to_bytes + +from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code + +from ansible_collections.amazon.aws.plugins.modules import ec2_key + +module_name = "ansible_collections.amazon.aws.plugins.modules.ec2_key" + + +def raise_botocore_exception_clienterror(action): + + params = { + 'Error': { + 'Code': 1, + 'Message': 'error creating key' + }, + 'ResponseMetadata': { + 'RequestId': '01234567-89ab-cdef-0123-456789abcdef' + } + } + + if action == 'create_key_pair': + params['Error']['Message'] = 'error creating key' + + elif action == 'describe_key_pair': + params['Error']['Code'] = 'InvalidKeyPair.NotFound' + params['Error']['Message'] = 'The key pair does not exist' + + elif action == 'import_key_pair': + params['Error']['Message'] = 'error importing key' + + elif action == 'delete_key_pair': + params['Error']['Message'] = 'error deleting key' + + return botocore.exceptions.ClientError(params, action) + + +def test__import_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + key_material = "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com" + + expected_params = { + 'KeyName': name, + 'PublicKeyMaterial': to_bytes(key_material), + } + + ec2_client.import_key_pair.return_value = { + 'KeyFingerprint': 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-012345678905a208d' + } + + result = ec2_key._import_key_pair(ec2_client, name, key_material) + + assert result == ec2_client.import_key_pair.return_value + assert ec2_client.import_key_pair.call_count == 1 + ec2_client.import_key_pair.assert_called_with(aws_retry=True, **expected_params) + + +def test_api_failure__import_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + key_material = "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com" + + expected_params = { + 'KeyName': name, + 'PublicKeyMaterial': to_bytes(key_material), + } + + ec2_client.import_key_pair.side_effect = raise_botocore_exception_clienterror('import_key_pair') + + with pytest.raises(ec2_key.Ec2KeyFailure): + ec2_key._import_key_pair(ec2_client, name, key_material) + + +def test_extract_key_data_describe_key_pairs(): + + key = { + "CreateTime": datetime.datetime(2022, 9, 15, 20, 10, 15, tzinfo=tzutc()), + "KeyFingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "KeyName": "my_keypair", + "KeyPairId": "key-043046ef2a9a80b56", + "Tags": [], + } + + key_type = "rsa" + + expected_result = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + result = ec2_key.extract_key_data(key, key_type) + + assert result == expected_result + + +def test_extract_key_data_create_key_pair(): + + key = { + 'KeyFingerprint': '11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-043046ef2a9a80b56' + } + + key_type = "rsa" + + expected_result = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + result = ec2_key.extract_key_data(key, key_type) + + assert result == expected_result + + +@patch(module_name + '.delete_key_pair') +@patch(module_name + '._import_key_pair') +@patch(module_name + '.find_key_pair') +def test_get_key_fingerprint(m_find_key_pair, m_import_key_pair, m_delete_key_pair): + + module = MagicMock() + ec2_client = MagicMock() + + m_find_key_pair.return_value = None + + m_import_key_pair.return_value = { + 'KeyFingerprint': 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-043046ef2a9a80b56' + } + + m_delete_key_pair.return_value = { + 'changed': True, + 'key': None, + 'msg': 'key deleted' + } + + expected_result = 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62' + + key_material = "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com" + + result = ec2_key.get_key_fingerprint(module, ec2_client, key_material) + + assert result == expected_result + assert m_find_key_pair.call_count == 1 + assert m_import_key_pair.call_count == 1 + assert m_delete_key_pair.call_count == 1 + + +def test_find_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + + ec2_client.describe_key_pairs.return_value = { + 'KeyPairs': [ + { + 'CreateTime': datetime.datetime(2022, 9, 15, 20, 10, 15, tzinfo=tzutc()), + 'KeyFingerprint': '11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-043046ef2a9a80b56', + 'KeyType': 'rsa', + 'Tags': [] + } + ], + } + + ec2_key.find_key_pair(ec2_client, name) + + assert ec2_client.describe_key_pairs.call_count == 1 + ec2_client.describe_key_pairs.assert_called_with(aws_retry=True, KeyNames=[name]) + + +def test_api_failure_find_key_pair(): + ec2_client = MagicMock() + name = 'non_existing_keypair' + + ec2_client.describe_key_pairs.side_effect = botocore.exceptions.BotoCoreError + + with pytest.raises(ec2_key.Ec2KeyFailure): + ec2_key.find_key_pair(ec2_client, name) + + +def test_invalid_key_pair_find_key_pair(): + ec2_client = MagicMock() + name = 'non_existing_keypair' + + ec2_client.describe_key_pairs.side_effect = raise_botocore_exception_clienterror('describe_key_pair') + + result = ec2_key.find_key_pair(ec2_client, name) + + assert result is None + + +def test__create_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + tag_spec = None + key_type = None + + expected_params = {'KeyName': name} + + ec2_client.create_key_pair.return_value = { + "KeyFingerprint": "d7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62", + "KeyMaterial": ( + "-----BEGIN RSA PRIVATE KEY-----\n" # gitleaks:allow + "MIIEXm7/Bi9wba2m0Qtclu\nCXQw2paSIZb\n" + "-----END RSA PRIVATE KEY-----" + ), + "KeyName": "my_keypair", + "KeyPairId": "key-012345678905a208d", + } + + result = ec2_key._create_key_pair(ec2_client, name, tag_spec, key_type) + + assert result == ec2_client.create_key_pair.return_value + assert ec2_client.create_key_pair.call_count == 1 + ec2_client.create_key_pair.assert_called_with(aws_retry=True, **expected_params) + + +def test_api_failure__create_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + tag_spec = None + key_type = None + + ec2_client.create_key_pair.side_effect = raise_botocore_exception_clienterror('create_key_pair') + + with pytest.raises(ec2_key.Ec2KeyFailure): + ec2_key._create_key_pair(ec2_client, name, tag_spec, key_type) + + +@patch(module_name + '.extract_key_data') +@patch(module_name + '._import_key_pair') +def test_create_new_key_pair_key_material(m_import_key_pair, m_extract_key_data): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key_material = "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com" + key_type = 'rsa' + tags = None + + module.check_mode = False + + m_import_key_pair.return_value = { + 'KeyFingerprint': 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-012345678905a208d' + } + + m_extract_key_data.return_value = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + expected_result = {'changed': True, 'key': m_extract_key_data.return_value, 'msg': 'key pair created'} + + result = ec2_key.create_new_key_pair(ec2_client, name, key_material, key_type, tags, module.check_mode) + + assert result == expected_result + assert m_import_key_pair.call_count == 1 + assert m_extract_key_data.call_count == 1 + + +@patch(module_name + '.extract_key_data') +@patch(module_name + '._create_key_pair') +def test_create_new_key_pair_no_key_material(m_create_key_pair, m_extract_key_data): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key_type = 'rsa' + key_material = None + tags = None + + module.check_mode = False + + m_create_key_pair.return_value = { + 'KeyFingerprint': 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-012345678905a208d' + } + + m_extract_key_data.return_value = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + expected_result = {'changed': True, 'key': m_extract_key_data.return_value, 'msg': 'key pair created'} + + result = ec2_key.create_new_key_pair(ec2_client, name, key_material, key_type, tags, module.check_mode) + + assert result == expected_result + assert m_create_key_pair.call_count == 1 + assert m_extract_key_data.call_count == 1 + + +def test__delete_key_pair(): + ec2_client = MagicMock() + + key_name = 'my_keypair' + ec2_key._delete_key_pair(ec2_client, key_name) + + assert ec2_client.delete_key_pair.call_count == 1 + ec2_client.delete_key_pair.assert_called_with(aws_retry=True, KeyName=key_name) + + +def test_api_failure__delete_key_pair(): + ec2_client = MagicMock() + name = 'my_keypair' + + ec2_client.delete_key_pair.side_effect = raise_botocore_exception_clienterror('delete_key_pair') + + with pytest.raises(ec2_key.Ec2KeyFailure): + ec2_key._delete_key_pair(ec2_client, name) + + +@patch(module_name + '.extract_key_data') +@patch(module_name + '._import_key_pair') +@patch(module_name + '.delete_key_pair') +@patch(module_name + '.get_key_fingerprint') +def test_update_key_pair_by_key_material_update_needed(m_get_key_fingerprint, m_delete_key_pair, m__import_key_pair, m_extract_key_data): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key_material = "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com" + tag_spec = None + key = { + "KeyName": "my_keypair", + "KeyFingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "KeyPairId": "key-043046ef2a9a80b56", + "Tags": {}, + } + + module.check_mode = False + + m_get_key_fingerprint.return_value = 'd7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62' + m_delete_key_pair.return_value = None + m__import_key_pair.return_value = { + 'KeyFingerprint': '11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-043046ef2a9a80b56', + 'Tags': {}, + } + m_extract_key_data.return_value = { + "name": "my_keypair", + "fingerprint": "d7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62", + "id": "key-012345678905a208d", + "tags": {}, + } + + expected_result = {'changed': True, 'key': m_extract_key_data.return_value, 'msg': "key pair updated"} + + result = ec2_key.update_key_pair_by_key_material(module.check_mode, ec2_client, name, key, key_material, tag_spec) + + assert result == expected_result + assert m_get_key_fingerprint.call_count == 1 + assert m_delete_key_pair.call_count == 1 + assert m__import_key_pair.call_count == 1 + assert m_extract_key_data.call_count == 1 + m_get_key_fingerprint.assert_called_with(module.check_mode, ec2_client, key_material) + m_delete_key_pair.assert_called_with(module.check_mode, ec2_client, name, finish_task=False) + m__import_key_pair.assert_called_with(ec2_client, name, key_material, tag_spec) + m_extract_key_data.assert_called_with(key) + + +@patch(module_name + ".extract_key_data") +@patch(module_name + ".get_key_fingerprint") +def test_update_key_pair_by_key_material_key_exists(m_get_key_fingerprint, m_extract_key_data): + ec2_client = MagicMock() + + key_material = MagicMock() + key_fingerprint = MagicMock() + tag_spec = MagicMock() + key_id = MagicMock() + key_name = MagicMock() + key = { + "KeyName": key_name, + "KeyFingerprint": key_fingerprint, + "KeyPairId": key_id, + "Tags": {}, + } + + check_mode = False + m_get_key_fingerprint.return_value = key_fingerprint + m_extract_key_data.return_value = { + "name": key_name, + "fingerprint": key_fingerprint, + "id": key_id, + "tags": {}, + } + + expected_result = {"changed": False, "key": m_extract_key_data.return_value, "msg": "key pair already exists"} + + assert expected_result == ec2_key.update_key_pair_by_key_material( + check_mode, ec2_client, key_name, key, key_material, tag_spec + ) + + m_get_key_fingerprint.assert_called_once_with(check_mode, ec2_client, key_material) + m_extract_key_data.assert_called_once_with(key) + + +@patch(module_name + ".extract_key_data") +@patch(module_name + "._create_key_pair") +@patch(module_name + ".delete_key_pair") +def test_update_key_pair_by_key_type_update_needed(m_delete_key_pair, m__create_key_pair, m_extract_key_data): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key_type = 'rsa' + tag_spec = None + + module.check_mode = False + + m_delete_key_pair.return_value = None + m__create_key_pair.return_value = { + 'KeyFingerprint': '11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa', + 'Name': 'my_keypair', + 'Id': 'key-043046ef2a9a80b56', + 'Tags': {}, + 'Type': 'rsa' + } + m_extract_key_data.return_value = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + expected_result = {"changed": True, "key": m_extract_key_data.return_value, "msg": "key pair updated"} + + result = ec2_key.update_key_pair_by_key_type(module.check_mode, ec2_client, name, key_type, tag_spec) + + assert result == expected_result + assert m_delete_key_pair.call_count == 1 + assert m__create_key_pair.call_count == 1 + assert m_extract_key_data.call_count == 1 + m_delete_key_pair.assert_called_with(module.check_mode, ec2_client, name, finish_task=False) + m__create_key_pair.assert_called_with(ec2_client, name, tag_spec, key_type) + m_extract_key_data.assert_called_with(m__create_key_pair.return_value, key_type) + + +@patch(module_name + '.update_key_pair_by_key_material') +def test_handle_existing_key_pair_update_key_matrial_with_force(m_update_key_pair_by_key_material): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key = { + "KeyName": "my_keypair", + "KeyFingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "KeyPairId": "key-043046ef2a9a80b56", + "Tags": {}, + "KeyType": "rsa" + } + + module.params = { + 'key_material': "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com", + 'force': True, + 'key_type': 'rsa', + 'tags': None, + 'purge_tags': True, + 'tag_spec': None + } + + key_data = { + "name": "my_keypair", + "fingerprint": "d7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62", + "id": "key-012345678905a208d", + "tags": {}, + } + + m_update_key_pair_by_key_material.return_value = {'changed': True, 'key': key_data, 'msg': "key pair updated"} + + expected_result = {'changed': True, 'key': key_data, 'msg': "key pair updated"} + + result = ec2_key.handle_existing_key_pair_update(module, ec2_client, name, key) + + assert result == expected_result + assert m_update_key_pair_by_key_material.call_count == 1 + + +@patch(module_name + '.update_key_pair_by_key_type') +def test_handle_existing_key_pair_update_key_type(m_update_key_pair_by_key_type): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key = { + "KeyName": "my_keypair", + "KeyFingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "KeyPairId": "key-043046ef2a9a80b56", + "Tags": {}, + "KeyType": "ed25519" + } + + module.params = { + 'key_material': "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com", + 'force': False, + 'key_type': 'rsa', + 'tags': None, + 'purge_tags': True, + 'tag_spec': None + } + + key_data = { + "name": "my_keypair", + "fingerprint": "d7:ff:a6:63:18:64:9c:57:a1:ee:ca:a4:ad:c2:81:62", + "id": "key-012345678905a208d", + "tags": {}, + } + + m_update_key_pair_by_key_type.return_value = {'changed': True, 'key': key_data, 'msg': "key pair updated"} + + expected_result = {'changed': True, 'key': key_data, 'msg': "key pair updated"} + + result = ec2_key.handle_existing_key_pair_update(module, ec2_client, name, key) + + assert result == expected_result + assert m_update_key_pair_by_key_type.call_count == 1 + + +@patch(module_name + '.extract_key_data') +def test_handle_existing_key_pair_else(m_extract_key_data): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + key = { + "KeyName": "my_keypair", + "KeyFingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "KeyPairId": "key-043046ef2a9a80b56", + "Tags": {}, + "KeyType": "rsa" + } + + module.params = { + 'key_material': "ssh-rsa AAAAB3NzaC1yc2EAA email@example.com", + 'force': False, + 'key_type': 'rsa', + 'tags': None, + 'purge_tags': True, + 'tag_spec': None + } + + m_extract_key_data.return_value = { + "name": "my_keypair", + "fingerprint": "11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa", + "id": "key-043046ef2a9a80b56", + "tags": {}, + "type": "rsa" + } + + expected_result = {"changed": False, "key": m_extract_key_data.return_value, "msg": "key pair already exists"} + + result = ec2_key.handle_existing_key_pair_update(module, ec2_client, name, key) + + assert result == expected_result + assert m_extract_key_data.call_count == 1 + + +@patch(module_name + '._delete_key_pair') +@patch(module_name + '.find_key_pair') +def test_delete_key_pair_key_exists(m_find_key_pair, m_delete_key_pair): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + + module.check_mode = False + + m_find_key_pair.return_value = { + 'KeyPairs': [ + { + 'CreateTime': datetime.datetime(2022, 9, 15, 20, 10, 15, tzinfo=tzutc()), + 'KeyFingerprint': '11:12:13:14:bb:26:85:b2:e8:39:27:bc:ee:aa:ff:ee:dd:cc:bb:aa', + 'KeyName': 'my_keypair', + 'KeyPairId': 'key-043046ef2a9a80b56', + 'KeyType': 'rsa', + 'Tags': [] + } + ], + } + + expected_result = {'changed': True, 'key': None, 'msg': 'key deleted'} + + result = ec2_key.delete_key_pair(module.check_mode, ec2_client, name) + + assert m_find_key_pair.call_count == 1 + m_find_key_pair.assert_called_with(ec2_client, name) + assert m_delete_key_pair.call_count == 1 + m_delete_key_pair.assert_called_with(ec2_client, name) + assert result == expected_result + + +@patch(module_name + '._delete_key_pair') +@patch(module_name + '.find_key_pair') +def test_delete_key_pair_key_not_exist(m_find_key_pair, m_delete_key_pair): + module = MagicMock() + ec2_client = MagicMock() + + name = 'my_keypair' + + module.check_mode = False + + m_find_key_pair.return_value = None + + expected_result = {'key': None, 'msg': 'key did not exist'} + + result = ec2_key.delete_key_pair(module.check_mode, ec2_client, name) + + assert m_find_key_pair.call_count == 1 + m_find_key_pair.assert_called_with(ec2_client, name) + assert m_delete_key_pair.call_count == 0 + assert result == expected_result + + +@patch(module_name + ".AnsibleAWSModule") +def test_main_success(m_AnsibleAWSModule): + m_module = MagicMock() + m_AnsibleAWSModule.return_value = m_module + + ec2_key.main() + + m_module.client.assert_called_with("ec2", retry_decorator=ANY) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_security_group.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_security_group.py new file mode 100644 index 000000000..1ebbe86c6 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_security_group.py @@ -0,0 +1,83 @@ +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible_collections.amazon.aws.plugins.modules import ec2_security_group as group_module + + +def test_from_permission(): + internal_http = { + 'FromPort': 80, + 'IpProtocol': 'tcp', + 'IpRanges': [ + { + 'CidrIp': '10.0.0.0/8', + 'Description': 'Foo Bar Baz' + }, + ], + 'Ipv6Ranges': [ + {'CidrIpv6': 'fe80::94cc:8aff:fef6:9cc/64'}, + ], + 'PrefixListIds': [], + 'ToPort': 80, + 'UserIdGroupPairs': [], + } + perms = list(group_module.rule_from_group_permission(internal_http)) + assert len(perms) == 2 + assert perms[0].target == '10.0.0.0/8' + assert perms[0].target_type == 'ipv4' + assert perms[0].description == 'Foo Bar Baz' + assert perms[1].target == 'fe80::94cc:8aff:fef6:9cc/64' + + global_egress = { + 'IpProtocol': '-1', + 'IpRanges': [{'CidrIp': '0.0.0.0/0'}], + 'Ipv6Ranges': [], + 'PrefixListIds': [], + 'UserIdGroupPairs': [] + } + perms = list(group_module.rule_from_group_permission(global_egress)) + assert len(perms) == 1 + assert perms[0].target == '0.0.0.0/0' + assert perms[0].port_range == (None, None) + + internal_prefix_http = { + 'FromPort': 80, + 'IpProtocol': 'tcp', + 'PrefixListIds': [ + {'PrefixListId': 'p-1234'} + ], + 'ToPort': 80, + 'UserIdGroupPairs': [], + } + perms = list(group_module.rule_from_group_permission(internal_prefix_http)) + assert len(perms) == 1 + assert perms[0].target == 'p-1234' + + +def test_rule_to_permission(): + tests = [ + group_module.Rule((22, 22), 'udp', 'sg-1234567890', 'group', None), + group_module.Rule((1, 65535), 'tcp', '0.0.0.0/0', 'ipv4', "All TCP from everywhere"), + group_module.Rule((443, 443), 'tcp', 'ip-123456', 'ip_prefix', "Traffic to privatelink IPs"), + group_module.Rule((443, 443), 'tcp', 'feed:dead:::beef/64', 'ipv6', None), + ] + for test in tests: + perm = group_module.to_permission(test) + assert perm['FromPort'], perm['ToPort'] == test.port_range + assert perm['IpProtocol'] == test.protocol + + +def test_validate_ip(): + class Warner(object): + def warn(self, msg): + return + ips = [ + ('10.1.1.1/24', '10.1.1.0/24'), + ('192.168.56.101/16', '192.168.0.0/16'), + # Don't modify IPv6 CIDRs, AWS supports /128 and device ranges + ('fc00:8fe0:fe80:b897:8990:8a7c:99bf:323d/128', 'fc00:8fe0:fe80:b897:8990:8a7c:99bf:323d/128'), + ] + + for ip, net in ips: + assert group_module.validate_ip(Warner(), ip) == net diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_vpc_dhcp_option.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_vpc_dhcp_option.py new file mode 100644 index 000000000..73726590f --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_ec2_vpc_dhcp_option.py @@ -0,0 +1,71 @@ +# (c) 2021 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +# Magic... Incorrectly identified by pylint as unused +from ansible_collections.amazon.aws.tests.unit.utils.amazon_placebo_fixtures import placeboify # pylint: disable=unused-import +from ansible_collections.amazon.aws.tests.unit.compat.mock import patch + +from ansible_collections.amazon.aws.plugins.modules import ec2_vpc_dhcp_option as dhcp_module +from ansible_collections.amazon.aws.tests.unit.plugins.modules.utils import ModuleTestCase + +test_module_params = {'domain_name': 'us-west-2.compute.internal', + 'dns_servers': ['AmazonProvidedDNS'], + 'ntp_servers': ['10.10.2.3', '10.10.4.5'], + 'netbios_name_servers': ['10.20.2.3', '10.20.4.5'], + 'netbios_node_type': 2} + +test_create_config = [{'Key': 'domain-name', 'Values': [{'Value': 'us-west-2.compute.internal'}]}, + {'Key': 'domain-name-servers', 'Values': [{'Value': 'AmazonProvidedDNS'}]}, + {'Key': 'ntp-servers', 'Values': [{'Value': '10.10.2.3'}, {'Value': '10.10.4.5'}]}, + {'Key': 'netbios-name-servers', 'Values': [{'Value': '10.20.2.3'}, {'Value': '10.20.4.5'}]}, + {'Key': 'netbios-node-type', 'Values': 2}] + + +test_create_option_set = [{'Key': 'domain-name', 'Values': ['us-west-2.compute.internal']}, + {'Key': 'domain-name-servers', 'Values': ['AmazonProvidedDNS']}, + {'Key': 'ntp-servers', 'Values': ['10.10.2.3', '10.10.4.5']}, + {'Key': 'netbios-name-servers', 'Values': ['10.20.2.3', '10.20.4.5']}, + {'Key': 'netbios-node-type', 'Values': ['2']}] + +test_normalize_config = {'domain-name': ['us-west-2.compute.internal'], + 'domain-name-servers': ['AmazonProvidedDNS'], + 'ntp-servers': ['10.10.2.3', '10.10.4.5'], + 'netbios-name-servers': ['10.20.2.3', '10.20.4.5'], + 'netbios-node-type': '2' + } + + +class FakeModule(object): + def __init__(self, **kwargs): + self.params = kwargs + + def fail_json(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('FAIL') + + def fail_json_aws(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('FAIL') + + def exit_json(self, *args, **kwargs): + self.exit_args = args + self.exit_kwargs = kwargs + raise Exception('EXIT') + + +@patch.object(dhcp_module.AnsibleAWSModule, 'client') +class TestDhcpModule(ModuleTestCase): + + def test_normalize_config(self, client_mock): + result = dhcp_module.normalize_ec2_vpc_dhcp_config(test_create_config) + + print(result) + print(test_normalize_config) + assert result == test_normalize_config diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_kms_key.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_kms_key.py new file mode 100644 index 000000000..5a53e2ddb --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_kms_key.py @@ -0,0 +1,82 @@ +# +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import pytest + +from unittest.mock import MagicMock, call, patch +from ansible_collections.amazon.aws.plugins.modules import kms_key + + +module_name = "ansible_collections.amazon.aws.plugins.modules.kms_key" +key_details = { + "KeyMetadata": { + "aliases": ["mykey"], + "Arn": "arn:aws:kms:us-east-1:12345678:key/mrk-12345678", + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "description": "", + "enable_key_rotation": False, + "enabled": True, + "encryption_algorithms": ["SYMMETRIC_DEFAULT"], + "grants": [], + "key_arn": "arn:aws:kms:us-east-1:12345678:key/mrk-12345678", + "key_id": "mrk-12345678", + "key_manager": "CUSTOMER", + "key_policies": [ + { + "Id": "key-default-1", + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": {"AWS": "arn:aws:iam::12345678:root"}, + "Resource": "*", + "Sid": "Enable IAM User Permissions", + } + ], + "Version": "2012-10-17", + } + ], + "key_spec": "SYMMETRIC_DEFAULT", + "key_state": "Enabled", + "key_usage": "ENCRYPT_DECRYPT", + "multi_region": True, + "multi_region_configuration": { + "multi_region_key_type": "PRIM ARY", + "primary_key": { + "arn": "arn:aws:kms:us-east-1:12345678:key/mrk-12345678", + "region": "us-east-1", + }, + "replica_keys": [], + }, + "origin": "AWS_KMS", + "tags": {"Hello": "World2"}, + } +} + + +@patch(module_name + ".get_kms_metadata_with_backoff") +def test_fetch_key_metadata(m_get_kms_metadata_with_backoff): + + module = MagicMock() + kms_client = MagicMock() + + m_get_kms_metadata_with_backoff.return_value = key_details + kms_key.fetch_key_metadata(kms_client, module, "mrk-12345678", "mykey") + assert m_get_kms_metadata_with_backoff.call_count == 1 + + +def test_validate_params(): + + module = MagicMock() + module.params = { + "state": "present", + "multi_region": True + } + + result = kms_key.validate_params(module, key_details["KeyMetadata"]) + module.fail_json.assert_called_with( + msg="You cannot change the multi-region property on an existing key." + ) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer.py new file mode 100644 index 000000000..451a61766 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer.py @@ -0,0 +1,493 @@ +# +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest + +from unittest.mock import MagicMock, call, patch +from ansible_collections.amazon.aws.plugins.modules import lambda_layer + + +def raise_lambdalayer_exception(e=None, m=None): + e = e or "lambda layer exc" + m = m or "unit testing" + return lambda_layer.LambdaLayerFailure(exc=e, msg=m) + + +mod_list_layer = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer.list_layer_versions' +mod_create_layer = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer.create_layer_version' +mod_delete_layer = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer.delete_layer_version' + + +@pytest.mark.parametrize( + "params,api_result,calls,ansible_result", + [ + ( + { + "name": "testlayer", + "version": 4 + }, + [], + [], + {"changed": False, "layer_versions": []} + ), + ( + { + "name": "testlayer", + "version": 4 + }, + [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ], + [], + {"changed": False, "layer_versions": []} + ), + ( + { + "name": "testlayer", + "version": 2 + }, + [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ], + [ + call(LayerName='testlayer', VersionNumber=2) + ], + { + "changed": True, + "layer_versions": [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + } + ] + } + ), + ( + { + "name": "testlayer", + "version": -1 + }, + [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ], + [ + call(LayerName='testlayer', VersionNumber=2), + call(LayerName='testlayer', VersionNumber=1) + ], + { + "changed": True, + "layer_versions": [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ] + } + ) + ] +) +@patch(mod_list_layer) +def test_delete_layer(m_list_layer, params, api_result, calls, ansible_result): + + lambda_client = MagicMock() + lambda_client.delete_layer_version.return_value = None + + m_list_layer.return_value = api_result + result = lambda_layer.delete_layer_version(lambda_client, params) + assert result == ansible_result + + m_list_layer.assert_called_once_with( + lambda_client, params.get("name") + ) + + if not calls: + lambda_client.delete_layer_version.assert_not_called() + else: + lambda_client.delete_layer_version.assert_has_calls(calls, any_order=True) + + +@patch(mod_list_layer) +def test_delete_layer_check_mode(m_list_layer): + + lambda_client = MagicMock() + lambda_client.delete_layer_version.return_value = None + + m_list_layer.return_value = [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ] + params = {"name": "testlayer", "version": -1} + result = lambda_layer.delete_layer_version(lambda_client, params, check_mode=True) + ansible_result = { + "changed": True, + "layer_versions": [ + { + 'compatible_runtimes': ["python3.7"], + 'created_date': "2022-09-29T10:31:35.977+0000", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:2", + "license_info": "MIT", + 'version': 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ] + } + assert result == ansible_result + + m_list_layer.assert_called_once_with( + lambda_client, params.get("name") + ) + lambda_client.delete_layer_version.assert_not_called() + + +@patch(mod_list_layer) +def test_delete_layer_failure(m_list_layer): + + lambda_client = MagicMock() + lambda_client.delete_layer_version.side_effect = raise_lambdalayer_exception() + + m_list_layer.return_value = [ + { + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:testlayer:1", + "version": 1 + } + ] + params = {"name": "testlayer", "version": 1} + with pytest.raises(lambda_layer.LambdaLayerFailure): + lambda_layer.delete_layer_version(lambda_client, params) + + +@pytest.mark.parametrize( + "b_s3content", + [ + (True), + (False) + ] +) +@patch(mod_list_layer) +def test_create_layer(m_list_layer, b_s3content, tmp_path): + params = { + "name": "testlayer", + "description": "ansible units testing sample layer", + "content": {}, + "license_info": "MIT" + } + + lambda_client = MagicMock() + + lambda_client.publish_layer_version.return_value = { + 'CompatibleRuntimes': [ + 'python3.6', + 'python3.7', + ], + 'Content': { + 'CodeSha256': 'tv9jJO+rPbXUUXuRKi7CwHzKtLDkDRJLB3cC3Z/ouXo=', + 'CodeSize': 169, + 'Location': 'https://awslambda-us-west-2-layers.s3.us-west-2.amazonaws.com/snapshots/123456789012/my-layer-4aaa2fbb', + }, + 'CreatedDate': '2018-11-14T23:03:52.894+0000', + 'Description': "ansible units testing sample layer", + 'LayerArn': 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer', + 'LayerVersionArn': 'arn:aws:lambda:us-west-2:123456789012:layer:testlayer:1', + 'LicenseInfo': 'MIT', + 'Version': 1, + 'ResponseMetadata': { + 'http_header': 'true', + }, + } + + expected = { + "changed": True, + "layer_versions": [ + { + 'compatible_runtimes': ['python3.6', 'python3.7'], + 'content': { + 'code_sha256': 'tv9jJO+rPbXUUXuRKi7CwHzKtLDkDRJLB3cC3Z/ouXo=', + 'code_size': 169, + 'location': 'https://awslambda-us-west-2-layers.s3.us-west-2.amazonaws.com/snapshots/123456789012/my-layer-4aaa2fbb' + }, + 'created_date': '2018-11-14T23:03:52.894+0000', + 'description': 'ansible units testing sample layer', + 'layer_arn': 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer', + 'layer_version_arn': 'arn:aws:lambda:us-west-2:123456789012:layer:testlayer:1', + 'license_info': 'MIT', + 'version': 1 + } + ] + } + + if b_s3content: + params["content"] = { + "s3_bucket": "mybucket", + "s3_key": "mybucket-key", + "s3_object_version": "v1" + } + content_arg = { + "S3Bucket": "mybucket", + "S3Key": "mybucket-key", + "S3ObjectVersion": "v1" + } + else: + binary_data = b"simple lambda layer content" + test_dir = tmp_path / "lambda_layer" + test_dir.mkdir() + zipfile = test_dir / "lambda.zip" + zipfile.write_bytes(binary_data) + + params["content"] = {"zip_file": str(zipfile)} + content_arg = { + "ZipFile": binary_data, + } + + result = lambda_layer.create_layer_version(lambda_client, params) + + assert result == expected + + lambda_client.publish_layer_version.assert_called_with( + LayerName="testlayer", + Description="ansible units testing sample layer", + LicenseInfo="MIT", + Content=content_arg, + ) + + m_list_layer.assert_not_called() + + +@patch(mod_list_layer) +def test_create_layer_check_mode(m_list_layer): + params = { + "name": "testlayer", + "description": "ansible units testing sample layer", + "content": { + "s3_bucket": "mybucket", + "s3_key": "mybucket-key", + "s3_object_version": "v1" + }, + "license_info": "MIT" + } + + lambda_client = MagicMock() + + result = lambda_layer.create_layer_version(lambda_client, params, check_mode=True) + assert result == {"msg": "Create operation skipped - running in check mode", "changed": True} + + m_list_layer.assert_not_called() + lambda_client.publish_layer_version.assert_not_called() + + +def test_create_layer_failure(): + params = { + "name": "testlayer", + "description": "ansible units testing sample layer", + "content": { + "s3_bucket": "mybucket", + "s3_key": "mybucket-key", + "s3_object_version": "v1" + }, + "compatible_runtimes": [ + "nodejs", + "python3.9" + ], + "compatible_architectures": [ + 'x86_64', + 'arm64' + ] + } + lambda_client = MagicMock() + lambda_client.publish_layer_version.side_effect = raise_lambdalayer_exception() + + with pytest.raises(lambda_layer.LambdaLayerFailure): + lambda_layer.create_layer_version(lambda_client, params) + + +def test_create_layer_using_unexisting_file(): + params = { + "name": "testlayer", + "description": "ansible units testing sample layer", + "content": { + "zip_file": "this_file_does_not_exist", + }, + "compatible_runtimes": [ + "nodejs", + "python3.9" + ], + "compatible_architectures": [ + 'x86_64', + 'arm64' + ] + } + + lambda_client = MagicMock() + + lambda_client.publish_layer_version.return_value = {} + with pytest.raises(FileNotFoundError): + lambda_layer.create_layer_version(lambda_client, params) + + lambda_client.publish_layer_version.assert_not_called() + + +@pytest.mark.parametrize( + "params,failure", + [ + ( + {"name": "test-layer"}, + False + ), + ( + {"name": "test-layer", "state": "absent"}, + False + ), + ( + {"name": "test-layer"}, + True + ), + ( + {"name": "test-layer", "state": "absent"}, + True + ), + ] +) +@patch(mod_create_layer) +@patch(mod_delete_layer) +def test_execute_module(m_delete_layer, m_create_layer, params, failure): + + module = MagicMock() + module.params = params + module.check_mode = False + module.exit_json.side_effect = SystemExit(1) + module.fail_json_aws.side_effect = SystemExit(2) + + lambda_client = MagicMock() + + state = params.get("state", "present") + result = {"changed": True, "layers_versions": {}} + + if not failure: + if state == "present": + m_create_layer.return_value = result + with pytest.raises(SystemExit): + lambda_layer.execute_module(module, lambda_client) + + module.exit_json.assert_called_with(**result) + module.fail_json_aws.assert_not_called() + m_create_layer.assert_called_with( + lambda_client, params, module.check_mode + ) + m_delete_layer.assert_not_called() + + elif state == "absent": + m_delete_layer.return_value = result + with pytest.raises(SystemExit): + lambda_layer.execute_module(module, lambda_client) + + module.exit_json.assert_called_with(**result) + module.fail_json_aws.assert_not_called() + m_delete_layer.assert_called_with( + lambda_client, params, module.check_mode + ) + m_create_layer.assert_not_called() + else: + exc = "lambdalayer_execute_module_exception" + msg = "this_exception_is_used_for_unit_testing" + m_create_layer.side_effect = raise_lambdalayer_exception(exc, msg) + m_delete_layer.side_effect = raise_lambdalayer_exception(exc, msg) + + with pytest.raises(SystemExit): + lambda_layer.execute_module(module, lambda_client) + + module.exit_json.assert_not_called() + module.fail_json_aws.assert_called_with( + exc, msg=msg + ) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer_info.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer_info.py new file mode 100644 index 000000000..25a1f15ac --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_lambda_layer_info.py @@ -0,0 +1,358 @@ +# +# (c) 2022 Red Hat Inc. +# +# This file is part of Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import pytest +from botocore.exceptions import BotoCoreError + +from unittest.mock import MagicMock, call, patch +from ansible_collections.amazon.aws.plugins.modules import lambda_layer_info + + +mod__list_layer_versions = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer_info._list_layer_versions' +mod__list_layers = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer_info._list_layers' +mod_list_layer_versions = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer_info.list_layer_versions' +mod_list_layers = 'ansible_collections.amazon.aws.plugins.modules.lambda_layer_info.list_layers' + + +list_layers_paginate_result = { + 'NextMarker': '002', + 'Layers': [ + { + 'LayerName': "test-layer-01", + 'LayerArn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-01", + 'LatestMatchingVersion': { + 'LayerVersionArn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-01:1", + 'Version': 1, + 'Description': "lambda layer created for unit tests", + 'CreatedDate': "2022-09-29T10:31:26.341+0000", + 'CompatibleRuntimes': [ + 'nodejs', + 'nodejs4.3', + 'nodejs6.10' + ], + 'LicenseInfo': 'MIT', + 'CompatibleArchitectures': [ + 'arm64' + ] + } + }, + { + 'LayerName': "test-layer-02", + 'LayerArn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-02", + 'LatestMatchingVersion': { + 'LayerVersionArn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-02:1", + 'Version': 1, + 'CreatedDate': "2022-09-29T10:31:26.341+0000", + 'CompatibleArchitectures': [ + 'arm64' + ] + } + }, + ], + 'ResponseMetadata': { + 'http': 'true', + }, +} + +list_layers_result = [ + { + 'layer_name': "test-layer-01", + 'layer_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-01", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-01:1", + 'version': 1, + 'description': "lambda layer created for unit tests", + 'created_date': "2022-09-29T10:31:26.341+0000", + 'compatible_runtimes': [ + 'nodejs', + 'nodejs4.3', + 'nodejs6.10' + ], + 'license_info': 'MIT', + 'compatible_architectures': [ + 'arm64' + ] + }, + { + 'layer_name': "test-layer-02", + 'layer_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-02", + 'layer_version_arn': "arn:aws:lambda:eu-west-2:123456789012:layer:test-layer-02:1", + 'version': 1, + 'created_date': "2022-09-29T10:31:26.341+0000", + 'compatible_architectures': [ + 'arm64' + ] + } +] + + +list_layers_versions_paginate_result = { + 'LayerVersions': [ + { + 'CompatibleRuntimes': ["python3.7"], + 'CreatedDate': "2022-09-29T10:31:35.977+0000", + 'LayerVersionArn': "arn:aws:lambda:eu-west-2:123456789012:layer:layer-01:2", + "LicenseInfo": "MIT", + 'Version': 2, + 'CompatibleArchitectures': [ + 'arm64' + ] + }, + { + "CompatibleRuntimes": ["python3.7"], + "CreatedDate": "2022-09-29T10:31:26.341+0000", + "Description": "lambda layer first version", + "LayerVersionArn": "arn:aws:lambda:eu-west-2:123456789012:layer:layer-01:1", + "LicenseInfo": "GPL-3.0-only", + "Version": 1 + } + ], + 'ResponseMetadata': { + 'http': 'true', + }, + 'NextMarker': '001', +} + + +list_layers_versions_result = [ + { + "compatible_runtimes": ["python3.7"], + "created_date": "2022-09-29T10:31:35.977+0000", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:layer-01:2", + "license_info": "MIT", + "version": 2, + 'compatible_architectures': [ + 'arm64' + ] + }, + { + "compatible_runtimes": ["python3.7"], + "created_date": "2022-09-29T10:31:26.341+0000", + "description": "lambda layer first version", + "layer_version_arn": "arn:aws:lambda:eu-west-2:123456789012:layer:layer-01:1", + "license_info": "GPL-3.0-only", + "version": 1 + } +] + + +@pytest.mark.parametrize( + "params,call_args", + [ + ( + { + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + }, + { + "CompatibleRuntime": "nodejs", + "CompatibleArchitecture": "arm64" + } + ), + ( + { + "compatible_runtime": "nodejs", + }, + { + "CompatibleRuntime": "nodejs", + } + ), + ( + { + "compatible_architecture": "arm64" + }, + { + "CompatibleArchitecture": "arm64" + } + ), + ( + {}, {} + ) + ] +) +@patch(mod__list_layers) +def test_list_layers_with_latest_version(m__list_layers, params, call_args): + + lambda_client = MagicMock() + + m__list_layers.return_value = list_layers_paginate_result + layers = lambda_layer_info.list_layers(lambda_client, **params) + + m__list_layers.assert_has_calls( + [ + call(lambda_client, **call_args) + ] + ) + assert layers == list_layers_result + + +@pytest.mark.parametrize( + "params,call_args", + [ + ( + { + "name": "layer-01", + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + }, + { + "LayerName": "layer-01", + "CompatibleRuntime": "nodejs", + "CompatibleArchitecture": "arm64" + } + ), + ( + { + "name": "layer-01", + "compatible_runtime": "nodejs", + }, + { + "LayerName": "layer-01", + "CompatibleRuntime": "nodejs", + } + ), + ( + { + "name": "layer-01", + "compatible_architecture": "arm64" + }, + { + "LayerName": "layer-01", + "CompatibleArchitecture": "arm64" + } + ), + ( + {"name": "layer-01"}, {"LayerName": "layer-01"} + ) + ] +) +@patch(mod__list_layer_versions) +def test_list_layer_versions(m__list_layer_versions, params, call_args): + + lambda_client = MagicMock() + + m__list_layer_versions.return_value = list_layers_versions_paginate_result + layers = lambda_layer_info.list_layer_versions(lambda_client, **params) + + m__list_layer_versions.assert_has_calls( + [ + call(lambda_client, **call_args) + ] + ) + assert layers == list_layers_versions_result + + +def raise_botocore_exception(): + return BotoCoreError(error="failed", operation="list_layers") + + +@pytest.mark.parametrize( + "params", + [ + ( + { + "name": "test-layer", + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + } + ), + ( + { + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + } + ) + ] +) +@patch(mod__list_layers) +@patch(mod__list_layer_versions) +def test_list_layers_with_failure(m__list_layer_versions, m__list_layers, params): + + lambda_client = MagicMock() + + if "name" in params: + m__list_layer_versions.side_effect = raise_botocore_exception() + test_function = lambda_layer_info.list_layer_versions + else: + m__list_layers.side_effect = raise_botocore_exception() + test_function = lambda_layer_info.list_layers + + with pytest.raises(lambda_layer_info.LambdaLayerInfoFailure): + test_function(lambda_client, **params) + + +def raise_layer_info_exception(exc, msg): + return lambda_layer_info.LambdaLayerInfoFailure(exc=exc, msg=msg) + + +@pytest.mark.parametrize( + "params,failure", + [ + ( + { + "name": "test-layer", + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + }, + False + ), + ( + { + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + }, + False + ), + ( + { + "name": "test-layer", + "compatible_runtime": "nodejs", + "compatible_architecture": "arm64" + }, + True + ) + ] +) +@patch(mod_list_layers) +@patch(mod_list_layer_versions) +def test_execute_module(m_list_layer_versions, m_list_layers, params, failure): + + lambda_client = MagicMock() + + module = MagicMock() + module.params = params + module.exit_json.side_effect = SystemExit(1) + module.fail_json_aws.side_effect = SystemExit(2) + + method_called, method_not_called = m_list_layers, m_list_layer_versions + if "name" in params: + method_not_called, method_called = m_list_layers, m_list_layer_versions + + if failure: + exc = "lambda_layer_exception" + msg = "this exception has been generated for unit tests" + + method_called.side_effect = raise_layer_info_exception(exc, msg) + + with pytest.raises(SystemExit): + lambda_layer_info.execute_module(module, lambda_client) + + module.fail_json_aws.assert_called_with(exception=exc, msg=msg) + + else: + result = {"A": "valueA", "B": "valueB"} + method_called.return_value = result + + with pytest.raises(SystemExit): + lambda_layer_info.execute_module(module, lambda_client) + + module.exit_json.assert_called_with( + changed=False, layers_versions=result + ) + method_called.assert_called_with(lambda_client, **params) + method_not_called.list_layers.assert_not_called() diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_s3_object.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_s3_object.py new file mode 100644 index 000000000..b02513072 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/test_s3_object.py @@ -0,0 +1,29 @@ +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.module_utils.six.moves.urllib.parse import urlparse + +from ansible_collections.amazon.aws.plugins.modules import s3_object + + +class TestUrlparse(): + + def test_urlparse(self): + actual = urlparse("http://test.com/here") + assert actual.scheme == "http" + assert actual.netloc == "test.com" + assert actual.path == "/here" + + def test_is_fakes3(self): + actual = s3_object.is_fakes3("fakes3://bla.blubb") + assert actual is True + + def test_get_s3_connection(self): + aws_connect_kwargs = dict(aws_access_key_id="access_key", + aws_secret_access_key="secret_key") + location = None + rgw = True + s3_url = "http://bla.blubb" + actual = s3_object.get_s3_connection(None, aws_connect_kwargs, location, rgw, s3_url) + assert "bla.blubb" in str(actual._endpoint) diff --git a/ansible_collections/amazon/aws/tests/unit/plugins/modules/utils.py b/ansible_collections/amazon/aws/tests/unit/plugins/modules/utils.py new file mode 100644 index 000000000..058a5b605 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/plugins/modules/utils.py @@ -0,0 +1,50 @@ +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.amazon.aws.tests.unit.compat import unittest +from ansible_collections.amazon.aws.tests.unit.compat.mock import patch +from ansible.module_utils import basic +from ansible.module_utils._text import to_bytes + + +def set_module_args(args): + if '_ansible_remote_tmp' not in args: + args['_ansible_remote_tmp'] = '/tmp' + if '_ansible_keep_remote_files' not in args: + args['_ansible_keep_remote_files'] = False + + args = json.dumps({'ANSIBLE_MODULE_ARGS': args}) + basic._ANSIBLE_ARGS = to_bytes(args) + + +class AnsibleExitJson(Exception): + pass + + +class AnsibleFailJson(Exception): + pass + + +def exit_json(*args, **kwargs): + if 'changed' not in kwargs: + kwargs['changed'] = False + raise AnsibleExitJson(kwargs) + + +def fail_json(*args, **kwargs): + kwargs['failed'] = True + raise AnsibleFailJson(kwargs) + + +class ModuleTestCase(unittest.TestCase): + + def setUp(self): + self.mock_module = patch.multiple(basic.AnsibleModule, exit_json=exit_json, fail_json=fail_json) + self.mock_module.start() + self.mock_sleep = patch('time.sleep') + self.mock_sleep.start() + set_module_args({}) + self.addCleanup(self.mock_module.stop) + self.addCleanup(self.mock_sleep.stop) diff --git a/ansible_collections/amazon/aws/tests/unit/requirements.txt b/ansible_collections/amazon/aws/tests/unit/requirements.txt new file mode 100644 index 000000000..49f392832 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/requirements.txt @@ -0,0 +1,5 @@ +# Our code is based on the AWS SDKs +botocore +boto3 + +placebo diff --git a/ansible_collections/amazon/aws/tests/unit/utils/amazon_placebo_fixtures.py b/ansible_collections/amazon/aws/tests/unit/utils/amazon_placebo_fixtures.py new file mode 100644 index 000000000..6912c2e32 --- /dev/null +++ b/ansible_collections/amazon/aws/tests/unit/utils/amazon_placebo_fixtures.py @@ -0,0 +1,213 @@ +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import errno +import os +import time +import mock +import pytest + +boto3 = pytest.importorskip("boto3") +botocore = pytest.importorskip("botocore") +placebo = pytest.importorskip("placebo") + +""" +Using Placebo to test modules using boto3: + +This is an example test, using the placeboify fixture to test that a module +will fail if resources it depends on don't exist. + +> from placebo_fixtures import placeboify, scratch_vpc +> +> def test_create_with_nonexistent_launch_config(placeboify): +> connection = placeboify.client('autoscaling') +> module = FakeModule('test-asg-created', None, min_size=0, max_size=0, desired_capacity=0) +> with pytest.raises(FailJSON) as excinfo: +> asg_module.create_autoscaling_group(connection, module) +> .... asserts based on module state/exceptions .... + +In more advanced cases, use unrecorded resource fixtures to fill in ARNs/IDs of +things modules depend on, such as: + +> def test_create_in_vpc(placeboify, scratch_vpc): +> connection = placeboify.client('autoscaling') +> module = FakeModule(name='test-asg-created', +> min_size=0, max_size=0, desired_capacity=0, +> availability_zones=[s['az'] for s in scratch_vpc['subnets']], +> vpc_zone_identifier=[s['id'] for s in scratch_vpc['subnets']], +> ) +> ..... so on and so forth .... +""" + + +@pytest.fixture +def placeboify(request, monkeypatch): + """This fixture puts a recording/replaying harness around `boto3_conn` + + Placeboify patches the `boto3_conn` function in ec2 module_utils to return + a boto3 session that in recording or replaying mode, depending on the + PLACEBO_RECORD environment variable. Unset PLACEBO_RECORD (the common case + for just running tests) will put placebo in replay mode, set PLACEBO_RECORD + to any value to turn off replay & operate on real AWS resources. + + The recorded sessions are stored in the test file's directory, under the + namespace `placebo_recordings/{testfile name}/{test function name}` to + distinguish them. + """ + session = boto3.Session(region_name='us-west-2') + + recordings_path = os.path.join( + request.fspath.dirname, + 'placebo_recordings', + request.fspath.basename.replace('.py', ''), + request.function.__name__ + # remove the test_ prefix from the function & file name + ).replace('test_', '') + + if not os.getenv('PLACEBO_RECORD'): + if not os.path.isdir(recordings_path): + raise NotImplementedError('Missing Placebo recordings in directory: %s' % recordings_path) + else: + try: + # make sure the directory for placebo test recordings is available + os.makedirs(recordings_path) + except OSError as e: + if e.errno != errno.EEXIST: + raise + + pill = placebo.attach(session, data_path=recordings_path) + if os.getenv('PLACEBO_RECORD'): + pill.record() + else: + pill.playback() + + def boto3_middleman_connection(module, conn_type, resource, region='us-west-2', **kwargs): + if conn_type != 'client': + # TODO support resource-based connections + raise ValueError('Mocker only supports client, not %s' % conn_type) + return session.client(resource, region_name=region) + + import ansible_collections.amazon.aws.plugins.module_utils.ec2 + monkeypatch.setattr( + ansible_collections.amazon.aws.plugins.module_utils.ec2, + 'boto3_conn', + boto3_middleman_connection, + ) + yield session + + # tear down + pill.stop() + + +@pytest.fixture(scope='module') +def basic_launch_config(): + """Create an EC2 launch config whose creation *is not* recorded and return its name + + This fixture is module-scoped, since launch configs are immutable and this + can be reused for many tests. + """ + if not os.getenv('PLACEBO_RECORD'): + yield 'pytest_basic_lc' + return + + # use a *non recording* session to make the launch config + # since that's a prereq of the ec2_asg module, and isn't what + # we're testing. + asg = boto3.client('autoscaling') + asg.create_launch_configuration( + LaunchConfigurationName='pytest_basic_lc', + ImageId='ami-9be6f38c', # Amazon Linux 2016.09 us-east-1 AMI, can be any valid AMI + SecurityGroups=[], + UserData='#!/bin/bash\necho hello world', + InstanceType='t2.micro', + InstanceMonitoring={'Enabled': False}, + AssociatePublicIpAddress=True + ) + + yield 'pytest_basic_lc' + + try: + asg.delete_launch_configuration(LaunchConfigurationName='pytest_basic_lc') + except botocore.exceptions.ClientError as e: + if 'not found' in e.message: + return + raise + + +@pytest.fixture(scope='module') +def scratch_vpc(): + if not os.getenv('PLACEBO_RECORD'): + yield { + 'vpc_id': 'vpc-123456', + 'cidr_range': '10.0.0.0/16', + 'subnets': [ + { + 'id': 'subnet-123456', + 'az': 'us-east-1d', + }, + { + 'id': 'subnet-654321', + 'az': 'us-east-1e', + }, + ] + } + return + + # use a *non recording* session to make the base VPC and subnets + ec2 = boto3.client('ec2') + vpc_resp = ec2.create_vpc( + CidrBlock='10.0.0.0/16', + AmazonProvidedIpv6CidrBlock=False, + ) + subnets = ( + ec2.create_subnet( + VpcId=vpc_resp['Vpc']['VpcId'], + CidrBlock='10.0.0.0/24', + ), + ec2.create_subnet( + VpcId=vpc_resp['Vpc']['VpcId'], + CidrBlock='10.0.1.0/24', + ) + ) + time.sleep(3) + + yield { + 'vpc_id': vpc_resp['Vpc']['VpcId'], + 'cidr_range': '10.0.0.0/16', + 'subnets': [ + { + 'id': s['Subnet']['SubnetId'], + 'az': s['Subnet']['AvailabilityZone'], + } for s in subnets + ] + } + + try: + for s in subnets: + try: + ec2.delete_subnet(SubnetId=s['Subnet']['SubnetId']) + except botocore.exceptions.ClientError as e: + if 'not found' in e.message: + continue + raise + ec2.delete_vpc(VpcId=vpc_resp['Vpc']['VpcId']) + except botocore.exceptions.ClientError as e: + if 'not found' in e.message: + return + raise + + +@pytest.fixture(scope='module') +def maybe_sleep(): + """If placebo is reading saved sessions, make sleep always take 0 seconds. + + AWS modules often perform polling or retries, but when using recorded + sessions there's no reason to wait. We can still exercise retry and other + code paths without waiting for wall-clock time to pass.""" + if not os.getenv('PLACEBO_RECORD'): + p = mock.patch('time.sleep', return_value=None) + p.start() + yield + p.stop() + else: + yield |