diff --git a/tests/unit/plugin_utils/base/test_plugin.py b/tests/unit/plugin_utils/base/test_plugin.py index f168cb4deff..d7acb652471 100644 --- a/tests/unit/plugin_utils/base/test_plugin.py +++ b/tests/unit/plugin_utils/base/test_plugin.py @@ -120,6 +120,16 @@ def test_client_wrapper(monkeypatch): region=sentinel.CONN_REGION, endpoint=sentinel.CONN_URL,) + # Check that we can override parameters + wrapped_conn = base_plugin.client(sentinel.PARAM_SERVICE, sentinel.PARAM_WRAPPER, region=sentinel.PARAM_REGION) + assert wrapped_conn.client is sentinel.BOTO3_CONN + assert wrapped_conn.retry is sentinel.PARAM_WRAPPER + assert get_aws_connection_info.call_args == call(base_plugin) + assert boto3_conn.call_args == call(base_plugin, conn_type='client', + resource=sentinel.PARAM_SERVICE, + region=sentinel.PARAM_REGION, + endpoint=sentinel.CONN_URL,) + def test_resource(monkeypatch): get_aws_connection_info = MagicMock(name='get_aws_connection_info') @@ -137,3 +147,10 @@ def test_resource(monkeypatch): resource=sentinel.PARAM_SERVICE, region=sentinel.CONN_REGION, endpoint=sentinel.CONN_URL,) + + assert base_plugin.resource(sentinel.PARAM_SERVICE, region=sentinel.PARAM_REGION) is sentinel.BOTO3_CONN + assert get_aws_connection_info.call_args == call(base_plugin) + assert boto3_conn.call_args == call(base_plugin, conn_type='resource', + resource=sentinel.PARAM_SERVICE, + region=sentinel.PARAM_REGION, + endpoint=sentinel.CONN_URL,) diff --git a/tests/unit/plugin_utils/inventory/test_inventory.py b/tests/unit/plugin_utils/inventory/test_inventory.py deleted file mode 100644 index 5c76c9e3b83..00000000000 --- a/tests/unit/plugin_utils/inventory/test_inventory.py +++ /dev/null @@ -1,143 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright 2017 Sloane Hertel -# -# 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 . - -# Make coding more python3-ish -import pytest -from unittest.mock import MagicMock, patch, call - -from ansible.errors import AnsibleError -from ansible.parsing.dataloader import DataLoader -from ansible_collections.amazon.aws.plugins.plugin_utils.inventory import AWSInventoryBase - - -@pytest.fixture() -def inventory(): - inventory = AWSInventoryBase() - 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 - - -@pytest.fixture() -def loader(): - return DataLoader() - - -def test_boto3_conn(inventory, loader): - 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} - inventory._set_credentials(loader) - with pytest.raises(AnsibleError) as error_message: - for _connection, _region in inventory._boto3_conn(regions=['us-east-1'], resource="test"): - assert "Insufficient credentials found" in error_message - - -def test_set_credentials(inventory, loader): - 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'} - 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, loader): - 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: - inventory._set_credentials(loader) - assert "Insufficient credentials found" in error_message - - -@pytest.mark.skip(reason="skipping for now, will be reactivated later") -@pytest.mark.parametrize("hasregions", [False]) -def test_boto3_conn(inventory, hasregions): - - credentials = MagicMock() - iam_role_arn = MagicMock() - resource = MagicMock() - - inventory._get_credentials = MagicMock() - inventory._get_credentials.return_value = credentials - inventory.iam_role_arn = iam_role_arn - - regions = [] - if hasregions: - regions = ["us-east-1", "us-west-1", "eu-east-1"] - - inventory._boto3_regions = MagicMock() - inventory._boto3_regions.return_value = regions - - inventory.fail_aws = MagicMock() - inventory.fail_aws.side_effect = SystemExit(1) - - connections = [MagicMock(name="connection_%d" % c) for c in range(len(regions))] - - inventory._get_boto3_connection = MagicMock() - inventory._get_boto3_connection.side_effect = connections - - if hasregions: - assert list(inventory._boto3_conn(regions, resource)) == [(connections[i], regions[i]) for i in range(len(regions))] - else: - result = inventory._boto3_conn(regions, resource) - - inventory._get_credentials.assert_called_once() - inventory._boto3_regions.assert_called_with(credentials, iam_role_arn, resource) - if hasregions: - inventory._get_boto3_connection.assert_has_calls( - [call(credentials, iam_role_arn, resource, region) for region in regions], - any_order=True - ) diff --git a/tests/unit/plugin_utils/inventory/test_inventory_base.py b/tests/unit/plugin_utils/inventory/test_inventory_base.py new file mode 100644 index 00000000000..14bde838775 --- /dev/null +++ b/tests/unit/plugin_utils/inventory/test_inventory_base.py @@ -0,0 +1,31 @@ +# (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 unittest.mock import call +from unittest.mock import MagicMock +from unittest.mock import patch +from unittest.mock import sentinel + +import ansible_collections.amazon.aws.plugins.plugin_utils.inventory as utils_inventory + + +@patch("ansible.plugins.inventory.BaseInventoryPlugin.parse", MagicMock) +def test_parse(monkeypatch): + require_aws_sdk = MagicMock(name='require_aws_sdk') + require_aws_sdk.return_value = sentinel.RETURNED_SDK + config_data = MagicMock(name='_read_config_data') + config_data.return_value = sentinel.RETURNED_OPTIONS + frozen_credentials = MagicMock(name='_set_frozen_credentials') + frozen_credentials.return_value = sentinel.RETURNED_CREDENTIALS + + inventory_plugin = utils_inventory.AWSInventoryBase() + monkeypatch.setattr(inventory_plugin, 'require_aws_sdk', require_aws_sdk) + monkeypatch.setattr(inventory_plugin, '_read_config_data', config_data) + monkeypatch.setattr(inventory_plugin, '_set_frozen_credentials', frozen_credentials) + + inventory_plugin.parse(sentinel.PARAM_INVENTORY, sentinel.PARAM_LOADER, sentinel.PARAM_PATH) + assert require_aws_sdk.call_args == call(botocore_version=None, boto3_version=None) + assert config_data.call_args == call(sentinel.PARAM_PATH) + assert frozen_credentials.call_args == call() diff --git a/tests/unit/plugin_utils/inventory/test_inventory_clients.py b/tests/unit/plugin_utils/inventory/test_inventory_clients.py new file mode 100644 index 00000000000..0aac20808d7 --- /dev/null +++ b/tests/unit/plugin_utils/inventory/test_inventory_clients.py @@ -0,0 +1,112 @@ +# (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 unittest.mock import call +from unittest.mock import MagicMock +from unittest.mock import sentinel + +import ansible_collections.amazon.aws.plugins.plugin_utils.inventory as utils_inventory +import ansible_collections.amazon.aws.plugins.plugin_utils.base as utils_base +# import ansible_collections.amazon.aws.plugins.module_utils. + + +def test_client(monkeypatch): + super_client = MagicMock(name="client") + super_client.return_value = sentinel.SUPER_CLIENT + monkeypatch.setattr(utils_base.AWSPluginBase, "client", super_client) + inventory_plugin = utils_inventory.AWSInventoryBase() + + client = inventory_plugin.client(sentinel.SERVICE_NAME) + assert super_client.call_args == call(sentinel.SERVICE_NAME) + assert client is sentinel.SUPER_CLIENT + + client = inventory_plugin.client(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert super_client.call_args == call(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert client is sentinel.SUPER_CLIENT + + frozen_creds = {"credential_one": sentinel.CREDENTIAL_ONE} + inventory_plugin._frozen_credentials = frozen_creds + + client = inventory_plugin.client(sentinel.SERVICE_NAME) + assert super_client.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ONE + ) + assert client is sentinel.SUPER_CLIENT + + client = inventory_plugin.client(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert super_client.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ONE, + extra_arg=sentinel.EXTRA_ARG + ) + assert client is sentinel.SUPER_CLIENT + + client = inventory_plugin.client(sentinel.SERVICE_NAME, credential_one=sentinel.CREDENTIAL_ARG) + assert super_client.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ARG, + ) + assert client is sentinel.SUPER_CLIENT + + +def test_resource(monkeypatch): + super_resource = MagicMock(name="resource") + super_resource.return_value = sentinel.SUPER_RESOURCE + monkeypatch.setattr(utils_base.AWSPluginBase, "resource", super_resource) + inventory_plugin = utils_inventory.AWSInventoryBase() + + resource = inventory_plugin.resource(sentinel.SERVICE_NAME) + assert super_resource.call_args == call(sentinel.SERVICE_NAME) + assert resource is sentinel.SUPER_RESOURCE + + resource = inventory_plugin.resource(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert super_resource.call_args == call(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert resource is sentinel.SUPER_RESOURCE + + frozen_creds = {"credential_one": sentinel.CREDENTIAL_ONE} + inventory_plugin._frozen_credentials = frozen_creds + + resource = inventory_plugin.resource(sentinel.SERVICE_NAME) + assert super_resource.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ONE + ) + assert resource is sentinel.SUPER_RESOURCE + + resource = inventory_plugin.resource(sentinel.SERVICE_NAME, extra_arg=sentinel.EXTRA_ARG) + assert super_resource.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ONE, + extra_arg=sentinel.EXTRA_ARG + ) + assert resource is sentinel.SUPER_RESOURCE + + resource = inventory_plugin.resource(sentinel.SERVICE_NAME, credential_one=sentinel.CREDENTIAL_ARG) + assert super_resource.call_args == call( + sentinel.SERVICE_NAME, + credential_one=sentinel.CREDENTIAL_ARG, + ) + assert resource is sentinel.SUPER_RESOURCE + + +def test_all_clients(monkeypatch): + test_regions = ['us-east-1', 'us-east-2'] + inventory_plugin = utils_inventory.AWSInventoryBase() + mock_client = MagicMock(name="client") + mock_client.return_value = sentinel.RETURN_CLIENT + monkeypatch.setattr(inventory_plugin, "client", mock_client) + boto3_regions = MagicMock(name="_boto3_regions") + boto3_regions.return_value = test_regions + monkeypatch.setattr(inventory_plugin, "_boto3_regions", boto3_regions) + + regions = [] + for (client, region) in inventory_plugin.all_clients(sentinel.ARG_SERVICE): + assert boto3_regions.call_args == call(service=sentinel.ARG_SERVICE) + assert mock_client.call_args == call(sentinel.ARG_SERVICE, region=region) + assert client is sentinel.RETURN_CLIENT + regions.append(region) + + assert set(regions) == set(test_regions) diff --git a/tests/unit/plugins/inventory/test_aws_ec2.py b/tests/unit/plugins/inventory/test_aws_ec2.py index f1daa144ef0..6ccd364636b 100644 --- a/tests/unit/plugins/inventory/test_aws_ec2.py +++ b/tests/unit/plugins/inventory/test_aws_ec2.py @@ -473,8 +473,8 @@ def test_inventory_get_instances_by_region(m_describe_ec2_instances, inventory, (MagicMock(), "us-east-1"), (MagicMock(), "us-east-2") ] - inventory._boto3_conn = MagicMock() - inventory._boto3_conn.return_value = boto3_conn + inventory.all_clients = MagicMock() + inventory.all_clients.return_value = boto3_conn m_describe_ec2_instances.side_effect = [ { @@ -526,7 +526,7 @@ def test_inventory_get_instances_by_region(m_describe_ec2_instances, inventory, regions = ["us-east-2", "us-east-4"] assert inventory._get_instances_by_region(regions, filters, False) == expected - inventory._boto3_conn.assert_called_with(regions, "ec2") + inventory.all_clients.assert_called_with("ec2") if any((f['Name'] == 'instance-state-name' for f in filters)): filters.append(default_filter) @@ -564,8 +564,8 @@ def test_inventory_get_instances_by_region(m_describe_ec2_instances, inventory, @patch("ansible_collections.amazon.aws.plugins.inventory.aws_ec2._describe_ec2_instances") def test_inventory_get_instances_by_region_failures(m_describe_ec2_instances, inventory, strict, error): - inventory._boto3_conn = MagicMock() - inventory._boto3_conn.return_value = [(MagicMock(), "us-west-2")] + inventory.all_clients = MagicMock() + inventory.all_clients.return_value = [(MagicMock(), "us-west-2")] inventory.fail_aws = MagicMock() inventory.fail_aws.side_effect = SystemExit(1) diff --git a/tests/unit/plugins/inventory/test_aws_rds.py b/tests/unit/plugins/inventory/test_aws_rds.py index 2a2d0fb8b5b..a915df5c050 100644 --- a/tests/unit/plugins/inventory/test_aws_rds.py +++ b/tests/unit/plugins/inventory/test_aws_rds.py @@ -17,11 +17,13 @@ # You should have received a copy of the GNU General Public License # along with Ansible. If not, see . +import copy import pytest -from unittest.mock import MagicMock, patch, call, ANY import random import string -import copy +from unittest.mock import MagicMock +from unittest.mock import call +from unittest.mock import patch try: import botocore @@ -30,6 +32,7 @@ pass from ansible.errors import AnsibleError +from ansible_collections.amazon.aws.plugins.module_utils.botocore import HAS_BOTO3 from ansible_collections.amazon.aws.plugins.inventory.aws_rds import ( InventoryModule, _find_hosts_with_valid_statuses, @@ -37,7 +40,6 @@ _add_tags_for_rds_hosts, _describe_db_clusters, _describe_db_instances, - HAS_BOTO3, ansible_dict_to_boto3_filter_list, ) @@ -66,7 +68,7 @@ def inventory(): inventory.inventory = MagicMock() inventory._populate_host_vars = MagicMock() - inventory._boto3_conn = MagicMock() + inventory.all_clients = MagicMock() inventory.get_option = MagicMock() inventory._set_composite_vars = MagicMock() @@ -449,7 +451,7 @@ def test_inventory_get_all_db_hosts(m_find_hosts, m_describe_db_clusters, m_desc connections = [MagicMock() for i in range(regions)] - inventory._boto3_conn.return_value = [ + inventory.all_clients.return_value = [ (connections[i], "us-east-%d" % i) for i in range(regions) ] @@ -471,14 +473,14 @@ def test_inventory_get_all_db_hosts(m_find_hosts, m_describe_db_clusters, m_desc m_find_hosts.return_value = result assert result == inventory._get_all_db_hosts(**params) - inventory._boto3_conn.assert_called_with(params["regions"], "rds") + inventory.all_clients.assert_called_with("rds") m_describe_db_instances.assert_has_calls( - [call(connections[i], params["strict"], params["instance_filters"]) for i in range(regions)] + [call(connections[i], params["instance_filters"], strict=params["strict"]) for i in range(regions)] ) if gather_clusters: m_describe_db_clusters.assert_has_calls( - [call(connections[i], params["strict"], params["cluster_filters"]) for i in range(regions)] + [call(connections[i], params["cluster_filters"], strict=params["strict"]) for i in range(regions)] ) m_find_hosts.assert_called_with(result, params["statuses"]) @@ -625,23 +627,7 @@ def _get_option_side_effect(x): ) -MOCK_HAS_BOTO3 = "ansible_collections.amazon.aws.plugins.inventory.aws_rds.HAS_BOTO3" BASE_INVENTORY_PARSE = "ansible_collections.amazon.aws.plugins.inventory.aws_rds.AWSInventoryBase.parse" -MISSING_REQUIRED_LIB = "ansible_collections.amazon.aws.plugins.inventory.aws_rds.missing_required_lib" - - -@patch(MISSING_REQUIRED_LIB) -@patch(BASE_INVENTORY_PARSE) -@patch(MOCK_HAS_BOTO3, False) -def test_inventory_parse_with_missing_boto3(m_parse, m_missing_required_lib, inventory): - - loader = MagicMock() - path = generate_random_string(with_punctuation=False, with_digits=False) - m_missing_required_lib.side_effect = lambda x: f"Failed to import the required Python library ({x})" - - with pytest.raises(AnsibleError) as err: - inventory.parse(MagicMock(), loader, path) - assert "Failed to import the required Python library (botocore and boto3)" in err @pytest.mark.parametrize("include_clusters", [True, False]) @@ -701,9 +687,7 @@ def get_option_side_effect(v): inventory.parse(inventory_data, loader, path, cache) - m_parse.assert_called_with(inventory_data, loader, path) - inventory._read_config_data.assert_called_with(path) - inventory._set_credentials.assert_called_with(ANY) + m_parse.assert_called_with(inventory_data, loader, path, cache=cache) boto3_instance_filters = ansible_dict_to_boto3_filter_list(options["filters"]) boto3_cluster_filters = []