Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prepare modules ec2_vpc_egress_igw for promotion #2152

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
minor_changes:
- ec2_vpc_egress_igw - Refactor module to use shared code from ``amazon.aws.plugins.module_utils.ec2`` util (https://github.com/ansible-collections/community.aws/pull/2152).
- ec2_vpc_egress_igw - Add the possibility to update/add tags on Egress only internet gateway (https://github.com/ansible-collections/community.aws/pull/2152).
191 changes: 116 additions & 75 deletions plugins/modules/ec2_vpc_egress_igw.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,29 @@
default: present
choices: [ 'present', 'absent' ]
type: str
notes:
- Support for O(tags) and O(purge_tags) was added in release 9.0.0.
extends_documentation_fragment:
- amazon.aws.common.modules
- amazon.aws.region.modules
- amazon.aws.boto3
- amazon.aws.tags.modules
"""

EXAMPLES = r"""
# Note: These examples do not set authentication details, see the AWS Guide for details.

# Ensure that the VPC has an Internet Gateway.
# The Internet Gateway ID is can be accessed via {{eigw.gateway_id}} for use in setting up NATs etc.
- community.aws.ec2_vpc_egress_igw:
- name: Create Egress internet only gateway
community.aws.ec2_vpc_egress_igw:
vpc_id: vpc-abcdefgh
state: present
register: eigw

- name: Delete Egress internet only gateway
community.aws.ec2_vpc_egress_igw:
vpc_id: vpc-abcdefgh
state: absent
"""

RETURN = r"""
Expand All @@ -53,138 +61,171 @@
returned: always
type: str
sample: vpc-012345678
tags:
description: Any tags assigned to the internet gateway.
returned: always
type: dict
"""

try:
import botocore
except ImportError:
pass # caught by AnsibleAWSModule
from typing import Any
from typing import Dict
from typing import Optional
from typing import Union

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict

from ansible_collections.amazon.aws.plugins.module_utils.botocore import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.retries import AWSRetry
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AnsibleEC2Error
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import create_egress_only_internet_gateway
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import delete_egress_only_internet_gateway
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import describe_egress_only_internet_gateways
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ensure_ec2_tags
from ansible_collections.amazon.aws.plugins.module_utils.tagging import boto3_tag_list_to_ansible_dict

from ansible_collections.community.aws.plugins.module_utils.modules import AnsibleCommunityAWSModule as AnsibleAWSModule


def delete_eigw(module, connection, eigw_id):
def delete_eigw(module: AnsibleAWSModule, connection, eigw_id: str) -> Dict[str, Union[str, bool]]:
"""
Delete EIGW.

module : AnsibleAWSModule object
connection : boto3 client connection object
eigw_id : ID of the EIGW to delete
"""
changed = False

try:
response = connection.delete_egress_only_internet_gateway(
aws_retry=True, DryRun=module.check_mode, EgressOnlyInternetGatewayId=eigw_id
vpc_id = module.params.get("vpc_id")

if module.check_mode:
return dict(
changed=True, msg=f"Would have deleted Egress internet only Gateway id '{eigw_id}' if not in check mode."
)
except is_boto3_error_code("DryRunOperation"):
changed = True
except (
botocore.exceptions.ClientError,
botocore.exceptions.BotoCoreError,
) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg=f"Could not delete Egress-Only Internet Gateway {eigw_id} from VPC {module.vpc_id}")

if not module.check_mode:
changed = response.get("ReturnCode", False)
try:
changed = delete_egress_only_internet_gateway(connection, egress_only_internet_gateway_id=eigw_id)
except AnsibleEC2Error as e:
module.fail_json_aws(e)

return changed
return dict(changed=changed)


def create_eigw(module, connection, vpc_id):
def create_eigw(module: AnsibleAWSModule, connection, vpc_id: str) -> Dict[str, Union[str, bool]]:
"""
Create EIGW.

module : AnsibleAWSModule object
connection : boto3 client connection object
vpc_id : ID of the VPC we are operating on
"""

if module.check_mode:
return dict(changed=True, msg="Would have created Egress internet only Gateway if not in check mode.")

gateway_id = None
changed = False

try:
response = connection.create_egress_only_internet_gateway(
aws_retry=True, DryRun=module.check_mode, VpcId=vpc_id
)
except is_boto3_error_code("DryRunOperation"):
# When boto3 method is run with DryRun=True it returns an error on success
# We need to catch the error and return something valid
response = create_egress_only_internet_gateway(connection, vpc_id=vpc_id, tags=module.params.get("tags"))
changed = True
except is_boto3_error_code("InvalidVpcID.NotFound") as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg=f"invalid vpc ID '{vpc_id}' provided")
except (
botocore.exceptions.ClientError,
botocore.exceptions.BotoCoreError,
) as e: # pylint: disable=duplicate-except
module.fail_json_aws(e, msg=f"Could not create Egress-Only Internet Gateway for vpc ID {vpc_id}")

if not module.check_mode:
gateway = response.get("EgressOnlyInternetGateway", {})
state = gateway.get("Attachments", [{}])[0].get("State")
gateway_id = gateway.get("EgressOnlyInternetGatewayId")

if gateway_id and state in ("attached", "attaching"):
changed = True
else:
# EIGW gave back a bad attachment state or an invalid response so we error out
module.fail_json(
msg=f"Unable to create and attach Egress Only Internet Gateway to VPCId: {vpc_id}. Bad or no state in response",
**camel_dict_to_snake_dict(response),
)
except AnsibleEC2Error as e:
module.fail_json_aws(e)

gateway = response.get("EgressOnlyInternetGateway", {})
state = gateway.get("Attachments", [{}])[0].get("State")
gateway_id = gateway.get("EgressOnlyInternetGatewayId")
tags = boto3_tag_list_to_ansible_dict(gateway.get("Tags", []))

if not gateway_id or state not in ("attached", "attaching"):
# EIGW gave back a bad attachment state or an invalid response so we error out
module.fail_json(
msg=f"Unable to create and attach Egress Only Internet Gateway to VPCId: {vpc_id}. Bad or no state in response",
**camel_dict_to_snake_dict(response),
)

return changed, gateway_id
return dict(changed=changed, gateway_id=gateway_id, tags=tags)


def describe_eigws(module, connection, vpc_id):
def find_egress_only_igw(module: AnsibleAWSModule, connection, vpc_id: str) -> Optional[Dict[str, Any]]:
"""
Describe EIGWs.

module : AnsibleAWSModule object
connection : boto3 client connection object
vpc_id : ID of the VPC we are operating on
"""
gateway_id = None
result = None

try:
response = connection.describe_egress_only_internet_gateways(aws_retry=True)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, msg="Could not get list of existing Egress-Only Internet Gateways")
for eigw in describe_egress_only_internet_gateways(connection):
for attachment in eigw.get("Attachments", []):
if attachment.get("VpcId") == vpc_id and attachment.get("State") in ("attached", "attaching"):
return {
"gateway_id": eigw.get("EgressOnlyInternetGatewayId"),
"tags": boto3_tag_list_to_ansible_dict(eigw.get("Tags", [])),
}
except AnsibleEC2Error as e:
module.fail_json_aws(e)

for eigw in response.get("EgressOnlyInternetGateways", []):
for attachment in eigw.get("Attachments", []):
if attachment.get("VpcId") == vpc_id and attachment.get("State") in ("attached", "attaching"):
gateway_id = eigw.get("EgressOnlyInternetGatewayId")
return result

return gateway_id

def ensure_present(connection, module: AnsibleAWSModule, existing: Optional[Dict[str, Any]]) -> None:
vpc_id = module.params.get("vpc_id")
result = dict(vpc_id=vpc_id, changed=False)

if not existing:
result.update(create_eigw(module, connection, vpc_id))
else:
egress_only_igw_id = existing.get("gateway_id")
changed = False
result = existing
tags = module.params.get("tags")
purge_tags = module.params.get("purge_tags")
if tags is not None:
changed = ensure_ec2_tags(
connection,
module,
egress_only_igw_id,
resource_type="egress-only-internet-gateway",
tags=tags,
purge_tags=purge_tags,
)
result.update(dict(changed=changed, vpc_id=vpc_id))

module.exit_json(**result)


def ensure_absent(connection, module: AnsibleAWSModule, existing: Optional[Dict[str, Any]]) -> None:
vpc_id = module.params.get("vpc_id")
if not existing:
module.exit_json(changed=False, msg=f"No Egress only internet gateway attached to the VPC id '{vpc_id}'")

egress_only_igw_id = existing.get("gateway_id")
result = dict(gateway_id=egress_only_igw_id, vpc_id=vpc_id, changed=False)
result.update(delete_eigw(module, connection, egress_only_igw_id))
module.exit_json(**result)


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

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

retry_decorator = AWSRetry.jittered_backoff(retries=10)
connection = module.client("ec2", retry_decorator=retry_decorator)
connection = module.client("ec2")

vpc_id = module.params.get("vpc_id")
state = module.params.get("state")

eigw_id = describe_eigws(module, connection, vpc_id)

result = dict(gateway_id=eigw_id, vpc_id=vpc_id)
changed = False

if state == "present" and not eigw_id:
changed, result["gateway_id"] = create_eigw(module, connection, vpc_id)
elif state == "absent" and eigw_id:
changed = delete_eigw(module, connection, eigw_id)
existing_egress_only_igw = find_egress_only_igw(module, connection, vpc_id)

module.exit_json(changed=changed, **result)
if state == "present":
ensure_present(connection, module, existing_egress_only_igw)
else:
ensure_absent(connection, module, existing_egress_only_igw)


if __name__ == "__main__":
Expand Down
1 change: 0 additions & 1 deletion tests/integration/targets/ec2_vpc_egress_igw/meta/main.yml

This file was deleted.

Loading
Loading