1#!/usr/local/bin/python3.8 2# Copyright: Ansible Project 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5from __future__ import absolute_import, division, print_function 6__metaclass__ = type 7 8 9DOCUMENTATION = ''' 10--- 11module: ec2_vpc_igw 12version_added: 1.0.0 13short_description: Manage an AWS VPC Internet gateway 14description: 15 - Manage an AWS VPC Internet gateway 16author: Robert Estelle (@erydo) 17options: 18 vpc_id: 19 description: 20 - The VPC ID for the VPC in which to manage the Internet Gateway. 21 required: true 22 type: str 23 tags: 24 description: 25 - A dict of tags to apply to the internet gateway. 26 - To remove all tags set I(tags={}) and I(purge_tags=true). 27 aliases: [ 'resource_tags' ] 28 type: dict 29 purge_tags: 30 description: 31 - Remove tags not listed in I(tags). 32 type: bool 33 default: true 34 version_added: 1.3.0 35 state: 36 description: 37 - Create or terminate the IGW 38 default: present 39 choices: [ 'present', 'absent' ] 40 type: str 41extends_documentation_fragment: 42- amazon.aws.aws 43- amazon.aws.ec2 44 45requirements: 46 - botocore 47 - boto3 48''' 49 50EXAMPLES = ''' 51# Note: These examples do not set authentication details, see the AWS Guide for details. 52 53# Ensure that the VPC has an Internet Gateway. 54# The Internet Gateway ID is can be accessed via {{igw.gateway_id}} for use in setting up NATs etc. 55- name: Create Internet gateway 56 community.aws.ec2_vpc_igw: 57 vpc_id: vpc-abcdefgh 58 state: present 59 register: igw 60 61- name: Create Internet gateway with tags 62 community.aws.ec2_vpc_igw: 63 vpc_id: vpc-abcdefgh 64 state: present 65 tags: 66 Tag1: tag1 67 Tag2: tag2 68 register: igw 69 70- name: Delete Internet gateway 71 community.aws.ec2_vpc_igw: 72 state: absent 73 vpc_id: vpc-abcdefgh 74 register: vpc_igw_delete 75''' 76 77RETURN = ''' 78changed: 79 description: If any changes have been made to the Internet Gateway. 80 type: bool 81 returned: always 82 sample: 83 changed: false 84gateway_id: 85 description: The unique identifier for the Internet Gateway. 86 type: str 87 returned: I(state=present) 88 sample: 89 gateway_id: "igw-XXXXXXXX" 90tags: 91 description: The tags associated the Internet Gateway. 92 type: dict 93 returned: I(state=present) 94 sample: 95 tags: 96 "Ansible": "Test" 97vpc_id: 98 description: The VPC ID associated with the Internet Gateway. 99 type: str 100 returned: I(state=present) 101 sample: 102 vpc_id: "vpc-XXXXXXXX" 103''' 104 105try: 106 import botocore 107except ImportError: 108 pass # caught by AnsibleAWSModule 109 110from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule 111from ansible_collections.amazon.aws.plugins.module_utils.waiters import get_waiter 112from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry 113from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list 114from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_tag_list 115from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict 116from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict 117from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags 118 119 120class AnsibleEc2Igw(object): 121 122 def __init__(self, module, results): 123 self._module = module 124 self._results = results 125 self._connection = self._module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) 126 self._check_mode = self._module.check_mode 127 128 def process(self): 129 vpc_id = self._module.params.get('vpc_id') 130 state = self._module.params.get('state', 'present') 131 tags = self._module.params.get('tags') 132 purge_tags = self._module.params.get('purge_tags') 133 134 if state == 'present': 135 self.ensure_igw_present(vpc_id, tags, purge_tags) 136 elif state == 'absent': 137 self.ensure_igw_absent(vpc_id) 138 139 def get_matching_igw(self, vpc_id): 140 filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id}) 141 igws = [] 142 try: 143 response = self._connection.describe_internet_gateways(aws_retry=True, Filters=filters) 144 igws = response.get('InternetGateways', []) 145 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 146 self._module.fail_json_aws(e) 147 148 igw = None 149 if len(igws) > 1: 150 self._module.fail_json( 151 msg='EC2 returned more than one Internet Gateway for VPC {0}, aborting'.format(vpc_id)) 152 elif igws: 153 igw = camel_dict_to_snake_dict(igws[0]) 154 155 return igw 156 157 def ensure_tags(self, igw_id, tags, purge_tags): 158 final_tags = [] 159 160 filters = ansible_dict_to_boto3_filter_list({'resource-id': igw_id, 'resource-type': 'internet-gateway'}) 161 cur_tags = None 162 try: 163 cur_tags = self._connection.describe_tags(aws_retry=True, Filters=filters) 164 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 165 self._module.fail_json_aws(e, msg="Couldn't describe tags") 166 167 if tags is None: 168 return boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')) 169 170 to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags) 171 final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')) 172 173 if to_update: 174 try: 175 if self._check_mode: 176 final_tags.update(to_update) 177 else: 178 self._connection.create_tags( 179 aws_retry=True, 180 Resources=[igw_id], 181 Tags=ansible_dict_to_boto3_tag_list(to_update) 182 ) 183 184 self._results['changed'] = True 185 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 186 self._module.fail_json_aws(e, msg="Couldn't create tags") 187 188 if to_delete: 189 try: 190 if self._check_mode: 191 for key in to_delete: 192 del final_tags[key] 193 else: 194 tags_list = [] 195 for key in to_delete: 196 tags_list.append({'Key': key}) 197 198 self._connection.delete_tags(aws_retry=True, Resources=[igw_id], Tags=tags_list) 199 200 self._results['changed'] = True 201 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 202 self._module.fail_json_aws(e, msg="Couldn't delete tags") 203 204 if not self._check_mode and (to_update or to_delete): 205 try: 206 response = self._connection.describe_tags(aws_retry=True, Filters=filters) 207 final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags')) 208 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 209 self._module.fail_json_aws(e, msg="Couldn't describe tags") 210 211 return final_tags 212 213 @staticmethod 214 def get_igw_info(igw): 215 return { 216 'gateway_id': igw['internet_gateway_id'], 217 'tags': igw['tags'], 218 'vpc_id': igw['vpc_id'] 219 } 220 221 def ensure_igw_absent(self, vpc_id): 222 igw = self.get_matching_igw(vpc_id) 223 if igw is None: 224 return self._results 225 226 if self._check_mode: 227 self._results['changed'] = True 228 return self._results 229 230 try: 231 self._results['changed'] = True 232 self._connection.detach_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) 233 self._connection.delete_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id']) 234 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 235 self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway") 236 237 return self._results 238 239 def ensure_igw_present(self, vpc_id, tags, purge_tags): 240 igw = self.get_matching_igw(vpc_id) 241 242 if igw is None: 243 if self._check_mode: 244 self._results['changed'] = True 245 self._results['gateway_id'] = None 246 return self._results 247 248 try: 249 response = self._connection.create_internet_gateway(aws_retry=True) 250 251 # Ensure the gateway exists before trying to attach it or add tags 252 waiter = get_waiter(self._connection, 'internet_gateway_exists') 253 waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']]) 254 255 igw = camel_dict_to_snake_dict(response['InternetGateway']) 256 self._connection.attach_internet_gateway(aws_retry=True, InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id) 257 self._results['changed'] = True 258 except botocore.exceptions.WaiterError as e: 259 self._module.fail_json_aws(e, msg="No Internet Gateway exists.") 260 except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: 261 self._module.fail_json_aws(e, msg='Unable to create Internet Gateway') 262 263 igw['vpc_id'] = vpc_id 264 265 igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, purge_tags=purge_tags) 266 267 igw_info = self.get_igw_info(igw) 268 self._results.update(igw_info) 269 270 return self._results 271 272 273def main(): 274 argument_spec = dict( 275 vpc_id=dict(required=True), 276 state=dict(default='present', choices=['present', 'absent']), 277 tags=dict(required=False, type='dict', aliases=['resource_tags']), 278 purge_tags=dict(default=True, type='bool'), 279 ) 280 281 module = AnsibleAWSModule( 282 argument_spec=argument_spec, 283 supports_check_mode=True, 284 ) 285 results = dict( 286 changed=False 287 ) 288 igw_manager = AnsibleEc2Igw(module=module, results=results) 289 igw_manager.process() 290 291 module.exit_json(**results) 292 293 294if __name__ == '__main__': 295 main() 296