diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e564b2abf5d..2a61b0a1120 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ General information about setting up your Python environment, testing modules, Ansible coding styles, and more can be found in the [Ansible Community Guide]( https://docs.ansible.com/ansible/latest/community/index.html). -Information about boto library usage, module utils, testing, and more can be +Information about AWS SDK library usage, module utils, testing, and more can be found in the [AWS Guidelines](https://docs.ansible.com/ansible/devel/dev_guide/platforms/aws_guidelines.html) documentation. @@ -41,7 +41,7 @@ issue, or by reporting any additional information ## Pull Requests -All modules MUST have integration tests for new features. Upgrading to boto3 shall be considered a feature request. +All modules MUST have integration tests for new features. Bug fixes for modules that currently have integration tests SHOULD have tests added. New modules should be submitted to the [community.aws](https://github.com/ansible-collections/community.aws) collection and MUST have integration tests. diff --git a/README.md b/README.md index acee1044843..df65cb6ff59 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Starting with the 2.0.0 releases of amazon.aws and community.aws, it is generall Version 3.0.0 of this collection supports `boto3 >= 1.16.0` and `botocore >= 1.19.0` -Support for the original AWS SDK `boto` has been deprecated and the module_utils library code to support it will be removed in release 4.0.0. +All support for the original AWS SDK `boto` was removed in release 4.0.0. ## Included content @@ -110,7 +110,7 @@ be manually installed using pip: or: - pip install boto boto3 botocore + pip install boto3 botocore ## Using this collection diff --git a/changelogs/fragments/630-remove-boto.yml b/changelogs/fragments/630-remove-boto.yml new file mode 100644 index 00000000000..1fd979ff7de --- /dev/null +++ b/changelogs/fragments/630-remove-boto.yml @@ -0,0 +1,4 @@ +breaking_changes: +- ec2 - The ``ec2`` module has been removed in release 4.0.0 and replaced by the ``ec2_instance`` module (https://github.com/ansible-collections/amazon.aws/pull/630). +- module_utils - Support for the original AWS SDK aka ``boto`` has been removed, including all relevant helper functions. + All modules should now use the ``boto3``/``botocore`` AWS SDK (https://github.com/ansible-collections/amazon.aws/pull/630) diff --git a/plugins/doc_fragments/aws.py b/plugins/doc_fragments/aws.py index a9b5747c13b..8db07f9c5ef 100644 --- a/plugins/doc_fragments/aws.py +++ b/plugins/doc_fragments/aws.py @@ -53,7 +53,6 @@ class ModuleDocFragment(object): aws_ca_bundle: description: - "The location of a CA Bundle to use when validating SSL certificates." - - "Not used by boto 2 based modules." - "Note: The CA Bundle is read 'module' side and may need to be explicitly copied from the controller if not run locally." type: path validate_certs: @@ -73,7 +72,6 @@ class ModuleDocFragment(object): description: - A dictionary to modify the botocore configuration. - Parameters can be found at U(https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html#botocore.config.Config). - - Only the 'user_agent' key is used for boto modules. See U(http://boto.cloudhackers.com/en/latest/boto_config_tut.html#boto) for more boto configuration. type: dict requirements: - python >= 3.6 @@ -94,10 +92,6 @@ class ModuleDocFragment(object): C(~/.aws/credentials)). See U(https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html) for more information. - - Modules based on the original AWS SDK (boto) may read their default - configuration from different files. - See U(https://boto.readthedocs.io/en/latest/boto_config_tut.html) for more - information. - C(AWS_REGION) or C(EC2_REGION) can be typically be used to specify the AWS region, when required, but this can also be defined in the configuration files. diff --git a/plugins/module_utils/ec2.py b/plugins/module_utils/ec2.py index ea2e21f383b..9815f8c3170 100644 --- a/plugins/module_utils/ec2.py +++ b/plugins/module_utils/ec2.py @@ -31,7 +31,6 @@ import os import re -import sys import traceback from ansible.module_utils._text import to_native @@ -61,15 +60,6 @@ # Used to live here, moved into # ansible_collections.amazon.aws.plugins.module_utils.retries from .retries import AWSRetry # pylint: disable=unused-import -BOTO_IMP_ERR = None -try: - import boto - import boto.ec2 # boto does weird import stuff - HAS_BOTO = True -except ImportError: - BOTO_IMP_ERR = traceback.format_exc() - HAS_BOTO = False - BOTO3_IMP_ERR = None try: import boto3 @@ -173,12 +163,15 @@ def ec2_argument_spec(): return spec -def get_aws_region(module, boto3=False): +def get_aws_region(module, boto3=None): region = module.params.get('region') if region: return region + if not HAS_BOTO3: + module.fail_json(msg=missing_required_lib('boto3'), exception=BOTO3_IMP_ERR) + if 'AWS_REGION' in os.environ: return os.environ['AWS_REGION'] if 'AWS_DEFAULT_REGION' in os.environ: @@ -186,18 +179,6 @@ def get_aws_region(module, boto3=False): if 'EC2_REGION' in os.environ: return os.environ['EC2_REGION'] - if not boto3: - if not HAS_BOTO: - module.fail_json(msg=missing_required_lib('boto'), exception=BOTO_IMP_ERR) - # boto.config.get returns None if config not found - region = boto.config.get('Boto', 'aws_region') - if region: - return region - return boto.config.get('Boto', 'ec2_region') - - if not HAS_BOTO3: - module.fail_json(msg=missing_required_lib('boto3'), exception=BOTO3_IMP_ERR) - # here we don't need to make an additional call, will default to 'us-east-1' if the below evaluates to None. try: profile_name = module.params.get('profile') @@ -206,7 +187,7 @@ def get_aws_region(module, boto3=False): return None -def get_aws_connection_info(module, boto3=False): +def get_aws_connection_info(module, boto3=None): # Check module args for credentials, then check environment vars # access_key @@ -248,10 +229,6 @@ def get_aws_connection_info(module, boto3=False): access_key = os.environ['AWS_ACCESS_KEY'] elif os.environ.get('EC2_ACCESS_KEY'): access_key = os.environ['EC2_ACCESS_KEY'] - elif HAS_BOTO and boto.config.get('Credentials', 'aws_access_key_id'): - access_key = boto.config.get('Credentials', 'aws_access_key_id') - elif HAS_BOTO and boto.config.get('default', 'aws_access_key_id'): - access_key = boto.config.get('default', 'aws_access_key_id') else: # in case access_key came in as empty string access_key = None @@ -263,10 +240,6 @@ def get_aws_connection_info(module, boto3=False): secret_key = os.environ['AWS_SECRET_KEY'] elif os.environ.get('EC2_SECRET_KEY'): secret_key = os.environ['EC2_SECRET_KEY'] - elif HAS_BOTO and boto.config.get('Credentials', 'aws_secret_access_key'): - secret_key = boto.config.get('Credentials', 'aws_secret_access_key') - elif HAS_BOTO and boto.config.get('default', 'aws_secret_access_key'): - secret_key = boto.config.get('default', 'aws_secret_access_key') else: # in case secret_key came in as empty string secret_key = None @@ -278,10 +251,6 @@ def get_aws_connection_info(module, boto3=False): security_token = os.environ['AWS_SESSION_TOKEN'] elif os.environ.get('EC2_SECURITY_TOKEN'): security_token = os.environ['EC2_SECURITY_TOKEN'] - elif HAS_BOTO and boto.config.get('Credentials', 'aws_security_token'): - security_token = boto.config.get('Credentials', 'aws_security_token') - elif HAS_BOTO and boto.config.get('default', 'aws_security_token'): - security_token = boto.config.get('default', 'aws_security_token') else: # in case secret_token came in as empty string security_token = None @@ -290,37 +259,21 @@ def get_aws_connection_info(module, boto3=False): if os.environ.get('AWS_CA_BUNDLE'): ca_bundle = os.environ.get('AWS_CA_BUNDLE') - if HAS_BOTO3 and boto3: - boto_params = dict(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - aws_session_token=security_token) - - if profile_name: - boto_params = dict(aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None) - boto_params['profile_name'] = profile_name + boto_params = dict(aws_access_key_id=access_key, + aws_secret_access_key=secret_key, + aws_session_token=security_token) - if validate_certs and ca_bundle: - boto_params['verify'] = ca_bundle - else: - boto_params['verify'] = validate_certs + if profile_name: + boto_params = dict(aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None) + boto_params['profile_name'] = profile_name + if validate_certs and ca_bundle: + boto_params['verify'] = ca_bundle else: - boto_params = dict(aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - security_token=security_token) - - # only set profile_name if passed as an argument - if profile_name: - boto_params['profile_name'] = profile_name - - boto_params['validate_certs'] = validate_certs + boto_params['verify'] = validate_certs if config is not None: - if HAS_BOTO3 and boto3: - boto_params['aws_config'] = botocore.config.Config(**config) - elif HAS_BOTO and not boto3: - if 'user_agent' in config: - sys.modules["boto.connection"].UserAgent = config['user_agent'] + boto_params['aws_config'] = botocore.config.Config(**config) for param, value in boto_params.items(): if isinstance(value, binary_type): @@ -329,61 +282,6 @@ def get_aws_connection_info(module, boto3=False): return region, ec2_url, boto_params -def get_ec2_creds(module): - ''' for compatibility mode with old modules that don't/can't yet - use ec2_connect method ''' - region, ec2_url, boto_params = get_aws_connection_info(module) - return ec2_url, boto_params['aws_access_key_id'], boto_params['aws_secret_access_key'], region - - -def boto_fix_security_token_in_profile(conn, profile_name): - ''' monkey patch for boto issue boto/boto#2100 ''' - profile = 'profile ' + profile_name - if boto.config.has_option(profile, 'aws_security_token'): - conn.provider.set_security_token(boto.config.get(profile, 'aws_security_token')) - return conn - - -def connect_to_aws(aws_module, region, **params): - try: - conn = aws_module.connect_to_region(region, **params) - except(boto.provider.ProfileNotFoundError): - raise AnsibleAWSError("Profile given for AWS was not found. Please fix and retry.") - if not conn: - if region not in [aws_module_region.name for aws_module_region in aws_module.regions()]: - raise AnsibleAWSError("Region %s does not seem to be available for aws module %s. If the region definitely exists, you may need to upgrade " - "boto or extend with endpoints_path" % (region, aws_module.__name__)) - else: - raise AnsibleAWSError("Unknown problem connecting to region %s for aws module %s." % (region, aws_module.__name__)) - if params.get('profile_name'): - conn = boto_fix_security_token_in_profile(conn, params['profile_name']) - return conn - - -def ec2_connect(module): - - """ Return an ec2 connection""" - - region, ec2_url, boto_params = get_aws_connection_info(module) - - # If ec2_url is present use it - if ec2_url: - try: - ec2 = boto.connect_ec2_endpoint(ec2_url, **boto_params) - except (boto.exception.NoAuthHandlerFound, AnsibleAWSError, boto.provider.ProfileNotFoundError) as e: - module.fail_json(msg=str(e)) - # Otherwise, if we have a region specified, connect to its endpoint. - elif region: - try: - ec2 = connect_to_aws(boto.ec2, region, **boto_params) - except (boto.exception.NoAuthHandlerFound, AnsibleAWSError, boto.provider.ProfileNotFoundError) as e: - module.fail_json(msg=str(e)) - else: - module.fail_json(msg="Either region or ec2_url must be specified") - - return ec2 - - def ansible_dict_to_boto3_filter_list(filters_dict): """ Convert an Ansible dict of filters to list of dicts that boto3 can use @@ -424,7 +322,7 @@ def ansible_dict_to_boto3_filter_list(filters_dict): return filters_list -def get_ec2_security_group_ids_from_names(sec_group_list, ec2_connection, vpc_id=None, boto3=True): +def get_ec2_security_group_ids_from_names(sec_group_list, ec2_connection, vpc_id=None, boto3=None): """ Return list of security group IDs from security group names. Note that security group names are not unique across VPCs. If a name exists across multiple VPCs and no VPC ID is supplied, all matching IDs will be returned. This @@ -432,19 +330,11 @@ def get_ec2_security_group_ids_from_names(sec_group_list, ec2_connection, vpc_id a try block """ - def get_sg_name(sg, boto3): - - if boto3: - return sg['GroupName'] - else: - return sg.name + def get_sg_name(sg, boto3=None): + return str(sg['GroupName']) - def get_sg_id(sg, boto3): - - if boto3: - return sg['GroupId'] - else: - return sg.id + def get_sg_id(sg, boto3=None): + return str(sg['GroupId']) sec_group_id_list = [] @@ -452,25 +342,18 @@ def get_sg_id(sg, boto3): sec_group_list = [sec_group_list] # Get all security groups - if boto3: - if vpc_id: - filters = [ - { - 'Name': 'vpc-id', - 'Values': [ - vpc_id, - ] - } - ] - all_sec_groups = ec2_connection.describe_security_groups(Filters=filters)['SecurityGroups'] - else: - all_sec_groups = ec2_connection.describe_security_groups()['SecurityGroups'] + if vpc_id: + filters = [ + { + 'Name': 'vpc-id', + 'Values': [ + vpc_id, + ] + } + ] + all_sec_groups = ec2_connection.describe_security_groups(Filters=filters)['SecurityGroups'] else: - if vpc_id: - filters = {'vpc-id': vpc_id} - all_sec_groups = ec2_connection.get_all_security_groups(filters=filters) - else: - all_sec_groups = ec2_connection.get_all_security_groups() + all_sec_groups = ec2_connection.describe_security_groups()['SecurityGroups'] unmatched = set(sec_group_list).difference(str(get_sg_name(all_sg, boto3)) for all_sg in all_sec_groups) sec_group_name_list = list(set(sec_group_list) - set(unmatched)) @@ -482,7 +365,7 @@ def get_sg_id(sg, boto3): if len(still_unmatched) > 0: raise ValueError("The following group names are not valid: %s" % ', '.join(still_unmatched)) - sec_group_id_list += [str(get_sg_id(all_sg, boto3)) for all_sg in all_sec_groups if str(get_sg_name(all_sg, boto3)) in sec_group_name_list] + sec_group_id_list += [get_sg_id(all_sg) for all_sg in all_sec_groups if get_sg_name(all_sg) in sec_group_name_list] return sec_group_id_list diff --git a/plugins/modules/ec2.py b/plugins/modules/ec2.py deleted file mode 100644 index 8cbee640619..00000000000 --- a/plugins/modules/ec2.py +++ /dev/null @@ -1,2041 +0,0 @@ -#!/usr/bin/python -# 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 - - -DOCUMENTATION = ''' ---- -module: ec2 -version_added: 1.0.0 -short_description: create, terminate, start or stop an instance in ec2 -deprecated: - removed_in: 4.0.0 - why: The ec2 module is based upon a deprecated version of the AWS SDK. - alternative: Use M(amazon.aws.ec2_instance). -description: - - Creates or terminates ec2 instances. - - > - Note: This module uses the older boto Python module to interact with the EC2 API. - M(amazon.aws.ec2) will still receive bug fixes, but no new features. - Consider using the M(amazon.aws.ec2_instance) module instead. - If M(amazon.aws.ec2_instance) does not support a feature you need that is available in M(amazon.aws.ec2), please - file a feature request. -options: - key_name: - description: - - Key pair to use on the instance. - - The SSH key must already exist in AWS in order to use this argument. - - Keys can be created / deleted using the M(amazon.aws.ec2_key) module. - aliases: ['keypair'] - type: str - id: - description: - - Identifier for this instance or set of instances, so that the module will be idempotent with respect to EC2 instances. - - This identifier is valid for at least 24 hours after the termination of the instance, and should not be reused for another call later on. - - For details, see the description of client token at U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Run_Instance_Idempotency.html). - type: str - group: - description: - - Security group (or list of groups) to use with the instance. - aliases: [ 'groups' ] - type: list - elements: str - group_id: - description: - - Security group id (or list of ids) to use with the instance. - type: list - elements: str - zone: - description: - - AWS availability zone in which to launch the instance. - aliases: [ 'aws_zone', 'ec2_zone' ] - type: str - instance_type: - description: - - Instance type to use for the instance, see U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html). - - Required when creating a new instance. - type: str - aliases: ['type'] - tenancy: - description: - - An instance with a tenancy of C(dedicated) runs on single-tenant hardware and can only be launched into a VPC. - - Note that to use dedicated tenancy you MUST specify a I(vpc_subnet_id) as well. - - Dedicated tenancy is not available for EC2 "micro" instances. - default: default - choices: [ "default", "dedicated" ] - type: str - spot_price: - description: - - Maximum spot price to bid. If not set, a regular on-demand instance is requested. - - A spot request is made with this maximum bid. When it is filled, the instance is started. - type: str - spot_type: - description: - - The type of spot request. - - After being interrupted a C(persistent) spot instance will be started once there is capacity to fill the request again. - default: "one-time" - choices: [ "one-time", "persistent" ] - type: str - image: - description: - - I(ami) ID to use for the instance. - - Required when I(state=present). - type: str - kernel: - description: - - Kernel eki to use for the instance. - type: str - ramdisk: - description: - - Ramdisk eri to use for the instance. - type: str - wait: - description: - - Wait for the instance to reach its desired state before returning. - - Does not wait for SSH, see the 'wait_for_connection' example for details. - type: bool - default: false - wait_timeout: - description: - - How long before wait gives up, in seconds. - default: 300 - type: int - spot_wait_timeout: - description: - - How long to wait for the spot instance request to be fulfilled. Affects 'Request valid until' for setting spot request lifespan. - default: 600 - type: int - count: - description: - - Number of instances to launch. - default: 1 - type: int - monitoring: - description: - - Enable detailed monitoring (CloudWatch) for the instance. - type: bool - default: false - user_data: - description: - - Opaque blob of data which is made available to the EC2 instance. - type: str - instance_tags: - description: - - > - A hash/dictionary of tags to add to the new instance or for - instances to start/stop by tag. For example C({"key":"value"}) or - C({"key":"value","key2":"value2"}). - type: dict - placement_group: - description: - - Placement group for the instance when using EC2 Clustered Compute. - type: str - vpc_subnet_id: - description: - - The subnet ID in which to launch the instance (VPC). - type: str - assign_public_ip: - description: - - When provisioning within vpc, assign a public IP address. Boto library must be 2.13.0+. - type: bool - private_ip: - description: - - The private ip address to assign the instance (from the vpc subnet). - type: str - instance_profile_name: - description: - - Name of the IAM instance profile (i.e. what the EC2 console refers to as an "IAM Role") to use. Boto library must be 2.5.0+. - type: str - instance_ids: - description: - - "list of instance ids, currently used for states: absent, running, stopped" - aliases: ['instance_id'] - type: list - elements: str - source_dest_check: - description: - - Enable or Disable the Source/Destination checks (for NAT instances and Virtual Routers). - When initially creating an instance the EC2 API defaults this to C(True). - type: bool - termination_protection: - description: - - Enable or Disable the Termination Protection. - - Defaults to C(false). - type: bool - instance_initiated_shutdown_behavior: - description: - - Set whether AWS will Stop or Terminate an instance on shutdown. This parameter is ignored when using instance-store. - images (which require termination on shutdown). - default: 'stop' - choices: [ "stop", "terminate" ] - type: str - state: - description: - - Create, terminate, start, stop or restart instances. - - When I(state=absent), I(instance_ids) is required. - - When I(state=running), I(state=stopped) or I(state=restarted) then either I(instance_ids) or I(instance_tags) is required. - default: 'present' - choices: ['absent', 'present', 'restarted', 'running', 'stopped'] - type: str - volumes: - description: - - A list of hash/dictionaries of volumes to add to the new instance. - type: list - elements: dict - suboptions: - device_name: - type: str - required: true - description: - - A name for the device (For example C(/dev/sda)). - delete_on_termination: - type: bool - default: false - description: - - Whether the volume should be automatically deleted when the instance is terminated. - ephemeral: - type: str - description: - - Whether the volume should be ephemeral. - - Data on ephemeral volumes is lost when the instance is stopped. - - Mutually exclusive with the I(snapshot) parameter. - encrypted: - type: bool - default: false - description: - - Whether the volume should be encrypted using the 'aws/ebs' KMS CMK. - snapshot: - type: str - description: - - The ID of an EBS snapshot to copy when creating the volume. - - Mutually exclusive with the I(ephemeral) parameter. - volume_type: - type: str - description: - - The type of volume to create. - - See U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSVolumeTypes.html) for more information on the available volume types. - volume_size: - type: int - description: - - The size of the volume (in GiB). - iops: - type: int - description: - - The number of IOPS per second to provision for the volume. - - Required when I(volume_type=io1). - ebs_optimized: - description: - - Whether instance is using optimized EBS volumes, see U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html). - default: false - type: bool - exact_count: - description: - - An integer value which indicates how many instances that match the 'count_tag' parameter should be running. - Instances are either created or terminated based on this value. - type: int - count_tag: - description: - - Used with I(exact_count) to determine how many nodes based on a specific tag criteria should be running. - This can be expressed in multiple ways and is shown in the EXAMPLES section. For instance, one can request 25 servers - that are tagged with C(class=webserver). The specified tag must already exist or be passed in as the I(instance_tags) option. - type: raw - network_interfaces: - description: - - A list of existing network interfaces to attach to the instance at launch. When specifying existing network interfaces, - none of the I(assign_public_ip), I(private_ip), I(vpc_subnet_id), I(group), or I(group_id) parameters may be used. (Those parameters are - for creating a new network interface at launch.) - aliases: ['network_interface'] - type: list - elements: str - spot_launch_group: - description: - - Launch group for spot requests, see U(https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/how-spot-instances-work.html#spot-launch-group). - type: str -author: - - "Tim Gerla (@tgerla)" - - "Lester Wade (@lwade)" - - "Seth Vidal (@skvidal)" -extends_documentation_fragment: -- amazon.aws.aws -- amazon.aws.ec2 -requirements: -- python >= 2.6 -- boto - -''' - -EXAMPLES = ''' -# Note: These examples do not set authentication details, see the AWS Guide for details. - -# Basic provisioning example -- amazon.aws.ec2: - key_name: mykey - instance_type: t2.micro - image: ami-123456 - wait: yes - group: webserver - count: 3 - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# Advanced example with tagging and CloudWatch -- amazon.aws.ec2: - key_name: mykey - group: databases - instance_type: t2.micro - image: ami-123456 - wait: yes - wait_timeout: 500 - count: 5 - instance_tags: - db: postgres - monitoring: yes - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# Single instance with additional IOPS volume from snapshot and volume delete on termination -- amazon.aws.ec2: - key_name: mykey - group: webserver - instance_type: c3.medium - image: ami-123456 - wait: yes - wait_timeout: 500 - volumes: - - device_name: /dev/sdb - snapshot: snap-abcdef12 - volume_type: io1 - iops: 1000 - volume_size: 100 - delete_on_termination: true - monitoring: yes - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# Single instance with ssd gp2 root volume -- amazon.aws.ec2: - key_name: mykey - group: webserver - instance_type: c3.medium - image: ami-123456 - wait: yes - wait_timeout: 500 - volumes: - - device_name: /dev/xvda - volume_type: gp2 - volume_size: 8 - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - count_tag: - Name: dbserver - exact_count: 1 - -# Multiple groups example -- amazon.aws.ec2: - key_name: mykey - group: ['databases', 'internal-services', 'sshable', 'and-so-forth'] - instance_type: m1.large - image: ami-6e649707 - wait: yes - wait_timeout: 500 - count: 5 - instance_tags: - db: postgres - monitoring: yes - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# Multiple instances with additional volume from snapshot -- amazon.aws.ec2: - key_name: mykey - group: webserver - instance_type: m1.large - image: ami-6e649707 - wait: yes - wait_timeout: 500 - count: 5 - volumes: - - device_name: /dev/sdb - snapshot: snap-abcdef12 - volume_size: 10 - monitoring: yes - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# Dedicated tenancy example -- amazon.aws.ec2: - assign_public_ip: yes - group_id: sg-1dc53f72 - key_name: mykey - image: ami-6e649707 - instance_type: m1.small - tenancy: dedicated - vpc_subnet_id: subnet-29e63245 - wait: yes - -# Spot instance example -- amazon.aws.ec2: - spot_price: 0.24 - spot_wait_timeout: 600 - keypair: mykey - group_id: sg-1dc53f72 - instance_type: m1.small - image: ami-6e649707 - wait: yes - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - spot_launch_group: report_generators - instance_initiated_shutdown_behavior: terminate - -# Examples using pre-existing network interfaces -- amazon.aws.ec2: - key_name: mykey - instance_type: t2.small - image: ami-f005ba11 - network_interface: eni-deadbeef - -- amazon.aws.ec2: - key_name: mykey - instance_type: t2.small - image: ami-f005ba11 - network_interfaces: ['eni-deadbeef', 'eni-5ca1ab1e'] - -# Launch instances, runs some tasks -# and then terminate them - -- name: Create a sandbox instance - hosts: localhost - gather_facts: False - vars: - keypair: my_keypair - instance_type: m1.small - security_group: my_securitygroup - image: my_ami_id - region: us-east-1 - tasks: - - name: Launch instance - amazon.aws.ec2: - key_name: "{{ keypair }}" - group: "{{ security_group }}" - instance_type: "{{ instance_type }}" - image: "{{ image }}" - wait: true - region: "{{ region }}" - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - register: ec2 - - - name: Add new instance to host group - add_host: - hostname: "{{ item.public_ip }}" - groupname: launched - loop: "{{ ec2.instances }}" - - - name: Wait for SSH to come up - delegate_to: "{{ item.public_dns_name }}" - wait_for_connection: - delay: 60 - timeout: 320 - loop: "{{ ec2.instances }}" - -- name: Configure instance(s) - hosts: launched - become: True - gather_facts: True - roles: - - my_awesome_role - - my_awesome_test - -- name: Terminate instances - hosts: localhost - tasks: - - name: Terminate instances that were previously launched - amazon.aws.ec2: - state: 'absent' - instance_ids: '{{ ec2.instance_ids }}' - -# Start a few existing instances, run some tasks -# and stop the instances - -- name: Start sandbox instances - hosts: localhost - gather_facts: false - vars: - instance_ids: - - 'i-xxxxxx' - - 'i-xxxxxx' - - 'i-xxxxxx' - region: us-east-1 - tasks: - - name: Start the sandbox instances - amazon.aws.ec2: - instance_ids: '{{ instance_ids }}' - region: '{{ region }}' - state: running - wait: True - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - roles: - - do_neat_stuff - - do_more_neat_stuff - -- name: Stop sandbox instances - hosts: localhost - gather_facts: false - vars: - instance_ids: - - 'i-xxxxxx' - - 'i-xxxxxx' - - 'i-xxxxxx' - region: us-east-1 - tasks: - - name: Stop the sandbox instances - amazon.aws.ec2: - instance_ids: '{{ instance_ids }}' - region: '{{ region }}' - state: stopped - wait: True - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# -# Start stopped instances specified by tag -# -- amazon.aws.ec2: - instance_tags: - Name: ExtraPower - state: running - -# -# Restart instances specified by tag -# -- amazon.aws.ec2: - instance_tags: - Name: ExtraPower - state: restarted - -# -# Enforce that 5 instances with a tag "foo" are running -# (Highly recommended!) -# - -- amazon.aws.ec2: - key_name: mykey - instance_type: c1.medium - image: ami-40603AD1 - wait: yes - group: webserver - instance_tags: - foo: bar - exact_count: 5 - count_tag: foo - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# -# Enforce that 5 running instances named "database" with a "dbtype" of "postgres" -# - -- amazon.aws.ec2: - key_name: mykey - instance_type: c1.medium - image: ami-40603AD1 - wait: yes - group: webserver - instance_tags: - Name: database - dbtype: postgres - exact_count: 5 - count_tag: - Name: database - dbtype: postgres - vpc_subnet_id: subnet-29e63245 - assign_public_ip: yes - -# -# count_tag complex argument examples -# - - # instances with tag foo -- amazon.aws.ec2: - count_tag: - foo: - - # instances with tag foo=bar -- amazon.aws.ec2: - count_tag: - foo: bar - - # instances with tags foo=bar & baz -- amazon.aws.ec2: - count_tag: - foo: bar - baz: - - # instances with tags foo & bar & baz=bang -- amazon.aws.ec2: - count_tag: - - foo - - bar - - baz: bang - -''' - -RETURN = r''' -changed: - description: If the EC2 instance has changed. - type: bool - returned: always - sample: true -instances: - description: The instances. - type: list - returned: always - contains: - ami_launch_index: - description: The AMI launch index, which can be used to find this instance in the launch group. - type: int - returned: always - sample: 0 - architecture: - description: The architecture of the image. - type: str - returned: always - sample: "x86_64" - block_device_mapping: - description: Any block device mapping entries for the instance. - type: dict - returned: always - sample: { - "/dev/xvda": { - "delete_on_termination": true, - "status": "attached", - "volume_id": "vol-06d364586f5550b62" - } - } - dns_name: - description: The public DNS name assigned to the instance. - type: str - returned: always - sample: "ec2-203-0-113-1.z-2.compute-1.amazonaws.com" - ebs_optimized: - description: Indicates whether the instance is optimized for Amazon EBS I/O. - type: bool - returned: always - sample: false - groups: - description: One or more security groups. - type: dict - returned: always - sample: { - "sg-0c6562ab3d435619f": "ansible-test--88312190_setup" - } - hypervisor: - description: The hypervisor type of the instance. - type: str - returned: always - sample: "xen" - image_id: - description: The ID of the AMI used to launch the instance. - type: str - returned: always - sample: "ami-0d5eff06f840b45e9" - instance_id: - description: The ID of the instance. - type: str - returned: always - sample: "i-0250719204c428be1" - instance_type: - description: The instance type. - type: str - returned: always - sample: "t2.micro" - kernel: - description: The kernel associated with this instance, if applicable. - type: str - returned: always - sample: "" - key_name: - description: The name of the key pair, if this instance was launched with an associated key pair. - type: str - returned: always - sample: "ansible-test-88312190_setup" - launch_time: - description: The time the instance was launched. - type: str - returned: always - sample: "2021-05-09T19:30:26.000Z" - placement: - description: The location where the instance launched, if applicable. - type: dict - returned: always - sample: { - "availability_zone": "us-east-1a", - "group_name": "", - "tenancy": "default" - } - private_dns_name: - description: The private DNS hostname name assigned to the instance. - type: str - returned: always - sample: "ip-10-176-1-249.ec2.internal" - private_ip: - description: The private IPv4 address assigned to the instance. - type: str - returned: always - sample: "10.176.1.249" - public_dns_name: - description: The public DNS name assigned to the instance. - type: str - returned: always - sample: "ec2-203-0-113-1.z-2.compute-1.amazonaws.com" - public_ip: - description: The public IPv4 address, or the Carrier IP address assigned to the instance, if applicable. - type: str - returned: always - sample: "203.0.113.1" - ramdisk: - description: The RAM disk associated with this instance, if applicable. - type: str - returned: always - sample: "" - root_device_name: - description: The device name of the root device volume. - type: str - returned: always - sample: "/dev/xvda" - root_device_type: - description: The root device type used by the AMI. - type: str - returned: always - sample: "ebs" - state: - description: The current state of the instance. - type: dict - returned: always - sample: { - "code": 80, - "name": "stopped" - } - tags: - description: Any tags assigned to the instance. - type: dict - returned: always - sample: { - "ResourcePrefix": "ansible-test-88312190-integration_tests" - } - tenancy: - description: The tenancy of the instance (if the instance is running in a VPC). - type: str - returned: always - sample: "default" - virtualization_type: - description: The virtualization type of the instance. - type: str - returned: always - sample: "hvm" - monitoring: - description: The monitoring for the instance. - type: dict - returned: always - sample: { - "state": "disabled" - } - capacity_reservation_specification: - description: Information about the Capacity Reservation targeting option. - type: dict - returned: always - sample: { - "capacity_reservation_preference": "open" - } - client_token: - description: The idempotency token you provided when you launched the instance, if applicable. - type: str - returned: always - sample: "" - cpu_options: - description: The CPU options for the instance. - type: dict - returned: always - sample: { - "core_count": 1, - "threads_per_core": 1 - } - ena_support: - description: Specifies whether enhanced networking with ENA is enabled. - type: bool - returned: always - sample: true - enclave_options: - description: Indicates whether the instance is enabled for AWS Nitro Enclaves. - type: dict - returned: always - sample: { - "enabled": false - } - hibernation_options: - description: Indicates whether the instance is enabled for hibernation. - type: dict - returned: always - sample: { - "configured": false - } - network_interfaces: - description: The network interfaces for the instance. - type: list - returned: always - sample: [ - { - "attachment": { - "attach_time": "2021-05-09T19:30:57+00:00", - "attachment_id": "eni-attach-07341f2560be6c8fc", - "delete_on_termination": true, - "device_index": 0, - "network_card_index": 0, - "status": "attached" - }, - "description": "", - "groups": [ - { - "group_id": "sg-0c6562ab3d435619f", - "group_name": "ansible-test-88312190_setup" - } - ], - "interface_type": "interface", - "ipv6_addresses": [], - "mac_address": "0e:0e:36:60:67:cf", - "network_interface_id": "eni-061dee20eba3b445a", - "owner_id": "721066863947", - "private_dns_name": "ip-10-176-1-178.ec2.internal", - "private_ip_address": "10.176.1.178", - "private_ip_addresses": [ - { - "primary": true, - "private_dns_name": "ip-10-176-1-178.ec2.internal", - "private_ip_address": "10.176.1.178" - } - ], - "source_dest_check": true, - "status": "in-use", - "subnet_id": "subnet-069d3e2eab081955d", - "vpc_id": "vpc-0b6879b6ca2e9be2b" - } - ] - vpc_id: - description: The ID of the VPC in which the instance is running. - type: str - returned: always - sample: "vpc-0b6879b6ca2e9be2b" - subnet_id: - description: The ID of the subnet in which the instance is running. - type: str - returned: always - sample: "subnet-069d3e2eab081955d" - state_transition_reason: - description: The reason for the most recent state transition. This might be an empty string. - type: str - returned: always - sample: "User initiated (2021-05-09 19:31:28 GMT)" - state_reason: - description: The reason for the most recent state transition. - type: dict - returned: always - sample: { - "code": "Client.UserInitiatedShutdown", - "message": "Client.UserInitiatedShutdown: User initiated shutdown" - } - security_groups: - description: The security groups for the instance. - type: list - returned: always - sample: [ - { - "group_id": "sg-0c6562ab3d435619f", - "group_name": "ansible-test-alinas-mbp-88312190_setup" - } - ] - source_dest_check: - description: Indicates whether source/destination checking is enabled. - type: bool - returned: always - sample: true - metadata: - description: The metadata options for the instance. - type: dict - returned: always - sample: { - "http_endpoint": "enabled", - "http_put_response_hop_limit": 1, - "http_tokens": "optional", - "state": "applied" - } -''' - - -import time -import datetime -from ast import literal_eval - -try: - import boto.ec2 - from boto.ec2.blockdevicemapping import BlockDeviceType - from boto.ec2.blockdevicemapping import BlockDeviceMapping - from boto.exception import EC2ResponseError - from boto import connect_ec2_endpoint - from boto import connect_vpc -except ImportError: - pass # Taken care of by ec2.HAS_BOTO - -from ansible.module_utils.six import get_function_code -from ansible.module_utils.six import string_types -from ansible.module_utils._text import to_bytes -from ansible.module_utils._text import to_text - -from ..module_utils.core import AnsibleAWSModule -from ..module_utils.ec2 import HAS_BOTO -from ..module_utils.ec2 import ec2_connect -from ..module_utils.ec2 import get_aws_connection_info -from ..module_utils.version import LooseVersion - - -def find_running_instances_by_count_tag(module, ec2, vpc, count_tag, zone=None): - - # get reservations for instances that match tag(s) and are in the desired state - state = module.params.get('state') - if state not in ['running', 'stopped']: - state = None - reservations = get_reservations(module, ec2, vpc, tags=count_tag, state=state, zone=zone) - - instances = [] - for res in reservations: - if hasattr(res, 'instances'): - for inst in res.instances: - if inst.state == 'terminated' or inst.state == 'shutting-down': - continue - instances.append(inst) - - return reservations, instances - - -def _set_none_to_blank(dictionary): - result = dictionary - for k in result: - if isinstance(result[k], dict): - result[k] = _set_none_to_blank(result[k]) - elif not result[k]: - result[k] = "" - return result - - -def get_reservations(module, ec2, vpc, tags=None, state=None, zone=None): - # TODO: filters do not work with tags that have underscores - filters = dict() - - vpc_subnet_id = module.params.get('vpc_subnet_id') - vpc_id = None - if vpc_subnet_id: - filters.update({"subnet-id": vpc_subnet_id}) - if vpc: - vpc_id = vpc.get_all_subnets(subnet_ids=[vpc_subnet_id])[0].vpc_id - - if vpc_id: - filters.update({"vpc-id": vpc_id}) - - if tags is not None: - - if isinstance(tags, str): - try: - tags = literal_eval(tags) - except Exception: - pass - - # if not a string type, convert and make sure it's a text string - if isinstance(tags, int): - tags = to_text(tags) - - # if string, we only care that a tag of that name exists - if isinstance(tags, str): - filters.update({"tag-key": tags}) - - # if list, append each item to filters - if isinstance(tags, list): - for x in tags: - if isinstance(x, dict): - x = _set_none_to_blank(x) - filters.update(dict(("tag:" + tn, tv) for (tn, tv) in x.items())) - else: - filters.update({"tag-key": x}) - - # if dict, add the key and value to the filter - if isinstance(tags, dict): - tags = _set_none_to_blank(tags) - filters.update(dict(("tag:" + tn, tv) for (tn, tv) in tags.items())) - - # lets check to see if the filters dict is empty, if so then stop - if not filters: - module.fail_json(msg="Filters based on tag is empty => tags: %s" % (tags)) - - if state: - # http://stackoverflow.com/questions/437511/what-are-the-valid-instancestates-for-the-amazon-ec2-api - filters.update({'instance-state-name': state}) - - if zone: - filters.update({'availability-zone': zone}) - - if module.params.get('id'): - filters['client-token'] = module.params['id'] - - results = ec2.get_all_instances(filters=filters) - - return results - - -def get_instance_info(inst): - """ - Retrieves instance information from an instance - ID and returns it as a dictionary - """ - instance_info = {'id': inst.id, - 'ami_launch_index': inst.ami_launch_index, - 'private_ip': inst.private_ip_address, - 'private_dns_name': inst.private_dns_name, - 'public_ip': inst.ip_address, - 'dns_name': inst.dns_name, - 'public_dns_name': inst.public_dns_name, - 'state_code': inst.state_code, - 'architecture': inst.architecture, - 'image_id': inst.image_id, - 'key_name': inst.key_name, - 'placement': inst.placement, - 'region': inst.placement[:-1], - 'kernel': inst.kernel, - 'ramdisk': inst.ramdisk, - 'launch_time': inst.launch_time, - 'instance_type': inst.instance_type, - 'root_device_type': inst.root_device_type, - 'root_device_name': inst.root_device_name, - 'state': inst.state, - 'hypervisor': inst.hypervisor, - 'tags': inst.tags, - 'groups': dict((group.id, group.name) for group in inst.groups), - } - try: - instance_info['virtualization_type'] = getattr(inst, 'virtualization_type') - except AttributeError: - instance_info['virtualization_type'] = None - - try: - instance_info['ebs_optimized'] = getattr(inst, 'ebs_optimized') - except AttributeError: - instance_info['ebs_optimized'] = False - - try: - bdm_dict = {} - bdm = getattr(inst, 'block_device_mapping') - for device_name in bdm.keys(): - bdm_dict[device_name] = { - 'status': bdm[device_name].status, - 'volume_id': bdm[device_name].volume_id, - 'delete_on_termination': bdm[device_name].delete_on_termination - } - instance_info['block_device_mapping'] = bdm_dict - except AttributeError: - instance_info['block_device_mapping'] = False - - try: - instance_info['tenancy'] = getattr(inst, 'placement_tenancy') - except AttributeError: - instance_info['tenancy'] = 'default' - - return instance_info - - -def boto_supports_associate_public_ip_address(ec2): - """ - Check if Boto library has associate_public_ip_address in the NetworkInterfaceSpecification - class. Added in Boto 2.13.0 - - ec2: authenticated ec2 connection object - - Returns: - True if Boto library accepts associate_public_ip_address argument, else false - """ - - try: - network_interface = boto.ec2.networkinterface.NetworkInterfaceSpecification() - getattr(network_interface, "associate_public_ip_address") - return True - except AttributeError: - return False - - -def boto_supports_profile_name_arg(ec2): - """ - Check if Boto library has instance_profile_name argument. instance_profile_name has been added in Boto 2.5.0 - - ec2: authenticated ec2 connection object - - Returns: - True if Boto library accept instance_profile_name argument, else false - """ - run_instances_method = getattr(ec2, 'run_instances') - return 'instance_profile_name' in get_function_code(run_instances_method).co_varnames - - -def boto_supports_volume_encryption(): - """ - Check if Boto library supports encryption of EBS volumes (added in 2.29.0) - - Returns: - True if boto library has the named param as an argument on the request_spot_instances method, else False - """ - return hasattr(boto, 'Version') and LooseVersion(boto.Version) >= LooseVersion('2.29.0') - - -def create_block_device(module, ec2, volume): - # Not aware of a way to determine this programatically - # http://aws.amazon.com/about-aws/whats-new/2013/10/09/ebs-provisioned-iops-maximum-iops-gb-ratio-increased-to-30-1/ - MAX_IOPS_TO_SIZE_RATIO = 30 - - volume_type = volume.get('volume_type') - - if 'snapshot' not in volume and 'ephemeral' not in volume: - if 'volume_size' not in volume: - module.fail_json(msg='Size must be specified when creating a new volume or modifying the root volume') - if 'snapshot' in volume: - if volume_type == 'io1' and 'iops' not in volume: - module.fail_json(msg='io1 volumes must have an iops value set') - if 'iops' in volume: - snapshot = ec2.get_all_snapshots(snapshot_ids=[volume['snapshot']])[0] - size = volume.get('volume_size', snapshot.volume_size) - if int(volume['iops']) > MAX_IOPS_TO_SIZE_RATIO * int(size): - module.fail_json(msg='IOPS must be at most %d times greater than size' % MAX_IOPS_TO_SIZE_RATIO) - if 'ephemeral' in volume: - if 'snapshot' in volume: - module.fail_json(msg='Cannot set both ephemeral and snapshot') - if boto_supports_volume_encryption(): - return BlockDeviceType(snapshot_id=volume.get('snapshot'), - ephemeral_name=volume.get('ephemeral'), - size=volume.get('volume_size'), - volume_type=volume_type, - delete_on_termination=volume.get('delete_on_termination', False), - iops=volume.get('iops'), - encrypted=volume.get('encrypted', None)) - else: - return BlockDeviceType(snapshot_id=volume.get('snapshot'), - ephemeral_name=volume.get('ephemeral'), - size=volume.get('volume_size'), - volume_type=volume_type, - delete_on_termination=volume.get('delete_on_termination', False), - iops=volume.get('iops')) - - -def boto_supports_param_in_spot_request(ec2, param): - """ - Check if Boto library has a in its request_spot_instances() method. For example, the placement_group parameter wasn't added until 2.3.0. - - ec2: authenticated ec2 connection object - - Returns: - True if boto library has the named param as an argument on the request_spot_instances method, else False - """ - method = getattr(ec2, 'request_spot_instances') - return param in get_function_code(method).co_varnames - - -def await_spot_requests(module, ec2, spot_requests, count): - """ - Wait for a group of spot requests to be fulfilled, or fail. - - module: Ansible module object - ec2: authenticated ec2 connection object - spot_requests: boto.ec2.spotinstancerequest.SpotInstanceRequest object returned by ec2.request_spot_instances - count: Total number of instances to be created by the spot requests - - Returns: - list of instance ID's created by the spot request(s) - """ - spot_wait_timeout = int(module.params.get('spot_wait_timeout')) - wait_complete = time.time() + spot_wait_timeout - - spot_req_inst_ids = dict() - while time.time() < wait_complete: - reqs = ec2.get_all_spot_instance_requests() - for sirb in spot_requests: - if sirb.id in spot_req_inst_ids: - continue - for sir in reqs: - if sir.id != sirb.id: - continue # this is not our spot instance - if sir.instance_id is not None: - spot_req_inst_ids[sirb.id] = sir.instance_id - elif sir.state == 'open': - continue # still waiting, nothing to do here - elif sir.state == 'active': - continue # Instance is created already, nothing to do here - elif sir.state == 'failed': - module.fail_json(msg="Spot instance request %s failed with status %s and fault %s:%s" % ( - sir.id, sir.status.code, sir.fault.code, sir.fault.message)) - elif sir.state == 'cancelled': - module.fail_json(msg="Spot instance request %s was cancelled before it could be fulfilled." % sir.id) - elif sir.state == 'closed': - # instance is terminating or marked for termination - # this may be intentional on the part of the operator, - # or it may have been terminated by AWS due to capacity, - # price, or group constraints in this case, we'll fail - # the module if the reason for the state is anything - # other than termination by user. Codes are documented at - # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-bid-status.html - if sir.status.code == 'instance-terminated-by-user': - # do nothing, since the user likely did this on purpose - pass - else: - spot_msg = "Spot instance request %s was closed by AWS with the status %s and fault %s:%s" - module.fail_json(msg=spot_msg % (sir.id, sir.status.code, sir.fault.code, sir.fault.message)) - - if len(spot_req_inst_ids) < count: - time.sleep(5) - else: - return list(spot_req_inst_ids.values()) - module.fail_json(msg="wait for spot requests timeout on %s" % time.asctime()) - - -def enforce_count(module, ec2, vpc): - - exact_count = module.params.get('exact_count') - count_tag = module.params.get('count_tag') - zone = module.params.get('zone') - - # fail here if the exact count was specified without filtering - # on a tag, as this may lead to a undesired removal of instances - if exact_count and count_tag is None: - module.fail_json(msg="you must use the 'count_tag' option with exact_count") - - reservations, instances = find_running_instances_by_count_tag(module, ec2, vpc, count_tag, zone) - - changed = None - checkmode = False - instance_dict_array = [] - changed_instance_ids = None - - if len(instances) == exact_count: - changed = False - elif len(instances) < exact_count: - changed = True - to_create = exact_count - len(instances) - if not checkmode: - (instance_dict_array, changed_instance_ids, changed) \ - = create_instances(module, ec2, vpc, override_count=to_create) - - for inst in instance_dict_array: - instances.append(inst) - elif len(instances) > exact_count: - changed = True - to_remove = len(instances) - exact_count - if not checkmode: - all_instance_ids = sorted([x.id for x in instances]) - remove_ids = all_instance_ids[0:to_remove] - - instances = [x for x in instances if x.id not in remove_ids] - - (changed, instance_dict_array, changed_instance_ids) \ - = terminate_instances(module, ec2, remove_ids) - terminated_list = [] - for inst in instance_dict_array: - inst['state'] = "terminated" - terminated_list.append(inst) - instance_dict_array = terminated_list - - # ensure all instances are dictionaries - all_instances = [] - for inst in instances: - - if not isinstance(inst, dict): - warn_if_public_ip_assignment_changed(module, inst) - inst = get_instance_info(inst) - all_instances.append(inst) - - return (all_instances, instance_dict_array, changed_instance_ids, changed) - - -def create_instances(module, ec2, vpc, override_count=None): - """ - Creates new instances - - module : AnsibleAWSModule object - ec2: authenticated ec2 connection object - - Returns: - A list of dictionaries with instance information - about the instances that were launched - """ - - key_name = module.params.get('key_name') - id = module.params.get('id') - group_name = module.params.get('group') - group_id = module.params.get('group_id') - zone = module.params.get('zone') - instance_type = module.params.get('instance_type') - tenancy = module.params.get('tenancy') - spot_price = module.params.get('spot_price') - spot_type = module.params.get('spot_type') - image = module.params.get('image') - if override_count: - count = override_count - else: - count = module.params.get('count') - monitoring = module.params.get('monitoring') - kernel = module.params.get('kernel') - ramdisk = module.params.get('ramdisk') - wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) - spot_wait_timeout = int(module.params.get('spot_wait_timeout')) - placement_group = module.params.get('placement_group') - user_data = module.params.get('user_data') - instance_tags = module.params.get('instance_tags') - vpc_subnet_id = module.params.get('vpc_subnet_id') - assign_public_ip = module.boolean(module.params.get('assign_public_ip')) - private_ip = module.params.get('private_ip') - instance_profile_name = module.params.get('instance_profile_name') - volumes = module.params.get('volumes') - ebs_optimized = module.params.get('ebs_optimized') - exact_count = module.params.get('exact_count') - count_tag = module.params.get('count_tag') - source_dest_check = module.boolean(module.params.get('source_dest_check')) - termination_protection = module.boolean(module.params.get('termination_protection')) - network_interfaces = module.params.get('network_interfaces') - spot_launch_group = module.params.get('spot_launch_group') - instance_initiated_shutdown_behavior = module.params.get('instance_initiated_shutdown_behavior') - - vpc_id = None - if vpc_subnet_id: - if not vpc: - module.fail_json(msg="region must be specified") - else: - vpc_id = vpc.get_all_subnets(subnet_ids=[vpc_subnet_id])[0].vpc_id - else: - vpc_id = None - - try: - # Here we try to lookup the group id from the security group name - if group is set. - if group_name: - if vpc_id: - grp_details = ec2.get_all_security_groups(filters={'vpc_id': vpc_id}) - else: - grp_details = ec2.get_all_security_groups() - if isinstance(group_name, string_types): - group_name = [group_name] - unmatched = set(group_name).difference(str(grp.name) for grp in grp_details) - if len(unmatched) > 0: - module.fail_json(msg="The following group names are not valid: %s" % ', '.join(unmatched)) - group_id = [str(grp.id) for grp in grp_details if str(grp.name) in group_name] - # Now we try to lookup the group id testing if group exists. - elif group_id: - # wrap the group_id in a list if it's not one already - if isinstance(group_id, string_types): - group_id = [group_id] - grp_details = ec2.get_all_security_groups(group_ids=group_id) - group_name = [grp_item.name for grp_item in grp_details] - except boto.exception.NoAuthHandlerFound as e: - module.fail_json_aws(e, msg='Unable to authenticate to AWS') - - # Lookup any instances that much our run id. - - running_instances = [] - count_remaining = int(count) - - if id is not None: - filter_dict = {'client-token': id, 'instance-state-name': 'running'} - previous_reservations = ec2.get_all_instances(None, filter_dict) - for res in previous_reservations: - for prev_instance in res.instances: - running_instances.append(prev_instance) - count_remaining = count_remaining - len(running_instances) - - # Both min_count and max_count equal count parameter. This means the launch request is explicit (we want count, or fail) in how many instances we want. - - if count_remaining == 0: - changed = False - else: - changed = True - try: - params = {'image_id': image, - 'key_name': key_name, - 'monitoring_enabled': monitoring, - 'placement': zone, - 'instance_type': instance_type, - 'kernel_id': kernel, - 'ramdisk_id': ramdisk} - if user_data is not None: - params['user_data'] = to_bytes(user_data, errors='surrogate_or_strict') - - if ebs_optimized: - params['ebs_optimized'] = ebs_optimized - - # 'tenancy' always has a default value, but it is not a valid parameter for spot instance request - if not spot_price: - params['tenancy'] = tenancy - - if boto_supports_profile_name_arg(ec2): - params['instance_profile_name'] = instance_profile_name - else: - if instance_profile_name is not None: - module.fail_json( - msg="instance_profile_name parameter requires Boto version 2.5.0 or higher") - - if assign_public_ip is not None: - if not boto_supports_associate_public_ip_address(ec2): - module.fail_json( - msg="assign_public_ip parameter requires Boto version 2.13.0 or higher.") - elif not vpc_subnet_id: - module.fail_json( - msg="assign_public_ip only available with vpc_subnet_id") - - else: - if private_ip: - interface = boto.ec2.networkinterface.NetworkInterfaceSpecification( - subnet_id=vpc_subnet_id, - private_ip_address=private_ip, - groups=group_id, - associate_public_ip_address=assign_public_ip) - else: - interface = boto.ec2.networkinterface.NetworkInterfaceSpecification( - subnet_id=vpc_subnet_id, - groups=group_id, - associate_public_ip_address=assign_public_ip) - interfaces = boto.ec2.networkinterface.NetworkInterfaceCollection(interface) - params['network_interfaces'] = interfaces - else: - if network_interfaces: - if isinstance(network_interfaces, string_types): - network_interfaces = [network_interfaces] - interfaces = [] - for i, network_interface_id in enumerate(network_interfaces): - interface = boto.ec2.networkinterface.NetworkInterfaceSpecification( - network_interface_id=network_interface_id, - device_index=i) - interfaces.append(interface) - params['network_interfaces'] = \ - boto.ec2.networkinterface.NetworkInterfaceCollection(*interfaces) - else: - params['subnet_id'] = vpc_subnet_id - if vpc_subnet_id: - params['security_group_ids'] = group_id - else: - params['security_groups'] = group_name - - if volumes: - bdm = BlockDeviceMapping() - for volume in volumes: - if 'device_name' not in volume: - module.fail_json(msg='Device name must be set for volume') - # Minimum volume size is 1GiB. We'll use volume size explicitly set to 0 - # to be a signal not to create this volume - if 'volume_size' not in volume or int(volume['volume_size']) > 0: - bdm[volume['device_name']] = create_block_device(module, ec2, volume) - - params['block_device_map'] = bdm - - # check to see if we're using spot pricing first before starting instances - if not spot_price: - if assign_public_ip is not None and private_ip: - params.update( - dict( - min_count=count_remaining, - max_count=count_remaining, - client_token=id, - placement_group=placement_group, - ) - ) - else: - params.update( - dict( - min_count=count_remaining, - max_count=count_remaining, - client_token=id, - placement_group=placement_group, - private_ip_address=private_ip, - ) - ) - - # For ordinary (not spot) instances, we can select 'stop' - # (the default) or 'terminate' here. - params['instance_initiated_shutdown_behavior'] = instance_initiated_shutdown_behavior or 'stop' - - try: - res = ec2.run_instances(**params) - except boto.exception.EC2ResponseError as e: - if (params['instance_initiated_shutdown_behavior'] != 'terminate' and - "InvalidParameterCombination" == e.error_code): - params['instance_initiated_shutdown_behavior'] = 'terminate' - res = ec2.run_instances(**params) - else: - raise - - instids = [i.id for i in res.instances] - while True: - try: - ec2.get_all_instances(instids) - break - except boto.exception.EC2ResponseError as e: - if e.error_code == 'InvalidInstanceID.NotFound': - # there's a race between start and get an instance - continue - else: - module.fail_json_aws(e) - - # The instances returned through ec2.run_instances above can be in - # terminated state due to idempotency. See commit 7f11c3d for a complete - # explanation. - terminated_instances = [ - str(instance.id) for instance in res.instances if instance.state == 'terminated' - ] - if terminated_instances: - module.fail_json(msg="Instances with id(s) %s " % terminated_instances + - "were created previously but have since been terminated - " + - "use a (possibly different) 'instanceid' parameter") - - else: - if private_ip: - module.fail_json( - msg='private_ip only available with on-demand (non-spot) instances') - if boto_supports_param_in_spot_request(ec2, 'placement_group'): - params['placement_group'] = placement_group - elif placement_group: - module.fail_json( - msg="placement_group parameter requires Boto version 2.3.0 or higher.") - - # You can't tell spot instances to 'stop'; they will always be - # 'terminate'd. For convenience, we'll ignore the latter value. - if instance_initiated_shutdown_behavior and instance_initiated_shutdown_behavior != 'terminate': - module.fail_json( - msg="instance_initiated_shutdown_behavior=stop is not supported for spot instances.") - - if spot_launch_group and isinstance(spot_launch_group, string_types): - params['launch_group'] = spot_launch_group - - params.update(dict( - count=count_remaining, - type=spot_type, - )) - - # Set spot ValidUntil - # ValidUntil -> (timestamp). The end date of the request, in - # UTC format (for example, YYYY -MM -DD T*HH* :MM :SS Z). - utc_valid_until = ( - datetime.datetime.utcnow() - + datetime.timedelta(seconds=spot_wait_timeout)) - params['valid_until'] = utc_valid_until.strftime('%Y-%m-%dT%H:%M:%S.000Z') - - res = ec2.request_spot_instances(spot_price, **params) - - # Now we have to do the intermediate waiting - if wait: - instids = await_spot_requests(module, ec2, res, count) - else: - instids = [] - except boto.exception.BotoServerError as e: - module.fail_json_aws(e, msg='Instance creation failed') - - # wait here until the instances are up - num_running = 0 - wait_timeout = time.time() + wait_timeout - res_list = () - while wait_timeout > time.time() and num_running < len(instids): - try: - res_list = ec2.get_all_instances(instids) - except boto.exception.BotoServerError as e: - if e.error_code == 'InvalidInstanceID.NotFound': - time.sleep(1) - continue - else: - raise - - num_running = 0 - for res in res_list: - num_running += len([i for i in res.instances if i.state == 'running']) - if len(res_list) <= 0: - # got a bad response of some sort, possibly due to - # stale/cached data. Wait a second and then try again - time.sleep(1) - continue - if wait and num_running < len(instids): - time.sleep(5) - else: - break - - if wait and wait_timeout <= time.time(): - # waiting took too long - module.fail_json(msg="wait for instances running timeout on %s" % time.asctime()) - - # We do this after the loop ends so that we end up with one list - for res in res_list: - running_instances.extend(res.instances) - - # Enabled by default by AWS - if source_dest_check is False: - for inst in res.instances: - inst.modify_attribute('sourceDestCheck', False) - - # Disabled by default by AWS - if termination_protection is True: - for inst in res.instances: - inst.modify_attribute('disableApiTermination', True) - - # Leave this as late as possible to try and avoid InvalidInstanceID.NotFound - if instance_tags and instids: - try: - ec2.create_tags(instids, instance_tags) - except boto.exception.EC2ResponseError as e: - module.fail_json_aws(e, msg='Instance tagging failed') - - instance_dict_array = [] - created_instance_ids = [] - for inst in running_instances: - inst.update() - d = get_instance_info(inst) - created_instance_ids.append(inst.id) - instance_dict_array.append(d) - - return (instance_dict_array, created_instance_ids, changed) - - -def terminate_instances(module, ec2, instance_ids): - """ - Terminates a list of instances - - module: Ansible module object - ec2: authenticated ec2 connection object - termination_list: a list of instances to terminate in the form of - [ {id: }, ..] - - Returns a dictionary of instance information - about the instances terminated. - - If the instance to be terminated is running - "changed" will be set to False. - - """ - - # Whether to wait for termination to complete before returning - wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) - - changed = False - instance_dict_array = [] - - if not isinstance(instance_ids, list) or len(instance_ids) < 1: - module.fail_json(msg='instance_ids should be a list of instances, aborting') - - terminated_instance_ids = [] - for res in ec2.get_all_instances(instance_ids): - for inst in res.instances: - if inst.state == 'running' or inst.state == 'stopped': - terminated_instance_ids.append(inst.id) - instance_dict_array.append(get_instance_info(inst)) - try: - ec2.terminate_instances([inst.id]) - except EC2ResponseError as e: - module.fail_json_aws(e, msg='Unable to terminate instance {0}'.format(inst.id)) - changed = True - - # wait here until the instances are 'terminated' - if wait: - num_terminated = 0 - wait_timeout = time.time() + wait_timeout - while wait_timeout > time.time() and num_terminated < len(terminated_instance_ids): - response = ec2.get_all_instances(instance_ids=terminated_instance_ids, - filters={'instance-state-name': 'terminated'}) - try: - num_terminated = sum([len(res.instances) for res in response]) - except Exception as e: - # got a bad response of some sort, possibly due to - # stale/cached data. Wait a second and then try again - time.sleep(1) - continue - - if num_terminated < len(terminated_instance_ids): - time.sleep(5) - - # waiting took too long - if wait_timeout < time.time() and num_terminated < len(terminated_instance_ids): - module.fail_json(msg="wait for instance termination timeout on %s" % time.asctime()) - # Lets get the current state of the instances after terminating - issue600 - instance_dict_array = [] - for res in ec2.get_all_instances(instance_ids=terminated_instance_ids, filters={'instance-state-name': 'terminated'}): - for inst in res.instances: - instance_dict_array.append(get_instance_info(inst)) - - return (changed, instance_dict_array, terminated_instance_ids) - - -def startstop_instances(module, ec2, instance_ids, state, instance_tags): - """ - Starts or stops a list of existing instances - - module: Ansible module object - ec2: authenticated ec2 connection object - instance_ids: The list of instances to start in the form of - [ {id: }, ..] - instance_tags: A dict of tag keys and values in the form of - {key: value, ... } - state: Intended state ("running" or "stopped") - - Returns a dictionary of instance information - about the instances started/stopped. - - If the instance was not able to change state, - "changed" will be set to False. - - Note that if instance_ids and instance_tags are both non-empty, - this method will process the intersection of the two - """ - - wait = module.params.get('wait') - wait_timeout = int(module.params.get('wait_timeout')) - group_id = module.params.get('group_id') - group_name = module.params.get('group') - changed = False - instance_dict_array = [] - - if not isinstance(instance_ids, list) or len(instance_ids) < 1: - # Fail unless the user defined instance tags - if not instance_tags: - module.fail_json(msg='instance_ids should be a list of instances, aborting') - - # To make an EC2 tag filter, we need to prepend 'tag:' to each key. - # An empty filter does no filtering, so it's safe to pass it to the - # get_all_instances method even if the user did not specify instance_tags - filters = {} - if instance_tags: - for key, value in instance_tags.items(): - filters["tag:" + key] = value - - filters['instance-state-name'] = ["pending", "running", "stopping", "stopped"] - - if module.params.get('id'): - filters['client-token'] = module.params['id'] - # Check that our instances are not in the state we want to take - - # Check (and eventually change) instances attributes and instances state - existing_instances_array = [] - for res in ec2.get_all_instances(instance_ids, filters=filters): - for inst in res.instances: - - warn_if_public_ip_assignment_changed(module, inst) - - changed = (check_source_dest_attr(module, inst, ec2) or - check_termination_protection(module, inst) or changed) - - # Check security groups and if we're using ec2-vpc; ec2-classic security groups may not be modified - if inst.vpc_id and group_name: - grp_details = ec2.get_all_security_groups(filters={'vpc_id': inst.vpc_id}) - if isinstance(group_name, string_types): - group_name = [group_name] - unmatched = set(group_name) - set(to_text(grp.name) for grp in grp_details) - if unmatched: - module.fail_json(msg="The following group names are not valid: %s" % ', '.join(unmatched)) - group_ids = [to_text(grp.id) for grp in grp_details if to_text(grp.name) in group_name] - elif inst.vpc_id and group_id: - if isinstance(group_id, string_types): - group_id = [group_id] - grp_details = ec2.get_all_security_groups(group_ids=group_id) - group_ids = [grp_item.id for grp_item in grp_details] - if inst.vpc_id and (group_name or group_id): - if set(sg.id for sg in inst.groups) != set(group_ids): - changed = inst.modify_attribute('groupSet', group_ids) - - # Check instance state - if inst.state != state: - instance_dict_array.append(get_instance_info(inst)) - try: - if state == 'running': - inst.start() - else: - inst.stop() - except EC2ResponseError as e: - module.fail_json_aws(e, 'Unable to change state for instance {0}'.format(inst.id)) - changed = True - existing_instances_array.append(inst.id) - - instance_ids = list(set(existing_instances_array + (instance_ids or []))) - # Wait for all the instances to finish starting or stopping - wait_timeout = time.time() + wait_timeout - while wait and wait_timeout > time.time(): - instance_dict_array = [] - matched_instances = [] - for res in ec2.get_all_instances(instance_ids): - for i in res.instances: - if i.state == state: - instance_dict_array.append(get_instance_info(i)) - matched_instances.append(i) - if len(matched_instances) < len(instance_ids): - time.sleep(5) - else: - break - - if wait and wait_timeout <= time.time(): - # waiting took too long - module.fail_json(msg="wait for instances running timeout on %s" % time.asctime()) - - return (changed, instance_dict_array, instance_ids) - - -def restart_instances(module, ec2, instance_ids, state, instance_tags): - """ - Restarts a list of existing instances - - module: Ansible module object - ec2: authenticated ec2 connection object - instance_ids: The list of instances to start in the form of - [ {id: }, ..] - instance_tags: A dict of tag keys and values in the form of - {key: value, ... } - state: Intended state ("restarted") - - Returns a dictionary of instance information - about the instances. - - If the instance was not able to change state, - "changed" will be set to False. - - Wait will not apply here as this is a OS level operation. - - Note that if instance_ids and instance_tags are both non-empty, - this method will process the intersection of the two. - """ - - changed = False - instance_dict_array = [] - - if not isinstance(instance_ids, list) or len(instance_ids) < 1: - # Fail unless the user defined instance tags - if not instance_tags: - module.fail_json(msg='instance_ids should be a list of instances, aborting') - - # To make an EC2 tag filter, we need to prepend 'tag:' to each key. - # An empty filter does no filtering, so it's safe to pass it to the - # get_all_instances method even if the user did not specify instance_tags - filters = {} - if instance_tags: - for key, value in instance_tags.items(): - filters["tag:" + key] = value - if module.params.get('id'): - filters['client-token'] = module.params['id'] - - # Check that our instances are not in the state we want to take - - # Check (and eventually change) instances attributes and instances state - for res in ec2.get_all_instances(instance_ids, filters=filters): - for inst in res.instances: - - warn_if_public_ip_assignment_changed(module, inst) - - changed = (check_source_dest_attr(module, inst, ec2) or - check_termination_protection(module, inst) or changed) - - # Check instance state - if inst.state != state: - instance_dict_array.append(get_instance_info(inst)) - try: - inst.reboot() - except EC2ResponseError as e: - module.fail_json_aws(e, msg='Unable to change state for instance {0}'.format(inst.id)) - changed = True - - return (changed, instance_dict_array, instance_ids) - - -def check_termination_protection(module, inst): - """ - Check the instance disableApiTermination attribute. - - module: Ansible module object - inst: EC2 instance object - - returns: True if state changed None otherwise - """ - - termination_protection = module.params.get('termination_protection') - - if (inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection and termination_protection is not None): - inst.modify_attribute('disableApiTermination', termination_protection) - return True - - -def check_source_dest_attr(module, inst, ec2): - """ - Check the instance sourceDestCheck attribute. - - module: Ansible module object - inst: EC2 instance object - - returns: True if state changed None otherwise - """ - - source_dest_check = module.params.get('source_dest_check') - - if source_dest_check is not None: - try: - if inst.vpc_id is not None and inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check: - inst.modify_attribute('sourceDestCheck', source_dest_check) - return True - except boto.exception.EC2ResponseError as exc: - # instances with more than one Elastic Network Interface will - # fail, because they have the sourceDestCheck attribute defined - # per-interface - if exc.code == 'InvalidInstanceID': - for interface in inst.interfaces: - if interface.source_dest_check != source_dest_check: - ec2.modify_network_interface_attribute(interface.id, "sourceDestCheck", source_dest_check) - return True - else: - module.fail_json_aws(exc, msg='Failed to handle source_dest_check state for instance {0}'.format(inst.id)) - - -def warn_if_public_ip_assignment_changed(module, instance): - # This is a non-modifiable attribute. - assign_public_ip = module.params.get('assign_public_ip') - - # Check that public ip assignment is the same and warn if not - public_dns_name = getattr(instance, 'public_dns_name', None) - if (assign_public_ip or public_dns_name) and (not public_dns_name or assign_public_ip is False): - module.warn("Unable to modify public ip assignment to {0} for instance {1}. " - "Whether or not to assign a public IP is determined during instance creation.".format(assign_public_ip, instance.id)) - - -def main(): - argument_spec = dict( - key_name=dict(aliases=['keypair']), - id=dict(), - group=dict(type='list', elements='str', aliases=['groups']), - group_id=dict(type='list', elements='str'), - zone=dict(aliases=['aws_zone', 'ec2_zone']), - instance_type=dict(aliases=['type']), - spot_price=dict(), - spot_type=dict(default='one-time', choices=["one-time", "persistent"]), - spot_launch_group=dict(), - image=dict(), - kernel=dict(), - count=dict(type='int', default='1'), - monitoring=dict(type='bool', default=False), - ramdisk=dict(), - wait=dict(type='bool', default=False), - wait_timeout=dict(type='int', default=300), - spot_wait_timeout=dict(type='int', default=600), - placement_group=dict(), - user_data=dict(), - instance_tags=dict(type='dict'), - vpc_subnet_id=dict(), - assign_public_ip=dict(type='bool'), - private_ip=dict(), - instance_profile_name=dict(), - instance_ids=dict(type='list', elements='str', aliases=['instance_id']), - source_dest_check=dict(type='bool', default=None), - termination_protection=dict(type='bool', default=None), - state=dict(default='present', choices=['present', 'absent', 'running', 'restarted', 'stopped']), - instance_initiated_shutdown_behavior=dict(default='stop', choices=['stop', 'terminate']), - exact_count=dict(type='int', default=None), - count_tag=dict(type='raw'), - volumes=dict(type='list', elements='dict',), - ebs_optimized=dict(type='bool', default=False), - tenancy=dict(default='default', choices=['default', 'dedicated']), - network_interfaces=dict(type='list', elements='str', aliases=['network_interface']) - ) - - module = AnsibleAWSModule( - argument_spec=argument_spec, - check_boto3=False, - mutually_exclusive=[ - # Can be uncommented when we finish the deprecation cycle. - # ['group', 'group_id'], - ['exact_count', 'count'], - ['exact_count', 'state'], - ['exact_count', 'instance_ids'], - ['network_interfaces', 'assign_public_ip'], - ['network_interfaces', 'group'], - ['network_interfaces', 'group_id'], - ['network_interfaces', 'private_ip'], - ['network_interfaces', 'vpc_subnet_id'], - ], - ) - - module.deprecate("The 'ec2' module has been deprecated and replaced by the 'ec2_instance' module'", - version='4.0.0', collection_name='amazon.aws') - - if module.params.get('group') and module.params.get('group_id'): - module.deprecate( - msg='Support for passing both group and group_id has been deprecated. ' - 'Currently group_id is ignored, in future passing both will result in an error', - date='2022-06-01', collection_name='amazon.aws') - - if not HAS_BOTO: - module.fail_json(msg='boto required for this module') - - try: - region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module) - if module.params.get('region') or not module.params.get('ec2_url'): - ec2 = ec2_connect(module) - elif module.params.get('ec2_url'): - ec2 = connect_ec2_endpoint(ec2_url, **aws_connect_kwargs) - - if 'region' not in aws_connect_kwargs: - aws_connect_kwargs['region'] = ec2.region - - vpc = connect_vpc(**aws_connect_kwargs) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json_aws(e, msg='Failed to get connection') - - tagged_instances = [] - - state = module.params['state'] - - if state == 'absent': - instance_ids = module.params['instance_ids'] - if not instance_ids: - module.fail_json(msg='instance_ids list is required for absent state') - - (changed, instance_dict_array, new_instance_ids) = terminate_instances(module, ec2, instance_ids) - - elif state in ('running', 'stopped'): - instance_ids = module.params.get('instance_ids') - instance_tags = module.params.get('instance_tags') - if not (isinstance(instance_ids, list) or isinstance(instance_tags, dict)): - module.fail_json(msg='running list needs to be a list of instances or set of tags to run: %s' % instance_ids) - - (changed, instance_dict_array, new_instance_ids) = startstop_instances(module, ec2, instance_ids, state, instance_tags) - - elif state in ('restarted'): - instance_ids = module.params.get('instance_ids') - instance_tags = module.params.get('instance_tags') - if not (isinstance(instance_ids, list) or isinstance(instance_tags, dict)): - module.fail_json(msg='running list needs to be a list of instances or set of tags to run: %s' % instance_ids) - - (changed, instance_dict_array, new_instance_ids) = restart_instances(module, ec2, instance_ids, state, instance_tags) - - elif state == 'present': - # Changed is always set to true when provisioning new instances - if not module.params.get('image'): - module.fail_json(msg='image parameter is required for new instance') - - if module.params.get('exact_count') is None: - (instance_dict_array, new_instance_ids, changed) = create_instances(module, ec2, vpc) - else: - (tagged_instances, instance_dict_array, new_instance_ids, changed) = enforce_count(module, ec2, vpc) - - # Always return instances in the same order - if new_instance_ids: - new_instance_ids.sort() - if instance_dict_array: - instance_dict_array.sort(key=lambda x: x['id']) - if tagged_instances: - tagged_instances.sort(key=lambda x: x['id']) - - module.exit_json(changed=changed, instance_ids=new_instance_ids, instances=instance_dict_array, tagged_instances=tagged_instances) - - -if __name__ == '__main__': - main() diff --git a/requirements.txt b/requirements.txt index 1ff3ca2feb4..1a52353f680 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,3 @@ # - tests/integration/targets/setup_botocore_pip botocore>=1.19.0 boto3>=1.16.0 -# Final released version -boto>=2.49.0 diff --git a/test-requirements.txt b/test-requirements.txt index 77c76b86509..77a68ba03b5 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,5 @@ botocore boto3 -boto coverage==4.5.4 placebo diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt index 6e870975a35..49273391e3f 100644 --- a/tests/integration/requirements.txt +++ b/tests/integration/requirements.txt @@ -1,5 +1,4 @@ # Our code is based on the AWS SDKs -boto boto3 botocore diff --git a/tests/integration/targets/ec2/aliases b/tests/integration/targets/ec2/aliases deleted file mode 100644 index 4ef4b2067d0..00000000000 --- a/tests/integration/targets/ec2/aliases +++ /dev/null @@ -1 +0,0 @@ -cloud/aws diff --git a/tests/integration/targets/ec2/defaults/main.yml b/tests/integration/targets/ec2/defaults/main.yml deleted file mode 100644 index b9b4053818b..00000000000 --- a/tests/integration/targets/ec2/defaults/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -availability_zone: '{{ ec2_availability_zone_names[0] }}' - -vpc_cidr: '10.{{ 256 | random(seed=resource_prefix) }}.0.0/16' -subnet_cidr: '10.{{ 256 | random(seed=resource_prefix) }}.1.0/24' diff --git a/tests/integration/targets/ec2/meta/main.yml b/tests/integration/targets/ec2/meta/main.yml deleted file mode 100644 index a0c556a5cb1..00000000000 --- a/tests/integration/targets/ec2/meta/main.yml +++ /dev/null @@ -1,4 +0,0 @@ -dependencies: - - prepare_tests - - setup_ec2 - - setup_ec2_facts diff --git a/tests/integration/targets/ec2/tasks/main.yml b/tests/integration/targets/ec2/tasks/main.yml deleted file mode 100644 index 472289c530f..00000000000 --- a/tests/integration/targets/ec2/tasks/main.yml +++ /dev/null @@ -1,188 +0,0 @@ ---- -- module_defaults: - group/aws: - aws_region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key | default(omit) }}' - aws_secret_key: '{{ aws_secret_key | default(omit) }}' - security_token: '{{ security_token | default(omit) }}' - collections: - - community.aws - - block: - - # SETUP: vpc, ec2 key pair, subnet, security group, ec2 instance - - - name: create a VPC to work in - ec2_vpc_net: - cidr_block: '{{ vpc_cidr }}' - state: present - name: '{{ resource_prefix }}_setup' - resource_tags: - Name: '{{ resource_prefix }}_setup' - register: setup_vpc - - - name: create a key pair to use for creating an ec2 instance - ec2_key: - name: '{{ resource_prefix }}_setup' - state: present - register: setup_key - - - name: create a subnet to use for creating an ec2 instance - ec2_vpc_subnet: - az: '{{ availability_zone }}' - tags: '{{ resource_prefix }}_setup' - vpc_id: '{{ setup_vpc.vpc.id }}' - cidr: '{{ subnet_cidr }}' - state: present - resource_tags: - Name: '{{ resource_prefix }}_setup' - register: setup_subnet - - - name: create a security group to use for creating an ec2 instance - ec2_group: - name: '{{ resource_prefix }}_setup' - description: 'created by Ansible integration tests' - state: present - vpc_id: '{{ setup_vpc.vpc.id }}' - register: setup_sg - - # ============================================================ - - - name: test first instance is started - ec2: - instance_type: t2.micro - key_name: '{{ setup_key.key.name }}' - state: present - image: '{{ ec2_ami_id }}' - wait: yes - instance_tags: - ResourcePrefix: '{{ resource_prefix }}-integration_tests' - group_id: '{{ setup_sg.group_id }}' - vpc_subnet_id: '{{ setup_subnet.subnet.id }}' - register: test_instance_1 - - - name: test second instance is started - ec2: - instance_type: t2.micro - key_name: '{{ setup_key.key.name }}' - state: present - image: '{{ ec2_ami_id }}' - wait: yes - instance_tags: - ResourcePrefix: '{{ resource_prefix }}-another_tag' - group_id: '{{ setup_sg.group_id }}' - vpc_subnet_id: '{{ setup_subnet.subnet.id }}' - register: test_instance_2 - - - name: assert instances started - assert: - that: - - "test_instance_1.instances[0].state == 'running'" - - "test_instance_2.instances[0].state == 'running'" - - - name: test first instance is terminated - ec2: - instance_ids: "{{ test_instance_1.instance_ids }}" - state: absent - wait: yes - register: result - - - name: assert instance terminated - assert: - that: - - "result.instances[0].state == 'terminated'" - - - name: test terminated instance is ignored when stopping - ec2: - instance_tags: - ResourcePrefix: '{{ resource_prefix }}-integration_tests' - state: stopped - wait: yes - register: result - - - name: assert resource not changed - assert: - that: - - "result.changed == False" - - - name: test second instance not terminated - ec2_instance_info: - instance_ids: "{{ test_instance_2.instance_ids }}" - register: result - - - name: assert second instance still running - assert: - that: - - (result.instances|length) == 1 - - "result.instances[0].state.name == 'running'" - - - name: test second instance is stopped - ec2: - instance_ids: "{{ test_instance_2.instance_ids }}" - state: stopped - wait: yes - register: result - - - name: assert second instance is stopped - assert: - that: - - "result.instances[0].state == 'stopped'" - - # ======================================================== - - always: - - # ============================================================ - - - # TEAR DOWN: ec2 instance, ec2 key pair, security group, vpc - - name: Announce teardown start - debug: - msg: "***** TESTING COMPLETE. COMMENCE TEARDOWN *****" - - - name: get list of test instances - ec2_instance_info: - filters: - "tag:ResourcePrefix": "{{ resource_prefix }}-*" - register: test_instances - - - name: delete test instances - ec2: - instance_ids: "{{ test_instances.instances|map(attribute='instance_id') }}" - state: absent - wait: yes - ignore_errors: yes - - - name: remove setup keypair - ec2_key: - name: '{{resource_prefix}}_setup' - state: absent - ignore_errors: yes - - - name: remove setup security group - ec2_group: - name: '{{ resource_prefix }}_setup' - description: 'created by Ansible integration tests' - state: absent - vpc_id: '{{ setup_vpc.vpc.id }}' - ignore_errors: yes - - - name: remove setup subnet - ec2_vpc_subnet: - az: '{{ availability_zone }}' - tags: '{{resource_prefix}}_setup' - vpc_id: '{{ setup_vpc.vpc.id }}' - cidr: '{{ subnet_cidr }}' - state: absent - resource_tags: - Name: '{{ resource_prefix }}_setup' - ignore_errors: yes - - - name: remove setup VPC - ec2_vpc_net: - cidr_block: '{{ vpc_cidr }}' - state: absent - name: '{{ resource_prefix }}_setup' - resource_tags: - Name: '{{ resource_prefix }}_setup' - ignore_errors: yes diff --git a/tests/integration/targets/ec2_instance/main.yml b/tests/integration/targets/ec2_instance/main.yml index 230a48c74e9..34163e478e5 100644 --- a/tests/integration/targets/ec2_instance/main.yml +++ b/tests/integration/targets/ec2_instance/main.yml @@ -35,6 +35,6 @@ - hosts: all gather_facts: no strategy: free - serial: 5 + serial: 6 roles: - ec2_instance diff --git a/tests/integration/targets/module_utils_ec2/aliases b/tests/integration/targets/module_utils_ec2/aliases deleted file mode 100644 index 4ef4b2067d0..00000000000 --- a/tests/integration/targets/module_utils_ec2/aliases +++ /dev/null @@ -1 +0,0 @@ -cloud/aws diff --git a/tests/integration/targets/module_utils_ec2/connect_to_aws.yml b/tests/integration/targets/module_utils_ec2/connect_to_aws.yml deleted file mode 100644 index f0adfc72e59..00000000000 --- a/tests/integration/targets/module_utils_ec2/connect_to_aws.yml +++ /dev/null @@ -1,8 +0,0 @@ -- hosts: all - gather_facts: no - collections: - - community.aws - - amazon.aws - roles: - # Test the behaviour of module_utils.connect_to_aws - - 'connect_to_aws' diff --git a/tests/integration/targets/module_utils_ec2/ec2_connect.yml b/tests/integration/targets/module_utils_ec2/ec2_connect.yml deleted file mode 100644 index 75ecd297167..00000000000 --- a/tests/integration/targets/module_utils_ec2/ec2_connect.yml +++ /dev/null @@ -1,8 +0,0 @@ -- hosts: all - gather_facts: no - collections: - - community.aws - - amazon.aws - roles: - # Test the behaviour of module_utils.ec2.ec2_connect - - 'ec2_connect' diff --git a/tests/integration/targets/module_utils_ec2/inventory b/tests/integration/targets/module_utils_ec2/inventory deleted file mode 100644 index 5093e8582bc..00000000000 --- a/tests/integration/targets/module_utils_ec2/inventory +++ /dev/null @@ -1,6 +0,0 @@ -[tests] -localhost - -[all:vars] -ansible_connection=local -ansible_python_interpreter="{{ ansible_playbook_python }}" diff --git a/tests/integration/targets/module_utils_ec2/meta/main.yml b/tests/integration/targets/module_utils_ec2/meta/main.yml deleted file mode 100644 index 1f64f1169a9..00000000000 --- a/tests/integration/targets/module_utils_ec2/meta/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -dependencies: - - prepare_tests - - setup_ec2 diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/amazonroot.pem b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/amazonroot.pem deleted file mode 100644 index a6f3e92af58..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/amazonroot.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/isrg-x1.pem b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/isrg-x1.pem deleted file mode 100644 index b85c8037f6b..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/files/isrg-x1.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/library/example_module.py b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/library/example_module.py deleted file mode 100644 index 543776a048b..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/library/example_module.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# A bare-minimum Ansible Module based on AnsibleAWSModule used for testing some -# of the core behaviour around AWS/Boto3 connection details - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -try: - import boto.ec2 -except ImportError: - pass - -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AnsibleAWSError -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import connect_to_aws -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import get_aws_connection_info - - -def main(): - module = AnsibleAWSModule( - argument_spec={}, - supports_check_mode=True, - check_boto3=False, - ) - - region, ec2_url, aws_connect_params = get_aws_connection_info(module) - if not region: - module.fail_json(msg="Fail JSON: No Region") - - try: - client = connect_to_aws(boto.ec2, region, **aws_connect_params) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json_aws(e, msg='No Authentication Handler Found') - except AnsibleAWSError as e: - module.fail_json_aws(e, msg='Fail JSON AWS') - - filters = {'name': 'amzn2-ami-hvm-2.0.202006*-x86_64-gp2'} - - try: - images = client.get_all_images(image_ids=[], filters=filters, owners=['amazon'], executable_by=[]) - except (boto.exception.BotoServerError, AnsibleAWSError) as e: - module.fail_json_aws(e, msg='Fail JSON AWS') - - images_out = [] - for image in images: - images_out.append(image.id) - - # Return something, just because we can. - module.exit_json( - changed=False, - images=images_out) - - -if __name__ == '__main__': - main() diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/meta/main.yml b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/meta/main.yml deleted file mode 100644 index 77589cc2b48..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/meta/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - - prepare_tests - - setup_ec2 -collections: - - amazon.aws diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/credentials.yml b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/credentials.yml deleted file mode 100644 index 573532bb7f1..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/credentials.yml +++ /dev/null @@ -1,212 +0,0 @@ ---- -################################################################################## -# Tests using standard credential parameters - -- name: 'Test basic operation using simple credentials (simple-parameters)' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (aws-parameters)' - example_module: - aws_region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (ec2-parameters)' - example_module: - ec2_region: '{{ aws_region }}' - ec2_access_key: '{{ aws_access_key }}' - ec2_secret_key: '{{ aws_secret_key }}' - access_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -################################################################################## -# Tests using standard credentials from environment variables - -- name: 'Test basic operation using simple credentials (aws-environment)' - example_module: - environment: - AWS_REGION: '{{ aws_region }}' - AWS_ACCESS_KEY_ID: '{{ aws_access_key }}' - AWS_SECRET_ACCESS_KEY: '{{ aws_secret_key }}' - AWS_SECURITY_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (aws2-environment)' - example_module: - environment: - AWS_DEFAULT_REGION: '{{ aws_region }}' - AWS_ACCESS_KEY: '{{ aws_access_key }}' - AWS_SECRET_KEY: '{{ aws_secret_key }}' - AWS_SESSION_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (ec2-environment)' - example_module: - environment: - EC2_REGION: '{{ aws_region }}' - EC2_ACCESS_KEY: '{{ aws_access_key }}' - EC2_SECRET_KEY: '{{ aws_secret_key }}' - EC2_SECURITY_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -################################################################################## -# Tests for missing parameters - -- name: 'Test with missing region' - example_module: - region: '{{ omit }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: missing_region - ignore_errors: True - -- assert: - that: - - missing_region is failed - - '"Fail JSON: No Region" in missing_region.msg' - -- name: 'Test with missing access key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ omit }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: missing_access - ignore_errors: True - -- assert: - that: - - missing_access is failed - - '"No handler was ready to authenticate." in missing_access.msg' - #- '"aws_access_key_id" in missing_access.msg' - -- name: 'Test with missing secret key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ omit }}' - security_token: '{{ security_token }}' - register: missing_secret - ignore_errors: True - -- assert: - that: - - missing_secret is failed - - '"No handler was ready to authenticate." in missing_secret.msg' - #- '"aws_secret_access_key" in missing_secret.msg' - -- name: 'Test with missing security token' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ omit }}' - register: missing_token - ignore_errors: True - -- assert: - that: - - missing_token is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - - -################################################################################## -# Tests for bad parameters - -- name: 'Test with bad region' - example_module: - region: 'junk-example' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_region - ignore_errors: True - -- assert: - that: - - bad_region is failed - - '"msg" in bad_region' - - '"does not seem to be available" in bad_region.msg' - - '"If the region definitely exists, you may need to upgrade boto or extend with endpoints_path" in bad_region.msg' - -- name: 'Test with bad access key' - example_module: - region: '{{ aws_region }}' - access_key: 'junk-example' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_access - ignore_errors: True - -- assert: - that: - - bad_access is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - -- name: 'Test with bad secret key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: 'junk-example' - security_token: '{{ security_token }}' - register: bad_secret - ignore_errors: True - -- assert: - that: - - bad_secret is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - -- name: 'Test with bad security token' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: 'junk-example' - register: bad_token - ignore_errors: True - -- assert: - that: - - bad_token is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/endpoints.yml b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/endpoints.yml deleted file mode 100644 index a2531e9603c..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/endpoints.yml +++ /dev/null @@ -1,105 +0,0 @@ ---- -# Note: connect_to_aws currently *ignores* aws_endpoint_url -# -################################################################################## -# Tests using Endpoints - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - aws_endpoint_url: 'https://ec2.{{ aws_region }}.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - endpoint_url: 'https://ec2.us-east-1.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - ec2_url: 'https://ec2.us-east-1.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -################################################################################## -# Tests using environment variables - -- name: 'Test basic operation using standard endpoint (aws-environment)' - example_module: - region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - environment: - AWS_URL: 'https://ec2.us-east-1.amazonaws.com' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (ec2-environment)' - example_module: - region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - environment: - EC2_URL: 'https://ec2.us-east-1.amazonaws.com' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -################################################################################## -# Tests using a bad endpoint URL -# - This demonstrates that endpoint_url overrode region - -- name: 'Test with bad endpoint URL' - example_module: - region: '{{ aws_region }}' - endpoint_url: 'https://junk.{{ aws_region }}.amazonaws.com' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_endpoint - ignore_errors: True - -- assert: - that: - # endpoint_url is ignored by connect_to_aws - - bad_endpoint is successful - #- bad_endpoint is failed diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/main.yml b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/main.yml deleted file mode 100644 index 9e81b30802e..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/main.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -- name: 'Tests around standard credentials' - include_tasks: 'credentials.yml' - -- name: 'Tests around profiles' - include_tasks: 'profiles.yml' - -- name: 'Tests around endpoints' - include_tasks: 'endpoints.yml' - -#- name: 'Tests around CA Bundles' -# include_tasks: 'boto2_ec2/ca_bundle.yml' diff --git a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/profiles.yml b/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/profiles.yml deleted file mode 100644 index bab095e47ad..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/connect_to_aws/tasks/profiles.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -# Note: unlike boto3 modules, boto2 modules can't read region from the profile -# -################################################################################## -# Tests using profiles instead of directly consuming credentials - -- name: 'Test basic operation using profile (simple-parameters)' - example_module: - region: '{{ aws_region }}' - profile: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws-parameters)' - example_module: - region: '{{ aws_region }}' - aws_profile: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws-environment)' - example_module: - region: '{{ aws_region }}' - environment: - AWS_PROFILE: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws2-environment)' - example_module: - region: '{{ aws_region }}' - environment: - AWS_DEFAULT_PROFILE: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -################################################################################## -# Tests with bad profile - -- name: 'Test with bad profile' - example_module: - region: '{{ aws_region }}' - profile: 'junk-profile' - register: bad_profile - ignore_errors: True - -- assert: - that: - - bad_profile is failed - - '"msg" in bad_profile' - - '"Profile given for AWS was not found." in bad_profile.msg' diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/amazonroot.pem b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/amazonroot.pem deleted file mode 100644 index a6f3e92af58..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/amazonroot.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/isrg-x1.pem b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/isrg-x1.pem deleted file mode 100644 index b85c8037f6b..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/files/isrg-x1.pem +++ /dev/null @@ -1,31 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/library/example_module.py b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/library/example_module.py deleted file mode 100644 index 6bbc1a4a08c..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/library/example_module.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/python -# Copyright (c) 2017 Ansible Project -# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) - -# A bare-minimum Ansible Module based on AnsibleAWSModule used for testing some -# of the core behaviour around AWS/Boto3 connection details - -from __future__ import (absolute_import, division, print_function) -__metaclass__ = type - - -try: - import boto.ec2 -except ImportError: - pass - -from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ec2_connect - - -def main(): - module = AnsibleAWSModule( - argument_spec={}, - supports_check_mode=True, - check_boto3=False, - ) - - try: - client = ec2_connect(module) - except boto.exception.NoAuthHandlerFound as e: - module.fail_json_aws(e, msg='Failed to get connection') - - filters = {'name': 'amzn2-ami-hvm-2.0.202006*-x86_64-gp2'} - - try: - images = client.get_all_images(image_ids=[], filters=filters, owners=['amazon'], executable_by=[]) - except boto.exception.BotoServerError as e: - module.fail_json_aws(e, msg='Fail JSON AWS') - - images_out = [] - for image in images: - images_out.append(image.id) - - # Return something, just because we can. - module.exit_json( - changed=False, - images=images_out) - - -if __name__ == '__main__': - main() diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/meta/main.yml b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/meta/main.yml deleted file mode 100644 index 77589cc2b48..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/meta/main.yml +++ /dev/null @@ -1,5 +0,0 @@ -dependencies: - - prepare_tests - - setup_ec2 -collections: - - amazon.aws diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/credentials.yml b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/credentials.yml deleted file mode 100644 index 1843a497d18..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/credentials.yml +++ /dev/null @@ -1,212 +0,0 @@ ---- -################################################################################## -# Tests using standard credential parameters - -- name: 'Test basic operation using simple credentials (simple-parameters)' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (aws-parameters)' - example_module: - aws_region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (ec2-parameters)' - example_module: - ec2_region: '{{ aws_region }}' - ec2_access_key: '{{ aws_access_key }}' - ec2_secret_key: '{{ aws_secret_key }}' - access_token: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -################################################################################## -# Tests using standard credentials from environment variables - -- name: 'Test basic operation using simple credentials (aws-environment)' - example_module: - environment: - AWS_REGION: '{{ aws_region }}' - AWS_ACCESS_KEY_ID: '{{ aws_access_key }}' - AWS_SECRET_ACCESS_KEY: '{{ aws_secret_key }}' - AWS_SECURITY_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (aws2-environment)' - example_module: - environment: - AWS_DEFAULT_REGION: '{{ aws_region }}' - AWS_ACCESS_KEY: '{{ aws_access_key }}' - AWS_SECRET_KEY: '{{ aws_secret_key }}' - AWS_SESSION_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -- name: 'Test basic operation using simple credentials (ec2-environment)' - example_module: - environment: - EC2_REGION: '{{ aws_region }}' - EC2_ACCESS_KEY: '{{ aws_access_key }}' - EC2_SECRET_KEY: '{{ aws_secret_key }}' - EC2_SECURITY_TOKEN: '{{ security_token }}' - register: credential_result - -- assert: - that: - - credential_result is successful - -################################################################################## -# Tests for missing parameters - -- name: 'Test with missing region' - example_module: - region: '{{ omit }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: missing_region - ignore_errors: True - -- assert: - that: - - missing_region is failed - - '"Either region or ec2_url must be specified" in missing_region.msg' - -- name: 'Test with missing access key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ omit }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: missing_access - ignore_errors: True - -- assert: - that: - - missing_access is failed - - '"No handler was ready to authenticate." in missing_access.msg' - #- '"aws_access_key_id" in missing_access.msg' - -- name: 'Test with missing secret key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ omit }}' - security_token: '{{ security_token }}' - register: missing_secret - ignore_errors: True - -- assert: - that: - - missing_secret is failed - - '"No handler was ready to authenticate." in missing_secret.msg' - #- '"aws_secret_access_key" in missing_secret.msg' - -- name: 'Test with missing security token' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ omit }}' - register: missing_token - ignore_errors: True - -- assert: - that: - - missing_token is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - - -################################################################################## -# Tests for bad parameters - -- name: 'Test with bad region' - example_module: - region: 'junk-example' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_region - ignore_errors: True - -- assert: - that: - - bad_region is failed - - '"msg" in bad_region' - - '"does not seem to be available" in bad_region.msg' - - '"If the region definitely exists, you may need to upgrade boto or extend with endpoints_path" in bad_region.msg' - -- name: 'Test with bad access key' - example_module: - region: '{{ aws_region }}' - access_key: 'junk-example' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_access - ignore_errors: True - -- assert: - that: - - bad_access is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - -- name: 'Test with bad secret key' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: 'junk-example' - security_token: '{{ security_token }}' - register: bad_secret - ignore_errors: True - -- assert: - that: - - bad_secret is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' - -- name: 'Test with bad security token' - example_module: - region: '{{ aws_region }}' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: 'junk-example' - register: bad_token - ignore_errors: True - -- assert: - that: - - bad_token is failed - # Caught when we try to do something, and passed to fail_json_aws - - '"Fail JSON AWS" in missing_token.msg' - - '"AWS was not able to validate the provided access credentials" in missing_token.msg' diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/endpoints.yml b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/endpoints.yml deleted file mode 100644 index a8a6ba20ca7..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/endpoints.yml +++ /dev/null @@ -1,119 +0,0 @@ ---- -# Note 1: With boto3 we can use the FIPS endpoints as a minimal proxy for testing that -# we're using something different. With boto2 the authentication fails. -# -################################################################################## -# Tests using Endpoints - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - aws_endpoint_url: 'https://ec2.{{ aws_region }}.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint - no region (aws-parameters)' - example_module: - region: '{{ omit }}' - aws_endpoint_url: 'https://ec2.{{ aws_region }}.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - endpoint_url: 'https://ec2.{{ aws_region }}.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (aws-parameters)' - example_module: - region: '{{ aws_region }}' - ec2_url: 'https://ec2.{{ aws_region }}.amazonaws.com' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -################################################################################## -# Tests using environment variables - -- name: 'Test basic operation using standard endpoint (aws-environment)' - example_module: - region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - environment: - AWS_URL: 'https://ec2.{{ aws_region }}.amazonaws.com' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -- name: 'Test basic operation using standard endpoint (ec2-environment)' - example_module: - region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - aws_security_token: '{{ security_token }}' - environment: - EC2_URL: 'https://ec2.{{ aws_region }}.amazonaws.com' - register: standard_endpoint_result - -- name: 'Check that we connected to the standard endpoint' - assert: - that: - - standard_endpoint_result is successful - #- '"ec2:DescribeImages" in standard_endpoint_result.resource_actions' - -################################################################################## -# Tests using a bad endpoint URL -# - This demonstrates that endpoint_url overrode region - -- name: 'Test with bad endpoint URL' - example_module: - region: '{{ aws_region }}' - endpoint_url: 'https://junk.{{ aws_region }}.amazonaws.com' - access_key: '{{ aws_access_key }}' - secret_key: '{{ aws_secret_key }}' - security_token: '{{ security_token }}' - register: bad_endpoint - ignore_errors: True - -- assert: - that: - - bad_endpoint is failed diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/main.yml b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/main.yml deleted file mode 100644 index 9e81b30802e..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/main.yml +++ /dev/null @@ -1,12 +0,0 @@ ---- -- name: 'Tests around standard credentials' - include_tasks: 'credentials.yml' - -- name: 'Tests around profiles' - include_tasks: 'profiles.yml' - -- name: 'Tests around endpoints' - include_tasks: 'endpoints.yml' - -#- name: 'Tests around CA Bundles' -# include_tasks: 'boto2_ec2/ca_bundle.yml' diff --git a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/profiles.yml b/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/profiles.yml deleted file mode 100644 index bab095e47ad..00000000000 --- a/tests/integration/targets/module_utils_ec2/roles/ec2_connect/tasks/profiles.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -# Note: unlike boto3 modules, boto2 modules can't read region from the profile -# -################################################################################## -# Tests using profiles instead of directly consuming credentials - -- name: 'Test basic operation using profile (simple-parameters)' - example_module: - region: '{{ aws_region }}' - profile: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws-parameters)' - example_module: - region: '{{ aws_region }}' - aws_profile: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws-environment)' - example_module: - region: '{{ aws_region }}' - environment: - AWS_PROFILE: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -- name: 'Test basic operation using profile (aws2-environment)' - example_module: - region: '{{ aws_region }}' - environment: - AWS_DEFAULT_PROFILE: 'test_profile' - register: profile_result - -- assert: - that: - - profile_result is successful - -################################################################################## -# Tests with bad profile - -- name: 'Test with bad profile' - example_module: - region: '{{ aws_region }}' - profile: 'junk-profile' - register: bad_profile - ignore_errors: True - -- assert: - that: - - bad_profile is failed - - '"msg" in bad_profile' - - '"Profile given for AWS was not found." in bad_profile.msg' diff --git a/tests/integration/targets/module_utils_ec2/runme.sh b/tests/integration/targets/module_utils_ec2/runme.sh deleted file mode 100755 index cbf09a5422d..00000000000 --- a/tests/integration/targets/module_utils_ec2/runme.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -ANSIBLE_ROLES_PATH="../" -# Boto3 -AWS_CONFIG_FILE="$( pwd )/boto3_config" -# Boto2 -BOTO_CONFIG="$( pwd )/boto3_config" - -export ANSIBLE_ROLES_PATH -export AWS_CONFIG_FILE -export BOTO_CONFIG - -ansible-playbook setup.yml -i localhost "$@" -ansible-playbook ec2_connect.yml -i inventory "$@" -e "@session_credentials.yml" -ansible-playbook connect_to_aws.yml -i inventory "$@" -e "@session_credentials.yml" diff --git a/tests/integration/targets/module_utils_ec2/setup.yml b/tests/integration/targets/module_utils_ec2/setup.yml deleted file mode 100644 index 9b219eb20f9..00000000000 --- a/tests/integration/targets/module_utils_ec2/setup.yml +++ /dev/null @@ -1,40 +0,0 @@ ---- -- hosts: localhost - connection: local - gather_facts: no - tasks: - # =========================================================== - # While CI uses a dedicated session, the easiest way to run - # tests outside of CI is with a simple access/secret key pair. - # - # For consistency, use sts_session_token to grab session - # credentials if we're not already using a session - # Note: this can't be done within a session, hence the slightly - # strange dance - - name: 'Get a session token if we are using a basic key' - when: - - security_token is not defined - block: - - name: 'Get a session token' - sts_session_token: - region: '{{ aws_region }}' - aws_access_key: '{{ aws_access_key }}' - aws_secret_key: '{{ aws_secret_key }}' - register: session_token - no_log: true - - name: 'Override initial tokens' - set_fact: - session_access_key: '{{ session_token.sts_creds.access_key }}' - session_secret_key: '{{ session_token.sts_creds.secret_key }}' - session_security_token: '{{ session_token.sts_creds.session_token }}' - no_log: true - - - name: 'Write out credentials' - template: - dest: './session_credentials.yml' - src: 'session_credentials.yml.j2' - - - name: 'Write out boto config file' - template: - dest: './boto3_config' - src: 'boto_config.j2' diff --git a/tests/integration/targets/module_utils_ec2/templates/boto_config.j2 b/tests/integration/targets/module_utils_ec2/templates/boto_config.j2 deleted file mode 100644 index f8668f057fc..00000000000 --- a/tests/integration/targets/module_utils_ec2/templates/boto_config.j2 +++ /dev/null @@ -1,5 +0,0 @@ -[profile test_profile] -region = {{ aws_region }} -aws_access_key_id = {{ session_access_key | default(aws_access_key) }} -aws_secret_access_key = {{ session_secret_key | default(aws_secret_key) }} -aws_security_token = {{ session_security_token | default(security_token) }} diff --git a/tests/integration/targets/module_utils_ec2/templates/session_credentials.yml.j2 b/tests/integration/targets/module_utils_ec2/templates/session_credentials.yml.j2 deleted file mode 100644 index bb030439392..00000000000 --- a/tests/integration/targets/module_utils_ec2/templates/session_credentials.yml.j2 +++ /dev/null @@ -1,3 +0,0 @@ -aws_access_key: {{ session_access_key | default(aws_access_key) }} -aws_secret_key: {{ session_secret_key | default(aws_secret_key) }} -security_token: {{ session_security_token | default(security_token) }} diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 9950c296ab7..e8ec7663805 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,8 +1,5 @@ plugins/module_utils/ec2.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability plugins/modules/aws_az_info.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability -plugins/modules/ec2.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability -plugins/modules/ec2.py validate-modules:deprecation-mismatch # Ansible 2.9 docs don't support deprecation properly -plugins/modules/ec2.py validate-modules:invalid-documentation # Ansible 2.9 docs don't support deprecation properly plugins/modules/ec2_tag.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability plugins/modules/ec2_vol.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability plugins/modules/ec2_vpc_dhcp_option.py pylint:ansible-deprecated-no-version # We use dates for deprecations, Ansible 2.9 only supports this for compatability diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt index 704c73a25b2..49f392832ab 100644 --- a/tests/unit/requirements.txt +++ b/tests/unit/requirements.txt @@ -1,6 +1,5 @@ # Our code is based on the AWS SDKs botocore boto3 -boto placebo