-
Notifications
You must be signed in to change notification settings - Fork 342
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ec2_instance - update build_run_instance_spec to skip TagSpecificatio…
…n if None
- Loading branch information
Showing
8 changed files
with
265 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
minor_changes: | ||
- ec2_instance - the ``instance_role`` parameter has been renamed to ``iam_instance_profile`` to | ||
better reflect what it is, ``instance_role`` remains as an alias | ||
(https://github.com/ansible-collections/amazon.aws/pull/1151). | ||
bugfixes: | ||
- ec2_instance - fixes ``Invalid type for parameter TagSpecifications, value None`` error when | ||
tags aren't specified (https://github.com/ansible-collections/amazon.aws/issues/1148). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,5 @@ | ||
time=10m | ||
|
||
cloud/aws | ||
|
||
ec2_vpc_nat_gateway_info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
time=10m | ||
|
||
cloud/aws | ||
|
||
rds_cluster_info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
time=10m | ||
|
||
cloud/aws | ||
|
||
rds_snapshot_info |
123 changes: 123 additions & 0 deletions
123
tests/unit/plugins/modules/ec2_instance/test_build_run_instance_spec.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# (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=[]): | ||
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 |
101 changes: 101 additions & 0 deletions
101
tests/unit/plugins/modules/ec2_instance/test_determine_iam_role.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# (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 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: | ||
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, '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'] |