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