Add route table and route module (#41175)

* add route module

* add test

* add table

* add  route table

* fix dict

* fix

* fix

* route  table accept no name

* add default

* fix

* fix

* fix

* fix pep

* support route table

* fix line ending

* fix pep

* fix

* fix

* set the default to 'None'

* make return value only id

* camel and snake

* set test alias

* change facts line ending

* change test

* fix

* add default

* fix

* fix line break

* remove unsafe args

* fix comment

* fix
This commit is contained in:
Yuwei Zhou 2018-08-23 06:24:57 +08:00 committed by Matt Davis
parent 6da6652e6b
commit fff5fb2077
8 changed files with 853 additions and 2 deletions

View file

@ -0,0 +1,219 @@
#!/usr/bin/python
#
# Copyright (c) 2018 Yuwei Zhou, <yuwzho@microsoft.com>
#
# 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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_route
version_added: "2.7"
short_description: Manage Azure route resource.
description:
- Create, update or delete a route.
options:
resource_group:
description:
- name of resource group.
required: true
name:
description:
- name of the route.
required: true
state:
description:
- Assert the state of the route. Use 'present' to create or update and
'absent' to delete.
default: present
choices:
- absent
- present
address_prefix:
description:
- The destination CIDR to which the route applies.
next_hop_type:
description:
- The type of Azure hop the packet should be sent to.
choices:
- virtual_network_gateway
- vnet_local
- internet
- virtual_appliance
- none
default: 'none'
next_hop_ip_address:
description:
- The IP address packets should be forwarded to.
- Next hop values are only allowed in routes where the next hop type is VirtualAppliance.
route_table_name:
description:
- The name of the route table.
required: true
extends_documentation_fragment:
- azure
- azure_tags
author:
- "Yuwei Zhou (@yuwzho)"
'''
EXAMPLES = '''
- name: Create a route
azure_rm_route:
name: foobar
resource_group: Testing
address_prefix: 10.1.0.0/16
next_hop_type: virtual_network_gateway
route_table_name: table
- name: Delete a route
azure_rm_route:
name: foobar
resource_group: Testing
route_table_name: table
state: absent
'''
RETURN = '''
id:
description: Current state of the route.
returned: success
type: str
'''
try:
from msrestazure.azure_exceptions import CloudError
except ImportError:
# This is handled in azure_rm_common
pass
from ansible.module_utils.azure_rm_common import AzureRMModuleBase
from ansible.module_utils.common.dict_transformations import _snake_to_camel
class AzureRMRoute(AzureRMModuleBase):
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
address_prefix=dict(type='str'),
next_hop_type=dict(type='str',
choices=['virtual_network_gateway',
'vnet_local',
'internet',
'virtual_appliance',
'none'],
default='none'),
next_hop_ip_address=dict(type='str'),
route_table_name=dict(type='str', required=True)
)
required_if = [
('state', 'present', ['next_hop_type'])
]
self.resource_group = None
self.name = None
self.state = None
self.address_prefix = None
self.next_hop_type = None
self.next_hop_ip_address = None
self.route_table_name = None
self.results = dict(
changed=False,
id=None
)
super(AzureRMRoute, self).__init__(self.module_arg_spec,
required_if=required_if,
supports_check_mode=True)
def exec_module(self, **kwargs):
for key in list(self.module_arg_spec.keys()):
setattr(self, key, kwargs[key])
result = dict()
changed = False
self.next_hop_type = _snake_to_camel(self.next_hop_type, capitalize_first=True)
result = self.get_route()
if self.state == 'absent' and result:
changed = True
if not self.check_mode:
self.delete_route()
elif self.state == 'present':
if not result:
changed = True # create new route
else: # check update
if result.next_hop_type != self.next_hop_type:
self.log('Update: {0} next_hop_type from {1} to {2}'.format(self.name, result.next_hop_type, self.next_hop_type))
changed = True
if result.next_hop_ip_address != self.next_hop_ip_address:
self.log('Update: {0} next_hop_ip_address from {1} to {2}'.format(self.name, result.next_hop_ip_address, self.next_hop_ip_address))
changed = True
if result.address_prefix != self.address_prefix:
self.log('Update: {0} address_prefix from {1} to {2}'.format(self.name, result.address_prefix, self.address_prefix))
changed = True
if changed:
result = self.network_models.Route(name=self.name,
address_prefix=self.address_prefix,
next_hop_type=self.next_hop_type,
next_hop_ip_address=self.next_hop_ip_address)
if not self.check_mode:
result = self.create_or_update_route(result)
self.results['id'] = result.id if result else None
self.results['changed'] = changed
return self.results
def create_or_update_route(self, param):
try:
poller = self.network_client.routes.create_or_update(self.resource_group, self.route_table_name, self.name, param)
return self.get_poller_result(poller)
except Exception as exc:
self.fail("Error creating or updating route {0} - {1}".format(self.name, str(exc)))
def delete_route(self):
try:
poller = self.network_client.routes.delete(self.resource_group, self.route_table_name, self.name)
result = self.get_poller_result(poller)
return result
except Exception as exc:
self.fail("Error deleting route {0} - {1}".format(self.name, str(exc)))
def get_route(self):
try:
return self.network_client.routes.get(self.resource_group, self.route_table_name, self.name)
except CloudError as cloud_err:
# Return None iff the resource is not found
if cloud_err.status_code == 404:
self.log('{0}'.format(str(cloud_err)))
return None
self.fail('Error: failed to get resource {0} - {1}'.format(self.name, str(cloud_err)))
except Exception as exc:
self.fail('Error: failed to get resource {0} - {1}'.format(self.name, str(exc)))
def main():
AzureRMRoute()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,200 @@
#!/usr/bin/python
#
# Copyright (c) 2018 Yuwei Zhou, <yuwzho@microsoft.com>
#
# 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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_routetable
version_added: "2.7"
short_description: Manage Azure route table resource.
description:
- Create, update or delete a route table.
options:
resource_group:
description:
- name of resource group.
required: true
name:
description:
- name of the route table.
required: true
state:
description:
- Assert the state of the route table. Use 'present' to create or update and
'absent' to delete.
default: present
choices:
- absent
- present
disable_bgp_route_propagation:
description:
- Specified whether to disable the routes learned by BGP on that route table.
type: bool
default: False
location:
description:
- Region of the resource.
- Derived from C(resource_group) if not specified
extends_documentation_fragment:
- azure
- azure_tags
author:
- "Yuwei Zhou (@yuwzho)"
'''
EXAMPLES = '''
- name: Create a route table
azure_rm_routetable:
name: foobar
resource_group: Testing
disable_bgp_route_propagation: False
tags:
purpose: testing
- name: Update the subnet (idempotent)
azure_rm_subnet:
name: subnet
virtual_network_name: virtualnetwork
resource_group: Testing
address_prefix_cidr: "10.1.0.0/16"
route_table: foobar
- name: Delete a route table
azure_rm_routetable:
name: foobar
resource_group: Testing
state: absent
'''
RETURN = '''
changed:
description: Whether the resource is changed.
returned: always
type: bool
id:
description: resource id.
returned: success
type: str
'''
try:
from msrestazure.azure_exceptions import CloudError
except ImportError:
# This is handled in azure_rm_common
pass
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, normalize_location_name
class AzureRMRouteTable(AzureRMModuleBase):
def __init__(self):
self.module_arg_spec = dict(
resource_group=dict(type='str', required=True),
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present', 'absent']),
location=dict(type='str'),
disable_bgp_route_propagation=dict(type='bool', default=False)
)
self.resource_group = None
self.name = None
self.state = None
self.location = None
self.tags = None
self.disable_bgp_route_propagation = None
self.results = dict(
changed=False
)
super(AzureRMRouteTable, self).__init__(self.module_arg_spec,
supports_check_mode=True)
def exec_module(self, **kwargs):
for key in list(self.module_arg_spec.keys()) + ['tags']:
setattr(self, key, kwargs[key])
resource_group = self.get_resource_group(self.resource_group)
if not self.location:
# Set default location
self.location = resource_group.location
self.location = normalize_location_name(self.location)
result = dict()
changed = False
result = self.get_table()
if self.state == 'absent' and result:
changed = True
if not self.check_mode:
self.delete_table()
elif self.state == 'present':
if not result:
changed = True # create new route table
else: # check update
update_tags, self.tags = self.update_tags(result.tags)
if update_tags:
changed = True
if self.disable_bgp_route_propagation != result.disable_bgp_route_propagation:
changed = True
if changed:
result = self.network_models.RouteTable(location=self.location,
tags=self.tags,
disable_bgp_route_propagation=self.disable_bgp_route_propagation)
if not self.check_mode:
result = self.create_or_update_table(result)
self.results['id'] = result.id if result else None
self.results['changed'] = changed
return self.results
def create_or_update_table(self, param):
try:
poller = self.network_client.route_tables.create_or_update(self.resource_group, self.name, param)
return self.get_poller_result(poller)
except Exception as exc:
self.fail("Error creating or updating route table {0} - {1}".format(self.name, str(exc)))
def delete_table(self):
try:
poller = self.network_client.route_tables.delete(self.resource_group, self.name)
result = self.get_poller_result(poller)
return result
except Exception as exc:
self.fail("Error deleting virtual network {0} - {1}".format(self.name, str(exc)))
def get_table(self):
try:
return self.network_client.route_tables.get(self.resource_group, self.name)
except CloudError as cloud_err:
# Return None iff the resource is not found
if cloud_err.status_code == 404:
self.log('{0}'.format(str(cloud_err)))
return None
self.fail('Error: failed to get resource {0} - {1}'.format(self.name, str(cloud_err)))
except Exception as exc:
self.fail('Error: failed to get resource {0} - {1}'.format(self.name, str(exc)))
def main():
AzureRMRouteTable()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,204 @@
#!/usr/bin/python
#
# Copyright (c) 2018 Yuwei Zhou, <yuwzho@microsoft.com>
#
# 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
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: azure_rm_routetable_facts
version_added: "2.7"
short_description: Get route table facts.
description:
- Get facts for a specific route table or all route table in a resource group or subscription.
options:
name:
description:
- Limit results to a specific route table.
resource_group:
description:
- Limit results in a specific resource group.
tags:
description:
- Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
extends_documentation_fragment:
- azure
author:
- "Yuwei Zhou (@yuwzho)"
'''
EXAMPLES = '''
- name: Get facts for one route table
azure_rm_routetable_facts:
name: Testing
resource_group: foo
- name: Get facts for all route tables
azure_rm_routetable_facts:
resource_group: foo
- name: Get facts by tags
azure_rm_routetable_facts:
tags:
- testing
- foo:bar
'''
RETURN = '''
id:
description: Resource id.
returned: success
type: str
name:
description: Name of the resource.
returned: success
type: str
resource_group:
description: Resource group of the route table.
returned: success
type: str
disable_bgp_route_propagation:
description: Whether the routes learned by BGP on that route table disabled.
returned: success
type: bool
tags:
description: Tags of the route table.
returned: success
type: list
routes:
description: Current routes of the route table.
returned: success
type: list
sample: [
{
"id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/Testing/providers/Microsoft.Network/routeTables/foobar/routes/route",
"name": "route",
"resource_group": "Testing",
"routeTables": "foobar",
"address_prefix": "192.0.0.1",
"next_hop_type": "virtual_networkGateway"
}
]
'''
try:
from msrestazure.azure_exceptions import CloudError
except:
# This is handled in azure_rm_common
pass
from ansible.module_utils.azure_rm_common import AzureRMModuleBase, azure_id_to_dict
from ansible.module_utils.common.dict_transformations import _camel_to_snake
def route_to_dict(route):
id_dict = azure_id_to_dict(route.id)
return dict(
id=route.id,
name=route.name,
resource_group=id_dict.get('resourceGroups'),
route_table_name=id_dict.get('routeTables'),
address_prefix=route.address_prefix,
next_hop_type=_camel_to_snake(route.next_hop_type),
next_hop_ip_address=route.next_hop_ip_address
)
def instance_to_dict(table):
return dict(
id=table.id,
name=table.name,
resource_group=azure_id_to_dict(table.id).get('resourceGroups'),
location=table.location,
routes=[route_to_dict(i) for i in table.routes] if table.routes else [],
disable_bgp_route_propagation=table.disable_bgp_route_propagation,
tags=table.tags
)
class AzureRMRouteTableFacts(AzureRMModuleBase):
def __init__(self):
self.module_arg_spec = dict(
name=dict(type='str'),
resource_group=dict(type='str'),
tags=dict(type='list')
)
self.results = dict(
changed=False,
route_tables=[]
)
self.name = None
self.resource_group = None
self.tags = None
super(AzureRMRouteTableFacts, self).__init__(self.module_arg_spec,
supports_tags=False,
facts_module=True)
def exec_module(self, **kwargs):
for key in self.module_arg_spec:
setattr(self, key, kwargs[key])
response = []
if self.name:
response = self.get_item()
elif self.resource_group:
response = self.list_items()
else:
response = self.list_all_items()
self.results['route_tables'] = [instance_to_dict(x) for x in response if self.has_tags(x.tags, self.tags)]
return self.results
def get_item(self):
self.log('Get route table for {0}-{1}'.format(self.resource_group, self.name))
try:
item = self.network_client.route_tables.get(self.resource_group, self.name)
return [item]
except CloudError:
pass
return []
def list_items(self):
self.log('List all items in resource group')
try:
return self.network_client.route_tables.list(self.resource_group)
except CloudError as exc:
self.fail("Failed to list items - {0}".format(str(exc)))
return []
def list_all_items(self):
self.log("List all items in subscription")
try:
return self.network_client.route_tables.list_all()
except CloudError as exc:
self.fail("Failed to list all items - {0}".format(str(exc)))
return []
def main():
AzureRMRouteTableFacts()
if __name__ == '__main__':
main()

View file

@ -61,6 +61,13 @@ options:
required: true
aliases:
- virtual_network
route_table:
description:
- The reference of the RouteTable resource.
- It can accept both a str or a dict.
- The str can be the name or resource id of the route table.
- The dict can contains C(name) and C(resource_group) of the route_table.
version_added: "2.7"
extends_documentation_fragment:
- azure
@ -89,6 +96,7 @@ EXAMPLES = '''
security_group:
name: secgroupfoo
resource_group: Testing1
route_table: route
- name: Delete a subnet
azure_rm_subnet:
@ -149,11 +157,18 @@ def subnet_to_dict(subnet):
provisioning_state=subnet.provisioning_state,
address_prefix=subnet.address_prefix,
network_security_group=dict(),
route_table=dict()
)
if subnet.network_security_group:
id_keys = azure_id_to_dict(subnet.network_security_group.id)
result['network_security_group']['id'] = subnet.network_security_group.id
result['network_security_group']['name'] = id_keys['networkSecurityGroups']
result['network_security_group']['resource_group'] = id_keys['resourceGroups']
if subnet.route_table:
id_keys = azure_id_to_dict(subnet.route_table.id)
result['route_table']['id'] = subnet.route_table.id
result['route_table']['name'] = id_keys['routeTables']
result['route_table']['resource_group'] = id_keys['resourceGroups']
return result
@ -167,7 +182,8 @@ class AzureRMSubnet(AzureRMModuleBase):
state=dict(type='str', default='present', choices=['present', 'absent']),
virtual_network_name=dict(type='str', required=True, aliases=['virtual_network']),
address_prefix_cidr=dict(type='str', aliases=['address_prefix']),
security_group=dict(type='raw', aliases=['security_group_name'])
security_group=dict(type='raw', aliases=['security_group_name']),
route_table=dict(type='raw')
)
required_if = [
@ -185,6 +201,7 @@ class AzureRMSubnet(AzureRMModuleBase):
self.virtual_network_name = None
self.address_prefix_cidr = None
self.security_group = None
self.route_table = None
super(AzureRMSubnet, self).__init__(self.module_arg_spec,
supports_check_mode=True,
@ -204,6 +221,14 @@ class AzureRMSubnet(AzureRMModuleBase):
if self.security_group:
nsg = self.parse_nsg()
if self.route_table:
route_table = self.parse_resource_to_dict(self.route_table)
self.route_table = format_resource_id(val=route_table['name'],
subscription_id=route_table['subscription_id'],
namespace='Microsoft.Network',
types='routeTables',
resource_group=route_table['resource_group'])
results = dict()
changed = False
@ -228,6 +253,10 @@ class AzureRMSubnet(AzureRMModuleBase):
changed = True
results['network_security_group']['id'] = nsg.get('id')
results['network_security_group']['name'] = nsg.get('name')
if self.route_table != results['route_table'].get('id'):
changed = True
results['route_table']['id'] = self.route_table
self.log("CHANGED: subnet {0} route_table to {1}".format(self.name, route_table['name']))
elif self.state == 'absent':
changed = True
except CloudError:
@ -249,7 +278,8 @@ class AzureRMSubnet(AzureRMModuleBase):
)
if nsg:
subnet.network_security_group = self.network_models.NetworkSecurityGroup(id=nsg.get('id'))
if self.route_table:
subnet.route_table = self.network_models.RouteTable(id=self.route_table)
else:
# update subnet
self.log('Updating subnet {0}'.format(self.name))
@ -258,6 +288,8 @@ class AzureRMSubnet(AzureRMModuleBase):
)
if results['network_security_group'].get('id'):
subnet.network_security_group = self.network_models.NetworkSecurityGroup(results['network_security_group'].get('id'))
if self.route_table:
subnet.route_table = self.network_models.RouteTable(id=self.route_table)
self.results['state'] = self.create_or_update_subnet(subnet)
elif self.state == 'absent' and changed:

