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:
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue