Skip to content

Commit

Permalink
Add new module for Azure NAT gateway (#860)
Browse files Browse the repository at this point in the history
Gateway association with subnets to be handled in the subnet instance.
Add relevant integration tests
  • Loading branch information
andreadecorte authored Jun 20, 2022
1 parent 2fa49f0 commit 7b1611d
Show file tree
Hide file tree
Showing 8 changed files with 1,049 additions and 3 deletions.
414 changes: 414 additions & 0 deletions plugins/modules/azure_rm_natgateway.py

Large diffs are not rendered by default.

219 changes: 219 additions & 0 deletions plugins/modules/azure_rm_natgateway_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/python
#
# Copyright (c) 2022 Andrea Decorte, <[email protected]>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''
---
module: azure_rm_natgateway_info
version_added: "1.13.0"
short_description: Retrieve NAT Gateway instance facts
description:
- Get facts for a NAT Gateway instance.
options:
name:
description:
- Only show results for a specific NAT gateway.
type: str
resource_group:
description:
- Limit results by resource group.
type: str
extends_documentation_fragment:
- azure.azcollection.azure
author:
- Andrea Decorte (@andreadecorte)
'''

EXAMPLES = '''
- name: Get facts for NAT gateway by name.
azure_rm_natgateway_info:
name: Mynatgw
resource_group: MyResourceGroup
- name: Get facts for all NAT gateways in resource group.
azure_rm_natgateway_info:
resource_group: MyResourceGroup
- name: Get facts for all NAT gateways.
azure_rm_natgateway_info:
'''

RETURN = '''
gateways:
description:
- A list of dictionaries containing facts for a NAT gateway.
returned: always
type: list
elements: dict
contains:
id:
description:
- NAT gateway resource ID.
returned: always
type: str
sample: /subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/natGateways/mynatgw
name:
description:
- Name of NAT gateway.
returned: always
type: str
sample: mynatgw
resource_group:
description:
- Name of resource group.
returned: always
type: str
sample: myResourceGroup
location:
description:
- Location of NAT gateway.
returned: always
type: str
sample: centralus
idle_timeout_in_minutes:
description:
- The idle timeout of the NAT gateway.
returned: always
type: int
sample: 4
sku:
description:
- SKU of the NAT gateway.
returned: always
type: dict
contains:
name:
description:
- The name of the SKU.
returned: always
type: str
sample: Standard
zones:
description:
- Availability Zones of the NAT gateway.
returned: always
type: list
elements: str
public_ip_addresses:
description:
- List of ids of public IP addresses associated to the NAT Gateway.
returned: always
type: list
elements: str
'''

from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase

try:
from azure.core.exceptions import ResourceNotFoundError
from msrestazure.tools import parse_resource_id
except ImportError:
# This is handled in azure_rm_common
pass


class AzureRMNATGatewayInfo(AzureRMModuleBase):

def __init__(self):
self.module_arg_spec = dict(
name=dict(type='str'),
resource_group=dict(type='str'),
)

self.results = dict(
changed=False,
)

self.name = None
self.resource_group = None

super(AzureRMNATGatewayInfo, self).__init__(self.module_arg_spec,
supports_check_mode=True,
supports_tags=False,
facts_module=True)

def exec_module(self, **kwargs):
for key in self.module_arg_spec:
setattr(self, key, kwargs[key])

if self.name is not None:
self.results["gateways"] = self.get()
elif self.resource_group is not None:
self.results["gateways"] = self.list_by_rg()
else:
self.results["gateways"] = self.list_all()

return self.results

def get(self):
response = None
results = []
try:
response = self.network_client.nat_gateways.get(resource_group_name=self.resource_group, nat_gateway_name=self.name)
except ResourceNotFoundError:
pass

if response is not None:
results.append(self.format_response(response))

return results

def list_by_rg(self):
response = None
results = []
try:
response = self.network_client.nat_gateways.list(resource_group_name=self.resource_group)
except Exception as exc:
request_id = exc.request_id if exc.request_id else ''
self.fail("Error listing NAT gateways in resource groups {0}: {1} - {2}".format(self.resource_group, request_id, str(exc)))

for item in response:
results.append(self.format_response(item))

return results

def list_all(self):
response = None
results = []
try:
response = self.network_client.nat_gateways.list_all()
except Exception as exc:
request_id = exc.request_id if exc.request_id else ''
self.fail("Error listing all NAT gateways: {0} - {1}".format(request_id, str(exc)))

for item in response:
results.append(self.format_response(item))

return results

def format_response(self, natgw):
d = natgw.as_dict()
id = d.get("id")
id_dict = parse_resource_id(id)
d = {
"id": id,
"name": d.get("name"),
"resource_group": id_dict.get("resource_group", self.resource_group),
"location": d.get("location"),
"sku": d.get("sku"),
"zones": d.get("zones"),
"idle_timeout_in_minutes": d.get("idle_timeout_in_minutes"),
"public_ip_addresses": d.get("public_ip_addresses")
}
return d


def main():
AzureRMNATGatewayInfo()


if __name__ == "__main__":
main()
57 changes: 55 additions & 2 deletions plugins/modules/azure_rm_subnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@
description:
- A list of actions.
type: list
nat_gateway:
description:
- Existing NAT Gateway with which to associate the subnet.
- It can be the NAT Gateway name which is in the same resource group.
- Can be the resource ID of the NAT Gateway.
- Can be a dict containing the I(name) and I(resource_group) of the NAT Gateway.
type: str
extends_documentation_fragment:
- azure.azcollection.azure
Expand Down Expand Up @@ -193,6 +200,14 @@
- name: 'mydeleg'
serviceName: 'Microsoft.ContainerInstance/containerGroups'
- name: Create a subnet with an associated NAT Gateway
azure_rm_subnet:
resource_group: myResourceGroup
virtual_network_name: myVirtualNetwork
name: mySubnet
address_prefix_cidr: "10.1.0.0/16"
nat_gateway: myNatGateway
- name: Delete a subnet
azure_rm_subnet:
resource_group: myResourceGroup
Expand Down Expand Up @@ -298,6 +313,7 @@

try:
from azure.core.exceptions import ResourceNotFoundError
from msrestazure.tools import is_valid_resource_id
except ImportError:
# This is handled in azure_rm_common
pass
Expand Down Expand Up @@ -339,7 +355,8 @@ def subnet_to_dict(subnet):
network_security_group=dict(),
route_table=dict(),
private_endpoint_network_policies=subnet.private_endpoint_network_policies,
private_link_service_network_policies=subnet.private_link_service_network_policies
private_link_service_network_policies=subnet.private_link_service_network_policies,
nat_gateway=None
)
if subnet.network_security_group:
id_keys = azure_id_to_dict(subnet.network_security_group.id)
Expand All @@ -355,6 +372,8 @@ def subnet_to_dict(subnet):
result['service_endpoints'] = [{'service': item.service, 'locations': item.locations or []} for item in subnet.service_endpoints]
if subnet.delegations:
result['delegations'] = [{'name': item.name, 'serviceName': item.service_name, 'actions': item.actions or []} for item in subnet.delegations]
if subnet.nat_gateway:
result['nat_gateway'] = subnet.nat_gateway.id
return result


Expand Down Expand Up @@ -388,7 +407,8 @@ def __init__(self):
type='list',
elements='dict',
options=delegations_spec
)
),
nat_gateway=dict(type='str')
)

mutually_exclusive = [['address_prefix_cidr', 'address_prefixes_cidr']]
Expand All @@ -410,6 +430,7 @@ def __init__(self):
self.private_link_service_network_policies = None
self.private_endpoint_network_policies = None
self.delegations = None
self.nat_gateway = None

super(AzureRMSubnet, self).__init__(self.module_arg_spec,
supports_check_mode=True,
Expand All @@ -433,6 +454,8 @@ def exec_module(self, **kwargs):
if self.security_group:
nsg = self.parse_nsg()

nat_gateway = self.build_nat_gateway_id(self.nat_gateway)

route_table = dict()
if self.route_table:
route_table = self.parse_resource_to_dict(self.route_table)
Expand Down Expand Up @@ -528,6 +551,17 @@ def exec_module(self, **kwargs):
changed = True
results['delegations'] = self.delegations

if nat_gateway is not None:
if nat_gateway != results['nat_gateway']:
changed = True
# Update associated NAT Gateway
results['nat_gateway'] = nat_gateway
else:
if results['nat_gateway'] is not None:
changed = True
# Disassociate NAT Gateway
results['nat_gateway'] = None

elif self.state == 'absent':
changed = True
except ResourceNotFoundError:
Expand Down Expand Up @@ -562,6 +596,8 @@ def exec_module(self, **kwargs):
subnet.private_link_service_network_policies = self.private_link_service_network_policies
if self.delegations:
subnet.delegations = self.delegations
if nat_gateway:
subnet.nat_gateway = self.network_models.SubResource(id=nat_gateway)
else:
# update subnet
self.log('Updating subnet {0}'.format(self.name))
Expand All @@ -582,6 +618,8 @@ def exec_module(self, **kwargs):
subnet.private_endpoint_network_policies = results['private_endpoint_network_policies']
if results.get('delegations') is not None:
subnet.delegations = results['delegations']
if results.get('nat_gateway') is not None:
subnet.nat_gateway = self.network_models.SubResource(id=results['nat_gateway'])

self.results['state'] = self.create_or_update_subnet(subnet)
elif self.state == 'absent' and changed:
Expand Down Expand Up @@ -631,6 +669,21 @@ def parse_nsg(self):
name = azure_id_to_dict(id).get('name')
return dict(id=id, name=name)

def build_nat_gateway_id(self, resource):
"""
Common method to build a resource id from different inputs
"""
if resource is None:
return None
if is_valid_resource_id(resource):
return resource
resource_dict = self.parse_resource_to_dict(resource)
return format_resource_id(val=resource_dict['name'],
subscription_id=resource_dict.get('subscription_id'),
namespace='Microsoft.Network',
types='natGateways',
resource_group=resource_dict.get('resource_group'))


def main():
AzureRMSubnet()
Expand Down
9 changes: 8 additions & 1 deletion plugins/modules/azure_rm_subnet_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@
returned: always
type: str
sample: Succeeded
nat_gateway:
description:
- ID of the associated NAT Gateway.
returned: when available
type: str
sample: "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myResourceGroup/providers/Microsoft.Network/natGateways/myGw"
'''

from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
Expand Down Expand Up @@ -279,7 +285,8 @@ def format_response(self, item):
'service_endpoints': d.get('service_endpoints'),
'private_endpoint_network_policies': d.get('private_endpoint_network_policies'),
'private_link_service_network_policies': d.get('private_link_service_network_policies'),
'delegations': d.get('delegations')
'delegations': d.get('delegations'),
'nat_gateway': d.get('nat_gateway', {}).get('id')
}
return d

Expand Down
1 change: 1 addition & 0 deletions pr-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ parameters:
- "azure_rm_monitordiagnosticsetting"
- "azure_rm_monitorlogprofile"
- "azure_rm_mysqlserver"
- "azure_rm_natgateway"
- "azure_rm_networkinterface"
- "azure_rm_notificationhub"
- "azure_rm_openshiftmanagedcluster"
Expand Down
4 changes: 4 additions & 0 deletions tests/integration/targets/azure_rm_natgateway/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cloud/azure
destructive
shippable/azure/group6
disabled
2 changes: 2 additions & 0 deletions tests/integration/targets/azure_rm_natgateway/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dependencies:
- setup_azure
Loading

0 comments on commit 7b1611d

Please sign in to comment.