ec2_ami_copy: add tag_equality option for idempotence using tags (#40088)

* Allow idempotent use of ec2_ami_copy

When `tag_equality` is set true, use tags to determine
if AMIs in different accounts are the same, and don't
copy the AMI twice if they are the same.

Use AnsibleAWSModule and make imports more consistent
with other modules

* Update version added

* More code review changes

* Review changes - Recommended way to start EC2 connection
This commit is contained in:
Ramki Subramanian 2018-05-14 13:11:48 -06:00 committed by Sloane Hertel
parent dbb4493f17
commit 3955e528b1

View file

@ -65,6 +65,12 @@ options:
tags: tags:
description: description:
- A hash/dictionary of tags to add to the new copied AMI; '{"key":"value"}' and '{"key":"value","key":"value"}' - A hash/dictionary of tags to add to the new copied AMI; '{"key":"value"}' and '{"key":"value","key":"value"}'
tag_equality:
description:
- Whether to use tags if the source AMI already exists in the target region. If this is set, and all tags match
in an existing AMI, the AMI will not be copied again.
default: false
version_added: 2.6
author: "Amir Moulavi <amir.moulavi@gmail.com>, Tim C <defunct@defunct.io>" author: "Amir Moulavi <amir.moulavi@gmail.com>, Tim C <defunct@defunct.io>"
extends_documentation_fragment: extends_documentation_fragment:
- aws - aws
@ -97,7 +103,7 @@ EXAMPLES = '''
name: My-Awesome-AMI name: My-Awesome-AMI
description: latest patch description: latest patch
# Tagged AMI copy # Tagged AMI copy (will not copy the same AMI twice)
- ec2_ami_copy: - ec2_ami_copy:
source_region: us-east-1 source_region: us-east-1
region: eu-west-1 region: eu-west-1
@ -105,6 +111,7 @@ EXAMPLES = '''
tags: tags:
Name: My-Super-AMI Name: My-Super-AMI
Patch: 1.2.3 Patch: 1.2.3
tag_equality: yes
# Encrypted AMI copy # Encrypted AMI copy
- ec2_ami_copy: - ec2_ami_copy:
@ -130,13 +137,12 @@ image_id:
sample: ami-e689729e sample: ami-e689729e
''' '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import (boto3_conn, ec2_argument_spec, get_aws_connection_info) from ansible.module_utils.ec2 import ec2_argument_spec
from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ansible_dict_to_boto3_tag_list
import traceback
try: try:
from botocore.exceptions import ClientError, NoCredentialsError, WaiterError from botocore.exceptions import ClientError, NoCredentialsError, WaiterError, BotoCoreError
HAS_BOTO3 = True HAS_BOTO3 = True
except ImportError: except ImportError:
HAS_BOTO3 = False HAS_BOTO3 = False
@ -150,6 +156,10 @@ def copy_image(module, ec2):
ec2: ec2 connection object ec2: ec2 connection object
""" """
image = None
changed = False
tags = module.params.get('tags')
params = {'SourceRegion': module.params.get('source_region'), params = {'SourceRegion': module.params.get('source_region'),
'SourceImageId': module.params.get('source_image_id'), 'SourceImageId': module.params.get('source_image_id'),
'Name': module.params.get('name'), 'Name': module.params.get('name'),
@ -160,7 +170,20 @@ def copy_image(module, ec2):
params['KmsKeyId'] = module.params.get('kms_key_id') params['KmsKeyId'] = module.params.get('kms_key_id')
try: try:
image_id = ec2.copy_image(**params)['ImageId'] if module.params.get('tag_equality'):
filters = [{'Name': 'tag:%s' % k, 'Values': [v]} for (k, v) in module.params.get('tags').items()]
filters.append(dict(Name='state', Values=['available', 'pending']))
images = ec2.describe_images(Filters=filters)
if len(images['Images']) > 0:
image = images['Images'][0]
if not image:
image = ec2.copy_image(**params)
image_id = image['ImageId']
if tags:
ec2.create_tags(Resources=[image_id],
Tags=ansible_dict_to_boto3_tag_list(tags))
changed = True
if module.params.get('wait'): if module.params.get('wait'):
delay = 15 delay = 15
max_attempts = module.params.get('wait_timeout') // delay max_attempts = module.params.get('wait_timeout') // delay
@ -168,19 +191,12 @@ def copy_image(module, ec2):
ImageIds=[image_id], ImageIds=[image_id],
WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts} WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts}
) )
if module.params.get('tags'):
ec2.create_tags(
Resources=[image_id],
Tags=[{'Key': k, 'Value': v} for k, v in module.params.get('tags').items()]
)
module.exit_json(changed=True, image_id=image_id) module.exit_json(changed=changed, **camel_dict_to_snake_dict(image))
except WaiterError as we: except WaiterError as e:
module.fail_json(msg='An error occurred waiting for the image to become available. (%s)' % str(we), exception=traceback.format_exc()) module.fail_json_aws(e, msg='An error occurred waiting for the image to become available')
except ClientError as ce: except (ClientError, BotoCoreError) as e:
module.fail_json(msg=ce.message) module.fail_json_aws(e, msg="Could not copy AMI")
except NoCredentialsError:
module.fail_json(msg='Unable to authenticate, AWS credentials are invalid.')
except Exception as e: except Exception as e:
module.fail_json(msg='Unhandled exception. (%s)' % str(e)) module.fail_json(msg='Unhandled exception. (%s)' % str(e))
@ -196,14 +212,12 @@ def main():
kms_key_id=dict(type='str', required=False), kms_key_id=dict(type='str', required=False),
wait=dict(type='bool', default=False), wait=dict(type='bool', default=False),
wait_timeout=dict(type='int', default=600), wait_timeout=dict(type='int', default=600),
tags=dict(type='dict'))) tags=dict(type='dict')),
tag_equality=dict(type='bool', default=False))
module = AnsibleModule(argument_spec=argument_spec)
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
ec2 = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url,
**aws_connect_params)
module = AnsibleAWSModule(argument_spec=argument_spec)
# TODO: Check botocore version
ec2 = module.client('ec2')
copy_image(module, ec2) copy_image(module, ec2)