1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3# This file is part of Ansible 4# 5# Ansible is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# Ansible is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 17 18ANSIBLE_METADATA = {'metadata_version': '1.1', 19 'status': ['preview'], 20 'supported_by': 'community'} 21 22 23DOCUMENTATION = ''' 24--- 25module: ec2_ami_copy 26short_description: copies AMI between AWS regions, return new image id 27description: 28 - Copies AMI from a source region to a destination region. B(Since version 2.3 this module depends on boto3.) 29version_added: "2.0" 30options: 31 source_region: 32 description: 33 - The source region the AMI should be copied from. 34 required: true 35 source_image_id: 36 description: 37 - The ID of the AMI in source region that should be copied. 38 required: true 39 name: 40 description: 41 - The name of the new AMI to copy. (As of 2.3 the default is 'default', in prior versions it was 'null'.) 42 default: "default" 43 description: 44 description: 45 - An optional human-readable string describing the contents and purpose of the new AMI. 46 encrypted: 47 description: 48 - Whether or not the destination snapshots of the copied AMI should be encrypted. 49 version_added: "2.2" 50 type: bool 51 kms_key_id: 52 description: 53 - KMS key id used to encrypt image. If not specified, uses default EBS Customer Master Key (CMK) for your account. 54 version_added: "2.2" 55 wait: 56 description: 57 - Wait for the copied AMI to be in state 'available' before returning. 58 type: bool 59 default: 'no' 60 wait_timeout: 61 description: 62 - How long before wait gives up, in seconds. Prior to 2.3 the default was 1200. 63 - From 2.3-2.5 this option was deprecated in favor of boto3 waiter defaults. 64 This was reenabled in 2.6 to allow timeouts greater than 10 minutes. 65 default: 600 66 tags: 67 description: 68 - A hash/dictionary of tags to add to the new copied AMI; '{"key":"value"}' and '{"key":"value","key":"value"}' 69 tag_equality: 70 description: 71 - Whether to use tags if the source AMI already exists in the target region. If this is set, and all tags match 72 in an existing AMI, the AMI will not be copied again. 73 default: false 74 type: bool 75 version_added: 2.6 76author: 77- Amir Moulavi (@amir343) <amir.moulavi@gmail.com> 78- Tim C (@defunctio) <defunct@defunct.io> 79extends_documentation_fragment: 80 - aws 81 - ec2 82requirements: 83 - boto3 84''' 85 86EXAMPLES = ''' 87# Basic AMI Copy 88- ec2_ami_copy: 89 source_region: us-east-1 90 region: eu-west-1 91 source_image_id: ami-xxxxxxx 92 93# AMI copy wait until available 94- ec2_ami_copy: 95 source_region: us-east-1 96 region: eu-west-1 97 source_image_id: ami-xxxxxxx 98 wait: yes 99 wait_timeout: 1200 # Default timeout is 600 100 register: image_id 101 102# Named AMI copy 103- ec2_ami_copy: 104 source_region: us-east-1 105 region: eu-west-1 106 source_image_id: ami-xxxxxxx 107 name: My-Awesome-AMI 108 description: latest patch 109 110# Tagged AMI copy (will not copy the same AMI twice) 111- ec2_ami_copy: 112 source_region: us-east-1 113 region: eu-west-1 114 source_image_id: ami-xxxxxxx 115 tags: 116 Name: My-Super-AMI 117 Patch: 1.2.3 118 tag_equality: yes 119 120# Encrypted AMI copy 121- ec2_ami_copy: 122 source_region: us-east-1 123 region: eu-west-1 124 source_image_id: ami-xxxxxxx 125 encrypted: yes 126 127# Encrypted AMI copy with specified key 128- ec2_ami_copy: 129 source_region: us-east-1 130 region: eu-west-1 131 source_image_id: ami-xxxxxxx 132 encrypted: yes 133 kms_key_id: arn:aws:kms:us-east-1:XXXXXXXXXXXX:key/746de6ea-50a4-4bcb-8fbc-e3b29f2d367b 134''' 135 136RETURN = ''' 137image_id: 138 description: AMI ID of the copied AMI 139 returned: always 140 type: str 141 sample: ami-e689729e 142''' 143 144from ansible.module_utils.aws.core import AnsibleAWSModule 145from ansible.module_utils.ec2 import ec2_argument_spec 146from ansible.module_utils.ec2 import camel_dict_to_snake_dict, ansible_dict_to_boto3_tag_list 147from ansible.module_utils._text import to_native 148 149try: 150 from botocore.exceptions import ClientError, NoCredentialsError, WaiterError, BotoCoreError 151 HAS_BOTO3 = True 152except ImportError: 153 HAS_BOTO3 = False 154 155 156def copy_image(module, ec2): 157 """ 158 Copies an AMI 159 160 module : AnsibleModule object 161 ec2: ec2 connection object 162 """ 163 164 image = None 165 changed = False 166 tags = module.params.get('tags') 167 168 params = {'SourceRegion': module.params.get('source_region'), 169 'SourceImageId': module.params.get('source_image_id'), 170 'Name': module.params.get('name'), 171 'Description': module.params.get('description'), 172 'Encrypted': module.params.get('encrypted'), 173 } 174 if module.params.get('kms_key_id'): 175 params['KmsKeyId'] = module.params.get('kms_key_id') 176 177 try: 178 if module.params.get('tag_equality'): 179 filters = [{'Name': 'tag:%s' % k, 'Values': [v]} for (k, v) in module.params.get('tags').items()] 180 filters.append(dict(Name='state', Values=['available', 'pending'])) 181 images = ec2.describe_images(Filters=filters) 182 if len(images['Images']) > 0: 183 image = images['Images'][0] 184 if not image: 185 image = ec2.copy_image(**params) 186 image_id = image['ImageId'] 187 if tags: 188 ec2.create_tags(Resources=[image_id], 189 Tags=ansible_dict_to_boto3_tag_list(tags)) 190 changed = True 191 192 if module.params.get('wait'): 193 delay = 15 194 max_attempts = module.params.get('wait_timeout') // delay 195 image_id = image.get('ImageId') 196 ec2.get_waiter('image_available').wait( 197 ImageIds=[image_id], 198 WaiterConfig={'Delay': delay, 'MaxAttempts': max_attempts} 199 ) 200 201 module.exit_json(changed=changed, **camel_dict_to_snake_dict(image)) 202 except WaiterError as e: 203 module.fail_json_aws(e, msg='An error occurred waiting for the image to become available') 204 except (ClientError, BotoCoreError) as e: 205 module.fail_json_aws(e, msg="Could not copy AMI") 206 except Exception as e: 207 module.fail_json(msg='Unhandled exception. (%s)' % to_native(e)) 208 209 210def main(): 211 argument_spec = ec2_argument_spec() 212 argument_spec.update(dict( 213 source_region=dict(required=True), 214 source_image_id=dict(required=True), 215 name=dict(default='default'), 216 description=dict(default=''), 217 encrypted=dict(type='bool', default=False, required=False), 218 kms_key_id=dict(type='str', required=False), 219 wait=dict(type='bool', default=False), 220 wait_timeout=dict(type='int', default=600), 221 tags=dict(type='dict')), 222 tag_equality=dict(type='bool', default=False)) 223 224 module = AnsibleAWSModule(argument_spec=argument_spec) 225 # TODO: Check botocore version 226 ec2 = module.client('ec2') 227 copy_image(module, ec2) 228 229 230if __name__ == '__main__': 231 main() 232