Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
yugesk authored Oct 31, 2023
2 parents 68e4135 + 35ea344 commit d0417b9
Show file tree
Hide file tree
Showing 4 changed files with 492 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
minor_changes:
- ec2_vpc_igw - Add ability to delete a vpc internet gateway using the id of the gateway (https://github.com/ansible-collections/amazon.aws/pull/1786).
- ec2_vpc_igw - Add ability to create an internet gateway without attaching a VPC (https://github.com/ansible-collections/amazon.aws/pull/1786).
- ec2_vpc_igw - Add ability to attach/detach VPC to/from internet gateway (https://github.com/ansible-collections/amazon.aws/pull/1786).
- ec2_vpc_igw - Add ability to change VPC attached to internet gateway (https://github.com/ansible-collections/amazon.aws/pull/1786).
206 changes: 185 additions & 21 deletions plugins/modules/ec2_vpc_igw.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,39 @@
- Manage an AWS VPC Internet gateway
author: Robert Estelle (@erydo)
options:
internet_gateway_id:
version_added: 7.0.0
description:
- The ID of Internet Gateway to manage.
required: false
type: str
vpc_id:
description:
- The VPC ID for the VPC in which to manage the Internet Gateway.
required: true
- The VPC ID for the VPC to attach (when state=present)
- VPC ID can also be provided to find the internet gateway to manage that the VPC is attached to
required: false
type: str
state:
description:
- Create or terminate the IGW
default: present
choices: [ 'present', 'absent' ]
type: str
force_attach:
version_added: 7.0.0
description:
- Force attaching VPC to I(vpc_id).
- Setting this option to true will detach an existing VPC attachment and attach to the supplied I(vpc_id).
- Ignored when I(state=absent).
- I(vpc_id) must be specified when I(force_attach) is true
default: false
type: bool
detach_vpc:
version_added: 7.0.0
description:
- Remove attached VPC from gateway
default: false
type: bool
notes:
- Support for I(purge_tags) was added in release 1.3.0.
extends_documentation_fragment:
Expand Down Expand Up @@ -53,11 +75,38 @@
Tag2: tag2
register: igw
- name: Delete Internet gateway
- name: Create a detached gateway
amazon.aws.ec2_vpc_igw:
state: present
register: igw
- name: Change the VPC the gateway is attached to
amazon.aws.ec2_vpc_igw:
internet_gateway_id: igw-abcdefgh
vpc_id: vpc-stuvwxyz
force_attach: true
state: present
register: igw
- name: Delete Internet gateway using the attached vpc id
amazon.aws.ec2_vpc_igw:
state: absent
vpc_id: vpc-abcdefgh
register: vpc_igw_delete
- name: Delete Internet gateway with gateway id
amazon.aws.ec2_vpc_igw:
state: absent
internet_gateway_id: igw-abcdefgh
register: vpc_igw_delete
- name: Delete Internet gateway ensuring attached VPC is correct
amazon.aws.ec2_vpc_igw:
state: absent
internet_gateway_id: igw-abcdefgh
vpc_id: vpc-abcdefgh
register: vpc_igw_delete
"""

RETURN = r"""
Expand Down Expand Up @@ -109,6 +158,11 @@ def describe_igws_with_backoff(connection, **params):
return paginator.paginate(**params).build_full_result()["InternetGateways"]


def describe_vpcs_with_backoff(connection, **params):
paginator = connection.get_paginator("describe_vpcs")
return paginator.paginate(**params).build_full_result()["Vpcs"]


class AnsibleEc2Igw:
def __init__(self, module, results):
self._module = module
Expand All @@ -117,15 +171,18 @@ def __init__(self, module, results):
self._check_mode = self._module.check_mode

def process(self):
internet_gateway_id = self._module.params.get("internet_gateway_id")
vpc_id = self._module.params.get("vpc_id")
state = self._module.params.get("state", "present")
tags = self._module.params.get("tags")
purge_tags = self._module.params.get("purge_tags")
force_attach = self._module.params.get("force_attach")
detach_vpc = self._module.params.get("detach_vpc")

if state == "present":
self.ensure_igw_present(vpc_id, tags, purge_tags)
self.ensure_igw_present(internet_gateway_id, vpc_id, tags, purge_tags, force_attach, detach_vpc)
elif state == "absent":
self.ensure_igw_absent(vpc_id)
self.ensure_igw_absent(internet_gateway_id, vpc_id)

def get_matching_igw(self, vpc_id, gateway_id=None):
"""
Expand All @@ -136,11 +193,11 @@ def get_matching_igw(self, vpc_id, gateway_id=None):
Returns:
igw (dict): dict of igw found, None if none found
"""
filters = ansible_dict_to_boto3_filter_list({"attachment.vpc-id": vpc_id})
try:
# If we know the gateway_id, use it to avoid bugs with using filters
# See https://github.com/ansible-collections/amazon.aws/pull/766
if not gateway_id:
filters = ansible_dict_to_boto3_filter_list({"attachment.vpc-id": vpc_id})
igws = describe_igws_with_backoff(self._connection, Filters=filters)
else:
igws = describe_igws_with_backoff(self._connection, InternetGatewayIds=[gateway_id])
Expand All @@ -155,6 +212,30 @@ def get_matching_igw(self, vpc_id, gateway_id=None):

return igw

def get_matching_vpc(self, vpc_id):
"""
Returns the virtual private cloud found.
Parameters:
vpc_id (str): VPC ID
Returns:
vpc (dict): dict of vpc found, None if none found
"""
try:
vpcs = describe_vpcs_with_backoff(self._connection, VpcIds=[vpc_id])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
# self._module.fail_json(msg=f"{str(e)}")
if "InvalidVpcID.NotFound" in str(e):
self._module.fail_json(msg=f"VPC with Id {vpc_id} not found, aborting")
self._module.fail_json_aws(e)

vpc = None
if len(vpcs) > 1:
self._module.fail_json(msg=f"EC2 returned more than one VPC for {vpc_id}, aborting")
elif vpcs:
vpc = camel_dict_to_snake_dict(vpcs[0])

return vpc

@staticmethod
def get_igw_info(igw, vpc_id):
return {
Expand All @@ -163,55 +244,124 @@ def get_igw_info(igw, vpc_id):
"vpc_id": vpc_id,
}

def ensure_igw_absent(self, vpc_id):
igw = self.get_matching_igw(vpc_id)
def detach_vpc(self, igw_id, vpc_id):
try:
self._connection.detach_internet_gateway(aws_retry=True, InternetGatewayId=igw_id, VpcId=vpc_id)

self._results["changed"] = True
except botocore.exceptions.WaiterError as e:
self._module.fail_json_aws(e, msg="Unable to detach VPC.")

def attach_vpc(self, igw_id, vpc_id):
try:
self._connection.attach_internet_gateway(aws_retry=True, InternetGatewayId=igw_id, VpcId=vpc_id)

# Ensure the gateway is attached before proceeding
waiter = get_waiter(self._connection, "internet_gateway_attached")
waiter.wait(InternetGatewayIds=[igw_id])

self._results["changed"] = True
except botocore.exceptions.WaiterError as e:
self._module.fail_json_aws(e, msg="Failed to attach VPC.")

def ensure_igw_absent(self, igw_id, vpc_id):
igw = self.get_matching_igw(vpc_id, gateway_id=igw_id)
if igw is None:
return self._results

igw_vpc_id = ""

if len(igw["attachments"]) > 0:
igw_vpc_id = igw["attachments"][0]["vpc_id"]

if vpc_id and (igw_vpc_id != vpc_id):
self._module.fail_json(msg=f"Supplied VPC ({vpc_id}) does not match found VPC ({igw_vpc_id}), aborting")

if self._check_mode:
self._results["changed"] = True
return self._results

try:
self._results["changed"] = True
self._connection.detach_internet_gateway(
aws_retry=True, InternetGatewayId=igw["internet_gateway_id"], VpcId=vpc_id
)

if igw_vpc_id:
self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)

self._connection.delete_internet_gateway(aws_retry=True, InternetGatewayId=igw["internet_gateway_id"])
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway")

return self._results

def ensure_igw_present(self, vpc_id, tags, purge_tags):
igw = self.get_matching_igw(vpc_id)
def ensure_igw_present(self, igw_id, vpc_id, tags, purge_tags, force_attach, detach_vpc):
igw = None

if igw_id:
igw = self.get_matching_igw(None, gateway_id=igw_id)
elif vpc_id:
igw = self.get_matching_igw(vpc_id)

if igw is None:
if self._check_mode:
self._results["changed"] = True
self._results["gateway_id"] = None
return self._results

if vpc_id:
self.get_matching_vpc(vpc_id)

try:
response = self._connection.create_internet_gateway(aws_retry=True)

# Ensure the gateway exists before trying to attach it or add tags
waiter = get_waiter(self._connection, "internet_gateway_exists")
waiter.wait(InternetGatewayIds=[response["InternetGateway"]["InternetGatewayId"]])
self._results["changed"] = True

igw = camel_dict_to_snake_dict(response["InternetGateway"])
self._connection.attach_internet_gateway(
aws_retry=True, InternetGatewayId=igw["internet_gateway_id"], VpcId=vpc_id
)

# Ensure the gateway is attached before proceeding
waiter = get_waiter(self._connection, "internet_gateway_attached")
waiter.wait(InternetGatewayIds=[igw["internet_gateway_id"]])
self._results["changed"] = True
if vpc_id:
self.attach_vpc(igw["internet_gateway_id"], vpc_id)
except botocore.exceptions.WaiterError as e:
self._module.fail_json_aws(e, msg="No Internet Gateway exists.")
except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
self._module.fail_json_aws(e, msg="Unable to create Internet Gateway")
else:
igw_vpc_id = None

if len(igw["attachments"]) > 0:
igw_vpc_id = igw["attachments"][0]["vpc_id"]

if detach_vpc:
if self._check_mode:
self._results["changed"] = True
self._results["gateway_id"] = igw["internet_gateway_id"]
return self._results

self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)

elif igw_vpc_id != vpc_id:
if self._check_mode:
self._results["changed"] = True
self._results["gateway_id"] = igw["internet_gateway_id"]
return self._results

if force_attach:
self.get_matching_vpc(vpc_id)

self.detach_vpc(igw["internet_gateway_id"], igw_vpc_id)
self.attach_vpc(igw["internet_gateway_id"], vpc_id)
else:
self._module.fail_json(msg="VPC already attached, but does not match requested VPC.")

elif vpc_id:
if self._check_mode:
self._results["changed"] = True
self._results["gateway_id"] = igw["internet_gateway_id"]
return self._results

self.get_matching_vpc(vpc_id)
self.attach_vpc(igw["internet_gateway_id"], vpc_id)

# Modify tags
self._results["changed"] |= ensure_ec2_tags(
Expand All @@ -234,16 +384,30 @@ def ensure_igw_present(self, vpc_id, tags, purge_tags):

def main():
argument_spec = dict(
vpc_id=dict(required=True),
internet_gateway_id=dict(),
vpc_id=dict(),
state=dict(default="present", choices=["present", "absent"]),
tags=dict(required=False, type="dict", aliases=["resource_tags"]),
purge_tags=dict(default=True, type="bool"),
force_attach=dict(default=False, type="bool"),
detach_vpc=dict(default=False, type="bool"),
)

required_if = [
("force_attach", True, ("vpc_id",), False),
("state", "absent", ("internet_gateway_id", "vpc_id"), True),
("detach_vpc", True, ("internet_gateway_id", "vpc_id"), True),
]

mutually_exclusive = [("force_attach", "detach_vpc")]

module = AnsibleAWSModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
mutually_exclusive=mutually_exclusive,
)

results = dict(changed=False)
igw_manager = AnsibleEc2Igw(module=module, results=results)
igw_manager.process()
Expand Down
3 changes: 3 additions & 0 deletions tests/integration/targets/ec2_vpc_igw/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
vpc_name: '{{ resource_prefix }}-vpc'
vpc_seed: '{{ resource_prefix }}'
vpc_cidr: 10.{{ 256 | random(seed=vpc_seed) }}.0.0/16
vpc_name_2: '{{ tiny_prefix }}-vpc-2'
vpc_seed_2: '{{ tiny_prefix }}'
vpc_cidr_2: 10.{{ 256 | random(seed=vpc_seed_2) }}.0.0/16
Loading

0 comments on commit d0417b9

Please sign in to comment.