parent
16bbc19674
commit
310eb833a4
3 changed files with 303 additions and 225 deletions
|
@ -25,46 +25,33 @@ module: ec2_ami
|
||||||
version_added: "1.3"
|
version_added: "1.3"
|
||||||
short_description: create or destroy an image in ec2
|
short_description: create or destroy an image in ec2
|
||||||
description:
|
description:
|
||||||
- Creates or deletes ec2 images.
|
- Registers or deregisters ec2 images.
|
||||||
options:
|
options:
|
||||||
instance_id:
|
instance_id:
|
||||||
description:
|
description:
|
||||||
- Instance ID to create the AMI from.
|
- Instance ID to create the AMI from.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
name:
|
name:
|
||||||
description:
|
description:
|
||||||
- The name of the new AMI.
|
- The name of the new AMI.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
architecture:
|
architecture:
|
||||||
version_added: "2.3"
|
version_added: "2.3"
|
||||||
description:
|
description:
|
||||||
- The target architecture of the image to register
|
- The target architecture of the image to register
|
||||||
required: false
|
|
||||||
default: x86_64
|
|
||||||
kernel_id:
|
kernel_id:
|
||||||
version_added: "2.3"
|
version_added: "2.3"
|
||||||
description:
|
description:
|
||||||
- The target kernel id of the image to register
|
- The target kernel id of the image to register.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
virtualization_type:
|
virtualization_type:
|
||||||
version_added: "2.3"
|
version_added: "2.3"
|
||||||
description:
|
description:
|
||||||
- The virtualization type of the image to register
|
- The virtualization type of the image to register.
|
||||||
required: false
|
|
||||||
default: hvm
|
|
||||||
root_device_name:
|
root_device_name:
|
||||||
version_added: "2.3"
|
version_added: "2.3"
|
||||||
description:
|
description:
|
||||||
- The root device name of the image to register
|
- The root device name of the image to register.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
wait:
|
wait:
|
||||||
description:
|
description:
|
||||||
- Wait for the AMI to be in state 'available' before returning.
|
- Wait for the AMI to be in state 'available' before returning.
|
||||||
required: false
|
|
||||||
default: "no"
|
default: "no"
|
||||||
choices: [ "yes", "no" ]
|
choices: [ "yes", "no" ]
|
||||||
wait_timeout:
|
wait_timeout:
|
||||||
|
@ -73,61 +60,67 @@ options:
|
||||||
default: 900
|
default: 900
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Create or deregister/delete AMI.
|
- Register or deregister an AMI.
|
||||||
required: false
|
|
||||||
default: 'present'
|
default: 'present'
|
||||||
choices: [ "absent", "present" ]
|
choices: [ "absent", "present" ]
|
||||||
description:
|
description:
|
||||||
description:
|
description:
|
||||||
- Human-readable string describing the contents and purpose of the AMI.
|
- Human-readable string describing the contents and purpose of the AMI.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
no_reboot:
|
no_reboot:
|
||||||
description:
|
description:
|
||||||
- Flag indicating that the bundling process should not attempt to shutdown the instance before bundling. If this flag is True, the
|
- Flag indicating that the bundling process should not attempt to shutdown the instance before bundling. If this flag is True, the
|
||||||
responsibility of maintaining file system integrity is left to the owner of the instance.
|
responsibility of maintaining file system integrity is left to the owner of the instance.
|
||||||
required: false
|
|
||||||
default: no
|
default: no
|
||||||
choices: [ "yes", "no" ]
|
choices: [ "yes", "no" ]
|
||||||
image_id:
|
image_id:
|
||||||
description:
|
description:
|
||||||
- Image ID to be deregistered.
|
- Image ID to be deregistered.
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
device_mapping:
|
device_mapping:
|
||||||
version_added: "2.0"
|
version_added: "2.0"
|
||||||
description:
|
description:
|
||||||
- List of device hashes/dictionaries with custom configurations (same block-device-mapping parameters)
|
- List of device hashes/dictionaries with custom configurations (same block-device-mapping parameters).
|
||||||
- >
|
- >
|
||||||
Valid properties include: device_name, volume_type, size (in GB), delete_on_termination (boolean), no_device (boolean),
|
Valid properties include: device_name, volume_type, size/volume_size (in GB), delete_on_termination (boolean), no_device (boolean),
|
||||||
snapshot_id, iops (for io1 volume_type)
|
snapshot_id, iops (for io1 volume_type), encrypted
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
delete_snapshot:
|
delete_snapshot:
|
||||||
description:
|
description:
|
||||||
- Delete snapshots when deregistering the AMI.
|
- Delete snapshots when deregistering the AMI.
|
||||||
required: false
|
|
||||||
default: "no"
|
default: "no"
|
||||||
choices: [ "yes", "no" ]
|
choices: [ "yes", "no" ]
|
||||||
tags:
|
tags:
|
||||||
description:
|
description:
|
||||||
- A dictionary of tags to add to the new image; '{"key":"value"}' and '{"key":"value","key":"value"}'
|
- A dictionary of tags to add to the new image; '{"key":"value"}' and '{"key":"value","key":"value"}'
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
version_added: "2.0"
|
version_added: "2.0"
|
||||||
launch_permissions:
|
launch_permissions:
|
||||||
description:
|
description:
|
||||||
- Users and groups that should be able to launch the AMI. Expects
|
- Users and groups that should be able to launch the AMI. Expects dictionary with a key of user_ids and/or group_names. user_ids should
|
||||||
dictionary with a key of user_ids and/or group_names. user_ids should
|
be a list of account ids. group_name should be a list of groups, "all" is the only acceptable value currently.
|
||||||
be a list of account ids. group_name should be a list of groups, "all"
|
|
||||||
is the only acceptable value currently.
|
|
||||||
required: false
|
|
||||||
default: null
|
|
||||||
version_added: "2.0"
|
version_added: "2.0"
|
||||||
|
image_location:
|
||||||
|
description:
|
||||||
|
- The s3 location of an image to use for the AMI.
|
||||||
|
version_added: "2.5"
|
||||||
|
enhanced_networking:
|
||||||
|
description:
|
||||||
|
- A boolean representing whether enhanced networking with ENA is enabled or not.
|
||||||
|
version_added: "2.5"
|
||||||
|
billing_products:
|
||||||
|
description:
|
||||||
|
- A list of valid billing codes. To be used with valid accounts by aws marketplace vendors.
|
||||||
|
version_added: "2.5"
|
||||||
|
ramdisk_id:
|
||||||
|
description:
|
||||||
|
- The ID of the RAM disk.
|
||||||
|
version_added: "2.5"
|
||||||
|
sriov_net_support:
|
||||||
|
description:
|
||||||
|
- Set to simple to enable enhanced networking with the Intel 82599 Virtual Function interface for the AMI and any instances that you launch from the AMI.
|
||||||
|
version_added: "2.5"
|
||||||
author:
|
author:
|
||||||
- "Evan Duffield (@scicoin-project) <eduffield@iacquire.com>"
|
- "Evan Duffield (@scicoin-project) <eduffield@iacquire.com>"
|
||||||
- "Constantin Bugneac (@Constantin07) <constantin.bugneac@endava.com>"
|
- "Constantin Bugneac (@Constantin07) <constantin.bugneac@endava.com>"
|
||||||
- "Ross Williams (@gunzy83) <gunzy83au@gmail.com>"
|
- "Ross Williams (@gunzy83) <gunzy83au@gmail.com>"
|
||||||
|
- "Willem van Ketwich (@wilvk) <willvk@gmail.com>"
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- aws
|
- aws
|
||||||
- ec2
|
- ec2
|
||||||
|
@ -162,7 +155,7 @@ EXAMPLES = '''
|
||||||
root_device_name: /dev/xvda
|
root_device_name: /dev/xvda
|
||||||
device_mapping:
|
device_mapping:
|
||||||
- device_name: /dev/xvda
|
- device_name: /dev/xvda
|
||||||
size: 8
|
volume_size: 8
|
||||||
snapshot_id: snap-xxxxxxxx
|
snapshot_id: snap-xxxxxxxx
|
||||||
delete_on_termination: true
|
delete_on_termination: true
|
||||||
volume_type: gp2
|
volume_type: gp2
|
||||||
|
@ -324,74 +317,72 @@ snapshots_deleted:
|
||||||
|
|
||||||
# import module snippets
|
# import module snippets
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
from ansible.module_utils.ec2 import ec2_connect, ec2_argument_spec
|
from ansible.module_utils.ec2 import ec2_connect, ec2_argument_spec, ansible_dict_to_boto3_tag_list
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import traceback
|
||||||
|
from ansible.module_utils.ec2 import get_aws_connection_info, ec2_argument_spec, ec2_connect, boto3_conn, camel_dict_to_snake_dict, HAS_BOTO3
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import boto
|
import botocore
|
||||||
import boto.ec2
|
|
||||||
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
|
|
||||||
HAS_BOTO = True
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_BOTO = False
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_block_device_mapping(image):
|
def get_block_device_mapping(image):
|
||||||
"""
|
|
||||||
Retrieves block device mapping from AMI
|
|
||||||
"""
|
|
||||||
|
|
||||||
bdm_dict = dict()
|
bdm_dict = dict()
|
||||||
|
if image is not None and image.get('block_device_mappings') is not None:
|
||||||
if image is not None and hasattr(image, 'block_device_mapping'):
|
bdm = image.get('block_device_mappings')
|
||||||
bdm = getattr(image, 'block_device_mapping')
|
for device in bdm:
|
||||||
for device_name in bdm.keys():
|
device_name = device.get('device_name')
|
||||||
bdm_dict[device_name] = {
|
ebs = device.get("ebs")
|
||||||
'size': bdm[device_name].size,
|
bdm_dict_item = {
|
||||||
'snapshot_id': bdm[device_name].snapshot_id,
|
'size': ebs.get("volume_size"),
|
||||||
'volume_type': bdm[device_name].volume_type,
|
'snapshot_id': ebs.get("snapshot_id"),
|
||||||
'encrypted': bdm[device_name].encrypted,
|
'volume_type': ebs.get("volume_type"),
|
||||||
'delete_on_termination': bdm[device_name].delete_on_termination
|
'encrypted': ebs.get("encrypted"),
|
||||||
|
'delete_on_termination': ebs.get("delete_on_termination")
|
||||||
}
|
}
|
||||||
|
bdm_dict[device_name] = bdm_dict_item
|
||||||
return bdm_dict
|
return bdm_dict
|
||||||
|
|
||||||
|
|
||||||
def get_ami_info(image):
|
def get_ami_info(camel_image):
|
||||||
|
image = camel_dict_to_snake_dict(camel_image)
|
||||||
return dict(
|
return dict(
|
||||||
image_id=image.id,
|
image_id=image.get("image_id"),
|
||||||
state=image.state,
|
state=image.get("state"),
|
||||||
architecture=image.architecture,
|
architecture=image.get("architecture"),
|
||||||
block_device_mapping=get_block_device_mapping(image),
|
block_device_mapping=get_block_device_mapping(image),
|
||||||
creationDate=image.creationDate,
|
creationDate=image.get("creation_date"),
|
||||||
description=image.description,
|
description=image.get("description"),
|
||||||
hypervisor=image.hypervisor,
|
hypervisor=image.get("hypervisor"),
|
||||||
is_public=image.is_public,
|
is_public=image.get("public"),
|
||||||
location=image.location,
|
location=image.get("image_location"),
|
||||||
ownerId=image.ownerId,
|
ownerId=image.get("owner_id"),
|
||||||
root_device_name=image.root_device_name,
|
root_device_name=image.get("root_device_name"),
|
||||||
root_device_type=image.root_device_type,
|
root_device_type=image.get("root_device_type"),
|
||||||
tags=image.tags,
|
tags=image.get("tags"),
|
||||||
virtualization_type=image.virtualization_type,
|
virtualization_type=image.get("virtualization_type"),
|
||||||
name=image.name,
|
name=image.get("name"),
|
||||||
platform=image.platform
|
platform=image.get("platform"),
|
||||||
|
enhanced_networking=image.get("ena_support"),
|
||||||
|
image_owner_alias=image.get("image_owner_alias"),
|
||||||
|
image_type=image.get("image_type"),
|
||||||
|
kernel_id=image.get("kernel_id"),
|
||||||
|
product_codes=image.get("product_codes"),
|
||||||
|
ramdisk_id=image.get("ramdisk_id"),
|
||||||
|
sriov_net_support=image.get("sriov_net_support"),
|
||||||
|
state_reason=image.get("state_reason")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_image(module, ec2):
|
def create_image(module, connection, resource):
|
||||||
"""
|
|
||||||
Creates new AMI
|
|
||||||
|
|
||||||
module : AnsibleModule object
|
|
||||||
ec2: authenticated ec2 connection object
|
|
||||||
"""
|
|
||||||
|
|
||||||
instance_id = module.params.get('instance_id')
|
instance_id = module.params.get('instance_id')
|
||||||
name = module.params.get('name')
|
name = module.params.get('name')
|
||||||
wait = module.params.get('wait')
|
wait = module.params.get('wait')
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
wait_timeout = module.params.get('wait_timeout')
|
||||||
description = module.params.get('description')
|
description = module.params.get('description')
|
||||||
architecture = module.params.get('architecture')
|
architecture = module.params.get('architecture')
|
||||||
kernel_id = module.params.get('kernel_id')
|
kernel_id = module.params.get('kernel_id')
|
||||||
|
@ -401,193 +392,264 @@ def create_image(module, ec2):
|
||||||
device_mapping = module.params.get('device_mapping')
|
device_mapping = module.params.get('device_mapping')
|
||||||
tags = module.params.get('tags')
|
tags = module.params.get('tags')
|
||||||
launch_permissions = module.params.get('launch_permissions')
|
launch_permissions = module.params.get('launch_permissions')
|
||||||
|
image_location = module.params.get('image_location')
|
||||||
|
enhanced_networking = module.params.get('enhanced_networking')
|
||||||
|
billing_products = module.params.get('billing_products')
|
||||||
|
ramdisk_id = module.params.get('ramdisk_id')
|
||||||
|
sriov_net_support = module.params.get('sriov_net_support')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
params = {'name': name,
|
params = {
|
||||||
'description': description}
|
'Name': name,
|
||||||
|
'Description': description
|
||||||
|
}
|
||||||
|
|
||||||
images = ec2.get_all_images(filters={'name': name})
|
images = connection.describe_images(
|
||||||
|
Filters=[
|
||||||
|
{
|
||||||
|
'Name': 'name',
|
||||||
|
'Values': [name]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
).get('Images')
|
||||||
|
|
||||||
|
# ensure that launch_permissions are up to date
|
||||||
if images and images[0]:
|
if images and images[0]:
|
||||||
# ensure that launch_permissions are up to date
|
update_image(module, connection, images[0].get('ImageId'), resource)
|
||||||
update_image(module, ec2, images[0].id)
|
|
||||||
|
block_device_mapping = None
|
||||||
|
|
||||||
bdm = None
|
|
||||||
if device_mapping:
|
if device_mapping:
|
||||||
bdm = BlockDeviceMapping()
|
block_device_mapping = []
|
||||||
for device in device_mapping:
|
for device in device_mapping:
|
||||||
|
device['Ebs'] = {}
|
||||||
if 'device_name' not in device:
|
if 'device_name' not in device:
|
||||||
module.fail_json(msg='Device name must be set for volume')
|
module.fail_json(msg="Error - Device name must be set for volume.")
|
||||||
device_name = device['device_name']
|
device = rename_item_if_exists(device, 'device_name', 'DeviceName')
|
||||||
del device['device_name']
|
device = rename_item_if_exists(device, 'virtual_name', 'VirtualName')
|
||||||
bd = BlockDeviceType(**device)
|
device = rename_item_if_exists(device, 'no_device', 'NoDevice')
|
||||||
bdm[device_name] = bd
|
device = rename_item_if_exists(device, 'volume_type', 'VolumeType', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'snapshot_id', 'SnapshotId', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'delete_on_termination', 'DeleteOnTermination', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'size', 'VolumeSize', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'volume_size', 'VolumeSize', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'iops', 'Iops', 'Ebs')
|
||||||
|
device = rename_item_if_exists(device, 'encrypted', 'Encrypted', 'Ebs')
|
||||||
|
block_device_mapping.append(device)
|
||||||
|
if block_device_mapping:
|
||||||
|
params['BlockDeviceMappings'] = block_device_mapping
|
||||||
if instance_id:
|
if instance_id:
|
||||||
params['instance_id'] = instance_id
|
params['InstanceId'] = instance_id
|
||||||
params['no_reboot'] = no_reboot
|
params['NoReboot'] = no_reboot
|
||||||
if bdm:
|
image_id = connection.create_image(**params).get('ImageId')
|
||||||
params['block_device_mapping'] = bdm
|
|
||||||
image_id = ec2.create_image(**params)
|
|
||||||
else:
|
else:
|
||||||
params['architecture'] = architecture
|
if architecture:
|
||||||
params['virtualization_type'] = virtualization_type
|
params['Architecture'] = architecture
|
||||||
|
if virtualization_type:
|
||||||
|
params['VirtualizationType'] = virtualization_type
|
||||||
|
if image_location:
|
||||||
|
params['ImageLocation'] = image_location
|
||||||
|
if enhanced_networking:
|
||||||
|
params['EnaSupport'] = enhanced_networking
|
||||||
|
if billing_products:
|
||||||
|
params['BillingProducts'] = billing_products
|
||||||
|
if ramdisk_id:
|
||||||
|
params['RamdiskId'] = ramdisk_id
|
||||||
|
if sriov_net_support:
|
||||||
|
params['SriovNetSupport'] = sriov_net_support
|
||||||
if kernel_id:
|
if kernel_id:
|
||||||
params['kernel_id'] = kernel_id
|
params['KernelId'] = kernel_id
|
||||||
if root_device_name:
|
if root_device_name:
|
||||||
params['root_device_name'] = root_device_name
|
params['RootDeviceName'] = root_device_name
|
||||||
if bdm:
|
image_id = connection.register_image(**params).get('ImageId')
|
||||||
params['block_device_map'] = bdm
|
except botocore.exceptions.ClientError as e:
|
||||||
image_id = ec2.register_image(**params)
|
module.fail_json(msg="Error registering image - " + str(e), exception=traceback.format_exc(),
|
||||||
except boto.exception.BotoServerError as e:
|
**camel_dict_to_snake_dict(e.response))
|
||||||
module.fail_json(msg="%s: %s" % (e.error_code, e.error_message))
|
|
||||||
|
|
||||||
# Wait until the image is recognized. EC2 API has eventual consistency,
|
|
||||||
# such that a successful CreateImage API call doesn't guarantee the success
|
|
||||||
# of subsequent DescribeImages API call using the new image id returned.
|
|
||||||
for i in range(wait_timeout):
|
for i in range(wait_timeout):
|
||||||
try:
|
try:
|
||||||
img = ec2.get_image(image_id)
|
image = get_image_by_id(module, connection, image_id)
|
||||||
|
if image.get('State') == 'available':
|
||||||
if img.state == 'available':
|
|
||||||
break
|
break
|
||||||
elif img.state == 'failed':
|
elif image.get('State') == 'failed':
|
||||||
module.fail_json(msg="AMI creation failed, please see the AWS console for more details")
|
module.fail_json(msg="AMI creation failed, please see the AWS console for more details.")
|
||||||
except boto.exception.EC2ResponseError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
if ('InvalidAMIID.NotFound' not in e.error_code and 'InvalidAMIID.Unavailable' not in e.error_code) and wait and i == wait_timeout - 1:
|
if ('InvalidAMIID.NotFound' not in e.error_code and 'InvalidAMIID.Unavailable' not in e.error_code) and wait and i == wait_timeout - 1:
|
||||||
module.fail_json(msg="Error while trying to find the new image. Using wait=yes and/or a longer "
|
module.fail_json(msg="Error while trying to find the new image. Using wait=yes and/or a longer wait_timeout may help. %s: %s"
|
||||||
"wait_timeout may help. %s: %s" % (e.error_code, e.error_message))
|
% (e.error_code, e.error_message))
|
||||||
finally:
|
finally:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
if img.state != 'available':
|
|
||||||
module.fail_json(msg="Error while trying to find the new image. Using wait=yes and/or a longer wait_timeout may help.")
|
|
||||||
|
|
||||||
if tags:
|
if tags:
|
||||||
try:
|
try:
|
||||||
ec2.create_tags(image_id, tags)
|
connection.create_tags(Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags))
|
||||||
except boto.exception.EC2ResponseError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="Image tagging failed => %s: %s" % (e.error_code, e.error_message))
|
module.fail_json(msg="Error tagging image - " + str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
if launch_permissions:
|
if launch_permissions:
|
||||||
try:
|
try:
|
||||||
img = ec2.get_image(image_id)
|
image = get_image_by_id(module, connection, image_id)
|
||||||
img.set_launch_permissions(**launch_permissions)
|
image.set_launch_permissions(**launch_permissions)
|
||||||
except boto.exception.BotoServerError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="%s: %s" % (e.error_code, e.error_message), image_id=image_id)
|
module.fail_json(msg="Error setting launch permissions for image: " + image_id + " - " + str(e), exception=traceback.format_exc(),
|
||||||
|
**camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
module.exit_json(msg="AMI creation operation complete", changed=True, **get_ami_info(img))
|
module.exit_json(msg="AMI creation operation complete.", changed=True, **get_ami_info(image))
|
||||||
|
|
||||||
|
|
||||||
def deregister_image(module, ec2):
|
def deregister_image(module, connection):
|
||||||
"""
|
|
||||||
Deregisters AMI
|
|
||||||
"""
|
|
||||||
|
|
||||||
image_id = module.params.get('image_id')
|
image_id = module.params.get('image_id')
|
||||||
delete_snapshot = module.params.get('delete_snapshot')
|
delete_snapshot = module.params.get('delete_snapshot')
|
||||||
wait = module.params.get('wait')
|
wait = module.params.get('wait')
|
||||||
wait_timeout = int(module.params.get('wait_timeout'))
|
wait_timeout = module.params.get('wait_timeout')
|
||||||
|
image = get_image_by_id(module, connection, image_id)
|
||||||
|
|
||||||
img = ec2.get_image(image_id)
|
if image is None:
|
||||||
if img is None:
|
module.fail_json(msg="Image %s does not exist." % image_id, changed=False)
|
||||||
module.fail_json(msg="Image %s does not exist" % image_id, changed=False)
|
|
||||||
|
|
||||||
# Get all associated snapshot ids before deregistering image otherwise this information becomes unavailable
|
# Get all associated snapshot ids before deregistering image otherwise this information becomes unavailable.
|
||||||
snapshots = []
|
snapshots = []
|
||||||
if hasattr(img, 'block_device_mapping'):
|
if 'BlockDeviceMappings' in image:
|
||||||
for key in img.block_device_mapping:
|
for mapping in image.get('BlockDeviceMappings'):
|
||||||
snapshots.append(img.block_device_mapping[key].snapshot_id)
|
snapshot_id = mapping.get('SnapshotId')
|
||||||
|
if snapshot_id is not None:
|
||||||
|
snapshots.append(snapshot_id)
|
||||||
|
|
||||||
# When trying to re-delete already deleted image it doesn't raise an exception
|
# When trying to re-deregister an already deregistered image it doesn't raise an exception, it just returns an object without image attributes.
|
||||||
# It just returns an object without image attributes
|
if 'ImageId' in image:
|
||||||
if hasattr(img, 'id'):
|
|
||||||
try:
|
try:
|
||||||
params = {'image_id': image_id,
|
res = connection.deregister_image(ImageId=image_id)
|
||||||
'delete_snapshot': delete_snapshot}
|
except botocore.exceptions.ClientError as e:
|
||||||
ec2.deregister_image(**params)
|
module.fail_json(msg="Error deregistering image - " + str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
except boto.exception.BotoServerError as e:
|
|
||||||
module.fail_json(msg="%s: %s" % (e.error_code, e.error_message))
|
|
||||||
else:
|
else:
|
||||||
module.exit_json(msg="Image %s has already been deleted" % image_id, changed=False)
|
module.exit_json(msg="Image %s has already been deregistered." % image_id, changed=False)
|
||||||
|
|
||||||
# wait here until the image is gone
|
image = get_image_by_id(module, connection, image_id)
|
||||||
img = ec2.get_image(image_id)
|
|
||||||
wait_timeout = time.time() + wait_timeout
|
wait_timeout = time.time() + wait_timeout
|
||||||
while wait and wait_timeout > time.time() and img is not None:
|
|
||||||
img = ec2.get_image(image_id)
|
|
||||||
time.sleep(3)
|
|
||||||
if wait and wait_timeout <= time.time():
|
|
||||||
# waiting took too long
|
|
||||||
module.fail_json(msg="timed out waiting for image to be deregistered/deleted")
|
|
||||||
|
|
||||||
# Boto library has hardcoded the deletion of the snapshot for the root volume mounted as '/dev/sda1' only
|
while wait and wait_timeout > time.time() and image is not None:
|
||||||
# Make it possible to delete all snapshots which belong to image, including root block device mapped as '/dev/xvda'
|
image = get_image_by_id(module, connection, image_id)
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
if wait and wait_timeout <= time.time():
|
||||||
|
module.fail_json(msg="Timed out waiting for image to be deregistered.")
|
||||||
|
|
||||||
|
exit_params = {'msg': "AMI deregister operation complete.", 'changed': True}
|
||||||
|
|
||||||
if delete_snapshot:
|
if delete_snapshot:
|
||||||
try:
|
try:
|
||||||
for snapshot_id in snapshots:
|
for snapshot_id in snapshots:
|
||||||
ec2.delete_snapshot(snapshot_id)
|
connection.delete_snapshot(SnapshotId=snapshot_id)
|
||||||
except boto.exception.BotoServerError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
|
# Don't error out if root volume snapshot was already deregistered as part of deregister_image
|
||||||
if e.error_code == 'InvalidSnapshot.NotFound':
|
if e.error_code == 'InvalidSnapshot.NotFound':
|
||||||
# Don't error out if root volume snapshot was already deleted as part of deregister_image
|
|
||||||
pass
|
pass
|
||||||
module.exit_json(msg="AMI deregister/delete operation complete", changed=True, snapshots_deleted=snapshots)
|
exit_params['snapshots_deleted'] = snapshots
|
||||||
else:
|
|
||||||
module.exit_json(msg="AMI deregister/delete operation complete", changed=True)
|
module.exit_json(**exit_params)
|
||||||
|
|
||||||
|
|
||||||
def update_image(module, ec2, image_id):
|
def update_image(module, connection, image_id, resource):
|
||||||
"""
|
|
||||||
Updates AMI
|
|
||||||
"""
|
|
||||||
|
|
||||||
launch_permissions = module.params.get('launch_permissions') or []
|
launch_permissions = module.params.get('launch_permissions') or []
|
||||||
|
|
||||||
if 'user_ids' in launch_permissions:
|
if 'user_ids' in launch_permissions:
|
||||||
launch_permissions['user_ids'] = [str(user_id) for user_id in launch_permissions['user_ids']]
|
launch_permissions['user_ids'] = [str(user_id) for user_id in launch_permissions['user_ids']]
|
||||||
|
|
||||||
img = ec2.get_image(image_id)
|
image = resource.Image(image_id)
|
||||||
if img is None:
|
|
||||||
|
if image is None:
|
||||||
module.fail_json(msg="Image %s does not exist" % image_id, changed=False)
|
module.fail_json(msg="Image %s does not exist" % image_id, changed=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
set_permissions = img.get_launch_permissions()
|
set_permissions = connection.describe_image_attribute(Attribute='launchPermission', ImageId=image_id).get('LaunchPermissions')
|
||||||
if set_permissions != launch_permissions:
|
if set_permissions != launch_permissions:
|
||||||
if (('user_ids' in launch_permissions and launch_permissions['user_ids']) or
|
if ('user_ids' in launch_permissions or 'group_names' in launch_permissions):
|
||||||
('group_names' in launch_permissions and launch_permissions['group_names'])):
|
group_names = launch_permissions.get('group_names')[0] if launch_permissions.get('group_names') else None
|
||||||
img.set_launch_permissions(**launch_permissions)
|
user_ids = launch_permissions.get('user_ids')[0] if launch_permissions.get('user_ids') else None
|
||||||
elif ('user_ids' in set_permissions and set_permissions['user_ids']) or ('group_names' in set_permissions and set_permissions['group_names']):
|
launch_perms_add = {'Add': [{}]}
|
||||||
img.remove_launch_permissions(**set_permissions)
|
if group_names:
|
||||||
|
launch_perms_add['Add'][0]['Group'] = group_names
|
||||||
|
if user_ids:
|
||||||
|
launch_perms_add['Add'][0]['UserId'] = user_ids
|
||||||
|
image.modify_attribute(Attribute='launchPermission', LaunchPermission=launch_perms_add)
|
||||||
|
elif set_permissions and set_permissions[0].get('UserId') is not None and set_permissions[0].get('Group') is not None:
|
||||||
|
image.modify_attribute(
|
||||||
|
Attribute='launchPermission',
|
||||||
|
LaunchPermission={
|
||||||
|
'Remove': [{
|
||||||
|
'Group': (set_permissions.get('Group') or ''),
|
||||||
|
'UserId': (set_permissions.get('UserId') or '')
|
||||||
|
}]
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
module.exit_json(msg="AMI not updated", launch_permissions=set_permissions, changed=False, **get_ami_info(img))
|
module.exit_json(msg="AMI not updated.", launch_permissions=set_permissions, changed=False,
|
||||||
module.exit_json(msg="AMI launch permissions updated", launch_permissions=launch_permissions,
|
**get_ami_info(get_image_by_id(module, connection, image_id)))
|
||||||
set_perms=set_permissions, changed=True, **get_ami_info(img))
|
module.exit_json(msg="AMI launch permissions updated.", launch_permissions=launch_permissions, set_perms=set_permissions, changed=True,
|
||||||
|
**get_ami_info(get_image_by_id(module, connection, image_id)))
|
||||||
else:
|
else:
|
||||||
module.exit_json(msg="AMI not updated", launch_permissions=set_permissions, changed=False, **get_ami_info(img))
|
module.exit_json(msg="AMI not updated.", launch_permissions=set_permissions, changed=False,
|
||||||
|
**get_ami_info(get_image_by_id(module, connection, image_id)))
|
||||||
|
except botocore.exceptions.ClientError as e:
|
||||||
|
module.fail_json(msg="Error updating image - " + str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
except boto.exception.BotoServerError as e:
|
|
||||||
module.fail_json(msg="%s: %s" % (e.error_code, e.error_message))
|
def get_image_by_id(module, connection, image_id):
|
||||||
|
try:
|
||||||
|
images_response = connection.describe_images(ImageIds=[image_id])
|
||||||
|
images = images_response.get('Images')
|
||||||
|
no_images = len(images)
|
||||||
|
if no_images == 0:
|
||||||
|
return None
|
||||||
|
if no_images == 1:
|
||||||
|
return images[0]
|
||||||
|
module.fail_json(msg="Invalid number of instances (%s) found for image_id: %s." % (str(len(images)), image_id))
|
||||||
|
except botocore.exceptions.ClientError as e:
|
||||||
|
module.fail_json(msg="Error retreiving image by image_id - " + str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
|
|
||||||
|
def rename_item_if_exists(dict_object, attribute, new_attribute, child_node=None):
|
||||||
|
new_item = dict_object.get(attribute)
|
||||||
|
if new_item is not None:
|
||||||
|
if child_node is None:
|
||||||
|
dict_object[new_attribute] = dict_object.get(attribute)
|
||||||
|
else:
|
||||||
|
dict_object[child_node][new_attribute] = dict_object.get(attribute)
|
||||||
|
dict_object.pop(attribute)
|
||||||
|
return dict_object
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
argument_spec = ec2_argument_spec()
|
argument_spec = ec2_argument_spec()
|
||||||
argument_spec.update(
|
argument_spec.update(dict(
|
||||||
dict(
|
instance_id=dict(),
|
||||||
instance_id=dict(),
|
image_id=dict(),
|
||||||
image_id=dict(),
|
architecture=dict(default='x86_64'),
|
||||||
architecture=dict(default="x86_64"),
|
kernel_id=dict(),
|
||||||
kernel_id=dict(),
|
virtualization_type=dict(default='hvm'),
|
||||||
virtualization_type=dict(default="hvm"),
|
root_device_name=dict(),
|
||||||
root_device_name=dict(),
|
delete_snapshot=dict(default=False, type='bool'),
|
||||||
delete_snapshot=dict(default=False, type='bool'),
|
name=dict(),
|
||||||
name=dict(),
|
wait=dict(type='bool', default=False),
|
||||||
wait=dict(type='bool', default=False),
|
wait_timeout=dict(default=900, type='int'),
|
||||||
wait_timeout=dict(default=900),
|
description=dict(default=''),
|
||||||
description=dict(default=""),
|
no_reboot=dict(default=False, type='bool'),
|
||||||
no_reboot=dict(default=False, type='bool'),
|
state=dict(default='present'),
|
||||||
state=dict(default='present'),
|
device_mapping=dict(type='list'),
|
||||||
device_mapping=dict(type='list'),
|
tags=dict(type='dict'),
|
||||||
tags=dict(type='dict'),
|
launch_permissions=dict(type='dict'),
|
||||||
launch_permissions=dict(type='dict')
|
image_location=dict(),
|
||||||
)
|
enhanced_networking=dict(type='bool'),
|
||||||
|
billing_products=dict(type='list'),
|
||||||
|
ramdisk_id=dict(),
|
||||||
|
sriov_net_support=dict()
|
||||||
|
))
|
||||||
|
|
||||||
|
module = AnsibleModule(
|
||||||
|
argument_spec=argument_spec,
|
||||||
|
required_if=[
|
||||||
|
['state', 'absent', ['image_id']],
|
||||||
|
['state', 'present', ['name']],
|
||||||
|
]
|
||||||
)
|
)
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=argument_spec,
|
argument_spec=argument_spec,
|
||||||
|
@ -595,26 +657,24 @@ def main():
|
||||||
('state', 'absent', ('image_id',))]
|
('state', 'absent', ('image_id',))]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not HAS_BOTO:
|
if not HAS_BOTO3:
|
||||||
module.fail_json(msg='boto required for this module')
|
module.fail_json(msg='boto3 required for this module')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ec2 = ec2_connect(module)
|
region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True)
|
||||||
except Exception as e:
|
connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_kwargs)
|
||||||
module.fail_json(msg="Error while connecting to aws: %s" % str(e))
|
resource = boto3_conn(module, conn_type='resource', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_kwargs)
|
||||||
|
except botocore.exceptions.NoRegionError:
|
||||||
|
module.fail_json(msg=("Region must be specified as a parameter in AWS_DEFAULT_REGION environment variable or in boto configuration file."))
|
||||||
|
|
||||||
if module.params.get('state') == 'absent':
|
if module.params.get('state') == 'absent':
|
||||||
deregister_image(module, ec2)
|
deregister_image(module, connection)
|
||||||
|
|
||||||
elif module.params.get('state') == 'present':
|
elif module.params.get('state') == 'present':
|
||||||
if module.params.get('image_id') and module.params.get('launch_permissions'):
|
if module.params.get('image_id') and module.params.get('launch_permissions'):
|
||||||
# Update image's launch permissions
|
update_image(module, connection, module.params.get('image_id'), resource)
|
||||||
update_image(module, ec2, module.params.get('image_id'))
|
|
||||||
|
|
||||||
# Changed is always set to true when provisioning new AMI
|
|
||||||
if not module.params.get('instance_id') and not module.params.get('device_mapping'):
|
if not module.params.get('instance_id') and not module.params.get('device_mapping'):
|
||||||
module.fail_json(msg='instance_id or device_mapping (register from ebs snapshot) parameter is required for new image')
|
module.fail_json(msg="The parameters instance_id or device_mapping (register from EBS snapshot) are required for a new image.")
|
||||||
create_image(module, ec2)
|
create_image(module, connection, resource)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
key_name: '{{ setup_key.key.name }}'
|
key_name: '{{ setup_key.key.name }}'
|
||||||
instance_type: t2.micro
|
instance_type: t2.micro
|
||||||
state: present
|
state: present
|
||||||
image: '{{ ec2_ami_image[ec2_region] }}'
|
image: '{{ ec2_region_images[ec2_region] }}'
|
||||||
wait: yes
|
wait: yes
|
||||||
instance_tags:
|
instance_tags:
|
||||||
'{{ec2_ami_name}}_instance_setup': 'integration_tests'
|
'{{ec2_ami_name}}_instance_setup': 'integration_tests'
|
||||||
|
|
|
@ -1,2 +1,20 @@
|
||||||
---
|
---
|
||||||
# vars file for test_ec2_ami
|
# vars file for test_ec2_ami
|
||||||
|
|
||||||
|
# based on Amazon Linux AMI 2017.09.0 (HVM), SSD Volume Type
|
||||||
|
ec2_region_images:
|
||||||
|
us-east-1: ami-8c1be5f6
|
||||||
|
us-east-2: ami-c5062ba0
|
||||||
|
us-west-1: ami-02eada62
|
||||||
|
us-west-2: ami-e689729e
|
||||||
|
ca-central-1: ami-fd55ec99
|
||||||
|
eu-west-1: ami-acd005d5
|
||||||
|
eu-central-1: ami-c7ee5ca8
|
||||||
|
eu-west-2: ami-1a7f6d7e
|
||||||
|
ap-southeast-1: ami-0797ea64
|
||||||
|
ap-southeast-2: ami-8536d6e7
|
||||||
|
ap-northeast-2: ami-9bec36f5
|
||||||
|
ap-northeast-1: ami-2a69be4c
|
||||||
|
ap-south-1: ami-4fc58420
|
||||||
|
sa-east-1: ami-f1344b9d
|
||||||
|
cn-north-1: ami-fba67596
|
||||||
|
|
Loading…
Reference in a new issue