Skip to content

Commit

Permalink
ec2_eni: Add check_mode support to ec2_eni (#534)
Browse files Browse the repository at this point in the history
ec2_eni: Add check_mode support to ec2_eni

SUMMARY

Add check_mode support to ec2_eni.

ISSUE TYPE


Feature Pull Request

COMPONENT NAME

ec2_eni

Reviewed-by: Mark Chappell <None>
Reviewed-by: None <None>
  • Loading branch information
mandar242 authored Oct 15, 2021
1 parent 67839a3 commit 6a0194b
Show file tree
Hide file tree
Showing 9 changed files with 379 additions and 69 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/534-ec2_eni_add_check_mode_support.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- ec2_eni - add check mode support (https://github.com/ansible-collections/amazon.aws/pull/534).
168 changes: 99 additions & 69 deletions plugins/modules/ec2_eni.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@
'''

import time
from ipaddress import ip_address
from ipaddress import ip_network

try:
import botocore.exceptions
Expand All @@ -302,6 +304,7 @@
from ..module_utils.core import AnsibleAWSModule
from ..module_utils.core import is_boto3_error_code
from ..module_utils.ec2 import AWSRetry
from ..module_utils.ec2 import ansible_dict_to_boto3_tag_list
from ..module_utils.ec2 import get_ec2_security_group_ids_from_names
from ..module_utils.tagging import boto3_tag_list_to_ansible_dict
from ..module_utils.tagging import boto3_tag_specifications
Expand Down Expand Up @@ -441,6 +444,14 @@ def create_eni(connection, vpc_id, module):
if tags:
args["TagSpecifications"] = boto3_tag_specifications(tags, types='network-interface')

# check if provided private_ip_address is within the subnet's address range
cidr_block = connection.describe_subnets(SubnetIds=[str(subnet_id)])['Subnets'][0]['CidrBlock']
valid_private_ip = ip_address(private_ip_address) in ip_network(cidr_block)
if not valid_private_ip:
module.fail_json(changed=False, msg="Error: cannot create ENI - Address does not fall within the subnet's address range.")
if module.check_mode:
module.exit_json(changed=True, msg="Would have created ENI if not in check mode.")

eni_dict = connection.create_network_interface(aws_retry=True, **args)
eni = eni_dict["NetworkInterface"]
# Once we have an ID make sure we're always modifying the same object
Expand Down Expand Up @@ -521,43 +532,47 @@ def modify_eni(connection, module, eni):
try:
if description is not None:
if "Description" not in eni or eni["Description"] != description:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Description={'Value': description}
)
if not module.check_mode:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Description={'Value': description}
)
changed = True
if len(security_groups) > 0:
groups = get_ec2_security_group_ids_from_names(security_groups, connection, vpc_id=eni["VpcId"], boto3=True)
if sorted(get_sec_group_list(eni["Groups"])) != sorted(groups):
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Groups=groups
)
if not module.check_mode:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Groups=groups
)
changed = True
if source_dest_check is not None:
if "SourceDestCheck" not in eni or eni["SourceDestCheck"] != source_dest_check:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
SourceDestCheck={'Value': source_dest_check}
)
if not module.check_mode:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
SourceDestCheck={'Value': source_dest_check}
)
changed = True
if delete_on_termination is not None and "Attachment" in eni:
if eni["Attachment"]["DeleteOnTermination"] is not delete_on_termination:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Attachment={'AttachmentId': eni["Attachment"]["AttachmentId"],
'DeleteOnTermination': delete_on_termination}
)
if not module.check_mode:
connection.modify_network_interface_attribute(
aws_retry=True,
NetworkInterfaceId=eni_id,
Attachment={'AttachmentId': eni["Attachment"]["AttachmentId"],
'DeleteOnTermination': delete_on_termination}
)
if delete_on_termination:
waiter = "network_interface_delete_on_terminate"
else:
waiter = "network_interface_no_delete_on_terminate"
get_waiter(connection, waiter).wait(NetworkInterfaceIds=[eni_id])
changed = True
if delete_on_termination:
waiter = "network_interface_delete_on_terminate"
else:
waiter = "network_interface_no_delete_on_terminate"
get_waiter(connection, waiter).wait(NetworkInterfaceIds=[eni_id])

current_secondary_addresses = []
if "PrivateIpAddresses" in eni:
Expand All @@ -566,65 +581,71 @@ def modify_eni(connection, module, eni):
if secondary_private_ip_addresses is not None:
secondary_addresses_to_remove = list(set(current_secondary_addresses) - set(secondary_private_ip_addresses))
if secondary_addresses_to_remove and purge_secondary_private_ip_addresses:
connection.unassign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=list(set(current_secondary_addresses) - set(secondary_private_ip_addresses)),
)
wait_for(absent_ips, connection, secondary_addresses_to_remove, module, eni_id)
if not module.check_mode:
connection.unassign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=list(set(current_secondary_addresses) - set(secondary_private_ip_addresses)),
)
wait_for(absent_ips, connection, secondary_addresses_to_remove, module, eni_id)
changed = True
secondary_addresses_to_add = list(set(secondary_private_ip_addresses) - set(current_secondary_addresses))
if secondary_addresses_to_add:
connection.assign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=secondary_addresses_to_add,
AllowReassignment=allow_reassignment
)
wait_for(correct_ips, connection, secondary_addresses_to_add, module, eni_id)
if not module.check_mode:
connection.assign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=secondary_addresses_to_add,
AllowReassignment=allow_reassignment
)
wait_for(correct_ips, connection, secondary_addresses_to_add, module, eni_id)
changed = True

if secondary_private_ip_address_count is not None:
current_secondary_address_count = len(current_secondary_addresses)
if secondary_private_ip_address_count > current_secondary_address_count:
connection.assign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
SecondaryPrivateIpAddressCount=(secondary_private_ip_address_count - current_secondary_address_count),
AllowReassignment=allow_reassignment
)
wait_for(correct_ip_count, connection, secondary_private_ip_address_count, module, eni_id)
if not module.check_mode:
connection.assign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
SecondaryPrivateIpAddressCount=(secondary_private_ip_address_count - current_secondary_address_count),
AllowReassignment=allow_reassignment
)
wait_for(correct_ip_count, connection, secondary_private_ip_address_count, module, eni_id)
changed = True
elif secondary_private_ip_address_count < current_secondary_address_count:
# How many of these addresses do we want to remove
secondary_addresses_to_remove_count = current_secondary_address_count - secondary_private_ip_address_count
connection.unassign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=current_secondary_addresses[:secondary_addresses_to_remove_count]
)
wait_for(correct_ip_count, connection, secondary_private_ip_address_count, module, eni_id)
if not module.check_mode:
secondary_addresses_to_remove_count = current_secondary_address_count - secondary_private_ip_address_count
connection.unassign_private_ip_addresses(
aws_retry=True,
NetworkInterfaceId=eni_id,
PrivateIpAddresses=current_secondary_addresses[:secondary_addresses_to_remove_count]
)
wait_for(correct_ip_count, connection, secondary_private_ip_address_count, module, eni_id)
changed = True

if attached is True:
if "Attachment" in eni and eni["Attachment"]["InstanceId"] != instance_id:
detach_eni(connection, eni, module)
connection.attach_network_interface(
aws_retry=True,
InstanceId=instance_id,
DeviceIndex=device_index,
NetworkInterfaceId=eni_id,
)
get_waiter(connection, 'network_interface_attached').wait(NetworkInterfaceIds=[eni_id])
if not module.check_mode:
detach_eni(connection, eni, module)
connection.attach_network_interface(
aws_retry=True,
InstanceId=instance_id,
DeviceIndex=device_index,
NetworkInterfaceId=eni_id,
)
get_waiter(connection, 'network_interface_attached').wait(NetworkInterfaceIds=[eni_id])
changed = True
if "Attachment" not in eni:
connection.attach_network_interface(
aws_retry=True,
InstanceId=instance_id,
DeviceIndex=device_index,
NetworkInterfaceId=eni_id,
)
get_waiter(connection, 'network_interface_attached').wait(NetworkInterfaceIds=[eni_id])
if not module.check_mode:
connection.attach_network_interface(
aws_retry=True,
InstanceId=instance_id,
DeviceIndex=device_index,
NetworkInterfaceId=eni_id,
)
get_waiter(connection, 'network_interface_attached').wait(NetworkInterfaceIds=[eni_id])
changed = True

elif attached is False:
Expand All @@ -637,6 +658,8 @@ def modify_eni(connection, module, eni):
module.fail_json_aws(e, "Failed to modify eni {0}".format(eni_id))

eni = describe_eni(connection, module, eni_id)
if module.check_mode and changed:
module.exit_json(changed=changed, msg="Would have modified ENI: {0} if not in check mode".format(eni['NetworkInterfaceId']))
module.exit_json(changed=changed, interface=get_eni_info(eni))


Expand All @@ -656,6 +679,9 @@ def delete_eni(connection, module):
if not eni:
module.exit_json(changed=False)

if module.check_mode:
module.exit_json(changed=True, msg="Would have deleted ENI if not in check mode.")

eni_id = eni["NetworkInterfaceId"]
force_detach = module.params.get("force_detach")

Expand Down Expand Up @@ -683,6 +709,9 @@ def delete_eni(connection, module):

def detach_eni(connection, eni, module):

if module.check_mode:
module.exit_json(changed=True, msg="Would have detached ENI if not in check mode.")

attached = module.params.get("attached")
eni_id = eni["NetworkInterfaceId"]

Expand Down Expand Up @@ -836,7 +865,8 @@ def main():
required_if=([
('attached', True, ['instance_id']),
('purge_secondary_private_ip_addresses', True, ['secondary_private_ip_addresses'])
])
]),
supports_check_mode=True,
)

retry_decorator = AWSRetry.jittered_backoff(
Expand Down
58 changes: 58 additions & 0 deletions tests/integration/targets/ec2_eni/tasks/test_attachment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@
- "{{ instance_id_2 }}"
wait: True

- name: attach the network interface to instance 1 (check mode)
ec2_eni:
instance_id: "{{ instance_id_1 }}"
device_index: 1
private_ip_address: "{{ ip_1 }}"
subnet_id: "{{ vpc_subnet_result.subnet.id }}"
state: present
attached: True
check_mode: true
register: result_check_mode

- assert:
that:
- result_check_mode.changed

- name: attach the network interface to instance 1
ec2_eni:
instance_id: "{{ instance_id_1 }}"
Expand Down Expand Up @@ -84,6 +99,21 @@
_interface_0: '{{ eni_info.network_interfaces[0] }}'

# ============================================================
- name: test attaching the network interface to a different instance (check mode)
ec2_eni:
instance_id: "{{ instance_id_2 }}"
device_index: 1
private_ip_address: "{{ ip_1 }}"
subnet_id: "{{ vpc_subnet_result.subnet.id }}"
state: present
attached: True
check_mode: true
register: result_check_mode

- assert:
that:
- result_check_mode.changed

- name: test attaching the network interface to a different instance
ec2_eni:
instance_id: "{{ instance_id_2 }}"
Expand All @@ -109,6 +139,21 @@
_interface_0: '{{ eni_info.network_interfaces[0] }}'

# ============================================================
- name: detach the network interface (check mode)
ec2_eni:
instance_id: "{{ instance_id_2 }}"
device_index: 1
private_ip_address: "{{ ip_1 }}"
subnet_id: "{{ vpc_subnet_result.subnet.id }}"
state: present
attached: False
check_mode: true
register: result_check_mode

- assert:
that:
- result_check_mode.changed

- name: detach the network interface
ec2_eni:
instance_id: "{{ instance_id_2 }}"
Expand Down Expand Up @@ -182,6 +227,19 @@
- "{{ instance_id_2 }}"
wait: True

- name: delete an attached network interface with force_detach (check mode)
ec2_eni:
force_detach: True
eni_id: "{{ eni_id_1 }}"
state: absent
check_mode: true
register: result_check_mode
ignore_errors: True

- assert:
that:
- result_check_mode.changed

- name: delete an attached network interface with force_detach
ec2_eni:
force_detach: True
Expand Down
26 changes: 26 additions & 0 deletions tests/integration/targets/ec2_eni/tasks/test_deletion.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
---
# ============================================================
- name: test deleting the unattached network interface by using the ID (check mode)
ec2_eni:
eni_id: "{{ eni_id_1 }}"
name: "{{ resource_prefix }}"
subnet_id: "{{ vpc_subnet_id }}"
state: absent
check_mode: True
register: result_check_mode

- assert:
that:
- result_check_mode.changed

- name: test deleting the unattached network interface by using the ID
ec2_eni:
eni_id: "{{ eni_id_1 }}"
Expand All @@ -18,6 +31,19 @@
- '"network_interfaces" in eni_info'
- eni_id_1 not in ( eni_info.network_interfaces | selectattr('id') | map(attribute='id') | list )

- name: test removing the network interface by ID is idempotent (check mode)
ec2_eni:
eni_id: "{{ eni_id_1 }}"
name: "{{ resource_prefix }}"
subnet_id: "{{ vpc_subnet_id }}"
state: absent
check_mode: True
register: result_check_mode

- assert:
that:
- not result_check_mode.changed

- name: test removing the network interface by ID is idempotent
ec2_eni:
eni_id: "{{ eni_id_1 }}"
Expand Down
Loading

0 comments on commit 6a0194b

Please sign in to comment.