View file

@ -0,0 +1,3 @@
cloud/azure
shippable/azure/group4
destructive

View file

@ -0,0 +1,2 @@
dependencies:
- setup_azure

View file

@ -0,0 +1,183 @@
- name: Prepare random number
set_fact:
name: "table{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}"
route_name: "route{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}"
run_once: yes
- name: Create a route table (check mode)
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
tags:
purpose: testing
check_mode: yes
register: output
- assert:
that:
- not output.id
- output.changed
- name: Create a route table
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
tags:
purpose: testing
register: output
- assert:
that:
- output.changed
- output.id
- name: Create a route table (idemponent)
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
tags:
purpose: testing
register: output
- assert:
that:
- not output.changed
- name: Get facts of the table
azure_rm_routetable_facts:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
register: output
- assert:
that:
- "output.route_tables | length == 1"
- "output.route_tables[0].routes | length == 0"
- name: Create route (check mode)
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
next_hop_type: virtual_network_gateway
address_prefix: "10.1.0.0/16"
route_table_name: "{{ name }}"
check_mode: yes
register: output
- assert:
that:
- output.changed
- not output.id
- name: Create route
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
next_hop_type: virtual_network_gateway
address_prefix: "10.1.0.0/16"
route_table_name: "{{ name }}"
register: output
- assert:
that:
- output.changed
- output.id
- name: Create route (idemponent)
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
next_hop_type: virtual_network_gateway
address_prefix: "10.1.0.0/16"
route_table_name: "{{ name }}"
register: output
- assert:
that:
- not output.changed
- name: update route
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
next_hop_type: virtual_network_gateway
address_prefix: "10.1.0.0/24"
route_table_name: "{{ name }}"
register: output
- assert:
that:
- output.changed
- name: Get facts of the table
azure_rm_routetable_facts:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
register: output
- assert:
that:
- "output.route_tables | length == 1"
- "output.route_tables[0].routes | length == 1"
- output.route_tables[0].routes[0].address_prefix == '10.1.0.0/24'
- name: Delete route (check mode)
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
route_table_name: "{{ name }}"
state: absent
check_mode: yes
- name: Delete route
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
state: absent
route_table_name: "{{ name }}"
register: output
- assert:
that:
- output.changed
- name: Delete route (idemponent)
azure_rm_route:
name: "{{ route_name }}"
resource_group: "{{ resource_group }}"
state: absent
route_table_name: "{{ name }}"
register: output
- assert:
that:
- not output.changed
- name: Delete route table (check mode)
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
state: absent
check_mode: yes
- name: Delete route table
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
state: absent
register: output
- assert:
that:
- output.changed
- name: Delete route table (idemponent)
azure_rm_routetable:
name: "{{ name }}"
resource_group: "{{ resource_group }}"
state: absent
register: output
- assert:
that:
- not output.changed

View file

@ -12,6 +12,12 @@
delete: on-exit
resource_group: "{{ resource_group }}"
- name: Create route table
azure_rm_routetable:
name: routetableforsubnet
resource_group: "{{ resource_group }}"
register: route_table
- name: Remove subnet
azure_rm_subnet:
state: absent
@ -87,6 +93,7 @@
virtual_network_name: My_Virtual_Network
resource_group: "{{ resource_group }}"
address_prefix_cidr: "10.1.0.0/16"
route_table: "{{ route_table.id }}"
security_group:
name: secgroupfoo
resource_group: "{{ resource_group_secondary }}"
@ -106,6 +113,7 @@
virtual_network_name: My_Virtual_Network
resource_group: "{{ resource_group }}"
address_prefix_cidr: "10.1.0.0/16"
route_table: "{{ route_table.id }}"
security_group: "{{ nsg.state.id }}"
tags:
testing: testing