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:
parent
dbb4493f17
commit
3955e528b1
1 changed files with 40 additions and 26 deletions
|
@ -65,6 +65,12 @@ options:
|
|||
tags:
|
||||
description:
|
||||
- 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>"
|
||||
extends_documentation_fragment:
|
||||
- aws
|
||||
|
@ -97,7 +103,7 @@ EXAMPLES = '''
|
|||
name: My-Awesome-AMI
|
||||
description: latest patch
|
||||
|
||||
# Tagged AMI copy
|
||||
# Tagged AMI copy (will not copy the same AMI twice)
|
||||
- ec2_ami_copy:
|
||||
source_region: us-east-1
|
||||
region: eu-west-1
|
||||
|
@ -105,6 +111,7 @@ EXAMPLES = '''
|
|||
tags:
|
||||
Name: My-Super-AMI
|
||||
Patch: 1.2.3
|
||||
tag_equality: yes
|
||||
|
||||
# Encrypted AMI copy
|
||||
- ec2_ami_copy:
|
||||
|
@ -130,13 +137,12 @@ image_id:
|
|||
sample: ami-e689729e
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ec2 import (boto3_conn, ec2_argument_spec, get_aws_connection_info)
|
||||
|
||||
import traceback
|
||||
from ansible.module_utils.aws.core import AnsibleAWSModule
|
||||
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
|
||||
|
||||
try:
|
||||
from botocore.exceptions import ClientError, NoCredentialsError, WaiterError
|
||||
from botocore.exceptions import ClientError, NoCredentialsError, WaiterError, BotoCoreError
|
||||
HAS_BOTO3 = True
|
||||
except ImportError:
|
||||
HAS_BOTO3 = False
|
||||
|
@ -150,6 +156,10 @@ def copy_image(module, ec2):
|
|||
ec2: ec2 connection object
|
||||
"""
|
||||
|
||||
image = None
|
||||
changed = False
|
||||
tags = module.params.get('tags')
|
||||
|
||||
params = {'SourceRegion': module.params.get('source_region'),
|
||||
'SourceImageId': module.params.get('source_image_id'),
|
||||
'Name': module.params.get('name'),
|
||||
|
@ -160,7 +170,20 @@ def copy_image(module, ec2):
|
|||
params['KmsKeyId'] = module.params.get('kms_key_id')
|
||||
|
||||
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'):
|
||||
delay = 15
|
||||
max_attempts = module.params.get('wait_timeout') // delay
|
||||
|
@ -168,19 +191,12 @@ def copy_image(module, ec2):
|
|||
ImageIds=[image_id],
|
||||
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)
|
||||
except WaiterError as we:
|
||||
module.fail_json(msg='An error occurred waiting for the image to become available. (%s)' % str(we), exception=traceback.format_exc())
|
||||
except ClientError as ce:
|
||||
module.fail_json(msg=ce.message)
|
||||
except NoCredentialsError:
|
||||
module.fail_json(msg='Unable to authenticate, AWS credentials are invalid.')
|
||||
module.exit_json(changed=changed, **camel_dict_to_snake_dict(image))
|
||||
except WaiterError as e:
|
||||
module.fail_json_aws(e, msg='An error occurred waiting for the image to become available')
|
||||
except (ClientError, BotoCoreError) as e:
|
||||
module.fail_json_aws(e, msg="Could not copy AMI")
|
||||
except Exception as e:
|
||||
module.fail_json(msg='Unhandled exception. (%s)' % str(e))
|
||||
|
||||
|
@ -196,14 +212,12 @@ def main():
|
|||
kms_key_id=dict(type='str', required=False),
|
||||
wait=dict(type='bool', default=False),
|
||||
wait_timeout=dict(type='int', default=600),
|
||||
tags=dict(type='dict')))
|
||||
|
||||
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)
|
||||
tags=dict(type='dict')),
|
||||
tag_equality=dict(type='bool', default=False))
|
||||
|
||||
module = AnsibleAWSModule(argument_spec=argument_spec)
|
||||
# TODO: Check botocore version
|
||||
ec2 = module.client('ec2')
|
||||
copy_image(module, ec2)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue